feat!: implement defineJob() + register() API pattern#9
Conversation
- Separate job definition from durably instance for better modularity - Add JobDefinition type (static, not executable) - Add JobHandle type (returned by register(), executable via trigger()) - Add React bindings spec (spec-react.md): - DurablyProvider with dialectFactory prop - useJob hook with isReady and initialRunId options - useJobRun for run-only subscriptions - Update spec-streaming.md examples to new API pattern - Fix link to spec-streaming.md in spec.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document the breaking changes planned for this branch: - defineJob() as standalone function - JobDefinition type (static, not executable) - durably.register() to get JobHandle - Migration guide from old API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Covers all files requiring changes: - Source code: 3 files (define-job.ts, durably.ts, index.ts) - Tests: 8 files, 79 locations - Docs: llms.md - Examples: 3 files Includes migration patterns, test adaptation strategies, and step-by-step implementation order. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add 14 website files (7 English + 7 Japanese) to migration scope - Update summary table with website documentation counts - Add Phase 3.2 and 3.3 for website docs (EN/JA) - Renumber Steps: Step 4 for website, Step 5 for examples, Step 6 for verification - Add pnpm format:fix to verification steps 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
BREAKING CHANGE: Replace durably.defineJob(config, fn) with
durably.register(defineJob({ ...config, run: fn }))
- Add standalone defineJob() function in src/define-job.ts
- Add durably.register() method that accepts JobDefinition
- Implement idempotent registration (same JobDefinition returns same handle)
- Update all tests to use new API pattern
- All 232 tests passing (node: 113, browser: 113, react: 6)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update job definition examples to use standalone defineJob() - Update browser usage example - Add JobDefinition type to Type Definitions section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…) API - Update all guide pages (en/ja) - Update all API reference pages (en/ja) - Replace durably.defineJob() with durably.register(defineJob()) - Add JobDefinition type documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update node/basic.ts - Update browser/src/main.ts - Update react/src/App.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Single-person project, consolidating to English only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- lib/durably.ts: Singleton durably instance - hooks/useDurably.ts: React lifecycle management hook - jobs/processImage.ts: Job definition This structure prepares for easy migration to @coji/durably-react package. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Clarify that the simple singleton pattern does not handle HMR. Reference future @coji/durably-react DurablyProvider solution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Breaking change: defineJob() + register() API pattern 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThis pull request refactors job definition into a standalone Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer
participant Def as defineJob()
participant Durably as durably.register()
participant Registry as JobRegistry/Storage
participant Job as JobHandle
Dev->>Def: defineJob({ name, input, output, run })
Def-->>Dev: JobDefinition
Dev->>Durably: durably.register(JobDefinition)
Durably->>Registry: lookup by name
alt same JobDefinition reference
Registry-->>Durably: return existing handle
else name unused or same definition
Registry-->>Durably: store jobDef, return new handle
else name conflict (different definition)
Registry-->>Durably: throw error
end
Durably-->>Dev: JobHandle
Dev->>Job: trigger(input)
Job->>Durably: schedule/run (persisted steps/events)
Durably->>Job: run executes jobDef.run(step, input)
Job-->>Dev: Promise<Run<Result>>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/spec.md (2)
539-607: Update interface definition to match implementation: useinputandoutputinstead ofinputSchemaandoutputSchemaLines 93–99 show the interface definition with incorrect property names. The source implementation uses
inputandoutput, and all examples throughout the specification (lines 539–607, 843–895, etc.) consistently use these same names. The interface definition must be corrected to align with the actual implementation.Change:
interface JobDefinition<TName extends string, TInput, TOutput> { readonly name: TName readonly inputSchema: z.ZodSchema<TInput> readonly outputSchema: z.ZodSchema<TOutput> readonly run: (step: StepContext, payload: TInput) => Promise<TOutput> }To:
interface JobDefinition<TName extends string, TInput, TOutput> { readonly name: TName readonly input: z.ZodSchema<TInput> readonly output: z.ZodSchema<TOutput> readonly run: (step: StepContext, payload: TInput) => Promise<TOutput> }
59-99: Fix incorrect JobDefinition interface definition in documentationThe documented
JobDefinitioninterface (lines 93–99) does not match the actual implementation. The interface should useinputandoutputproperties (notinputSchema/outputSchema), usez.ZodType(notz.ZodSchema), and markoutputas optional.Update the interface definition to:
interface JobDefinition<TName extends string, TInput, TOutput> { readonly name: TName readonly input: z.ZodType<TInput> readonly output: z.ZodType<TOutput> | undefined readonly run: (step: StepContext, payload: TInput) => Promise<TOutput> }The examples at lines 59–87 are already correct and match the actual implementation.
♻️ Duplicate comments (1)
docs/spec-streaming.md (1)
423-495: API inconsistency: Sameinput/outputnaming issue in codingAssistant exampleLines 429–439 also use
input:andoutput:instead of the property names defined in the JobDefinition interface shown in spec.md. This must be resolved consistently.
🧹 Nitpick comments (7)
examples/react/src/lib/durably.ts (1)
1-25: Good interim pattern, but HMR limitation is a known trade-off.The singleton pattern is straightforward and the JSDoc comment properly warns developers about the HMR limitation. The timing configuration (100ms polling, 500ms heartbeat, 3s stale threshold) is reasonable for a React example.
However, note that in React development mode with Fast Refresh/HMR, this file won't reload properly and requires a full page refresh when modified. The comment mentions a future
@coji/durably-reactpackage withDurablyProviderthat will handle this correctly.Consider whether this limitation should be more prominently documented in the example's README or main entry point, as developers might not notice this comment and could be confused by the behavior.
📋 Optional: Add a console warning in development mode
export const durably = createDurably({ dialect, pollingInterval: 100, heartbeatInterval: 500, staleThreshold: 3000, }) + +if (import.meta.env.DEV) { + console.warn( + '[Durably] Using singleton pattern - HMR will not work. ' + + 'Reload the page if you modify this file. ' + + 'See docs/spec-react.md for future DurablyProvider pattern.' + ) +}examples/node/basic.ts (1)
12-17: Consider removing Japanese comments.The PR objectives state that Japanese documentation is being consolidated to English. These Japanese comments should be translated or removed for consistency.
Suggested change
-// Turso の場合は環境変数から URL と authToken を取得 -// ローカル開発では file:local.db を使用 +// For Turso, get URL and authToken from environment variables +// For local development, use file:local.db const dialect = new LibsqlDialect({ url: process.env.TURSO_DATABASE_URL ?? 'file:local.db', authToken: process.env.TURSO_AUTH_TOKEN, })docs/spec-react.md (1)
1-14: Consider translating to English for consistency.The PR objectives state that Japanese documentation is being consolidated to English. This new spec document is written entirely in Japanese. Consider translating it to maintain consistency with the documentation consolidation goal, or clarify if spec documents are intentionally kept in Japanese.
website/guide/index.md (1)
22-45: Missingz(Zod) import in the example.The code example uses
z.object({ orgId: z.string() })on line 26, but the import statement on line 22 only showsdefineJob. Consider adding the Zod import for completeness:Proposed fix
import { defineJob } from '@coji/durably' +import { z } from 'zod'examples/react/src/App.tsx (1)
46-56: Consider adding error handling for async operations.Both
handleRunandhandleResetare async functions called from buttononClickhandlers but don't handle potential errors. While theuseDurablyhook does handlerun:failevents, errors duringtrigger()itself (e.g., validation errors) won't be caught.Proposed fix
const handleRun = async () => { markUserTriggered() - await processImage.trigger({ filename: 'photo.jpg', width: 800 }) - refreshDashboard() + try { + await processImage.trigger({ filename: 'photo.jpg', width: 800 }) + refreshDashboard() + } catch (error) { + console.error('Failed to trigger job:', error) + } } const handleReset = async () => { - await durably.stop() - await deleteDatabaseFile() - location.reload() + try { + await durably.stop() + await deleteDatabaseFile() + location.reload() + } catch (error) { + console.error('Failed to reset database:', error) + } }website/api/create-durably.md (1)
106-117: Missing Zod import in the example.The example uses
z.object({ id: z.string() })on line 112, butzis not imported. For completeness, add the Zod import.Proposed fix
// Define and register jobs import { defineJob } from '@coji/durably' +import { z } from 'zod'website/guide/react.md (1)
303-310: Consider avoiding type assertion fore.output.The type cast
e.output as { processed: number }on line 307 bypasses type safety. SinceprocessDataJobhas a typed output schema, consider using a type guard or storing the output type for reuse.Proposed improvement
+// Infer output type from the job definition +type ProcessDataOutput = { processed: number } + function App() { const [ready, setReady] = useState(false) const [processing, setProcessing] = useState(false) - const [result, setResult] = useState<{ processed: number } | null>(null) + const [result, setResult] = useState<ProcessDataOutput | null>(null) // ... durably.on('run:complete', (e) => { setProcessing(false) - setResult(e.output as { processed: number }) + if (e.jobName === 'process-data') { + setResult(e.output as ProcessDataOutput) + } }),
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (50)
CHANGELOG.mddocs/spec-react.mddocs/spec-streaming.mddocs/spec.mdexamples/browser/src/main.tsexamples/node/basic.tsexamples/react/src/App.tsxexamples/react/src/hooks/useDurably.tsexamples/react/src/jobs/processImage.tsexamples/react/src/lib/durably.tspackages/durably/docs/llms.mdpackages/durably/package.jsonpackages/durably/src/define-job.tspackages/durably/src/durably.tspackages/durably/src/index.tspackages/durably/src/job.tspackages/durably/tests/react/strict-mode.test.tsxpackages/durably/tests/shared/concurrency.shared.tspackages/durably/tests/shared/job.shared.tspackages/durably/tests/shared/log.shared.tspackages/durably/tests/shared/plugin.shared.tspackages/durably/tests/shared/recovery.shared.tspackages/durably/tests/shared/run-api.shared.tspackages/durably/tests/shared/step.shared.tspackages/durably/tests/shared/worker.shared.tswebsite/.vitepress/config.tswebsite/api/create-durably.mdwebsite/api/define-job.mdwebsite/api/index.mdwebsite/api/step.mdwebsite/guide/events.mdwebsite/guide/getting-started.mdwebsite/guide/index.mdwebsite/guide/jobs-and-steps.mdwebsite/guide/react.mdwebsite/ja/api/create-durably.mdwebsite/ja/api/define-job.mdwebsite/ja/api/events.mdwebsite/ja/api/index.mdwebsite/ja/api/step.mdwebsite/ja/guide/browser.mdwebsite/ja/guide/deployment.mdwebsite/ja/guide/events.mdwebsite/ja/guide/getting-started.mdwebsite/ja/guide/index.mdwebsite/ja/guide/jobs-and-steps.mdwebsite/ja/guide/nodejs.mdwebsite/ja/guide/react.mdwebsite/ja/guide/resumability.mdwebsite/ja/index.md
💤 Files with no reviewable changes (16)
- website/ja/guide/getting-started.md
- website/ja/api/step.md
- website/ja/guide/events.md
- website/ja/api/events.md
- website/ja/guide/jobs-and-steps.md
- website/ja/guide/deployment.md
- website/.vitepress/config.ts
- website/ja/api/index.md
- website/ja/guide/index.md
- website/ja/api/define-job.md
- website/ja/guide/react.md
- website/ja/api/create-durably.md
- website/ja/guide/nodejs.md
- website/ja/index.md
- website/ja/guide/browser.md
- website/ja/guide/resumability.md
🧰 Additional context used
📓 Path-based instructions (1)
packages/durably/docs/llms.md
📄 CodeRabbit inference engine (CLAUDE.md)
Update
packages/durably/docs/llms.mdto keep it in sync with API changes, as this file is bundled in the npm package for coding agents to read fromnode_modulesand symlinked towebsite/public/llms.txtfor web access
Files:
packages/durably/docs/llms.md
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Job definitions must be created via `durably.defineJob()`, receiving a step context and payload
📚 Learning: 2025-12-22T12:46:22.679Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Job definitions must be created via `durably.defineJob()`, receiving a step context and payload
Applied to files:
packages/durably/src/index.tspackages/durably/src/define-job.tspackages/durably/tests/shared/step.shared.tswebsite/guide/events.mdpackages/durably/tests/shared/concurrency.shared.tspackages/durably/tests/shared/recovery.shared.tspackages/durably/tests/shared/job.shared.tspackages/durably/tests/react/strict-mode.test.tsxCHANGELOG.mdwebsite/guide/index.mdexamples/react/src/jobs/processImage.tswebsite/guide/jobs-and-steps.mdwebsite/api/define-job.mdwebsite/api/index.mdwebsite/guide/react.mdpackages/durably/tests/shared/run-api.shared.tspackages/durably/tests/shared/plugin.shared.tsexamples/node/basic.tspackages/durably/tests/shared/log.shared.tspackages/durably/tests/shared/worker.shared.tspackages/durably/src/job.tswebsite/api/step.mdwebsite/api/create-durably.mdpackages/durably/src/durably.tswebsite/guide/getting-started.mddocs/spec-streaming.mddocs/spec.mdpackages/durably/docs/llms.mdexamples/browser/src/main.ts
📚 Learning: 2025-12-22T12:46:22.679Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Applies to packages/durably/docs/llms.md : Update `packages/durably/docs/llms.md` to keep it in sync with API changes, as this file is bundled in the npm package for coding agents to read from `node_modules` and symlinked to `website/public/llms.txt` for web access
Applied to files:
packages/durably/src/index.tspackages/durably/package.jsondocs/spec-react.mdwebsite/guide/events.mdexamples/react/src/lib/durably.tswebsite/guide/index.mdwebsite/api/index.mdpackages/durably/tests/shared/run-api.shared.tspackages/durably/tests/shared/log.shared.tswebsite/api/create-durably.mdpackages/durably/src/durably.tswebsite/guide/getting-started.mddocs/spec.mdpackages/durably/docs/llms.md
📚 Learning: 2025-12-22T12:46:22.679Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Use the dialect injection pattern - pass Kysely dialect to `createDurably()` to abstract SQLite implementations across Node.js and browser environments
Applied to files:
packages/durably/tests/shared/step.shared.tsexamples/react/src/lib/durably.tspackages/durably/tests/shared/concurrency.shared.tspackages/durably/tests/shared/recovery.shared.tspackages/durably/tests/shared/job.shared.tswebsite/api/index.mdpackages/durably/tests/shared/run-api.shared.tspackages/durably/tests/shared/plugin.shared.tsexamples/node/basic.tspackages/durably/tests/shared/log.shared.tspackages/durably/tests/shared/worker.shared.tspackages/durably/src/durably.tswebsite/guide/getting-started.mdpackages/durably/docs/llms.mdexamples/browser/src/main.ts
📚 Learning: 2025-12-22T12:46:22.679Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Each step's success state and return value must be persisted, created via `step.run()`
Applied to files:
packages/durably/tests/shared/step.shared.tspackages/durably/tests/shared/recovery.shared.tswebsite/guide/jobs-and-steps.mdpackages/durably/tests/shared/run-api.shared.tspackages/durably/tests/shared/plugin.shared.tsdocs/spec.md
📚 Learning: 2025-12-22T12:46:22.679Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Use the event system for extensibility via events: `run:start`, `run:complete`, `run:fail`, `step:*`, `log:write`
Applied to files:
website/guide/events.md
🧬 Code graph analysis (13)
packages/durably/src/define-job.ts (1)
packages/durably/src/job.ts (1)
StepContext(9-33)
packages/durably/tests/shared/step.shared.ts (3)
packages/durably/src/define-job.ts (1)
defineJob(59-80)packages/durably/src/index.ts (1)
defineJob(10-10)examples/react/src/lib/durably.ts (1)
durably(20-25)
examples/react/src/hooks/useDurably.ts (1)
examples/react/src/lib/durably.ts (1)
durably(20-25)
packages/durably/tests/react/strict-mode.test.tsx (2)
packages/durably/src/define-job.ts (1)
defineJob(59-80)packages/durably/src/index.ts (1)
defineJob(10-10)
examples/react/src/jobs/processImage.ts (1)
examples/react/src/lib/durably.ts (1)
durably(20-25)
examples/react/src/App.tsx (4)
examples/react/src/hooks/useDurably.ts (1)
useDurably(19-98)examples/react/src/jobs/processImage.ts (1)
processImage(13-40)examples/browser/src/dashboard.ts (1)
refreshDashboard(37-89)examples/react/src/lib/durably.ts (1)
durably(20-25)
packages/durably/tests/shared/run-api.shared.ts (1)
packages/durably/src/define-job.ts (1)
defineJob(59-80)
packages/durably/tests/shared/plugin.shared.ts (1)
packages/durably/src/define-job.ts (1)
defineJob(59-80)
examples/node/basic.ts (3)
examples/react/src/jobs/processImage.ts (1)
processImage(13-40)examples/react/src/lib/durably.ts (1)
durably(20-25)packages/durably/src/index.ts (1)
defineJob(10-10)
packages/durably/tests/shared/worker.shared.ts (2)
packages/durably/src/define-job.ts (1)
defineJob(59-80)packages/durably/src/index.ts (1)
defineJob(10-10)
packages/durably/src/job.ts (1)
packages/durably/src/define-job.ts (1)
JobDefinition(16-21)
packages/durably/src/durably.ts (3)
packages/durably/src/define-job.ts (1)
JobDefinition(16-21)packages/durably/src/index.ts (2)
JobDefinition(11-11)JobHandle(32-32)packages/durably/src/job.ts (2)
JobHandle(86-120)createJobHandle(178-369)
examples/browser/src/main.ts (3)
examples/react/src/jobs/processImage.ts (1)
processImage(13-40)examples/react/src/lib/durably.ts (1)
durably(20-25)packages/durably/src/index.ts (1)
defineJob(10-10)
🔇 Additional comments (64)
CHANGELOG.md (2)
17-38: LGTM! Clear migration guide with comprehensive example.The migration example clearly demonstrates the three key changes:
- Importing
defineJob- Moving the handler function to a
runproperty- Wrapping with
durably.register()This will help users migrate their code effectively.
12-15: The CHANGELOG claim about idempotent registration is accurate and properly implemented. All concerns can be verified in the codebase:
- Duplicate detection: Uses reference equality (
===) on the JobDefinition object increateJobHandle()(job.ts:188)- Multiple registration behavior: Returns the same cached
JobHandleinstance when the same JobDefinition is registered multiple times- Job name as key: Confirmed—the job registry uses job name as the Map key for lookups
- Edge cases: When a different JobDefinition is registered with the same name, an error is thrown (job.ts:192-194)
React StrictMode safety is explicitly tested in the test suite and works because the same JobDefinition object reference triggers the idempotency check, returning the cached handle on double-invocation.
packages/durably/src/index.ts (1)
9-11: LGTM! Public API surface correctly updated.The new exports for
defineJobandJobDefinitionproperly expose the static job definition pattern introduced in this PR. The section comment "// Job Definition" clearly groups these related exports.website/guide/events.md (1)
75-92: LGTM! Documentation correctly updated for new API.The logging example properly demonstrates the new
defineJob+registerpattern while maintaining focus on the event system functionality. The step.log usage remains clear and the integration with event subscriptions is well illustrated.examples/browser/src/main.ts (2)
8-8: LGTM! Import correctly updated.The
defineJobimport is properly added alongsidecreateDurablyto support the new API pattern.
25-52: LGTM! Job definition successfully migrated to new pattern.The conversion from
durably.defineJob(config, handler)todurably.register(defineJob({ ...config, run: handler }))is correctly implemented. The three-step image processing workflow (download → resize → upload) logic is preserved, and the job can now benefit from idempotent registration.website/guide/jobs-and-steps.md (2)
5-22: LGTM! Clear documentation of two-step pattern.The updated documentation clearly illustrates the separation between job definition (
defineJob) and registration (durably.register). The example withmyJobDefas an intermediate variable makes the static nature ofJobDefinitionobjects explicit and helps users understand the new pattern.
24-31: LGTM! Job options table correctly updated.The table now includes
run: Functionas a required field, accurately reflecting the new API surface where the handler is a property of the job configuration rather than a separate argument.packages/durably/tests/shared/log.shared.ts (2)
4-9: LGTM! Imports correctly updated.The import statement properly includes
defineJobalongside the other required types and functions for the test suite.
32-40: LGTM! Comprehensive test migration to new API.All six test cases are correctly updated to use the new
defineJob+registerpattern. The test assertions remain unchanged, confirming that the API refactor is a pure interface change with no behavioral impact on logging functionality. Each test properly:
- Creates a unique
JobDefinitionwith a descriptive name- Registers it via
durably.register()- Maintains the original test assertions
This ensures continued coverage of log levels (
info,warn,error), structured data,runIdinclusion, andstepNametracking.Also applies to: 59-67, 86-94, 113-121, 139-147, 165-176
packages/durably/package.json (1)
57-57: No compatibility issues found with Zod 4.2.1 and React 19.2.3 in the current codebase.The package declares
zod: ">=4.0.0"as peer dependency and uses Zod 4.2.1 in devDependencies. Inspection shows Zod is imported as type-only (import type { z }) in production code, and the test schemas use only basicz.object()patterns that are compatible with both Zod versions. None of the breaking change APIs mentioned—such as.default()on optional fields,z.record()with dual arguments, or string validators—are used in the codebase.React 19.2.3 is only used in devDependencies for tests. All
useRef()calls already have initial arguments, meeting React 19's requirement. No deprecated patterns like string refs,createFactory, or module pattern factories are present.Likely an incorrect or invalid review comment.
packages/durably/tests/react/strict-mode.test.tsx (3)
15-15: LGTM!Import correctly updated to include
defineJobfrom the library, aligning with the new API surface.
207-217: LGTM!The job definition correctly uses the new
defineJob()+durably.register()pattern with therunfunction embedded in the configuration object. The typed input/output schemas are properly defined with Zod.
292-298: LGTM!Correctly migrated to the new API pattern. The empty input schema
z.object({})and void-returningrunfunction are appropriate for this event listener test.examples/node/basic.ts (2)
8-8: LGTM!Import correctly includes
defineJobalongsidecreateDurablyfrom the library.
27-54: LGTM!The
processImagejob correctly demonstrates the new API pattern:
defineJob()creates theJobDefinitionwith embeddedrunfunctiondurably.register()returns theJobHandle- Sequential steps use
step.run()with descriptive names- Proper typing with Zod schemas for input/output
docs/spec-react.md (2)
36-95: LGTM!The basic usage example correctly demonstrates the new API pattern:
defineJobimported from@coji/durably(not the React package)- Job definition is static and created outside React components
useJob(processTask)receives theJobDefinitionand handles registration internally
526-591: Internal implementation guidance is clear and helpful.The
useJobimplementation example correctly shows:
- Registering the job via
durably.register(jobDef)on mount- Proper cleanup of event listeners
- Event-based state updates for run lifecycle
One consideration: the
useEffectdependency array includesjobDef.namerather than the fulljobDefobject, which is appropriate for avoiding unnecessary re-registrations.website/api/step.md (1)
104-140: LGTM!The example correctly demonstrates the updated API pattern:
defineJobimported from@coji/durably- Job definition includes
runfunction with proper(step, payload)signature- Registration via
durably.register(processOrderJob)returnsJobHandle- Triggering via
processOrder.trigger({ orderId })uses the handleThe step usage within the
runfunction (step.run(),step.log, etc.) is well documented.packages/durably/tests/shared/concurrency.shared.ts (3)
4-4: LGTM!Import correctly updated to include
defineJobfrom the library source.
26-37: LGTM!The concurrency test job correctly uses the new API pattern. The
concurrencyTestDefvariable naming clearly indicates it's aJobDefinition, and the registration returns a usableJobHandle.
61-71: LGTM!All remaining job definitions (
differentKeysTestDef,noKeyTestDef,nullKeyTestDef) correctly follow the newdefineJob+registerpattern. The consistent naming convention withDefsuffix clearly distinguishesJobDefinitionobjects from registeredJobHandleinstances.Also applies to: 96-106, 131-143
packages/durably/tests/shared/step.shared.ts (4)
4-9: LGTM!Import block correctly updated to include
defineJoband theStepCompleteEventtype needed for event testing.
29-38: LGTM!The step execution tests (
stepReturnTestDef,stepRecordTestDef,stepFailTestDef) correctly use the new API pattern. Each test properly:
- Defines a
JobDefinitionviadefineJob()- Registers it with
durably.register()- Uses the returned handle for triggering and assertions
Also applies to: 54-62, 83-92
117-135: LGTM!The step resume tests (
stepResumeTestDef,stepOutputResumeTestDef) correctly verify that completed steps are skipped on retry. The API migration preserves the test semantics while using the new pattern.Also applies to: 173-196
238-245: LGTM!The remaining step tests (events, async handling, timing) are correctly migrated to the new API pattern while preserving their verification logic.
Also applies to: 261-273, 289-299
examples/react/src/hooks/useDurably.ts (2)
11-17: LGTM!The
DurablyStatustype provides clear, descriptive states for the durably lifecycle. The distinction between'running'and'resuming'is useful for UI feedback.
26-72: Consider StrictMode double-mount behavior.This hook imports a singleton
durablyinstance from../lib/durably. In React StrictMode, the effect runs twice, which means:
- First mount: subscribes, migrates, starts
- Cleanup: unsubscribes, stops
- Second mount: subscribes again, migrates again (should be idempotent), starts again
Since
migrate()is idempotent andstart()/stop()are called on the same singleton, this should work correctly. However, you may want to verify this aligns with the StrictMode tests in the test suite, particularly regarding the singleton pattern test at lines 93-151 instrict-mode.test.tsx.packages/durably/tests/shared/run-api.shared.ts (6)
4-4: LGTM!Import correctly updated to include
defineJobfrom the library source.
25-31: LGTM!The
getRun()tests correctly use the new API pattern with inlinedefineJob()+register()calls.Also applies to: 49-56
77-90: LGTM!The
getRuns()filter tests correctly demonstrate the new API pattern while testing status, jobName, and sorting filters.Also applies to: 101-107, 129-142
156-162: LGTM!The pagination tests (limit, offset, combined pagination) are correctly migrated and maintain comprehensive coverage of the pagination API.
Also applies to: 179-185, 206-212, 233-239, 280-286, 307-313
328-340: LGTM!The
triggerAndWait()tests correctly verify success, failure, options handling, and timeout behavior using the new API pattern. Therunfunction properly receives(step, payload)parameters where needed.Also applies to: 355-367, 377-386, 402-415
428-439: LGTM!The
step.progress()tests correctly verify progress reporting with various parameter combinations. The inlinedefineJob()+register()pattern is consistently applied.Also applies to: 457-468, 492-504
examples/react/src/jobs/processImage.ts (1)
1-40: LGTM! Clean implementation of the new defineJob + register pattern.The job definition correctly follows the new API pattern with proper Zod schema definitions and step-based workflow. The three-step image processing simulation (download → resize → upload) is well-structured.
examples/react/src/App.tsx (1)
25-44: LGTM! Clean hook integration and state management.The destructuring of
useDurably()and the status text mapping are well-structured. The processing state derivation (isProcessing) is appropriate.packages/durably/src/define-job.ts (2)
70-79: LGTM! Well-designed type-safe API.The type inference setup with conditional types for optional output schema is correct. The type assertion at line 75 is necessary due to the complexity of inferring conditional types, and the constraints ensure type safety at the call site.
16-21: Good use of readonly modifiers for immutability.The
readonlymodifiers onJobDefinitionproperties correctly enforce that job definitions are immutable after creation, preventing accidental mutations.website/api/create-durably.md (1)
57-65: LGTM! Clear register() method documentation.The signature and description accurately reflect the new API pattern, with a proper cross-reference to the defineJob documentation.
packages/durably/tests/shared/plugin.shared.ts (2)
43-51: LGTM! Tests correctly use the new defineJob + register pattern.The test job definitions properly demonstrate the new API pattern with
defineJob({ name, input, run })wrapped indurably.register().
107-140: LGTM! Log persistence tests are well-structured.The tests correctly verify that logs are persisted when
withLogPersistence()plugin is used and empty otherwise. The assertions check the expected log properties (message, level, data).packages/durably/tests/shared/worker.shared.ts (3)
25-31: LGTM! Clean two-step pattern with intermediate variable.Using a separate variable for the job definition (
pollingTestDef) before registration improves readability and allows for potential reuse.
82-111: LGTM! State transition tests are thorough.The test correctly verifies the complete lifecycle: pending → running → completed, with event tracking to confirm the state transitions occur in the expected order.
183-212: LGTM! Sequential execution test validates ordering guarantees.The test properly verifies that multiple pending runs are processed sequentially in order, which is important for the durability guarantees of the framework.
website/guide/react.md (2)
264-285: LGTM! Complete example correctly demonstrates the new API pattern.The example shows the proper flow: defining a job with
defineJob()at module level, then registering withdurably.register()to get a triggerable handle.
416-422: LGTM! Best practices section provides valuable guidance.The five best practices listed are appropriate for React integration and will help developers avoid common pitfalls, especially regarding StrictMode compatibility and cleanup.
packages/durably/tests/shared/recovery.shared.ts (1)
4-4: LGTM!The import and usage pattern correctly reflects the new API surface. The test cases consistently use
durably.register(defineJob({...}))with therunproperty embedded in the job definition config.Also applies to: 27-38
website/api/index.md (1)
10-10: LGTM!The API reference accurately documents the new
defineJob()+durably.register()pattern. The quick reference examples, type exports, and method signatures are consistent with the implementation.Also applies to: 38-40, 45-68
website/guide/getting-started.md (2)
50-95: LGTM!The Node.js example clearly demonstrates the new two-step pattern: define the job with
defineJob(), then register withdurably.register(). The code is well-structured and serves as a good introduction for users.
101-131: LGTM!The Browser example correctly mirrors the Node.js pattern and demonstrates inline registration with
durably.register(defineJob({...})). Both styles (separate definition vs inline) are valid and useful to show.packages/durably/docs/llms.md (2)
42-66: LGTM!The LLM documentation correctly reflects the new API pattern with
defineJob()anddurably.register(). The examples accurately demonstrate the two-step workflow. As per coding guidelines, this file is properly synced with the API changes.
268-273: LGTM!The
JobDefinitiontype definition accurately reflects the implementation, including therunfunction signature withstepandpayloadparameters.packages/durably/tests/shared/job.shared.ts (2)
37-49: LGTM!The idempotent registration test correctly verifies that registering the same
JobDefinitioninstance returns the identical handle reference. This is crucial for React StrictMode compatibility.
51-71: LGTM!The conflict detection test properly verifies that registering different
JobDefinitioninstances with the same name throws an error, preventing accidental job overwrites.packages/durably/src/durably.ts (2)
3-3: LGTM!Clean import separation:
JobDefinitionfrom./define-joband handle-related exports from./job. This modular structure supports the new API design.Also applies to: 12-12
88-94: LGTM!The
registermethod is well-documented with clear JSDoc explaining the idempotent behavior. The implementation cleanly delegates tocreateJobHandle, keeping the Durably interface thin.Also applies to: 169-173
packages/durably/src/job.ts (3)
184-195: Reference equality for idempotency is the correct design choice.Using
===forjobDefcomparison is intentional and appropriate here. It enables React StrictMode compatibility where the sameJobDefinitionobject reference is passed on re-renders, while still catching accidental registration of structurally different definitions with the same name.
357-366: LGTM!The registry population correctly stores all necessary metadata including the
jobDefreference andhandlefor later idempotency checks and job execution.
130-131: LGTM!The
RegisteredJobinterface now properly captures the full job lifecycle withjobDefandhandlereferences. Thesetmethod naming is cleaner and more accurate for the internal registry API.Also applies to: 139-141, 161-163
website/api/define-job.md (3)
3-16: LGTM!The signature and
DefineJobConfiginterface documentation accurately reflect the new API. Therunproperty is clearly documented as required.Also applies to: 21-27
45-53: LGTM!The documentation clearly explains the two-step workflow:
defineJob()returns aJobDefinition, which is then registered withdurably.register()to get a usable job handle.
157-170: LGTM!Excellent addition documenting the idempotent registration behavior with a clear example. The note about React component safety is valuable for users adopting this pattern.
docs/spec.md (2)
101-119: ✓ Registration API is clearly documentedThe separation between JobDefinition (static) and JobHandle (runtime), along with idempotent registration semantics, is well explained. Once the property naming inconsistency is resolved, this section will be correct.
361-361: VerifydefineJobexport from '@coji/durably'The import assumes
defineJobis properly exported from the package. Confirm this export is present in packages/durably/src/index.ts after the property naming issue is resolved.
- Fix spec.md: Update inputSchema/outputSchema to input/output - Add error handling for migrate() failure in useDurably hook 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/spec.md (1)
93-149: Missing Run type definition in interface documentation.The
JobHandleinterface (line 126–147) and usage examples (e.g., lines 184–187, 303–305) reference aRun<TOutput>type, but the spec never formally defines theRuninterface. Please add a type definition showing at least theid,status, andoutputproperties, as well as any other key fields users will access.
🧹 Nitpick comments (2)
docs/spec.md (2)
52-120: Consider adding a "Migration" or "Breaking Changes" note for upgrading from v0.3.The spec thoroughly documents the new
defineJob()+durably.register()API, but does not reference or compare the old pattern. For users upgrading from v0.3, a brief migration note (e.g., "Previously:durably.defineJob(config, run)→ Now:defineJob({...}); durably.register(...)) would reduce friction. If a separate migration guide exists, a cross-reference would help.
103-121: Clarify idempotency mechanism for durably.register().Line 121 states that registering the same
JobDefinitionmultiple times results in only one registered job: "同名のジョブは一度だけ登録される。" This is critical for React StrictMode compatibility (per the PR objectives). Please add implementation details: Is this enforced by a unique constraint on(job_name)in the database, an in-memory registration cache, or per-durably-instance deduplication? Knowing the mechanism will help users understand when and why idempotency is guaranteed.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
docs/spec.mdexamples/react/src/hooks/useDurably.ts
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Job definitions must be created via `durably.defineJob()`, receiving a step context and payload
📚 Learning: 2025-12-22T12:46:22.679Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Job definitions must be created via `durably.defineJob()`, receiving a step context and payload
Applied to files:
docs/spec.md
📚 Learning: 2025-12-22T12:46:22.679Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Applies to packages/durably/docs/llms.md : Update `packages/durably/docs/llms.md` to keep it in sync with API changes, as this file is bundled in the npm package for coding agents to read from `node_modules` and symlinked to `website/public/llms.txt` for web access
Applied to files:
docs/spec.md
📚 Learning: 2025-12-22T12:46:22.679Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-22T12:46:22.679Z
Learning: Each step's success state and return value must be persisted, created via `step.run()`
Applied to files:
docs/spec.md
🧬 Code graph analysis (1)
examples/react/src/hooks/useDurably.ts (2)
examples/react/src/lib/durably.ts (1)
durably(20-25)examples/browser/src/dashboard.ts (1)
refreshDashboard(37-89)
🔇 Additional comments (8)
docs/spec.md (1)
1-51: Verify: PR objective states "Removes Japanese documentation" but lines 1–51 remain in Japanese.Per the PR objectives, Japanese documentation should be consolidated to English only. However, the entire introduction and rationale sections (lines 1–51) remain in Japanese and are not marked as changed in this diff. Please clarify whether this inconsistency should be resolved: either remove the Japanese content or update the PR objectives if preservation was intentional.
examples/react/src/hooks/useDurably.ts (7)
1-9: LGTM! Clear documentation and appropriate imports.The JSDoc comment clearly explains the hook's purpose and future plans. The React hook imports and durably instance import are correct for the hook's needs.
11-17: LGTM! Well-defined status type.The DurablyStatus union type covers all expected lifecycle states with clear, descriptive names.
19-25: LGTM! Appropriate state initialization.The hook correctly uses useState for reactive UI state (status, currentStep, result) and useRef for non-reactive tracking (userTriggered, callback storage).
26-80: Excellent implementation! Error handling for migration failure is now in place.The useEffect hook properly manages the durably lifecycle:
- Event subscriptions with cancellation safety prevent state updates after unmount
- Migration error handling (lines 68-73) addresses the past review comment by catching failures, logging errors, and setting status to 'error'
- Cleanup function properly unsubscribes from events and stops durably
- The
cancelledflag pattern is safe for React 19 StrictMode double-invocation
82-87: LGTM! Helper provides optimistic UI feedback.The function appropriately resets state and marks the run as user-triggered. Setting status to 'running' immediately provides optimistic UI feedback, which will be confirmed when the run:start event fires.
89-95: LGTM! Clean callback pattern with safe invocation.The dashboard refresh helpers provide a simple, safe way to register and invoke refresh callbacks. The optional chaining ensures the callback is only invoked if set.
97-106: LGTM! Well-structured hook API.The return value provides a comprehensive API with state for UI rendering, helpers for user interactions, and direct access to the durably instance for flexibility.
- Add createDurably import and instantiation in guide/index.md - Add createDurably import and instantiation in guide/jobs-and-steps.md - Add tip box in guide/events.md explaining durably instance assumption 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Show complete dialect instantiation in the main example so readers understand where `dialect` comes from. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
defineJob()+durably.register()replacesdurably.defineJob()defineJob()is now a standalone function that creates aJobDefinitiondurably.register(jobDef)registers the definition and returns aJobHandle@coji/durably-reactpackageBreaking Changes
Changes
Core API
defineJob()standalone functiondurably.register()method for idempotent job registrationJobDefinitionandJobHandletypesDocumentation
React Example
lib/,hooks/,jobs/directoriesuseDurablyhook with proper lifecycle managementmigrate()failureFixes (from CodeRabbit review)
spec.md:inputSchema/outputSchema→input/outputmigrate()inuseDurablyhookTest plan
pnpm test:node)pnpm test:react)pnpm test:browser)pnpm typecheck)🤖 Generated with Claude Code