@coji/durably 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/llms.md CHANGED
@@ -10,10 +10,10 @@ Durably is a minimal workflow engine that persists step results to SQLite. If a
10
10
 
11
11
  ```bash
12
12
  # Node.js with libsql (recommended)
13
- npm install @coji/durably kysely zod @libsql/client @libsql/kysely-libsql
13
+ pnpm add @coji/durably kysely zod @libsql/client @libsql/kysely-libsql
14
14
 
15
15
  # Browser with SQLocal
16
- npm install @coji/durably kysely zod sqlocal
16
+ pnpm add @coji/durably kysely zod sqlocal
17
17
  ```
18
18
 
19
19
  ## Core Concepts
@@ -61,18 +61,21 @@ const syncUsersJob = defineJob({
61
61
  },
62
62
  })
63
63
 
64
- // Register the job with durably instance
65
- const syncUsers = durably.register(syncUsersJob)
64
+ // Register jobs with durably instance
65
+ const { syncUsers } = durably.register({
66
+ syncUsers: syncUsersJob,
67
+ })
66
68
  ```
67
69
 
68
70
  ### 3. Starting the Worker
69
71
 
70
72
  ```ts
71
- // Run migrations (creates tables if needed)
72
- await durably.migrate()
73
+ // Initialize: runs migrations and starts the worker
74
+ await durably.init()
73
75
 
74
- // Start the worker (polls for pending jobs)
75
- durably.start()
76
+ // Or separately if needed:
77
+ // await durably.migrate() // Run migrations only
78
+ // durably.start() // Start worker only
76
79
  ```
77
80
 
78
81
  ### 4. Triggering Jobs
@@ -184,25 +187,134 @@ await durably.deleteRun(runId)
184
187
  Subscribe to job execution events:
185
188
 
186
189
  ```ts
190
+ // Run lifecycle events
191
+ durably.on('run:trigger', (e) => console.log('Triggered:', e.runId))
187
192
  durably.on('run:start', (e) => console.log('Started:', e.runId))
188
193
  durably.on('run:complete', (e) => console.log('Done:', e.output))
189
194
  durably.on('run:fail', (e) => console.error('Failed:', e.error))
195
+ durably.on('run:cancel', (e) => console.log('Cancelled:', e.runId))
196
+ durably.on('run:retry', (e) => console.log('Retried:', e.runId))
197
+ durably.on('run:progress', (e) =>
198
+ console.log('Progress:', e.progress.current, '/', e.progress.total),
199
+ )
190
200
 
201
+ // Step events
191
202
  durably.on('step:start', (e) => console.log('Step:', e.stepName))
192
203
  durably.on('step:complete', (e) => console.log('Step done:', e.stepName))
193
- durably.on('step:skip', (e) =>
194
- console.log('Step skipped (cached):', e.stepName),
195
- )
204
+ durably.on('step:fail', (e) => console.error('Step failed:', e.stepName))
196
205
 
206
+ // Log events
197
207
  durably.on('log:write', (e) => console.log(`[${e.level}]`, e.message))
198
208
  ```
199
209
 
210
+ ## Advanced APIs
211
+
212
+ ### getJob
213
+
214
+ Get a registered job by name:
215
+
216
+ ```ts
217
+ const job = durably.getJob('sync-users')
218
+ if (job) {
219
+ const run = await job.trigger({ orgId: 'org_123' })
220
+ }
221
+ ```
222
+
223
+ ### subscribe
224
+
225
+ Subscribe to events for a specific run as a ReadableStream:
226
+
227
+ ```ts
228
+ const stream = durably.subscribe(runId)
229
+ const reader = stream.getReader()
230
+
231
+ while (true) {
232
+ const { done, value } = await reader.read()
233
+ if (done) break
234
+
235
+ switch (value.type) {
236
+ case 'run:start':
237
+ console.log('Started')
238
+ break
239
+ case 'run:complete':
240
+ console.log('Completed:', value.output)
241
+ break
242
+ case 'run:fail':
243
+ console.error('Failed:', value.error)
244
+ break
245
+ case 'run:progress':
246
+ console.log('Progress:', value.progress)
247
+ break
248
+ case 'log:write':
249
+ console.log(`[${value.level}]`, value.message)
250
+ break
251
+ }
252
+ }
253
+ ```
254
+
255
+ ### createDurablyHandler
256
+
257
+ Create HTTP handlers for client/server architecture using Web Standard Request/Response:
258
+
259
+ ```ts
260
+ import { createDurablyHandler } from '@coji/durably'
261
+
262
+ const handler = createDurablyHandler(durably)
263
+
264
+ // Use the unified handle() method with automatic routing
265
+ app.all('/api/durably/*', async (req) => {
266
+ return await handler.handle(req, '/api/durably')
267
+ })
268
+
269
+ // Or use individual endpoints
270
+ app.post('/api/durably/trigger', (req) => handler.trigger(req))
271
+ app.get('/api/durably/subscribe', (req) => handler.subscribe(req))
272
+ app.get('/api/durably/runs', (req) => handler.runs(req))
273
+ app.get('/api/durably/run', (req) => handler.run(req))
274
+ app.get('/api/durably/steps', (req) => handler.steps(req))
275
+ app.get('/api/durably/runs/subscribe', (req) => handler.runsSubscribe(req))
276
+ app.post('/api/durably/retry', (req) => handler.retry(req))
277
+ app.post('/api/durably/cancel', (req) => handler.cancel(req))
278
+ app.delete('/api/durably/run', (req) => handler.delete(req))
279
+ ```
280
+
281
+ **Handler Interface:**
282
+
283
+ ```ts
284
+ interface DurablyHandler {
285
+ // Unified routing handler
286
+ handle(request: Request, basePath: string): Promise<Response>
287
+
288
+ // Individual endpoints
289
+ trigger(request: Request): Promise<Response> // POST /trigger
290
+ subscribe(request: Request): Response // GET /subscribe?runId=xxx (SSE)
291
+ runs(request: Request): Promise<Response> // GET /runs
292
+ run(request: Request): Promise<Response> // GET /run?runId=xxx
293
+ steps(request: Request): Promise<Response> // GET /steps?runId=xxx
294
+ runsSubscribe(request: Request): Response // GET /runs/subscribe (SSE)
295
+ retry(request: Request): Promise<Response> // POST /retry?runId=xxx
296
+ cancel(request: Request): Promise<Response> // POST /cancel?runId=xxx
297
+ delete(request: Request): Promise<Response> // DELETE /run?runId=xxx
298
+ }
299
+
300
+ interface TriggerRequest {
301
+ jobName: string
302
+ input: Record<string, unknown>
303
+ idempotencyKey?: string
304
+ concurrencyKey?: string
305
+ }
306
+
307
+ interface TriggerResponse {
308
+ runId: string
309
+ }
310
+ ```
311
+
200
312
  ## Plugins
201
313
 
202
314
  ### Log Persistence
203
315
 
204
316
  ```ts
205
- import { withLogPersistence } from '@coji/durably/plugins'
317
+ import { withLogPersistence } from '@coji/durably'
206
318
 
207
319
  durably.use(withLogPersistence())
208
320
  ```
@@ -224,18 +336,18 @@ const durably = createDurably({
224
336
  })
225
337
 
226
338
  // Same API as Node.js
227
- const myJob = durably.register(
228
- defineJob({
339
+ const { myJob } = durably.register({
340
+ myJob: defineJob({
229
341
  name: 'my-job',
230
342
  input: z.object({}),
231
343
  run: async (step) => {
232
344
  /* ... */
233
345
  },
234
346
  }),
235
- )
347
+ })
236
348
 
237
- await durably.migrate()
238
- durably.start()
349
+ // Initialize (same as Node.js)
350
+ await durably.init()
239
351
  ```
240
352
 
241
353
  ## Run Lifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coji/durably",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Step-oriented resumable batch execution for Node.js and browsers using SQLite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "homepage": "https://github.com/coji/durably#readme",
43
43
  "peerDependencies": {
44
- "kysely": ">=0.27.0",
45
- "zod": ">=4.0.0"
44
+ "kysely": "^0.27.0",
45
+ "zod": "^4.0.0"
46
46
  },
47
47
  "dependencies": {
48
48
  "ulidx": "^2.4.1"
@@ -68,7 +68,7 @@
68
68
  "tsup": "^8.5.1",
69
69
  "typescript": "^5.9.3",
70
70
  "vitest": "^4.0.16",
71
- "zod": "^4.2.1"
71
+ "zod": "^4.3.4"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsup",