Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
828683e
docs: add React package spec and implementation plan
coji Dec 23, 2025
17a4593
Merge remote-tracking branch 'origin/main' into feature/durably-react
coji Dec 23, 2025
cd0c4d3
docs: rewrite spec-react.md with AI SDK v5 style architecture
coji Dec 24, 2025
1ced540
docs: add language identifiers to fenced code blocks
coji Dec 24, 2025
2af91fc
docs: fix API specification inconsistencies in spec-react.md
coji Dec 28, 2025
afb9629
docs: unify API consistency across browser and server modes
coji Dec 28, 2025
b1dcdae
docs: remove AI SDK v5 reference from spec-react.md
coji Dec 28, 2025
da2cf42
docs: clarify useJobLogs runId requirement and isReady behavior
coji Dec 28, 2025
d330146
docs: rewrite implementation plan for durably-react with dual mode su…
coji Dec 28, 2025
dbaa0f2
docs: rewrite implementation plan with TDD approach (30 phases)
coji Dec 28, 2025
5c45a0f
docs: improve implementation plan based on review feedback
coji Dec 28, 2025
a403aec
docs: fix phase numbering in implementation plan
coji Dec 28, 2025
b471c0f
docs: update implementation plan and specifications for runId handlin…
coji Dec 28, 2025
2efac24
feat(durably-react): add package foundation and DurablyProvider (Phas…
coji Dec 28, 2025
05e6ae0
feat(durably-react): add useJob hook for browser-complete mode (Phase…
coji Dec 28, 2025
de50eef
feat(durably-react): add useJobRun, useJobLogs hooks and common subsc…
coji Dec 28, 2025
97bfae7
feat(durably): add getJob, subscribe, and createDurablyHandler (Phase…
coji Dec 28, 2025
dc9f0d0
feat(durably-react): add client-mode hooks for server integration (Ph…
coji Dec 29, 2025
3789515
feat(durably-react): add client entry point and type tests (Phase 28-29)
coji Dec 29, 2025
8b3f2e2
chore: remove implementation plan phase references from test files
coji Dec 29, 2025
561d8b2
chore(durably-react): bump version to 0.5.0 to match durably
coji Dec 29, 2025
60489c9
docs(durably-react): add README and LLM documentation
coji Dec 29, 2025
3226d4c
docs(durably): add Advanced APIs section to LLM documentation
coji Dec 29, 2025
2d10bb4
docs: comprehensive documentation update for durably-react
coji Dec 29, 2025
4b5d61a
docs(website): remove redundant Low-Level Integration section from Re…
coji Dec 29, 2025
21d9b29
docs: remove browser guide and consolidate content into react guide
coji Dec 29, 2025
311f619
docs: reorganize guides by usage pattern
coji Dec 29, 2025
c4321e1
docs: improve introduction pages with clear use cases
coji Dec 29, 2025
1c6c863
docs: update biome configuration and add biomejs dependency
coji Dec 29, 2025
4d6e466
refactor: rename registerAll to register and update docs
coji Dec 29, 2025
664fca0
docs: simplify READMEs and update examples to new register API
coji Dec 29, 2025
0da6872
chore: bump version to 0.6.0
coji Dec 29, 2025
8f1fa84
refactor(examples/react): use @coji/durably-react instead of custom h…
coji Dec 29, 2025
3edb368
style: format README code examples
coji Dec 29, 2025
bae79fa
build: add turbo for monorepo task orchestration
coji Dec 29, 2025
4918047
feat(durably-react): add autoResume option and improve React example
coji Dec 30, 2025
39ba815
feat(durably-react): improve job tracking and add pagination
coji Dec 30, 2025
e7151c0
feat(durably-react): add useRuns hook and followLatest option
coji Dec 30, 2025
e30ea16
test(durably-react): add tests for useRuns hook and followLatest option
coji Dec 30, 2025
e1f69cb
test(durably-react): add tests for client factories and coverage config
coji Dec 30, 2025
3a6c925
style: format button and getRuns parameters for improved readability
coji Dec 30, 2025
93f8ce6
refactor: clean up tests and fix lint warnings
coji Dec 30, 2025
536f1c7
chore(durably): enable coverage reporting in vitest config
coji Dec 30, 2025
9498c0a
docs: update CHANGELOG for 0.6.0 release
coji Dec 30, 2025
4591fce
refactor(example-react): simplify using new useRuns and boolean helpers
coji Dec 30, 2025
532ce43
feat(durably-react): add SSE-based useRuns hook and server subscribeR…
coji Dec 30, 2025
94656ad
refactor(example-react-router): improve type safety and documentation
coji Dec 30, 2025
a47af9b
chore(example-react-router): add prettier and biome configuration
coji Dec 30, 2025
a51799a
chore(examples): add biome and prettier configuration to all examples
coji Dec 30, 2025
549d40f
chore: remove browser example
coji Dec 30, 2025
3b125e3
feat(example-react-router): add cancel button to dashboard
coji Dec 30, 2025
b33c220
feat(durably-react): add real-time progress updates via SSE
coji Dec 31, 2025
5d15314
test(durably-react): add client mode useRuns hook tests
coji Dec 31, 2025
2eeadab
style: fix formatting in use-runs files
coji Dec 31, 2025
5f809f3
fix(durably-react): add 'cancelled' to RunStatus type
coji Dec 31, 2025
e14cc42
test(durably-react): add useRunActions client mode tests
coji Dec 31, 2025
1835185
docs: sync spec.md with implementation
coji Dec 31, 2025
fe47fb7
feat(durably-react): add initialRunId to client mode useJob
coji Dec 31, 2025
e289296
docs(spec-react): sync with implementation
coji Dec 31, 2025
e9489ab
refactor(durably): rename subscribeRuns to runsSubscribe for API nami…
coji Dec 31, 2025
cf8e012
docs(spec): sync Storage interface with implementation
coji Dec 31, 2025
664bcc1
chore(docs): remove outdated spec-react update plan
coji Dec 31, 2025
5181699
docs(spec-streaming): revise for v2 planning, reflect v1 subscribe() …
coji Dec 31, 2025
013c2b8
refactor(examples): reorganize by use case and add SPA example
coji Dec 31, 2025
a1cae7e
fix(example): add comment for sqlocal instance in SPA example
coji Dec 31, 2025
f3d81bc
fix(example): fix unknown type check for output in SPA example
coji Dec 31, 2025
34eeb14
refactor(durably-react): simplify DurablyProvider to accept durably p…
coji Dec 31, 2025
8ffe82f
feat(durably): add durably.jobs for type-safe job access
coji Jan 2, 2026
66a85b3
fix(examples): fix typecheck OOM by aligning zod versions
coji Jan 2, 2026
842b1c0
refactor(durably): make register() return new Durably instance with t…
coji Jan 2, 2026
e2ceae9
refactor(examples): align structure and UI across all examples
coji Jan 2, 2026
252f1b2
test(durably): add server.ts HTTP handler tests
coji Jan 2, 2026
c9a4e43
feat(durably): add run:trigger and run:cancel events for real-time da…
coji Jan 2, 2026
f92782f
feat(durably): add run:retry event and fix subscribe() to handle run:…
coji Jan 2, 2026
6c8c2fb
feat(example): add retry button to fullstack dashboard
coji Jan 2, 2026
c7fa85f
feat(durably-react): add run:cancel and run:retry event subscriptions…
coji Jan 2, 2026
024d60f
feat(durably-react): add isCancelled to useJob hook and update examples
coji Jan 2, 2026
102eb58
fix(durably): keep SSE stream open on fail/cancel to allow retry trac…
coji Jan 2, 2026
53982e4
feat(durably): add delete endpoint and enhance useRunActions
coji Jan 2, 2026
9992fb1
feat(durably): add steps endpoint and unify example dashboards
coji Jan 2, 2026
272e0c9
docs: update documentation to match v0.6.0 API implementation
coji Jan 2, 2026
f12918e
docs(durably-react): fix useRunActions example in llms.md
coji Jan 2, 2026
c31418d
fix(durably-react): move setState out of render phase in client useJob
coji Jan 2, 2026
b883600
fix(durably-react): guard against non-JSON error responses in useRunA…
coji Jan 2, 2026
42959ef
fix(durably-react): handle cancelled status in triggerAndWait
coji Jan 2, 2026
9c06132
fix(durably-react): track effectiveStatus for callbacks in client use…
coji Jan 2, 2026
4486a12
fix(durably): add cancel handler for ReadableStream cleanup in subscribe
coji Jan 2, 2026
d6b133f
feat(durably): add stepCount to Run type for step progress tracking
coji Jan 2, 2026
b5794c0
refactor(examples): unify react-router examples UI
coji Jan 2, 2026
563ef71
refactor(examples): unify browser-vite-react dashboard with react-rou…
coji Jan 2, 2026
2b965dd
fix(examples): guard against division by zero in progress bar calcula…
coji Jan 2, 2026
7b0d055
feat(durably): add init() method and restructure documentation
coji Jan 3, 2026
b524877
refactor: unify browser examples to use init() instead of migrate()
coji Jan 3, 2026
371626b
refactor(durably-react): simplify DurablyProvider, remove autoStart/o…
coji Jan 3, 2026
b5a4f82
fix(durably-react): update tests for simplified DurablyProvider
coji Jan 3, 2026
b29e9b5
docs: update CHANGELOG and READMEs for 0.6.0 API changes
coji Jan 3, 2026
73964ed
fix: add real-time step/progress updates to dashboard
coji Jan 3, 2026
324ce09
docs: update website with React 19 requirement and SSE events
coji Jan 3, 2026
698b4a0
docs(website): restructure API docs with hierarchical navigation
coji Jan 3, 2026
e3c94a8
docs: update llms.md for both packages to match latest API
coji Jan 3, 2026
80c7188
build(website): auto-generate llms.txt from package docs
coji Jan 3, 2026
767f461
fix: improve button formatting in Dashboard component
coji Jan 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ dist/
__screenshots__/
local.db
.serena/
.turbo/

# VitePress
website/.vitepress/cache/
website/.vitepress/dist/

# test coverage
coverage
8 changes: 8 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
pnpm-lock.yaml
dist/
node_modules/
.turbo/
__screenshots__/
website/.vitepress/cache/
website/.vitepress/dist/
*.log
CHANGELOG.md
114 changes: 104 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,117 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [0.6.0] - 2026-01-02

### Breaking Changes

#### @coji/durably

- **`register()` API simplified**: `registerAll()` renamed to `register()`, old single-job signature removed
```diff
- const job = durably.register(jobDef)
+ const { job } = durably.register({ job: jobDef })
```

#### @coji/durably-react

- **`DurablyProvider` simplified**: Removed `autoStart`, `onReady` props and `isReady` state
- Provider now only provides context; durably instance must be initialized before passing
- All hooks no longer return `isReady` - always ready when durably is available
- Migration: Call `await durably.init()` before passing to `DurablyProvider`
```diff
- <DurablyProvider durably={durably} autoStart onReady={() => console.log('ready')}>
+ <DurablyProvider durably={durably}>
```
- **React 19 required**: `peerDependencies` now requires React 19+ (uses `React.use()` hook)

### Added

#### @coji/durably

- **`init()` method**: Combines `migrate()` and `start()` for simpler initialization
```ts
const durably = createDurably({ dialect })
await durably.init() // migrate + start in one call
```
- **Type-safe `durably.jobs` property**: Access registered jobs with full type inference
```ts
const durably = createDurably({ dialect }).register({ processImage, syncUsers })
await durably.jobs.processImage.trigger({ imageId: '123' }) // Type-safe
```
- **Retry from cancelled state**: `retry()` now works on both `failed` and `cancelled` runs
- **New events**: `run:trigger`, `run:cancel`, `run:retry` for complete run lifecycle tracking
- **`stepCount` on `Run` type**: Number of completed steps, available in `getRun()`, `getRuns()`

#### @coji/durably/server

- **New endpoints**: `GET /steps?runId=xxx`, `DELETE /run?runId=xxx`
- **SSE enhancements**: `/runs/subscribe` now streams all lifecycle events
- Run events: `run:trigger`, `run:start`, `run:complete`, `run:fail`, `run:cancel`, `run:retry`, `run:progress`
- Step events: `step:start`, `step:complete`, `step:fail`
- Log events: `log:write`

#### @coji/durably-react

- **`useRuns` hook**: List and paginate runs with filtering (`jobName`, `status`) and real-time updates
- Subscribes to step and progress events for live dashboard updates
- **`useJob` options**: `autoResume` (track pending/running jobs on mount), `followLatest` (switch to latest run)
- **`createDurablyClient`**: Type-safe client factory for server-connected mode
- **`createJobHooks`**: Per-job hook factory for server-connected mode

#### @coji/durably-react/client

- **`useRunActions` enhancements**: `deleteRun()`, `getRun()`, `getSteps()`
- **Step progress**: `stepCount` and `currentStepIndex` on `ClientRun` and `RunRecord` types
- **New type exports**: `RunRecord`, `StepRecord`

### Changed

- Simplified README files - detailed documentation moved to website
- Updated all examples to use new `register()` API pattern
- Added Turbo for monorepo task orchestration
- Unified dashboard UI across all examples

### Fixed

- Type inference for `register()` return value now works correctly
- SSE stream lifecycle properly cleaned up on client disconnect

## [0.5.0] - 2025-12-24

### Added

#### @coji/durably

- `run:progress` event: Now emitted when `step.progress()` is called
- Enables real-time progress tracking via event subscription
- Event payload: `{ runId, jobName, progress: { current, total?, message? } }`
- `getJob(name)`: Retrieve a registered job by name
- `subscribe(runId)`: Subscribe to run events as a ReadableStream
- `createDurablyHandler(durably)`: Create HTTP handlers for client/server architecture

#### @coji/durably-react (New Package)

- Initial release of React bindings for Durably
- **Browser-complete mode**: Run Durably entirely in the browser with SQLite WASM
- `DurablyProvider`: React context provider for Durably instance
- `useDurably`: Access the Durably instance directly
- `useJob`: Trigger and monitor a job with real-time updates
- `useJobRun`: Subscribe to an existing run by ID
- `useJobLogs`: Subscribe to logs from a run
- **Server-connected mode**: Connect to a remote Durably server via SSE
- `useJob`, `useJobRun`, `useJobLogs` from `@coji/durably-react/client`
- Works with `createDurablyHandler` on the server

## [0.4.0] - 2025-12-23

### Breaking Changes

- **New API pattern**: `defineJob()` + `durably.register()` replaces `durably.defineJob()`
- `defineJob()` is now a standalone function that creates a `JobDefinition`
- `durably.register(jobDef)` registers the definition and returns a `JobHandle`
- `durably.register({ name: jobDef })` registers jobs and returns an object of `JobHandle`s
- This enables idempotent registration (safe for React StrictMode)
- Supports registering multiple jobs in a single call

### Migration

Expand All @@ -34,15 +129,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- }, async (step, payload) => {
- // ...
- })
+ const myJob = durably.register(
+ defineJob({
+ name: 'my-job',
+ input: z.object({ id: z.string() }),
+ run: async (step, payload) => {
+ // ...
+ },
+ })
+ )
+ const myJobDef = defineJob({
+ name: 'my-job',
+ input: z.object({ id: z.string() }),
+ run: async (step, payload) => {
+ // ...
+ },
+ })
+ const { myJob } = durably.register({ myJob: myJobDef })
```

### Removed
Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ When API changes are made, update `packages/durably/docs/llms.md` to keep it in

## Core Concepts

- **Job**: Defined via `durably.defineJob()`, receives a step context and payload
- **Job**: Defined via `defineJob()` and registered with `durably.register()`, receives a step context and payload
- **Step**: Created via `step.run()`, each step's success state and return value is persisted
- **Run**: A job execution instance, created via `trigger()`, always persisted as `pending` before execution
- **Worker**: Polls for pending runs and executes them sequentially

## Key Design Decisions

- **ESM-only**: This library is ESM-only. CommonJS is not supported. Always use top-level `await` for async initialization (e.g., `await durably.migrate()`). Do not wrap in async IIFE or Promise chains.
- Single-threaded execution, no parallel run processing in minimal config
- No automatic retry - failures are immediate and explicit (`retry()` API for manual retry)
- Dialect injection pattern - Kysely dialect passed to `createDurably()` to abstract SQLite implementations
Expand Down
64 changes: 9 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Step-oriented resumable batch execution for Node.js and browsers using SQLite.

**[Documentation](https://coji.github.io/durably/)** | **[Live Demo](https://durably-demo.vercel.app)**

## Packages

| Package | Description |
|---------|-------------|
| [@coji/durably](./packages/durably) | Core library - job definitions, steps, and persistence |
| [@coji/durably-react](./packages/durably-react) | React bindings - hooks for triggering and monitoring jobs |

## Features

- Resumable batch processing with step-level persistence
Expand All @@ -13,62 +20,9 @@ Step-oriented resumable batch execution for Node.js and browsers using SQLite.
- Event system for monitoring and extensibility
- Type-safe input/output with Zod schemas

## Installation

```bash
# Node.js with better-sqlite3
npm install @coji/durably kysely zod better-sqlite3

# Node.js with libsql
npm install @coji/durably kysely zod @libsql/client @libsql/kysely-libsql

# Browser with SQLocal
npm install @coji/durably kysely zod sqlocal
```

## Usage

```ts
import { createDurably } from '@coji/durably'
import SQLite from 'better-sqlite3'
import { SqliteDialect } from 'kysely'
import { z } from 'zod'

const dialect = new SqliteDialect({
database: new SQLite('local.db'),
})

const durably = createDurably({ dialect })

const syncUsers = durably.defineJob(
{
name: 'sync-users',
input: z.object({ orgId: z.string() }),
output: z.object({ syncedCount: z.number() }),
},
async (step, payload) => {
const users = await step.run('fetch-users', async () => {
return api.fetchUsers(payload.orgId)
})

await step.run('save-to-db', async () => {
await db.upsertUsers(users)
})

return { syncedCount: users.length }
},
)

await durably.migrate()
durably.start()

await syncUsers.trigger({ orgId: 'org_123' })
```

## Documentation
## Quick Start

- [Specification](docs/spec.md) - Core API and concepts
- [Streaming Extension](docs/spec-streaming.md) - AI Agent workflow support (conceptual, not yet implemented)
See the [Getting Started Guide](https://coji.github.io/durably/guide/getting-started) for installation and usage instructions.

## License

Expand Down
57 changes: 29 additions & 28 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"files": {
"includes": ["**"]
},
"assist": { "actions": { "source": { "organizeImports": "off" } } },
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"overrides": [
{
"includes": ["**/tests/**"],
"linter": {
"rules": {
"style": {
"noNonNullAssertion": "off"
}
}
}
}
]
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
"root": true,
"files": {
"includes": ["**"]
},
"assist": { "actions": { "source": { "organizeImports": "off" } } },
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"overrides": [
{
"includes": ["**/tests/**"],
"linter": {
"rules": {
"style": {
"noNonNullAssertion": "off"
}
}
}
}
]
}
Loading