@coji/durably 0.3.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/README.md +15 -44
- package/dist/index.d.ts +281 -22
- package/dist/index.js +683 -63
- package/dist/index.js.map +1 -1
- package/docs/llms.md +151 -22
- package/package.json +4 -4
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
|
-
|
|
13
|
+
pnpm add @coji/durably kysely zod @libsql/client @libsql/kysely-libsql
|
|
14
14
|
|
|
15
15
|
# Browser with SQLocal
|
|
16
|
-
|
|
16
|
+
pnpm add @coji/durably kysely zod sqlocal
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Core Concepts
|
|
@@ -39,15 +39,14 @@ const durably = createDurably({
|
|
|
39
39
|
### 2. Job Definition
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
|
+
import { defineJob } from '@coji/durably'
|
|
42
43
|
import { z } from 'zod'
|
|
43
44
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
},
|
|
50
|
-
async (step, payload) => {
|
|
45
|
+
const syncUsersJob = defineJob({
|
|
46
|
+
name: 'sync-users',
|
|
47
|
+
input: z.object({ orgId: z.string() }),
|
|
48
|
+
output: z.object({ syncedCount: z.number() }),
|
|
49
|
+
run: async (step, payload) => {
|
|
51
50
|
// Step 1: Fetch users (result is persisted)
|
|
52
51
|
const users = await step.run('fetch-users', async () => {
|
|
53
52
|
return await api.fetchUsers(payload.orgId)
|
|
@@ -60,17 +59,23 @@ const syncUsers = durably.defineJob(
|
|
|
60
59
|
|
|
61
60
|
return { syncedCount: users.length }
|
|
62
61
|
},
|
|
63
|
-
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Register jobs with durably instance
|
|
65
|
+
const { syncUsers } = durably.register({
|
|
66
|
+
syncUsers: syncUsersJob,
|
|
67
|
+
})
|
|
64
68
|
```
|
|
65
69
|
|
|
66
70
|
### 3. Starting the Worker
|
|
67
71
|
|
|
68
72
|
```ts
|
|
69
|
-
//
|
|
70
|
-
await durably.
|
|
73
|
+
// Initialize: runs migrations and starts the worker
|
|
74
|
+
await durably.init()
|
|
71
75
|
|
|
72
|
-
//
|
|
73
|
-
durably.
|
|
76
|
+
// Or separately if needed:
|
|
77
|
+
// await durably.migrate() // Run migrations only
|
|
78
|
+
// durably.start() // Start worker only
|
|
74
79
|
```
|
|
75
80
|
|
|
76
81
|
### 4. Triggering Jobs
|
|
@@ -182,25 +187,134 @@ await durably.deleteRun(runId)
|
|
|
182
187
|
Subscribe to job execution events:
|
|
183
188
|
|
|
184
189
|
```ts
|
|
190
|
+
// Run lifecycle events
|
|
191
|
+
durably.on('run:trigger', (e) => console.log('Triggered:', e.runId))
|
|
185
192
|
durably.on('run:start', (e) => console.log('Started:', e.runId))
|
|
186
193
|
durably.on('run:complete', (e) => console.log('Done:', e.output))
|
|
187
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
|
+
)
|
|
188
200
|
|
|
201
|
+
// Step events
|
|
189
202
|
durably.on('step:start', (e) => console.log('Step:', e.stepName))
|
|
190
203
|
durably.on('step:complete', (e) => console.log('Step done:', e.stepName))
|
|
191
|
-
durably.on('step:
|
|
192
|
-
console.log('Step skipped (cached):', e.stepName),
|
|
193
|
-
)
|
|
204
|
+
durably.on('step:fail', (e) => console.error('Step failed:', e.stepName))
|
|
194
205
|
|
|
206
|
+
// Log events
|
|
195
207
|
durably.on('log:write', (e) => console.log(`[${e.level}]`, e.message))
|
|
196
208
|
```
|
|
197
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
|
+
|
|
198
312
|
## Plugins
|
|
199
313
|
|
|
200
314
|
### Log Persistence
|
|
201
315
|
|
|
202
316
|
```ts
|
|
203
|
-
import { withLogPersistence } from '@coji/durably
|
|
317
|
+
import { withLogPersistence } from '@coji/durably'
|
|
204
318
|
|
|
205
319
|
durably.use(withLogPersistence())
|
|
206
320
|
```
|
|
@@ -208,7 +322,7 @@ durably.use(withLogPersistence())
|
|
|
208
322
|
## Browser Usage
|
|
209
323
|
|
|
210
324
|
```ts
|
|
211
|
-
import { createDurably } from '@coji/durably'
|
|
325
|
+
import { createDurably, defineJob } from '@coji/durably'
|
|
212
326
|
import { SQLocalKysely } from 'sqlocal/kysely'
|
|
213
327
|
import { z } from 'zod'
|
|
214
328
|
|
|
@@ -222,10 +336,18 @@ const durably = createDurably({
|
|
|
222
336
|
})
|
|
223
337
|
|
|
224
338
|
// Same API as Node.js
|
|
225
|
-
const myJob = durably.
|
|
339
|
+
const { myJob } = durably.register({
|
|
340
|
+
myJob: defineJob({
|
|
341
|
+
name: 'my-job',
|
|
342
|
+
input: z.object({}),
|
|
343
|
+
run: async (step) => {
|
|
344
|
+
/* ... */
|
|
345
|
+
},
|
|
346
|
+
}),
|
|
347
|
+
})
|
|
226
348
|
|
|
227
|
-
|
|
228
|
-
durably.
|
|
349
|
+
// Initialize (same as Node.js)
|
|
350
|
+
await durably.init()
|
|
229
351
|
```
|
|
230
352
|
|
|
231
353
|
## Run Lifecycle
|
|
@@ -255,6 +377,13 @@ When a job resumes after interruption:
|
|
|
255
377
|
## Type Definitions
|
|
256
378
|
|
|
257
379
|
```ts
|
|
380
|
+
interface JobDefinition<TName, TInput, TOutput> {
|
|
381
|
+
name: TName
|
|
382
|
+
input: ZodType<TInput>
|
|
383
|
+
output?: ZodType<TOutput>
|
|
384
|
+
run: (step: StepContext, payload: TInput) => Promise<TOutput>
|
|
385
|
+
}
|
|
386
|
+
|
|
258
387
|
interface StepContext {
|
|
259
388
|
runId: string
|
|
260
389
|
run<T>(name: string, fn: () => T | Promise<T>): Promise<T>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coji/durably",
|
|
3
|
-
"version": "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": "
|
|
45
|
-
"zod": "
|
|
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.
|
|
71
|
+
"zod": "^4.3.4"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsup",
|