@coji/durably 0.8.1 → 0.10.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 +1 -1
- package/dist/{index-4aPZWn8r.d.ts → index-BjlCb0gP.d.ts} +69 -19
- package/dist/index.d.ts +15 -3
- package/dist/index.js +477 -173
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/docs/llms.md +65 -12
- package/package.json +16 -16
package/dist/plugins/index.d.ts
CHANGED
package/docs/llms.md
CHANGED
|
@@ -46,10 +46,10 @@ const syncUsersJob = defineJob({
|
|
|
46
46
|
name: 'sync-users',
|
|
47
47
|
input: z.object({ orgId: z.string() }),
|
|
48
48
|
output: z.object({ syncedCount: z.number() }),
|
|
49
|
-
run: async (step,
|
|
49
|
+
run: async (step, input) => {
|
|
50
50
|
// Step 1: Fetch users (result is persisted)
|
|
51
51
|
const users = await step.run('fetch-users', async () => {
|
|
52
|
-
return await api.fetchUsers(
|
|
52
|
+
return await api.fetchUsers(input.orgId)
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
// Step 2: Save to database
|
|
@@ -100,6 +100,15 @@ await syncUsers.trigger(
|
|
|
100
100
|
|
|
101
101
|
// With concurrency key (serializes execution)
|
|
102
102
|
await syncUsers.trigger({ orgId: 'org_123' }, { concurrencyKey: 'org_123' })
|
|
103
|
+
|
|
104
|
+
// With labels (for filtering)
|
|
105
|
+
await syncUsers.trigger({ orgId: 'org_123' }, { labels: { source: 'browser' } })
|
|
106
|
+
|
|
107
|
+
// Labels for multi-tenancy
|
|
108
|
+
await syncUsers.trigger(
|
|
109
|
+
{ orgId: 'org_123' },
|
|
110
|
+
{ labels: { organizationId: 'org_123', env: 'prod' } },
|
|
111
|
+
)
|
|
103
112
|
```
|
|
104
113
|
|
|
105
114
|
## Step Context API
|
|
@@ -108,17 +117,17 @@ The `step` object provides these methods:
|
|
|
108
117
|
|
|
109
118
|
### step.run(name, fn)
|
|
110
119
|
|
|
111
|
-
Executes a step and persists its result. On resume, returns cached result without re-executing.
|
|
120
|
+
Executes a step and persists its result. On resume, returns cached result without re-executing. The callback receives an `AbortSignal` that is aborted when the run is cancelled, enabling cooperative cancellation of long-running steps.
|
|
112
121
|
|
|
113
122
|
```ts
|
|
114
|
-
const result = await step.run('step-name', async () => {
|
|
115
|
-
return await someAsyncOperation()
|
|
123
|
+
const result = await step.run('step-name', async (signal) => {
|
|
124
|
+
return await someAsyncOperation({ signal })
|
|
116
125
|
})
|
|
117
126
|
```
|
|
118
127
|
|
|
119
128
|
### step.progress(current, total?, message?)
|
|
120
129
|
|
|
121
|
-
Updates progress information for the run.
|
|
130
|
+
Updates progress information for the run. Call freely in loops — SSE delivery is throttled by `sseThrottleMs` (default 100ms) so clients receive smooth updates without flooding.
|
|
122
131
|
|
|
123
132
|
```ts
|
|
124
133
|
step.progress(50, 100, 'Processing items...')
|
|
@@ -150,7 +159,7 @@ const run = await durably.getRun(runId)
|
|
|
150
159
|
|
|
151
160
|
// Via durably instance (typed with generic parameter)
|
|
152
161
|
type MyRun = Run & {
|
|
153
|
-
|
|
162
|
+
input: { userId: string }
|
|
154
163
|
output: { count: number } | null
|
|
155
164
|
}
|
|
156
165
|
const typedRun = await durably.getRun<MyRun>(runId)
|
|
@@ -170,9 +179,19 @@ const runs = await durably.getRuns({
|
|
|
170
179
|
offset: 0,
|
|
171
180
|
})
|
|
172
181
|
|
|
182
|
+
// Filter by labels
|
|
183
|
+
const browserRuns = await durably.getRuns({
|
|
184
|
+
labels: { source: 'browser' },
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// Filter by labels (multi-tenancy)
|
|
188
|
+
const orgRuns = await durably.getRuns({
|
|
189
|
+
labels: { organizationId: 'org_123' },
|
|
190
|
+
})
|
|
191
|
+
|
|
173
192
|
// Typed getRuns with generic parameter
|
|
174
193
|
type MyRun = Run & {
|
|
175
|
-
|
|
194
|
+
input: { userId: string }
|
|
176
195
|
output: { count: number } | null
|
|
177
196
|
}
|
|
178
197
|
const typedRuns = await durably.getRuns<MyRun>({ jobName: 'my-job' })
|
|
@@ -207,6 +226,7 @@ durably.on('run:start', (e) => console.log('Started:', e.runId))
|
|
|
207
226
|
durably.on('run:complete', (e) => console.log('Done:', e.output))
|
|
208
227
|
durably.on('run:fail', (e) => console.error('Failed:', e.error))
|
|
209
228
|
durably.on('run:cancel', (e) => console.log('Cancelled:', e.runId))
|
|
229
|
+
durably.on('run:delete', (e) => console.log('Deleted:', e.runId))
|
|
210
230
|
durably.on('run:retry', (e) => console.log('Retried:', e.runId))
|
|
211
231
|
durably.on('run:progress', (e) =>
|
|
212
232
|
console.log('Progress:', e.progress.current, '/', e.progress.total),
|
|
@@ -216,6 +236,7 @@ durably.on('run:progress', (e) =>
|
|
|
216
236
|
durably.on('step:start', (e) => console.log('Step:', e.stepName))
|
|
217
237
|
durably.on('step:complete', (e) => console.log('Step done:', e.stepName))
|
|
218
238
|
durably.on('step:fail', (e) => console.error('Step failed:', e.stepName))
|
|
239
|
+
durably.on('step:cancel', (e) => console.log('Step cancelled:', e.stepName))
|
|
219
240
|
|
|
220
241
|
// Log events
|
|
221
242
|
durably.on('log:write', (e) => console.log(`[${e.level}]`, e.message))
|
|
@@ -273,7 +294,9 @@ Create HTTP handlers for client/server architecture using Web Standard Request/R
|
|
|
273
294
|
```ts
|
|
274
295
|
import { createDurablyHandler } from '@coji/durably'
|
|
275
296
|
|
|
276
|
-
const handler = createDurablyHandler(durably
|
|
297
|
+
const handler = createDurablyHandler(durably, {
|
|
298
|
+
sseThrottleMs: 100, // default: throttle progress SSE events (0 to disable)
|
|
299
|
+
})
|
|
277
300
|
|
|
278
301
|
// Use the unified handle() method with automatic routing
|
|
279
302
|
app.all('/api/durably/*', async (req) => {
|
|
@@ -292,6 +315,22 @@ app.post('/api/durably/cancel', (req) => handler.cancel(req))
|
|
|
292
315
|
app.delete('/api/durably/run', (req) => handler.delete(req))
|
|
293
316
|
```
|
|
294
317
|
|
|
318
|
+
**Label filtering via query params:**
|
|
319
|
+
|
|
320
|
+
```http
|
|
321
|
+
GET /runs?label.organizationId=org_123
|
|
322
|
+
GET /runs/subscribe?label.organizationId=org_123&label.env=prod
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Response Shape:** The `/runs` and `/run` endpoints return `ClientRun` objects (internal fields like `heartbeatAt`, `idempotencyKey`, `concurrencyKey`, `updatedAt` are stripped). Use `toClientRun()` to apply the same projection in custom code:
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import { toClientRun } from '@coji/durably'
|
|
329
|
+
|
|
330
|
+
const run = await durably.getRun(runId)
|
|
331
|
+
const clientRun = toClientRun(run) // strips internal fields
|
|
332
|
+
```
|
|
333
|
+
|
|
295
334
|
**Handler Interface:**
|
|
296
335
|
|
|
297
336
|
```ts
|
|
@@ -316,6 +355,7 @@ interface TriggerRequest {
|
|
|
316
355
|
input: Record<string, unknown>
|
|
317
356
|
idempotencyKey?: string
|
|
318
357
|
concurrencyKey?: string
|
|
358
|
+
labels?: Record<string, string>
|
|
319
359
|
}
|
|
320
360
|
|
|
321
361
|
interface TriggerResponse {
|
|
@@ -395,12 +435,13 @@ interface JobDefinition<TName, TInput, TOutput> {
|
|
|
395
435
|
name: TName
|
|
396
436
|
input: ZodType<TInput>
|
|
397
437
|
output?: ZodType<TOutput>
|
|
398
|
-
run: (step: StepContext,
|
|
438
|
+
run: (step: StepContext, input: TInput) => Promise<TOutput>
|
|
399
439
|
}
|
|
400
440
|
|
|
441
|
+
// AbortSignal is aborted when the run is cancelled
|
|
401
442
|
interface StepContext {
|
|
402
443
|
runId: string
|
|
403
|
-
run<T>(name: string, fn: () => T | Promise<T>): Promise<T>
|
|
444
|
+
run<T>(name: string, fn: (signal: AbortSignal) => T | Promise<T>): Promise<T>
|
|
404
445
|
progress(current: number, total?: number, message?: string): void
|
|
405
446
|
log: {
|
|
406
447
|
info(message: string, data?: unknown): void
|
|
@@ -413,10 +454,13 @@ interface Run<TOutput = unknown> {
|
|
|
413
454
|
id: string
|
|
414
455
|
jobName: string
|
|
415
456
|
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
|
416
|
-
|
|
457
|
+
input: unknown
|
|
458
|
+
labels: Record<string, string>
|
|
417
459
|
output?: TOutput
|
|
418
460
|
error?: string
|
|
419
461
|
progress?: { current: number; total?: number; message?: string }
|
|
462
|
+
startedAt: string | null
|
|
463
|
+
completedAt: string | null
|
|
420
464
|
createdAt: string
|
|
421
465
|
updatedAt: string
|
|
422
466
|
}
|
|
@@ -436,8 +480,17 @@ interface JobHandle<TName, TInput, TOutput> {
|
|
|
436
480
|
interface TriggerOptions {
|
|
437
481
|
idempotencyKey?: string
|
|
438
482
|
concurrencyKey?: string
|
|
483
|
+
labels?: Record<string, string>
|
|
439
484
|
timeout?: number
|
|
440
485
|
}
|
|
486
|
+
|
|
487
|
+
interface RunFilter {
|
|
488
|
+
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
|
489
|
+
jobName?: string
|
|
490
|
+
labels?: Record<string, string>
|
|
491
|
+
limit?: number
|
|
492
|
+
offset?: number
|
|
493
|
+
}
|
|
441
494
|
```
|
|
442
495
|
|
|
443
496
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coji/durably",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|
|
@@ -48,27 +48,27 @@
|
|
|
48
48
|
"ulidx": "^2.4.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@biomejs/biome": "^2.
|
|
51
|
+
"@biomejs/biome": "^2.4.5",
|
|
52
52
|
"@libsql/client": "^0.17.0",
|
|
53
53
|
"@libsql/kysely-libsql": "^0.4.1",
|
|
54
|
-
"@testing-library/react": "^16.3.
|
|
55
|
-
"@types/react": "^19.2.
|
|
54
|
+
"@testing-library/react": "^16.3.2",
|
|
55
|
+
"@types/react": "^19.2.14",
|
|
56
56
|
"@types/react-dom": "^19.2.3",
|
|
57
|
-
"@vitejs/plugin-react": "^5.1.
|
|
58
|
-
"@vitest/browser": "^4.0.
|
|
59
|
-
"@vitest/browser-playwright": "4.0.
|
|
60
|
-
"jsdom": "^
|
|
61
|
-
"kysely": "^0.28.
|
|
62
|
-
"playwright": "^1.
|
|
63
|
-
"prettier": "^3.
|
|
57
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
58
|
+
"@vitest/browser": "^4.0.18",
|
|
59
|
+
"@vitest/browser-playwright": "4.0.18",
|
|
60
|
+
"jsdom": "^28.1.0",
|
|
61
|
+
"kysely": "^0.28.11",
|
|
62
|
+
"playwright": "^1.58.2",
|
|
63
|
+
"prettier": "^3.8.1",
|
|
64
64
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
65
|
-
"react": "^19.2.
|
|
66
|
-
"react-dom": "^19.2.
|
|
67
|
-
"sqlocal": "^0.
|
|
65
|
+
"react": "^19.2.4",
|
|
66
|
+
"react-dom": "^19.2.4",
|
|
67
|
+
"sqlocal": "^0.17.0",
|
|
68
68
|
"tsup": "^8.5.1",
|
|
69
69
|
"typescript": "^5.9.3",
|
|
70
|
-
"vitest": "^4.0.
|
|
71
|
-
"zod": "^4.3.
|
|
70
|
+
"vitest": "^4.0.18",
|
|
71
|
+
"zod": "^4.3.6"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"build": "tsup",
|