v1 Implementation: Core durably package with documentation#3
Conversation
- Add pnpm-workspace.yaml for monorepo configuration - Move source files to packages/durably/ - Configure Vitest for Node.js and browser testing - Add test helpers for libSQL and SQLocal dialects - Create example skeletons for Node.js and browser - Update root package.json with workspace scripts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove rootDir option that was conflicting with tests include pattern. tsup handles rootDir automatically during build. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add database schema types (runs, steps, logs, schema_versions) - Implement migration system with Kysely - Add createDurably() factory function with migrate() method - Migration is idempotent and tracks version in schema_versions table - Add React test environment with browser mode for OPFS support - Add StrictMode test placeholders for future implementation Tests pass in all three environments: - Node.js (libSQL) - Browser (SQLocal/OPFS) - React (browser mode with OPFS) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Storage interface with KyselyStorage implementation - Run CRUD operations with idempotency key support - Step operations with completion tracking - Concurrency key filtering for job scheduling - Add Event system with discriminated union types - Type-safe event emission and subscription - Auto-assigned sequence numbers and timestamps - Exception isolation between listeners - Add defineJob() with type-safe schemas - Zod schema validation for input/output - JobHandle with trigger, getRun, getRuns methods - Job registry to prevent duplicate registration - Remove duplicate React tests (keep only strict-mode.test.tsx) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Worker with start/stop and polling loop - Processes pending runs sequentially - Graceful shutdown waits for current run - Emits run:start, run:complete, run:fail events - Add JobContext for step execution - ctx.run() executes and persists step results - Completed steps are skipped on resume (replay) - Emits step:start, step:complete, step:fail events - ctx.progress() for progress reporting - ctx.log for structured logging - Add concurrencyKey serialization - Runs with same key wait for each other - Different keys or null keys run independently 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…etry) - Add heartbeat mechanism to update heartbeat_at during job execution - Add stale run recovery to reset abandoned runs to pending status - Add retry() API to reset failed runs for re-execution - Add ctx.runId property to JobContext for run identification - Fix undefined output handling in storage.updateRun() - Fix worker stop() to properly wait for in-progress operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add durably.getRun(id) to fetch a run by ID - Add durably.getRuns(filter) to list runs with optional filtering - Progress API already working via ctx.progress() - All tests passing for Node.js and browser environments 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Phase 6: Event system already implemented (run/step/log events) - Phase 7.1: ctx.log (info/warn/error) with structured data support - Phase 7.2: withLogPersistence plugin for database log storage - Phase 8: use() plugin system for extending Durably New features: - DurablyPlugin interface for creating plugins - durably.use(plugin) to register plugins - withLogPersistence() plugin persists log events to logs table - storage.createLog() and storage.getLogs() for log operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement 6 tests verifying Durably handles React StrictMode's double mount/unmount behavior safely: - Double mount/unmount handling - Singleton pattern preventing duplicate initialization - Concurrent migrate() calls safety - stop() during migrate() safety - Job execution after StrictMode double mount - Event listener cleanup on unmount Configure TypeScript and Vitest for React JSX support. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Exclude Vitest browser test failure screenshots from version control. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add batchTrigger() method to JobHandle that: - Creates multiple runs in a single call - Validates all inputs before creating any runs (atomic validation) - Supports both simple inputs and inputs with trigger options - Returns empty array for empty input Add batchCreateRuns() to Storage interface for batch insert operations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- format: check formatting with prettier - format:fix: fix formatting with prettier 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change lint script from 'biome check' to 'biome lint' - Add lint:fix script with 'biome lint --write' - Fix noNonNullAssertion errors in events.ts, worker.ts - Fix noConfusingVoidType error in job.ts (void -> undefined) - Disable noNonNullAssertion rule for test files in biome.json 🤖 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>
- examples/node: Turso/libSQL example with user sync job - examples/browser: SQLocal (SQLite WASM + OPFS) example with progress tracking 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove sqlocal/vite plugin (not available) - Fix ctx.progress() signature to use positional args 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added development tooling to both node and browser examples: - typecheck, lint, lint:fix, format, format:fix scripts - tsconfig.json for TypeScript configuration - devDependencies: biome, prettier, typescript 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- peerDependencies: zod >=4.0.0 - devDependencies and examples: zod ^4.2.1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add React example with StrictMode-safe singleton pattern - Add database stats display and refresh functionality - Add reset database button (deletes OPFS file and reloads) - Add Cross-Origin Isolation headers for OPFS persistence - Add ifNotExists() to migrations for idempotent schema creation - Export JobContext and JobHandle types from durably - Add root scripts: validate, dev:react, typecheck, lint:fix, format:fix 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Redesign browser/react UI with modern card-based layout - Add status indicator with color-coded states - Add progress bar with gradient animation - Add real-time stats grid display - Update job to process-image with sequential steps (download, resize, upload) - Add real-time event-based status updates - Unify job definition across all examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add triggerAndWait() method to JobHandle that triggers a run and waits for completion via events - Export TriggerAndWaitResult type from package - Add tests for success, failure, and options handling - Update node example to use triggerAndWait instead of manual polling - Add local.db to .gitignore 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This ensures the job is registered before the worker starts, fixing the "Unknown job" error on page reload during job execution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove stats display and progress bar from browser example - Add Reload Page button and resuming status detection - Reduce browser example from 226 to 131 lines - Reduce HTML from 244 to 65 lines - Match React example's simple structure 🤖 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>
- Add triggerAndWait and batchTrigger to JobHandle interface - Change ctx.setProgress to ctx.progress (positional args) - Fix logs index column name (timestamp -> created_at) - Mark $types as future implementation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Type-safe event subscription via $types is over-engineering. triggerAndWait provides type safety where it matters. Events are primarily for monitoring where unknown is fine. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ments across examples and tests
JSON.stringify never returns undefined, so ?? null is dead code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Wrap idempotency key checks and inserts in a transaction to prevent race conditions. Also switch test dialect from :memory: to temp files to work around libsql transaction compatibility issues with in-memory DBs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Track current step name in context, logs inside steps include stepName - Logs outside steps have stepName: null - Fix spec: logs table column timestamp -> created_at 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Warning Rate limit exceeded@coji has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 5 minutes and 50 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (4)
WalkthroughAdds a monorepo implementation of Durably: a typed job runtime with Kysely-backed storage, worker (polling/heartbeat/recovery), event system, plugins, migrations, extensive tests (node/browser/react), examples, documentation site, CI/docs workflows, formatting hooks, and packaging/tooling configs. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant Durably
participant Storage
participant Worker
participant JobCtx as JobContext
participant Events as EventEmitter
Client->>Durably: defineJob(...) / trigger(...)
Durably->>Storage: createRun(...)
Durably->>Events: emit(run:created)
Client->>Durably: start()
Durably->>Worker: start poll loop
Worker->>Storage: getNextPendingRun(excludeKeys)
alt run found
Worker->>Storage: transitionRunToRunning(runId)
Worker->>Events: emit(run:start)
Worker->>JobCtx: createJobContext(run)
JobCtx->>Events: emit(step:start)
JobCtx->>Storage: createStep / getCompletedStep
JobCtx->>JobCtx: execute step fn
JobCtx->>Storage: saveStepComplete / updateRunProgress/output
JobCtx->>Events: emit(step:complete)
par heartbeat
Worker->>Storage: updateHeartbeat(runId)
end
alt success
Worker->>Storage: finalizeRunCompleted(runId)
Worker->>Events: emit(run:complete)
else failure
Worker->>Storage: markRunFailed(runId, error)
Worker->>Events: emit(run:fail)
end
else no run
Worker->>Worker: wait(pollInterval)
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Areas needing focused review:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
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 |
Examples depend on built @coji/durably package for type declarations. 🤖 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: 9
🧹 Nitpick comments (19)
packages/durably/tests/react/strict-mode.test.tsx (2)
33-34: Consider using a more deterministic cleanup approach.The arbitrary 100ms delay is a common workaround but can cause flaky tests. Consider awaiting specific promises or using
vi.waitForwith a condition if flakiness is observed.
224-237: Consider using a loop-based polling pattern for clarity.The recursive
setTimeoutpattern works but can be harder to follow. A loop withawaitwould be more readable and easier to maintain.🔎 Alternative polling pattern
- // Wait for completion - const checkCompletion = async () => { - if (cleanedUp.current) return - try { - const updated = await job.getRun(run.id) - if (updated?.status === 'completed') { - setResult((updated.output as { processed: string }).processed) - } else if (!cleanedUp.current) { - setTimeout(checkCompletion, 50) - } - } catch { - // Ignore errors if cleaned up - } - } - checkCompletion() + // Wait for completion + const pollCompletion = async () => { + while (!cleanedUp.current) { + try { + const updated = await job.getRun(run.id) + if (updated?.status === 'completed') { + setResult((updated.output as { processed: string }).processed) + return + } + await new Promise((r) => setTimeout(r, 50)) + } catch { + if (cleanedUp.current) return + } + } + } + pollCompletion()packages/durably/tsconfig.json (2)
17-17: Tests are being compiled and will be included in the npm package.The
"include": ["src/**/*", "tests/**/*"]in tsconfig.json means test files are compiled todist/tests/. Whilepackage.jsonrestricts the published files to thedistdirectory, this includes all contents ofdist/, so compiled tests will be in the published npm package, increasing its size.Recommended approach:
- Use a separate
tsconfig.test.jsonfor type-checking tests without emitting to dist- Or exclude tests from include and handle test compilation separately
6-7: DOM types and JSX configuration are appropriate for this cross-environment package.The "DOM" lib addition supports browser functionality (SQLite WASM with OPFS), which is valid for a cross-platform library. The "jsx": "react-jsx" configuration is necessary because TypeScript must compile the
.tsxtest file (tests/react/strict-mode.test.tsx). Sincepackage.jsoncorrectly specifies"files": ["dist"], tests are not included in the published npm package—there's no bloat concern.That said, if JSX is used only in tests, consider creating a separate
tsconfig.test.jsonthat extends the base config and adds JSX support specifically for test compilation. This would make the core configuration cleaner and signal that the library itself doesn't export React components—only the tests use JSX..claude/hooks/format-on-edit.ts (1)
29-33: Consider logging formatting errors for debugging.The empty catch block silently swallows all errors. While this prevents the hook from failing, it makes debugging difficult when Prettier fails.
🔎 Proposed improvement
try { execSync(`pnpm exec prettier --write "${filePath}"`, { stdio: 'inherit', }) - } catch {} + } catch (error) { + // Log but don't fail - formatting errors shouldn't block the workflow + console.error(`Failed to format ${filePath}:`, error) + }.github/workflows/ci.yml (1)
1-99: LGTM! Well-structured CI workflow with proper optimization.The workflow is well-designed:
- Concurrency cancellation prevents redundant runs
- Frozen lockfile ensures reproducible builds
- Separate jobs for validation and testing improve parallelization
- Playwright browser caching reduces CI time and costs
- Conditional installation logic for cached vs. uncached browsers
Minor suggestion: The comment at line 7 is in Japanese. Consider translating it to English for broader accessibility:
-# 同じブランチ/PRの古いワークフローをキャンセル +# Cancel in-progress runs for the same branch/PR concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: truewebsite/.vitepress/theme/CopySourceButton.vue (1)
10-12: Hardcoded branch name may cause issues on feature branches.The source URL points to
mainbranch, but documentation previews on feature branches would fetch content frommaininstead of the current branch, potentially showing outdated or different content.Consider making the branch configurable
You could expose the branch as a build-time config or use VitePress's site data to determine the appropriate branch dynamically.
examples/react/src/Dashboard.tsx (3)
11-82: Style duplication withstyles.ts.This component defines its own
stylesobject while there's a sharedstyles.tsfile in the same directory. Some properties likeresultappear in both. Consider consolidating to avoid drift between the two style definitions.You could either extend the shared styles or move Dashboard-specific styles to the shared module if they're reusable.
104-111: Missing error handling inshowDetails.If
durably.getRunordurably.storage.getStepsfails, the error is unhandled and may cause silent failures. Consider adding a try-catch with user feedback.Proposed fix
const showDetails = async (runId: string) => { + try { const run = await durably.getRun(runId) if (run) { setSelectedRun(run) const stepsData = await durably.storage.getSteps(runId) setSteps(stepsData.map((s) => ({ name: s.name, status: s.status }))) } + } catch (error) { + console.error('Failed to load run details:', error) + } }
113-127: Missing error handling in action handlers.
handleRetry,handleCancel, andhandleDeletedon't handle potential errors from the durably API calls. Failed operations will silently fail while still triggering a refresh.Proposed fix for handleRetry (apply similar pattern to others)
const handleRetry = async (runId: string) => { + try { await durably.retry(runId) - refresh() + await refresh() + } catch (error) { + console.error('Failed to retry run:', error) + } }examples/react/src/main.tsx (1)
5-9: Standard React 18+ bootstrap pattern.The use of
createRootandStrictModefollows React 18+ best practices. While the type assertionas HTMLElementis a common pattern, a defensive null check could improve error messaging if the root element is missing. This is optional for an example application.🔎 Optional: More defensive root element handling
+const rootElement = document.getElementById('root') +if (!rootElement) { + throw new Error('Root element not found') +} + -createRoot(document.getElementById('root') as HTMLElement).render( +createRoot(rootElement).render( <StrictMode> <App /> </StrictMode>, )packages/durably/tests/shared/migrate.shared.ts (1)
14-60: Consider verifying table schemas, not just existence.The tests correctly verify that the migration creates all required tables (durably_runs, durably_steps, durably_logs, durably_schema_versions). However, verifying the table structure (columns, types, constraints) would provide stronger guarantees against schema drift or migration bugs.
Example: Verify table columns
it('durably_runs table has required columns', async () => { durably = createDurably({ dialect: createDialect() }) await durably.migrate() const result = await sql<{ name: string }>` SELECT name FROM pragma_table_info('durably_runs') `.execute(durably.db) const columns = result.rows.map(r => r.name) expect(columns).toContain('id') expect(columns).toContain('status') expect(columns).toContain('heartbeat_at') // ... other required columns })packages/durably/src/migrations.ts (2)
118-132: Consider narrowing the error catch.The
getCurrentVersionfunction catches all errors and assumes they indicate a missing table. While this works, it could potentially mask other database errors (connection issues, permission errors, etc.).Optional: More specific error handling
async function getCurrentVersion(db: Kysely<Database>): Promise<number> { try { const result = await db .selectFrom('durably_schema_versions') .select('version') .orderBy('version', 'desc') .limit(1) .executeTakeFirst() return result?.version ?? 0 - } catch { + } catch (error) { + // Only return 0 if table doesn't exist; otherwise re-throw + const message = error instanceof Error ? error.message : String(error) + if (message.includes('no such table') || message.includes('does not exist')) { + return 0 + } + throw error - // Table doesn't exist yet - return 0 } }
137-153: Consider adding transaction support for migrations.Currently, migrations run sequentially without wrapping them in a transaction. If a migration partially fails, the database could be left in an inconsistent state. Consider wrapping each migration in a transaction for atomicity.
Recommended: Transactional migrations
export async function runMigrations(db: Kysely<Database>): Promise<void> { const currentVersion = await getCurrentVersion(db) for (const migration of migrations) { if (migration.version > currentVersion) { - await migration.up(db) - - await db - .insertInto('durably_schema_versions') - .values({ - version: migration.version, - applied_at: new Date().toISOString(), - }) - .execute() + await db.transaction().execute(async (trx) => { + await migration.up(trx) + + await trx + .insertInto('durably_schema_versions') + .values({ + version: migration.version, + applied_at: new Date().toISOString(), + }) + .execute() + }) } } }Note: Verify that both better-sqlite3 and libsql dialects properly support DDL statements within transactions.
examples/react/src/App.tsx (2)
101-108: Consider error handling for trigger operation.The
runfunction doesn't handle potential errors fromprocessImage.trigger(). While errors will be caught by the event system (run:fail), users might expect immediate feedback if the trigger itself fails.🔎 Proposed error handling
const run = async () => { userTriggered.current = true setStatus('running') setStep(null) setResult(null) - await processImage.trigger({ filename: 'photo.jpg', width: 800 }) - refreshDashboardRef.current?.() + try { + await processImage.trigger({ filename: 'photo.jpg', width: 800 }) + refreshDashboardRef.current?.() + } catch (error) { + setStatus('error') + setResult(error instanceof Error ? error.message : String(error)) + } }
186-195: Consider error handling for database reset.The database reset operation might fail (e.g., file locked, permission issues), but errors aren't handled. While the page will reload regardless, showing a brief error message would improve UX.
🔎 Proposed error handling
<button type="button" onClick={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) + // Optionally show user-facing error + alert('Failed to reset database. Please refresh manually.') + } }} disabled={isProcessing} > Reset Database </button>packages/durably/src/durably.ts (1)
210-229: Consider documenting that cancelled runs can also be retried.The current implementation correctly allows both
failedandcancelledruns to be retried (since they fall through to theupdateRuncall). The comment on line 224 says "Only failed runs can be retried", but this is slightly misleading as cancelled runs can also be retried.Suggested documentation fix
- // Only failed runs can be retried + // Only failed or cancelled runs can be retried await storage.updateRun(runId, { status: 'pending', error: null, })packages/durably/src/worker.ts (1)
255-279: Consider wrapping poll errors to prevent silent worker death.If
recoverStaleRuns()or the storage operations inprocessNextRun()throw an unexpected error (e.g., database connection lost), the error will propagate unhandled and silently stop the polling loop. Consider catching and emitting aworker:errorevent to maintain observability while continuing the polling loop.Suggested error handling
async function poll(): Promise<void> { if (!running) { return } const doWork = async () => { // Recover stale runs before processing await recoverStaleRuns() await processNextRun() } try { currentRunPromise = doWork() await currentRunPromise + } catch (error) { + eventEmitter.emit({ + type: 'worker:error', + error: error instanceof Error ? error.message : String(error), + context: 'poll', + }) } finally { currentRunPromise = null } if (running) { pollingTimeout = setTimeout(() => poll(), config.pollingInterval) } else if (stopResolver) { stopResolver() stopResolver = null } }packages/durably/src/storage.ts (1)
136-152: Consider adding error handling for JSON parsing.The
JSON.parsecalls inrowToRun(and similarly inrowToStep,rowToLog) can throw if the database contains malformed JSON. While this shouldn't happen under normal operation, defensive error handling would improve robustness.🔎 Proposed defensive parsing
function rowToRun(row: Database['durably_runs']): Run { + const safeJsonParse = <T>(value: string | null, fallback: T): T => { + if (!value) return fallback + try { + return JSON.parse(value) + } catch { + return fallback + } + } + return { id: row.id, jobName: row.job_name, - payload: JSON.parse(row.payload), + payload: safeJsonParse(row.payload, null), status: row.status, // ... rest of fields } }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
-
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml -
website/public/logo.svgis excluded by!**/*.svg
📒 Files selected for processing (107)
-
.claude/hooks/format-on-edit.ts(1 hunks) -
.claude/hooks/tsconfig.json(1 hunks) -
.github/workflows/ci.yml(1 hunks) -
.github/workflows/docs.yml(1 hunks) -
.gitignore(1 hunks) -
CLAUDE.md(3 hunks) -
LICENSE(1 hunks) -
README.md(2 hunks) -
biome.json(1 hunks) -
docs/implementation-plan.md(0 hunks) -
docs/spec-streaming.md(8 hunks) -
docs/spec.md(20 hunks) -
examples/browser/index.html(1 hunks) -
examples/browser/package.json(1 hunks) -
examples/browser/src/dashboard.ts(1 hunks) -
examples/browser/src/main.ts(1 hunks) -
examples/browser/tsconfig.json(1 hunks) -
examples/browser/vite.config.ts(1 hunks) -
examples/node/basic.ts(1 hunks) -
examples/node/package.json(1 hunks) -
examples/node/tsconfig.json(1 hunks) -
examples/react/index.html(1 hunks) -
examples/react/package.json(1 hunks) -
examples/react/src/App.tsx(1 hunks) -
examples/react/src/Dashboard.tsx(1 hunks) -
examples/react/src/main.tsx(1 hunks) -
examples/react/src/styles.ts(1 hunks) -
examples/react/tsconfig.json(1 hunks) -
examples/react/vercel.json(1 hunks) -
examples/react/vite.config.ts(1 hunks) -
package.json(1 hunks) -
packages/durably/package.json(1 hunks) -
packages/durably/src/context.ts(1 hunks) -
packages/durably/src/durably.ts(1 hunks) -
packages/durably/src/errors.ts(1 hunks) -
packages/durably/src/events.ts(1 hunks) -
packages/durably/src/index.ts(1 hunks) -
packages/durably/src/job.ts(1 hunks) -
packages/durably/src/migrations.ts(1 hunks) -
packages/durably/src/plugins/index.ts(1 hunks) -
packages/durably/src/plugins/log-persistence.ts(1 hunks) -
packages/durably/src/schema.ts(1 hunks) -
packages/durably/src/storage.ts(1 hunks) -
packages/durably/src/worker.ts(1 hunks) -
packages/durably/tests/browser/concurrency.test.ts(1 hunks) -
packages/durably/tests/browser/durably.test.ts(1 hunks) -
packages/durably/tests/browser/events.test.ts(1 hunks) -
packages/durably/tests/browser/job.test.ts(1 hunks) -
packages/durably/tests/browser/log.test.ts(1 hunks) -
packages/durably/tests/browser/migrate.test.ts(1 hunks) -
packages/durably/tests/browser/plugin.test.ts(1 hunks) -
packages/durably/tests/browser/recovery.test.ts(1 hunks) -
packages/durably/tests/browser/run-api.test.ts(1 hunks) -
packages/durably/tests/browser/setup.test.ts(1 hunks) -
packages/durably/tests/browser/step.test.ts(1 hunks) -
packages/durably/tests/browser/storage.test.ts(1 hunks) -
packages/durably/tests/browser/worker.test.ts(1 hunks) -
packages/durably/tests/helpers/browser-dialect.ts(1 hunks) -
packages/durably/tests/helpers/node-dialect.ts(1 hunks) -
packages/durably/tests/node/concurrency.test.ts(1 hunks) -
packages/durably/tests/node/durably.test.ts(1 hunks) -
packages/durably/tests/node/events.test.ts(1 hunks) -
packages/durably/tests/node/job.test.ts(1 hunks) -
packages/durably/tests/node/log.test.ts(1 hunks) -
packages/durably/tests/node/migrate.test.ts(1 hunks) -
packages/durably/tests/node/plugin.test.ts(1 hunks) -
packages/durably/tests/node/recovery.test.ts(1 hunks) -
packages/durably/tests/node/run-api.test.ts(1 hunks) -
packages/durably/tests/node/setup.test.ts(1 hunks) -
packages/durably/tests/node/step.test.ts(1 hunks) -
packages/durably/tests/node/storage.test.ts(1 hunks) -
packages/durably/tests/node/worker.test.ts(1 hunks) -
packages/durably/tests/react/strict-mode.test.tsx(1 hunks) -
packages/durably/tests/shared/concurrency.shared.ts(1 hunks) -
packages/durably/tests/shared/durably.shared.ts(1 hunks) -
packages/durably/tests/shared/events.shared.ts(1 hunks) -
packages/durably/tests/shared/job.shared.ts(1 hunks) -
packages/durably/tests/shared/log.shared.ts(1 hunks) -
packages/durably/tests/shared/migrate.shared.ts(1 hunks) -
packages/durably/tests/shared/plugin.shared.ts(1 hunks) -
packages/durably/tests/shared/recovery.shared.ts(1 hunks) -
packages/durably/tests/shared/run-api.shared.ts(1 hunks) -
packages/durably/tests/shared/setup.shared.ts(1 hunks) -
packages/durably/tests/shared/step.shared.ts(1 hunks) -
packages/durably/tests/shared/storage.shared.ts(1 hunks) -
packages/durably/tests/shared/worker.shared.ts(1 hunks) -
packages/durably/tsconfig.json(1 hunks) -
packages/durably/vitest.browser.config.ts(1 hunks) -
packages/durably/vitest.config.ts(1 hunks) -
packages/durably/vitest.react.config.ts(1 hunks) -
pnpm-workspace.yaml(1 hunks) -
src/index.ts(0 hunks) -
website/.vitepress/config.ts(1 hunks) -
website/.vitepress/theme/CopySourceButton.vue(1 hunks) -
website/.vitepress/theme/index.ts(1 hunks) -
website/api/context.md(1 hunks) -
website/api/create-durably.md(1 hunks) -
website/api/define-job.md(1 hunks) -
website/api/events.md(1 hunks) -
website/api/index.md(1 hunks) -
website/guide/browser.md(1 hunks) -
website/guide/deployment.md(1 hunks) -
website/guide/events.md(1 hunks) -
website/guide/getting-started.md(1 hunks) -
website/guide/index.md(1 hunks) -
website/guide/jobs-and-steps.md(1 hunks) -
website/guide/nodejs.md(1 hunks)
⛔ Files not processed due to max files limit (19)
- website/guide/react.md
- website/guide/resumability.md
- website/index.md
- website/ja/api/context.md
- website/ja/api/create-durably.md
- website/ja/api/define-job.md
- website/ja/api/events.md
- website/ja/api/index.md
- website/ja/guide/browser.md
- website/ja/guide/deployment.md
- website/ja/guide/events.md
- website/ja/guide/getting-started.md
- website/ja/guide/index.md
- website/ja/guide/jobs-and-steps.md
- website/ja/guide/nodejs.md
- website/ja/guide/react.md
- website/ja/guide/resumability.md
- website/ja/index.md
- website/package.json
💤 Files with no reviewable changes (2)
- src/index.ts
- docs/implementation-plan.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{js,ts,jsx,tsx}: Usedurably.defineJob()to define jobs that receive a context object and payload
Usectx.run()to create steps, where each step's success state and return value is persisted
Create run instances viatrigger()API, which always persists runs aspendingbefore execution
Use the event system (run:start,run:complete,run:fail,step:*,log:write) for extensibility
Use the plugin architecture (durably.use()) for optional features like log persistence
Files:
packages/durably/tests/browser/recovery.test.tspackages/durably/tests/browser/concurrency.test.tspackages/durably/vitest.config.tspackages/durably/tests/helpers/browser-dialect.tspackages/durably/tests/browser/worker.test.tspackages/durably/tests/browser/step.test.tspackages/durably/tests/node/plugin.test.tspackages/durably/tests/node/job.test.tspackages/durably/tests/shared/durably.shared.tspackages/durably/src/errors.tsexamples/browser/src/main.tspackages/durably/tests/browser/durably.test.tspackages/durably/tests/browser/migrate.test.tspackages/durably/tests/browser/log.test.tspackages/durably/tests/browser/job.test.tspackages/durably/tests/node/run-api.test.tspackages/durably/tests/react/strict-mode.test.tsxpackages/durably/tests/browser/run-api.test.tsexamples/react/src/main.tsxexamples/react/vite.config.tspackages/durably/tests/node/setup.test.tspackages/durably/tests/shared/run-api.shared.tspackages/durably/src/context.tspackages/durably/src/plugins/log-persistence.tspackages/durably/tests/shared/events.shared.tspackages/durably/src/migrations.tsexamples/browser/src/dashboard.tsexamples/react/src/App.tsxpackages/durably/tests/shared/recovery.shared.tspackages/durably/tests/shared/plugin.shared.tsexamples/react/src/Dashboard.tsxpackages/durably/src/schema.tspackages/durably/src/worker.tspackages/durably/tests/node/step.test.tspackages/durably/src/index.tspackages/durably/tests/node/worker.test.tspackages/durably/src/events.tspackages/durably/tests/browser/setup.test.tspackages/durably/tests/node/durably.test.tspackages/durably/src/durably.tspackages/durably/vitest.browser.config.tsexamples/node/basic.tspackages/durably/tests/shared/setup.shared.tspackages/durably/tests/shared/migrate.shared.tspackages/durably/tests/browser/plugin.test.tspackages/durably/tests/browser/events.test.tspackages/durably/src/storage.tsexamples/react/src/styles.tspackages/durably/tests/node/log.test.tspackages/durably/tests/node/recovery.test.tspackages/durably/tests/shared/worker.shared.tspackages/durably/tests/node/concurrency.test.tspackages/durably/tests/helpers/node-dialect.tspackages/durably/tests/shared/storage.shared.tspackages/durably/src/job.tspackages/durably/tests/node/events.test.tspackages/durably/tests/shared/step.shared.tspackages/durably/vitest.react.config.tspackages/durably/tests/shared/concurrency.shared.tspackages/durably/tests/shared/job.shared.tspackages/durably/tests/browser/storage.test.tsexamples/browser/vite.config.tspackages/durably/src/plugins/index.tspackages/durably/tests/node/storage.test.tspackages/durably/tests/shared/log.shared.tspackages/durably/tests/node/migrate.test.ts
**/*browser*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
For browser implementations, assume single tab usage (OPFS exclusivity) and handle background tab interruptions via heartbeat recovery
Files:
packages/durably/tests/helpers/browser-dialect.tspackages/durably/vitest.browser.config.ts
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use the plugin architecture (`durably.use()`) for optional features like log persistence
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*browser*.{js,ts,jsx,tsx} : For browser implementations, assume single tab usage (OPFS exclusivity) and handle background tab interruptions via heartbeat recovery
Applied to files:
packages/durably/tests/browser/recovery.test.tspackages/durably/tests/browser/concurrency.test.tswebsite/guide/browser.mdexamples/browser/index.htmldocs/spec-streaming.mdCLAUDE.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use the plugin architecture (`durably.use()`) for optional features like log persistence
Applied to files:
packages/durably/tsconfig.jsonwebsite/guide/events.mdpackages/durably/tests/node/plugin.test.tswebsite/guide/getting-started.mdwebsite/api/events.mdpackages/durably/src/plugins/log-persistence.tspackages/durably/tests/shared/plugin.shared.tspackages/durably/package.jsonREADME.mdpackages/durably/src/index.tspackages/durably/src/durably.tspackage.jsonexamples/react/index.htmlpackages/durably/tests/browser/plugin.test.tspackages/durably/vitest.react.config.tspackages/durably/src/plugins/index.tsCLAUDE.mdwebsite/guide/index.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use `durably.defineJob()` to define jobs that receive a context object and payload
Applied to files:
website/guide/nodejs.mdwebsite/guide/events.mdpackages/durably/tests/node/job.test.tswebsite/guide/jobs-and-steps.mdwebsite/guide/getting-started.mdexamples/browser/src/main.tspackages/durably/tests/browser/job.test.tspackages/durably/tests/shared/run-api.shared.tswebsite/api/events.mdpackages/durably/src/context.tsexamples/react/src/App.tsxpackages/durably/package.jsonpackages/durably/src/worker.tsREADME.mdwebsite/api/define-job.mdpackages/durably/src/index.tspackages/durably/src/durably.tsexamples/node/basic.tswebsite/guide/deployment.mdwebsite/api/index.mdpackages/durably/tests/shared/worker.shared.tsdocs/spec-streaming.mddocs/spec.mdwebsite/api/context.mdpackages/durably/src/job.tspackages/durably/tests/shared/job.shared.tspackages/durably/src/plugins/index.tsCLAUDE.mdwebsite/guide/index.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use the event system (`run:start`, `run:complete`, `run:fail`, `step:*`, `log:write`) for extensibility
Applied to files:
website/guide/events.md.claude/hooks/format-on-edit.tswebsite/api/events.mdpackages/durably/src/events.tsdocs/spec.mdpackages/durably/tests/shared/step.shared.tspackages/durably/tests/shared/log.shared.ts
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Use the dialect injection pattern - pass Kysely dialect to `createDurably()` to abstract SQLite implementations
Applied to files:
packages/durably/tests/helpers/browser-dialect.tspackages/durably/tests/shared/durably.shared.tspackages/durably/tests/browser/durably.test.tswebsite/api/create-durably.mdpackages/durably/tests/node/durably.test.tspackages/durably/tests/shared/setup.shared.tspackages/durably/tests/shared/migrate.shared.tspackages/durably/tests/helpers/node-dialect.tsCLAUDE.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use `ctx.run()` to create steps, where each step's success state and return value is persisted
Applied to files:
packages/durably/tests/browser/step.test.ts.claude/hooks/format-on-edit.tswebsite/guide/jobs-and-steps.mdpackages/durably/tests/shared/run-api.shared.tspackages/durably/src/context.tspackages/durably/tests/node/step.test.tspackages/durably/src/index.tsdocs/spec-streaming.mdpackages/durably/tests/shared/storage.shared.tsdocs/spec.mdwebsite/api/context.mdpackages/durably/src/job.tspackages/durably/tests/shared/step.shared.tsCLAUDE.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{sql,ts,js}@(migration|schema)* : Steps table must include fields: `status` (completed/failed), `output` (JSON), indexed by `run_id` and `index`
Applied to files:
website/guide/jobs-and-steps.mdpackages/durably/src/migrations.tspackages/durably/src/schema.tspackages/durably/tests/shared/migrate.shared.tspackages/durably/src/storage.tspackages/durably/tests/shared/storage.shared.tsdocs/spec.mdpackages/durably/tests/shared/step.shared.tsCLAUDE.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Browser implementations must require Secure Context (HTTPS/localhost) for OPFS access
Applied to files:
website/guide/browser.mdCLAUDE.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Create run instances via `trigger()` API, which always persists runs as `pending` before execution
Applied to files:
packages/durably/tests/shared/run-api.shared.tspackages/durably/src/context.tsdocs/spec.mdpackages/durably/src/job.tspackages/durably/tests/shared/job.shared.ts
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{sql,ts,js}@(migration|schema)* : Runs table must include fields: `status` (pending/running/completed/failed), `idempotency_key`, `concurrency_key`, `heartbeat_at`
Applied to files:
packages/durably/src/migrations.tspackages/durably/src/schema.tspackages/durably/tests/shared/migrate.shared.tsdocs/spec.mdCLAUDE.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{sql,ts,js}@(migration|schema)* : Database schema must include four tables: `runs`, `steps`, `logs`, `schema_versions`
Applied to files:
packages/durably/src/migrations.tspackages/durably/src/schema.tspackages/durably/tests/shared/migrate.shared.tsCLAUDE.md
🧬 Code graph analysis (41)
packages/durably/tests/browser/recovery.test.ts (2)
packages/durably/tests/shared/recovery.shared.ts (1)
createRecoveryTests(6-667)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/tests/browser/concurrency.test.ts (2)
packages/durably/tests/shared/concurrency.shared.ts (1)
createConcurrencyTests(6-169)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/tests/browser/worker.test.ts (2)
packages/durably/tests/shared/worker.shared.ts (1)
createWorkerTests(6-222)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/tests/node/plugin.test.ts (2)
packages/durably/tests/shared/plugin.shared.ts (1)
createPluginTests(6-154)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/tests/node/job.test.ts (2)
packages/durably/tests/shared/job.shared.ts (1)
createJobTests(6-230)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/src/errors.ts (2)
packages/durably/src/index.ts (1)
CancelledError(43-43)packages/durably/src/context.ts (1)
runId(19-21)
examples/browser/src/main.ts (1)
examples/browser/src/dashboard.ts (2)
refreshDashboard(25-77)initDashboard(20-23)
packages/durably/tests/browser/durably.test.ts (2)
packages/durably/tests/shared/durably.shared.ts (1)
createDurablyTests(5-71)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/tests/browser/migrate.test.ts (2)
packages/durably/tests/shared/migrate.shared.ts (1)
createMigrateTests(6-89)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/tests/browser/job.test.ts (2)
packages/durably/tests/shared/job.shared.ts (1)
createJobTests(6-230)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/tests/react/strict-mode.test.tsx (3)
packages/durably/src/durably.ts (2)
Durably(59-154)createDurably(159-291)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)packages/durably/src/context.ts (1)
run(23-112)
packages/durably/tests/browser/run-api.test.ts (2)
packages/durably/tests/shared/run-api.shared.ts (1)
createRunApiTests(6-486)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
examples/react/src/main.tsx (1)
examples/react/src/App.tsx (1)
App(122-220)
packages/durably/tests/node/setup.test.ts (2)
packages/durably/tests/shared/setup.shared.ts (1)
createSetupTests(5-28)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/tests/shared/run-api.shared.ts (3)
packages/durably/src/durably.ts (2)
Durably(59-154)createDurably(159-291)packages/durably/src/index.ts (2)
Durably(7-7)createDurably(6-6)packages/durably/src/context.ts (1)
run(23-112)
packages/durably/tests/shared/events.shared.ts (3)
packages/durably/src/durably.ts (2)
Durably(59-154)createDurably(159-291)packages/durably/src/index.ts (3)
Durably(7-7)createDurably(6-6)DurablyEvent(14-14)packages/durably/src/events.ts (1)
DurablyEvent(103-111)
packages/durably/src/migrations.ts (2)
packages/durably/src/index.ts (1)
Database(32-32)packages/durably/src/schema.ts (1)
Database(48-53)
examples/browser/src/dashboard.ts (1)
packages/durably/src/context.ts (2)
run(23-112)runId(19-21)
examples/react/src/App.tsx (2)
packages/durably/src/index.ts (1)
createDurably(6-6)examples/react/src/styles.ts (1)
styles(5-56)
packages/durably/tests/shared/plugin.shared.ts (4)
packages/durably/src/index.ts (4)
Durably(7-7)createDurably(6-6)DurablyPlugin(7-7)withLogPersistence(10-10)packages/durably/src/plugins/index.ts (1)
withLogPersistence(7-11)packages/durably/src/plugins/log-persistence.ts (1)
withLogPersistence(6-21)packages/durably/src/context.ts (1)
run(23-112)
examples/react/src/Dashboard.tsx (2)
examples/react/src/styles.ts (1)
styles(5-56)packages/durably/src/context.ts (2)
runId(19-21)run(23-112)
packages/durably/src/schema.ts (1)
packages/durably/src/index.ts (5)
RunsTable(34-34)StepsTable(36-36)LogsTable(33-33)SchemaVersionsTable(35-35)Database(32-32)
packages/durably/src/worker.ts (5)
packages/durably/src/storage.ts (1)
Storage(113-131)packages/durably/src/events.ts (1)
EventEmitter(165-181)packages/durably/src/job.ts (1)
JobRegistry(147-162)packages/durably/src/context.ts (4)
run(23-112)error(144-153)runId(19-21)createJobContext(9-156)packages/durably/src/errors.ts (1)
CancelledError(6-11)
packages/durably/tests/node/step.test.ts (1)
packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/tests/node/worker.test.ts (2)
packages/durably/tests/shared/worker.shared.ts (1)
createWorkerTests(6-222)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/tests/browser/setup.test.ts (2)
packages/durably/tests/shared/setup.shared.ts (1)
createSetupTests(5-28)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/tests/node/durably.test.ts (2)
packages/durably/tests/shared/durably.shared.ts (1)
createDurablyTests(5-71)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
examples/node/basic.ts (1)
packages/durably/src/index.ts (1)
createDurably(6-6)
packages/durably/tests/shared/migrate.shared.ts (1)
packages/durably/src/durably.ts (2)
Durably(59-154)createDurably(159-291)
packages/durably/tests/browser/plugin.test.ts (2)
packages/durably/tests/shared/plugin.shared.ts (1)
createPluginTests(6-154)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/src/storage.ts (4)
packages/durably/src/index.ts (5)
Run(40-40)RunFilter(40-40)Step(40-40)Log(40-40)Database(32-32)packages/durably/src/job.ts (1)
RunFilter(68-71)packages/durably/src/context.ts (2)
runId(19-21)run(23-112)packages/durably/src/schema.ts (1)
Database(48-53)
packages/durably/tests/node/log.test.ts (2)
packages/durably/tests/shared/log.shared.ts (1)
createLogTests(6-178)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/tests/node/recovery.test.ts (2)
packages/durably/tests/shared/recovery.shared.ts (1)
createRecoveryTests(6-667)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/tests/node/concurrency.test.ts (2)
packages/durably/tests/shared/concurrency.shared.ts (1)
createConcurrencyTests(6-169)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/tests/shared/storage.shared.ts (3)
packages/durably/src/durably.ts (2)
Durably(59-154)createDurably(159-291)packages/durably/src/index.ts (2)
Durably(7-7)createDurably(6-6)packages/durably/src/context.ts (1)
run(23-112)
packages/durably/tests/node/events.test.ts (2)
packages/durably/tests/shared/events.shared.ts (1)
createEventsTests(5-193)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
packages/durably/tests/shared/concurrency.shared.ts (1)
packages/durably/src/durably.ts (2)
Durably(59-154)createDurably(159-291)
packages/durably/tests/shared/job.shared.ts (3)
packages/durably/src/durably.ts (2)
Durably(59-154)createDurably(159-291)packages/durably/src/index.ts (2)
Durably(7-7)createDurably(6-6)packages/durably/src/context.ts (1)
run(23-112)
packages/durably/tests/browser/storage.test.ts (2)
packages/durably/tests/shared/storage.shared.ts (1)
createStorageTests(5-182)packages/durably/tests/helpers/browser-dialect.ts (1)
createBrowserDialect(5-10)
packages/durably/tests/shared/log.shared.ts (3)
packages/durably/src/durably.ts (2)
Durably(59-154)createDurably(159-291)packages/durably/src/events.ts (1)
LogWriteEvent(81-88)packages/durably/src/context.ts (1)
run(23-112)
packages/durably/tests/node/migrate.test.ts (2)
packages/durably/tests/shared/migrate.shared.ts (1)
createMigrateTests(6-89)packages/durably/tests/helpers/node-dialect.ts (1)
createNodeDialect(6-12)
🪛 ast-grep (0.40.0)
examples/browser/src/dashboard.ts
[warning] 28-28: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: runsTbody.innerHTML = <tr><td colspan="5" class="empty-state">No runs yet</td></tr>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 33-50: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: runsTbody.innerHTML = runs
.map(
(run) => <tr data-id="${run.id}"> <td class="run-id">${run.id.slice(0, 8)}...</td> <td>${run.jobName}</td> <td><span class="status-badge status-${run.status}">${run.status}</span></td> <td>${formatDate(run.createdAt)}</td> <td> <button class="action-btn view-btn" data-id="${run.id}">View</button> ${run.status === 'failed' ?Retry: ''} ${run.status === 'running' || run.status === 'pending' ?Cancel: ''} ${run.status !== 'running' && run.status !== 'pending' ?Delete: ''} </td> </tr>,
)
.join('')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 87-107: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: detailsContent.innerHTML = <p><strong>ID:</strong> <span class="run-id">${run.id}</span></p> <p><strong>Job:</strong> ${run.jobName}</p> <p><strong>Status:</strong> <span class="status-badge status-${run.status}">${run.status}</span></p> <p><strong>Created:</strong> ${formatDate(run.createdAt)}</p> ${run.progress ?
Progress: ${run.progress.current}${run.progress.total ? /${run.progress.total} : ''} ${run.progress.message || ''}
: ''} ${run.error ?Error: ${run.error}
: ''} ${run.output ?Output:
${JSON.stringify(run.output, null, 2)}
: ''} <p><strong>Payload:</strong></p> <pre class="result">${JSON.stringify(run.payload, null, 2)}</pre> ${ steps.length > 0 ?Steps:
${steps.map((s) =>
<li><span>${s.name}</span><span class="status-badge status-${s.status === 'completed' ? 'completed' : 'failed'}">${s.status}</span></li>).join('')}: '' }Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 28-28: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: runsTbody.innerHTML = <tr><td colspan="5" class="empty-state">No runs yet</td></tr>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 33-50: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: runsTbody.innerHTML = runs
.map(
(run) => <tr data-id="${run.id}"> <td class="run-id">${run.id.slice(0, 8)}...</td> <td>${run.jobName}</td> <td><span class="status-badge status-${run.status}">${run.status}</span></td> <td>${formatDate(run.createdAt)}</td> <td> <button class="action-btn view-btn" data-id="${run.id}">View</button> ${run.status === 'failed' ?Retry: ''} ${run.status === 'running' || run.status === 'pending' ?Cancel: ''} ${run.status !== 'running' && run.status !== 'pending' ?Delete: ''} </td> </tr>,
)
.join('')
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 87-107: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: detailsContent.innerHTML = <p><strong>ID:</strong> <span class="run-id">${run.id}</span></p> <p><strong>Job:</strong> ${run.jobName}</p> <p><strong>Status:</strong> <span class="status-badge status-${run.status}">${run.status}</span></p> <p><strong>Created:</strong> ${formatDate(run.createdAt)}</p> ${run.progress ?
Progress: ${run.progress.current}${run.progress.total ? /${run.progress.total} : ''} ${run.progress.message || ''}
: ''} ${run.error ?Error: ${run.error}
: ''} ${run.output ?Output:
${JSON.stringify(run.output, null, 2)}
: ''} <p><strong>Payload:</strong></p> <pre class="result">${JSON.stringify(run.payload, null, 2)}</pre> ${ steps.length > 0 ?Steps:
${steps.map((s) =>
<li><span>${s.name}</span><span class="status-badge status-${s.status === 'completed' ? 'completed' : 'failed'}">${s.status}</span></li>).join('')}: '' }Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
🪛 GitHub Actions: CI
examples/node/basic.ts
[error] 8-8: Cannot find module '@coji/durably' or its corresponding type declarations. TS2307.
🪛 LanguageTool
website/api/events.md
[style] ~51-~51: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ... number // } }) #### `run:fail` Fired when a run fails. ts durably.on('ru...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~109-~109: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...number // } }) #### `step:fail` Fired when a step fails. ts durably.on('s...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
website/api/create-durably.md
[style] ~47-~47: Consider using “who” when you are referring to a person instead of an object.
Context: ...ly.start(): void ``` Starts the worker that processes pending jobs. ### stop() ...
(THAT_WHO)
- Add zod to installation commands (peer dependency) - Update description to mention both Kysely and Zod as peer deps - Add browser installation option with SQLocal 🤖 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
🧹 Nitpick comments (1)
README.md (1)
29-66: Consider adding plugin and event system examples.The usage example effectively demonstrates the core
defineJob()API with context-based execution and Zod schemas. However, the PR delivers both a plugin architecture (durably.use()) and an event system for monitoring, which are mentioned in the Features section but not demonstrated. Adding brief code snippets showing event subscription or plugin usage would help users discover these capabilities early.This is optional but would improve discoverability of the extensibility features noted in line 13.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
README.md(2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use `durably.defineJob()` to define jobs that receive a context object and payload
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use the plugin architecture (`durably.use()`) for optional features like log persistence
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use `durably.defineJob()` to define jobs that receive a context object and payload
Applied to files:
README.md
📚 Learning: 2025-12-20T14:01:28.808Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-20T14:01:28.808Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use the plugin architecture (`durably.use()`) for optional features like log persistence
Applied to files:
README.md
🔇 Additional comments (3)
README.md (3)
5-5: Verify external links are stable and correctly formatted.The documentation link and live demo link should be validated to ensure they resolve correctly and remain stable as the project evolves. Consider adding link-checking to CI/docs workflows if not already present.
[scratchpad_end] -->
19-27: Installation section clearly covers multiple environments.The three installation paths (better-sqlite3, libsql, SQLocal) are well-organized and appropriate for the cross-environment support delivered in this PR. Each option is clearly labeled and separated.
68-72: Documentation references are appropriately scoped.The specification and streaming extension links are helpful, and the note that streaming is "conceptual, not yet implemented" manages expectations appropriately. This aligns well with the PR's current delivery scope.
Remove db.destroy() call in afterEach as it can cause "driver has already been destroyed" errors when async operations like migrations are still pending. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add status check after event subscription to handle the case where the run completes before the event listeners are registered. This prevents the promise from hanging indefinitely when the worker processes the run very quickly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- browser: Add escapeHtml helper and sanitize user-controlled data in innerHTML - react: Use stable index property for step list keys instead of array index 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
Key Features
Changes
packages/durably)website/)examples/)z.ZodTypeinstead of deprecatedz.ZodTypeAny)Test plan
pnpm test:node)pnpm test:browser)pnpm test:react)pnpm typecheck)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.