@coji/durably 0.8.1 → 0.9.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.
@@ -1,3 +1,3 @@
1
- export { w as withLogPersistence } from '../index-4aPZWn8r.js';
1
+ export { H as withLogPersistence } from '../index-BjlCb0gP.js';
2
2
  import 'kysely';
3
3
  import 'zod';
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, payload) => {
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(payload.orgId)
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,11 +117,11 @@ 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
 
@@ -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
- payload: { userId: string }
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
- payload: { userId: string }
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))
@@ -292,6 +313,22 @@ app.post('/api/durably/cancel', (req) => handler.cancel(req))
292
313
  app.delete('/api/durably/run', (req) => handler.delete(req))
293
314
  ```
294
315
 
316
+ **Label filtering via query params:**
317
+
318
+ ```http
319
+ GET /runs?label.organizationId=org_123
320
+ GET /runs/subscribe?label.organizationId=org_123&label.env=prod
321
+ ```
322
+
323
+ **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:
324
+
325
+ ```ts
326
+ import { toClientRun } from '@coji/durably'
327
+
328
+ const run = await durably.getRun(runId)
329
+ const clientRun = toClientRun(run) // strips internal fields
330
+ ```
331
+
295
332
  **Handler Interface:**
296
333
 
297
334
  ```ts
@@ -316,6 +353,7 @@ interface TriggerRequest {
316
353
  input: Record<string, unknown>
317
354
  idempotencyKey?: string
318
355
  concurrencyKey?: string
356
+ labels?: Record<string, string>
319
357
  }
320
358
 
321
359
  interface TriggerResponse {
@@ -395,12 +433,13 @@ interface JobDefinition<TName, TInput, TOutput> {
395
433
  name: TName
396
434
  input: ZodType<TInput>
397
435
  output?: ZodType<TOutput>
398
- run: (step: StepContext, payload: TInput) => Promise<TOutput>
436
+ run: (step: StepContext, input: TInput) => Promise<TOutput>
399
437
  }
400
438
 
439
+ // AbortSignal is aborted when the run is cancelled
401
440
  interface StepContext {
402
441
  runId: string
403
- run<T>(name: string, fn: () => T | Promise<T>): Promise<T>
442
+ run<T>(name: string, fn: (signal: AbortSignal) => T | Promise<T>): Promise<T>
404
443
  progress(current: number, total?: number, message?: string): void
405
444
  log: {
406
445
  info(message: string, data?: unknown): void
@@ -413,10 +452,13 @@ interface Run<TOutput = unknown> {
413
452
  id: string
414
453
  jobName: string
415
454
  status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
416
- payload: unknown
455
+ input: unknown
456
+ labels: Record<string, string>
417
457
  output?: TOutput
418
458
  error?: string
419
459
  progress?: { current: number; total?: number; message?: string }
460
+ startedAt: string | null
461
+ completedAt: string | null
420
462
  createdAt: string
421
463
  updatedAt: string
422
464
  }
@@ -436,8 +478,17 @@ interface JobHandle<TName, TInput, TOutput> {
436
478
  interface TriggerOptions {
437
479
  idempotencyKey?: string
438
480
  concurrencyKey?: string
481
+ labels?: Record<string, string>
439
482
  timeout?: number
440
483
  }
484
+
485
+ interface RunFilter {
486
+ status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
487
+ jobName?: string
488
+ labels?: Record<string, string>
489
+ limit?: number
490
+ offset?: number
491
+ }
441
492
  ```
442
493
 
443
494
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coji/durably",
3
- "version": "0.8.1",
3
+ "version": "0.9.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.3.11",
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.1",
55
- "@types/react": "^19.2.7",
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.2",
58
- "@vitest/browser": "^4.0.16",
59
- "@vitest/browser-playwright": "4.0.16",
60
- "jsdom": "^27.4.0",
61
- "kysely": "^0.28.9",
62
- "playwright": "^1.57.0",
63
- "prettier": "^3.7.4",
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.3",
66
- "react-dom": "^19.2.3",
67
- "sqlocal": "^0.16.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.16",
71
- "zod": "^4.3.5"
70
+ "vitest": "^4.0.18",
71
+ "zod": "^4.3.6"
72
72
  },
73
73
  "scripts": {
74
74
  "build": "tsup",