@coji/durably 0.6.0 → 0.7.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/dist/chunk-UCUP6NMJ.js +22 -0
- package/dist/chunk-UCUP6NMJ.js.map +1 -0
- package/dist/index-4aPZWn8r.d.ts +650 -0
- package/dist/index.d.ts +5 -631
- package/dist/index.js +271 -353
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +3 -8
- package/dist/plugins/index.js +3 -6
- package/dist/plugins/index.js.map +1 -1
- package/docs/llms.md +15 -1
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/plugins/log-persistence.ts
|
|
2
|
+
function withLogPersistence() {
|
|
3
|
+
return {
|
|
4
|
+
name: "log-persistence",
|
|
5
|
+
install(durably) {
|
|
6
|
+
durably.on("log:write", async (event) => {
|
|
7
|
+
await durably.storage.createLog({
|
|
8
|
+
runId: event.runId,
|
|
9
|
+
stepName: event.stepName,
|
|
10
|
+
level: event.level,
|
|
11
|
+
message: event.message,
|
|
12
|
+
data: event.data
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
withLogPersistence
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=chunk-UCUP6NMJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/plugins/log-persistence.ts"],"sourcesContent":["import type { DurablyPlugin } from '../durably'\n\n/**\n * Plugin that persists log events to the database\n */\nexport function withLogPersistence(): DurablyPlugin {\n return {\n name: 'log-persistence',\n install(durably) {\n durably.on('log:write', async (event) => {\n await durably.storage.createLog({\n runId: event.runId,\n stepName: event.stepName,\n level: event.level,\n message: event.message,\n data: event.data,\n })\n })\n },\n }\n}\n"],"mappings":";AAKO,SAAS,qBAAoC;AAClD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,SAAS;AACf,cAAQ,GAAG,aAAa,OAAO,UAAU;AACvC,cAAM,QAAQ,QAAQ,UAAU;AAAA,UAC9B,OAAO,MAAM;AAAA,UACb,UAAU,MAAM;AAAA,UAChB,OAAO,MAAM;AAAA,UACb,SAAS,MAAM;AAAA,UACf,MAAM,MAAM;AAAA,QACd,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import { Dialect, Kysely } from 'kysely';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base event interface
|
|
6
|
+
*/
|
|
7
|
+
interface BaseEvent {
|
|
8
|
+
type: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
sequence: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Run trigger event (emitted when a job is triggered, before worker picks it up)
|
|
14
|
+
*/
|
|
15
|
+
interface RunTriggerEvent extends BaseEvent {
|
|
16
|
+
type: 'run:trigger';
|
|
17
|
+
runId: string;
|
|
18
|
+
jobName: string;
|
|
19
|
+
payload: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run start event
|
|
23
|
+
*/
|
|
24
|
+
interface RunStartEvent extends BaseEvent {
|
|
25
|
+
type: 'run:start';
|
|
26
|
+
runId: string;
|
|
27
|
+
jobName: string;
|
|
28
|
+
payload: unknown;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run complete event
|
|
32
|
+
*/
|
|
33
|
+
interface RunCompleteEvent extends BaseEvent {
|
|
34
|
+
type: 'run:complete';
|
|
35
|
+
runId: string;
|
|
36
|
+
jobName: string;
|
|
37
|
+
output: unknown;
|
|
38
|
+
duration: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Run fail event
|
|
42
|
+
*/
|
|
43
|
+
interface RunFailEvent extends BaseEvent {
|
|
44
|
+
type: 'run:fail';
|
|
45
|
+
runId: string;
|
|
46
|
+
jobName: string;
|
|
47
|
+
error: string;
|
|
48
|
+
failedStepName: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Run cancel event
|
|
52
|
+
*/
|
|
53
|
+
interface RunCancelEvent extends BaseEvent {
|
|
54
|
+
type: 'run:cancel';
|
|
55
|
+
runId: string;
|
|
56
|
+
jobName: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Run retry event (emitted when a failed run is retried)
|
|
60
|
+
*/
|
|
61
|
+
interface RunRetryEvent extends BaseEvent {
|
|
62
|
+
type: 'run:retry';
|
|
63
|
+
runId: string;
|
|
64
|
+
jobName: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run progress event
|
|
68
|
+
*/
|
|
69
|
+
interface RunProgressEvent extends BaseEvent {
|
|
70
|
+
type: 'run:progress';
|
|
71
|
+
runId: string;
|
|
72
|
+
jobName: string;
|
|
73
|
+
progress: {
|
|
74
|
+
current: number;
|
|
75
|
+
total?: number;
|
|
76
|
+
message?: string;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Step start event
|
|
81
|
+
*/
|
|
82
|
+
interface StepStartEvent extends BaseEvent {
|
|
83
|
+
type: 'step:start';
|
|
84
|
+
runId: string;
|
|
85
|
+
jobName: string;
|
|
86
|
+
stepName: string;
|
|
87
|
+
stepIndex: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Step complete event
|
|
91
|
+
*/
|
|
92
|
+
interface StepCompleteEvent extends BaseEvent {
|
|
93
|
+
type: 'step:complete';
|
|
94
|
+
runId: string;
|
|
95
|
+
jobName: string;
|
|
96
|
+
stepName: string;
|
|
97
|
+
stepIndex: number;
|
|
98
|
+
output: unknown;
|
|
99
|
+
duration: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Step fail event
|
|
103
|
+
*/
|
|
104
|
+
interface StepFailEvent extends BaseEvent {
|
|
105
|
+
type: 'step:fail';
|
|
106
|
+
runId: string;
|
|
107
|
+
jobName: string;
|
|
108
|
+
stepName: string;
|
|
109
|
+
stepIndex: number;
|
|
110
|
+
error: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Log write event
|
|
114
|
+
*/
|
|
115
|
+
interface LogWriteEvent extends BaseEvent {
|
|
116
|
+
type: 'log:write';
|
|
117
|
+
runId: string;
|
|
118
|
+
stepName: string | null;
|
|
119
|
+
level: 'info' | 'warn' | 'error';
|
|
120
|
+
message: string;
|
|
121
|
+
data: unknown;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Worker error event (internal errors like heartbeat failures)
|
|
125
|
+
*/
|
|
126
|
+
interface WorkerErrorEvent extends BaseEvent {
|
|
127
|
+
type: 'worker:error';
|
|
128
|
+
error: string;
|
|
129
|
+
context: string;
|
|
130
|
+
runId?: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* All event types as discriminated union
|
|
134
|
+
*/
|
|
135
|
+
type DurablyEvent = RunTriggerEvent | RunStartEvent | RunCompleteEvent | RunFailEvent | RunCancelEvent | RunRetryEvent | RunProgressEvent | StepStartEvent | StepCompleteEvent | StepFailEvent | LogWriteEvent | WorkerErrorEvent;
|
|
136
|
+
/**
|
|
137
|
+
* Event types for type-safe event names
|
|
138
|
+
*/
|
|
139
|
+
type EventType = DurablyEvent['type'];
|
|
140
|
+
/**
|
|
141
|
+
* Extract event by type
|
|
142
|
+
*/
|
|
143
|
+
type EventByType<T extends EventType> = Extract<DurablyEvent, {
|
|
144
|
+
type: T;
|
|
145
|
+
}>;
|
|
146
|
+
/**
|
|
147
|
+
* Event input (without auto-generated fields)
|
|
148
|
+
*/
|
|
149
|
+
type EventInput<T extends EventType> = Omit<EventByType<T>, 'timestamp' | 'sequence'>;
|
|
150
|
+
/**
|
|
151
|
+
* All possible event inputs as a union (properly distributed)
|
|
152
|
+
*/
|
|
153
|
+
type AnyEventInput = EventInput<'run:trigger'> | EventInput<'run:start'> | EventInput<'run:complete'> | EventInput<'run:fail'> | EventInput<'run:cancel'> | EventInput<'run:retry'> | EventInput<'run:progress'> | EventInput<'step:start'> | EventInput<'step:complete'> | EventInput<'step:fail'> | EventInput<'log:write'> | EventInput<'worker:error'>;
|
|
154
|
+
/**
|
|
155
|
+
* Event listener function
|
|
156
|
+
*/
|
|
157
|
+
type EventListener<T extends EventType> = (event: EventByType<T>) => void;
|
|
158
|
+
/**
|
|
159
|
+
* Unsubscribe function returned by on()
|
|
160
|
+
*/
|
|
161
|
+
type Unsubscribe = () => void;
|
|
162
|
+
/**
|
|
163
|
+
* Error handler function for listener exceptions
|
|
164
|
+
*/
|
|
165
|
+
type ErrorHandler = (error: Error, event: DurablyEvent) => void;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Database schema types for Durably
|
|
169
|
+
*/
|
|
170
|
+
interface RunsTable {
|
|
171
|
+
id: string;
|
|
172
|
+
job_name: string;
|
|
173
|
+
payload: string;
|
|
174
|
+
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
175
|
+
idempotency_key: string | null;
|
|
176
|
+
concurrency_key: string | null;
|
|
177
|
+
current_step_index: number;
|
|
178
|
+
progress: string | null;
|
|
179
|
+
output: string | null;
|
|
180
|
+
error: string | null;
|
|
181
|
+
heartbeat_at: string;
|
|
182
|
+
created_at: string;
|
|
183
|
+
updated_at: string;
|
|
184
|
+
}
|
|
185
|
+
interface StepsTable {
|
|
186
|
+
id: string;
|
|
187
|
+
run_id: string;
|
|
188
|
+
name: string;
|
|
189
|
+
index: number;
|
|
190
|
+
status: 'completed' | 'failed';
|
|
191
|
+
output: string | null;
|
|
192
|
+
error: string | null;
|
|
193
|
+
started_at: string;
|
|
194
|
+
completed_at: string | null;
|
|
195
|
+
}
|
|
196
|
+
interface LogsTable {
|
|
197
|
+
id: string;
|
|
198
|
+
run_id: string;
|
|
199
|
+
step_name: string | null;
|
|
200
|
+
level: 'info' | 'warn' | 'error';
|
|
201
|
+
message: string;
|
|
202
|
+
data: string | null;
|
|
203
|
+
created_at: string;
|
|
204
|
+
}
|
|
205
|
+
interface SchemaVersionsTable {
|
|
206
|
+
version: number;
|
|
207
|
+
applied_at: string;
|
|
208
|
+
}
|
|
209
|
+
interface Database {
|
|
210
|
+
durably_runs: RunsTable;
|
|
211
|
+
durably_steps: StepsTable;
|
|
212
|
+
durably_logs: LogsTable;
|
|
213
|
+
durably_schema_versions: SchemaVersionsTable;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Run data for creating a new run
|
|
218
|
+
*/
|
|
219
|
+
interface CreateRunInput {
|
|
220
|
+
jobName: string;
|
|
221
|
+
payload: unknown;
|
|
222
|
+
idempotencyKey?: string;
|
|
223
|
+
concurrencyKey?: string;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Run data returned from storage
|
|
227
|
+
*/
|
|
228
|
+
interface Run {
|
|
229
|
+
id: string;
|
|
230
|
+
jobName: string;
|
|
231
|
+
payload: unknown;
|
|
232
|
+
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
233
|
+
idempotencyKey: string | null;
|
|
234
|
+
concurrencyKey: string | null;
|
|
235
|
+
currentStepIndex: number;
|
|
236
|
+
stepCount: number;
|
|
237
|
+
progress: {
|
|
238
|
+
current: number;
|
|
239
|
+
total?: number;
|
|
240
|
+
message?: string;
|
|
241
|
+
} | null;
|
|
242
|
+
output: unknown | null;
|
|
243
|
+
error: string | null;
|
|
244
|
+
heartbeatAt: string;
|
|
245
|
+
createdAt: string;
|
|
246
|
+
updatedAt: string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Run update data
|
|
250
|
+
*/
|
|
251
|
+
interface UpdateRunInput {
|
|
252
|
+
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
253
|
+
currentStepIndex?: number;
|
|
254
|
+
progress?: {
|
|
255
|
+
current: number;
|
|
256
|
+
total?: number;
|
|
257
|
+
message?: string;
|
|
258
|
+
} | null;
|
|
259
|
+
output?: unknown;
|
|
260
|
+
error?: string | null;
|
|
261
|
+
heartbeatAt?: string;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Run filter options
|
|
265
|
+
*/
|
|
266
|
+
interface RunFilter$1 {
|
|
267
|
+
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
268
|
+
jobName?: string;
|
|
269
|
+
/** Maximum number of runs to return */
|
|
270
|
+
limit?: number;
|
|
271
|
+
/** Number of runs to skip (for pagination) */
|
|
272
|
+
offset?: number;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Step data for creating a new step
|
|
276
|
+
*/
|
|
277
|
+
interface CreateStepInput {
|
|
278
|
+
runId: string;
|
|
279
|
+
name: string;
|
|
280
|
+
index: number;
|
|
281
|
+
status: 'completed' | 'failed';
|
|
282
|
+
output?: unknown;
|
|
283
|
+
error?: string;
|
|
284
|
+
startedAt: string;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Step data returned from storage
|
|
288
|
+
*/
|
|
289
|
+
interface Step {
|
|
290
|
+
id: string;
|
|
291
|
+
runId: string;
|
|
292
|
+
name: string;
|
|
293
|
+
index: number;
|
|
294
|
+
status: 'completed' | 'failed';
|
|
295
|
+
output: unknown | null;
|
|
296
|
+
error: string | null;
|
|
297
|
+
startedAt: string;
|
|
298
|
+
completedAt: string | null;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Log data for creating a new log
|
|
302
|
+
*/
|
|
303
|
+
interface CreateLogInput {
|
|
304
|
+
runId: string;
|
|
305
|
+
stepName: string | null;
|
|
306
|
+
level: 'info' | 'warn' | 'error';
|
|
307
|
+
message: string;
|
|
308
|
+
data?: unknown;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Log data returned from storage
|
|
312
|
+
*/
|
|
313
|
+
interface Log {
|
|
314
|
+
id: string;
|
|
315
|
+
runId: string;
|
|
316
|
+
stepName: string | null;
|
|
317
|
+
level: 'info' | 'warn' | 'error';
|
|
318
|
+
message: string;
|
|
319
|
+
data: unknown | null;
|
|
320
|
+
createdAt: string;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Storage interface for database operations
|
|
324
|
+
*/
|
|
325
|
+
interface Storage {
|
|
326
|
+
createRun(input: CreateRunInput): Promise<Run>;
|
|
327
|
+
batchCreateRuns(inputs: CreateRunInput[]): Promise<Run[]>;
|
|
328
|
+
updateRun(runId: string, data: UpdateRunInput): Promise<void>;
|
|
329
|
+
deleteRun(runId: string): Promise<void>;
|
|
330
|
+
getRun<T extends Run = Run>(runId: string): Promise<T | null>;
|
|
331
|
+
getRuns<T extends Run = Run>(filter?: RunFilter$1): Promise<T[]>;
|
|
332
|
+
getNextPendingRun(excludeConcurrencyKeys: string[]): Promise<Run | null>;
|
|
333
|
+
createStep(input: CreateStepInput): Promise<Step>;
|
|
334
|
+
getSteps(runId: string): Promise<Step[]>;
|
|
335
|
+
getCompletedStep(runId: string, name: string): Promise<Step | null>;
|
|
336
|
+
createLog(input: CreateLogInput): Promise<Log>;
|
|
337
|
+
getLogs(runId: string): Promise<Log[]>;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Step context passed to the job function
|
|
342
|
+
*/
|
|
343
|
+
interface StepContext {
|
|
344
|
+
/**
|
|
345
|
+
* The ID of the current run
|
|
346
|
+
*/
|
|
347
|
+
readonly runId: string;
|
|
348
|
+
/**
|
|
349
|
+
* Execute a step with automatic persistence and replay
|
|
350
|
+
*/
|
|
351
|
+
run<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
|
|
352
|
+
/**
|
|
353
|
+
* Report progress for the current run
|
|
354
|
+
*/
|
|
355
|
+
progress(current: number, total?: number, message?: string): void;
|
|
356
|
+
/**
|
|
357
|
+
* Log a message
|
|
358
|
+
*/
|
|
359
|
+
log: {
|
|
360
|
+
info(message: string, data?: unknown): void;
|
|
361
|
+
warn(message: string, data?: unknown): void;
|
|
362
|
+
error(message: string, data?: unknown): void;
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Trigger options
|
|
367
|
+
*/
|
|
368
|
+
interface TriggerOptions {
|
|
369
|
+
idempotencyKey?: string;
|
|
370
|
+
concurrencyKey?: string;
|
|
371
|
+
/** Timeout in milliseconds for triggerAndWait() */
|
|
372
|
+
timeout?: number;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Run filter options
|
|
376
|
+
*/
|
|
377
|
+
interface RunFilter {
|
|
378
|
+
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
379
|
+
jobName?: string;
|
|
380
|
+
/** Maximum number of runs to return */
|
|
381
|
+
limit?: number;
|
|
382
|
+
/** Number of runs to skip (for pagination) */
|
|
383
|
+
offset?: number;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Typed run with output type
|
|
387
|
+
*/
|
|
388
|
+
interface TypedRun<TOutput> extends Omit<Run, 'output'> {
|
|
389
|
+
output: TOutput | null;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Batch trigger input - either just the input or input with options
|
|
393
|
+
*/
|
|
394
|
+
type BatchTriggerInput<TInput> = TInput | {
|
|
395
|
+
input: TInput;
|
|
396
|
+
options?: TriggerOptions;
|
|
397
|
+
};
|
|
398
|
+
/**
|
|
399
|
+
* Result of triggerAndWait
|
|
400
|
+
*/
|
|
401
|
+
interface TriggerAndWaitResult<TOutput> {
|
|
402
|
+
id: string;
|
|
403
|
+
output: TOutput;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Job handle returned by defineJob
|
|
407
|
+
*/
|
|
408
|
+
interface JobHandle<TName extends string, TInput, TOutput> {
|
|
409
|
+
readonly name: TName;
|
|
410
|
+
/**
|
|
411
|
+
* Trigger a new run
|
|
412
|
+
*/
|
|
413
|
+
trigger(input: TInput, options?: TriggerOptions): Promise<TypedRun<TOutput>>;
|
|
414
|
+
/**
|
|
415
|
+
* Trigger a new run and wait for completion
|
|
416
|
+
* Returns the output directly, throws if the run fails
|
|
417
|
+
*/
|
|
418
|
+
triggerAndWait(input: TInput, options?: TriggerOptions): Promise<TriggerAndWaitResult<TOutput>>;
|
|
419
|
+
/**
|
|
420
|
+
* Trigger multiple runs in a batch
|
|
421
|
+
* All inputs are validated before any runs are created
|
|
422
|
+
*/
|
|
423
|
+
batchTrigger(inputs: BatchTriggerInput<TInput>[]): Promise<TypedRun<TOutput>[]>;
|
|
424
|
+
/**
|
|
425
|
+
* Get a run by ID
|
|
426
|
+
*/
|
|
427
|
+
getRun(id: string): Promise<TypedRun<TOutput> | null>;
|
|
428
|
+
/**
|
|
429
|
+
* Get runs with optional filter
|
|
430
|
+
*/
|
|
431
|
+
getRuns(filter?: Omit<RunFilter, 'jobName'>): Promise<TypedRun<TOutput>[]>;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Job run function type
|
|
436
|
+
*/
|
|
437
|
+
type JobRunFunction<TInput, TOutput> = (step: StepContext, payload: TInput) => Promise<TOutput>;
|
|
438
|
+
/**
|
|
439
|
+
* Job definition - a standalone description of a job
|
|
440
|
+
* This is the result of calling defineJob() and can be passed to durably.register()
|
|
441
|
+
*/
|
|
442
|
+
interface JobDefinition<TName extends string, TInput, TOutput> {
|
|
443
|
+
readonly name: TName;
|
|
444
|
+
readonly input: z.ZodType<TInput>;
|
|
445
|
+
readonly output: z.ZodType<TOutput> | undefined;
|
|
446
|
+
readonly run: JobRunFunction<TInput, TOutput>;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Extract input type from a JobDefinition
|
|
450
|
+
* @example
|
|
451
|
+
* ```ts
|
|
452
|
+
* type Input = JobInput<typeof myJob> // { userId: string }
|
|
453
|
+
* ```
|
|
454
|
+
*/
|
|
455
|
+
type JobInput<T> = T extends JobDefinition<string, infer TInput, unknown> ? TInput : never;
|
|
456
|
+
/**
|
|
457
|
+
* Extract output type from a JobDefinition
|
|
458
|
+
* @example
|
|
459
|
+
* ```ts
|
|
460
|
+
* type Output = JobOutput<typeof myJob> // { count: number }
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
type JobOutput<T> = T extends JobDefinition<string, unknown, infer TOutput> ? TOutput : never;
|
|
464
|
+
/**
|
|
465
|
+
* Configuration for defining a job
|
|
466
|
+
*/
|
|
467
|
+
interface DefineJobConfig<TName extends string, TInputSchema extends z.ZodType, TOutputSchema extends z.ZodType | undefined> {
|
|
468
|
+
name: TName;
|
|
469
|
+
input: TInputSchema;
|
|
470
|
+
output?: TOutputSchema;
|
|
471
|
+
run: JobRunFunction<z.infer<TInputSchema>, TOutputSchema extends z.ZodType ? z.infer<TOutputSchema> : void>;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Define a job - creates a JobDefinition that can be registered with durably.register()
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* ```ts
|
|
478
|
+
* import { defineJob } from '@coji/durably'
|
|
479
|
+
* import { z } from 'zod'
|
|
480
|
+
*
|
|
481
|
+
* export const syncUsers = defineJob({
|
|
482
|
+
* name: 'sync-users',
|
|
483
|
+
* input: z.object({ orgId: z.string() }),
|
|
484
|
+
* output: z.object({ syncedCount: z.number() }),
|
|
485
|
+
* run: async (step, payload) => {
|
|
486
|
+
* const users = await step.run('fetch-users', () => fetchUsers(payload.orgId))
|
|
487
|
+
* return { syncedCount: users.length }
|
|
488
|
+
* },
|
|
489
|
+
* })
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
declare function defineJob<TName extends string, TInputSchema extends z.ZodType, TOutputSchema extends z.ZodType | undefined = undefined>(config: DefineJobConfig<TName, TInputSchema, TOutputSchema>): JobDefinition<TName, z.infer<TInputSchema>, TOutputSchema extends z.ZodType ? z.infer<TOutputSchema> : void>;
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Options for creating a Durably instance
|
|
496
|
+
*/
|
|
497
|
+
interface DurablyOptions {
|
|
498
|
+
dialect: Dialect;
|
|
499
|
+
pollingInterval?: number;
|
|
500
|
+
heartbeatInterval?: number;
|
|
501
|
+
staleThreshold?: number;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Plugin interface for extending Durably
|
|
505
|
+
*/
|
|
506
|
+
interface DurablyPlugin {
|
|
507
|
+
name: string;
|
|
508
|
+
install(durably: Durably<any>): void;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Helper type to transform JobDefinition record to JobHandle record
|
|
512
|
+
*/
|
|
513
|
+
type TransformToHandles<TJobs extends Record<string, JobDefinition<string, unknown, unknown>>> = {
|
|
514
|
+
[K in keyof TJobs]: TJobs[K] extends JobDefinition<infer TName, infer TInput, infer TOutput> ? JobHandle<TName & string, TInput, TOutput> : never;
|
|
515
|
+
};
|
|
516
|
+
/**
|
|
517
|
+
* Durably instance with type-safe jobs
|
|
518
|
+
*/
|
|
519
|
+
interface Durably<TJobs extends Record<string, JobHandle<string, unknown, unknown>> = Record<string, never>> {
|
|
520
|
+
/**
|
|
521
|
+
* Registered job handles (type-safe)
|
|
522
|
+
*/
|
|
523
|
+
readonly jobs: TJobs;
|
|
524
|
+
/**
|
|
525
|
+
* Initialize Durably: run migrations and start the worker
|
|
526
|
+
* This is the recommended way to start Durably.
|
|
527
|
+
* Equivalent to calling migrate() then start().
|
|
528
|
+
* @example
|
|
529
|
+
* ```ts
|
|
530
|
+
* const durably = createDurably({ dialect }).register({ ... })
|
|
531
|
+
* await durably.init()
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
init(): Promise<void>;
|
|
535
|
+
/**
|
|
536
|
+
* Run database migrations
|
|
537
|
+
* This is idempotent and safe to call multiple times
|
|
538
|
+
*/
|
|
539
|
+
migrate(): Promise<void>;
|
|
540
|
+
/**
|
|
541
|
+
* Get the underlying Kysely database instance
|
|
542
|
+
* Useful for testing and advanced use cases
|
|
543
|
+
*/
|
|
544
|
+
readonly db: Kysely<Database>;
|
|
545
|
+
/**
|
|
546
|
+
* Storage layer for database operations
|
|
547
|
+
*/
|
|
548
|
+
readonly storage: Storage;
|
|
549
|
+
/**
|
|
550
|
+
* Register an event listener
|
|
551
|
+
* @returns Unsubscribe function
|
|
552
|
+
*/
|
|
553
|
+
on<T extends EventType>(type: T, listener: EventListener<T>): Unsubscribe;
|
|
554
|
+
/**
|
|
555
|
+
* Emit an event (auto-assigns timestamp and sequence)
|
|
556
|
+
*/
|
|
557
|
+
emit(event: AnyEventInput): void;
|
|
558
|
+
/**
|
|
559
|
+
* Register an error handler for listener exceptions
|
|
560
|
+
*/
|
|
561
|
+
onError(handler: ErrorHandler): void;
|
|
562
|
+
/**
|
|
563
|
+
* Register job definitions and return a new Durably instance with type-safe jobs
|
|
564
|
+
* @example
|
|
565
|
+
* ```ts
|
|
566
|
+
* const durably = createDurably({ dialect })
|
|
567
|
+
* .register({
|
|
568
|
+
* importCsv: importCsvJob,
|
|
569
|
+
* syncUsers: syncUsersJob,
|
|
570
|
+
* })
|
|
571
|
+
* await durably.migrate()
|
|
572
|
+
* // Usage: durably.jobs.importCsv.trigger({ rows: [...] })
|
|
573
|
+
* ```
|
|
574
|
+
*/
|
|
575
|
+
register<TNewJobs extends Record<string, JobDefinition<string, any, any>>>(jobDefs: TNewJobs): Durably<TJobs & TransformToHandles<TNewJobs>>;
|
|
576
|
+
/**
|
|
577
|
+
* Start the worker polling loop
|
|
578
|
+
*/
|
|
579
|
+
start(): void;
|
|
580
|
+
/**
|
|
581
|
+
* Stop the worker after current run completes
|
|
582
|
+
*/
|
|
583
|
+
stop(): Promise<void>;
|
|
584
|
+
/**
|
|
585
|
+
* Retry a failed run by resetting it to pending
|
|
586
|
+
* @throws Error if run is not in failed status
|
|
587
|
+
*/
|
|
588
|
+
retry(runId: string): Promise<void>;
|
|
589
|
+
/**
|
|
590
|
+
* Cancel a pending or running run
|
|
591
|
+
* @throws Error if run is already completed, failed, or cancelled
|
|
592
|
+
*/
|
|
593
|
+
cancel(runId: string): Promise<void>;
|
|
594
|
+
/**
|
|
595
|
+
* Delete a completed, failed, or cancelled run and its associated steps and logs
|
|
596
|
+
* @throws Error if run is pending or running, or does not exist
|
|
597
|
+
*/
|
|
598
|
+
deleteRun(runId: string): Promise<void>;
|
|
599
|
+
/**
|
|
600
|
+
* Get a run by ID
|
|
601
|
+
* @example
|
|
602
|
+
* ```ts
|
|
603
|
+
* // Untyped (returns Run)
|
|
604
|
+
* const run = await durably.getRun(runId)
|
|
605
|
+
*
|
|
606
|
+
* // Typed (returns custom type)
|
|
607
|
+
* type MyRun = Run & { payload: { userId: string }; output: { count: number } | null }
|
|
608
|
+
* const typedRun = await durably.getRun<MyRun>(runId)
|
|
609
|
+
* ```
|
|
610
|
+
*/
|
|
611
|
+
getRun<T extends Run = Run>(runId: string): Promise<T | null>;
|
|
612
|
+
/**
|
|
613
|
+
* Get runs with optional filtering
|
|
614
|
+
* @example
|
|
615
|
+
* ```ts
|
|
616
|
+
* // Untyped (returns Run[])
|
|
617
|
+
* const runs = await durably.getRuns({ status: 'completed' })
|
|
618
|
+
*
|
|
619
|
+
* // Typed (returns custom type[])
|
|
620
|
+
* type MyRun = Run & { payload: { userId: string }; output: { count: number } | null }
|
|
621
|
+
* const typedRuns = await durably.getRuns<MyRun>({ jobName: 'my-job' })
|
|
622
|
+
* ```
|
|
623
|
+
*/
|
|
624
|
+
getRuns<T extends Run = Run>(filter?: RunFilter$1): Promise<T[]>;
|
|
625
|
+
/**
|
|
626
|
+
* Register a plugin
|
|
627
|
+
*/
|
|
628
|
+
use(plugin: DurablyPlugin): void;
|
|
629
|
+
/**
|
|
630
|
+
* Get a registered job handle by name
|
|
631
|
+
* Returns undefined if job is not registered
|
|
632
|
+
*/
|
|
633
|
+
getJob<TName extends string = string>(name: TName): JobHandle<TName, Record<string, unknown>, unknown> | undefined;
|
|
634
|
+
/**
|
|
635
|
+
* Subscribe to events for a specific run
|
|
636
|
+
* Returns a ReadableStream that can be used for SSE
|
|
637
|
+
*/
|
|
638
|
+
subscribe(runId: string): ReadableStream<DurablyEvent>;
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Create a Durably instance
|
|
642
|
+
*/
|
|
643
|
+
declare function createDurably(options: DurablyOptions): Durably<Record<string, never>>;
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Plugin that persists log events to the database
|
|
647
|
+
*/
|
|
648
|
+
declare function withLogPersistence(): DurablyPlugin;
|
|
649
|
+
|
|
650
|
+
export { type Durably as D, type ErrorHandler as E, type JobDefinition as J, type LogWriteEvent as L, type RunCompleteEvent as R, type StepCompleteEvent as S, type TriggerAndWaitResult as T, type WorkerErrorEvent as W, type DurablyOptions as a, type DurablyPlugin as b, createDurably as c, defineJob as d, type JobInput as e, type JobOutput as f, type DurablyEvent as g, type EventType as h, type RunFailEvent as i, type RunProgressEvent as j, type RunStartEvent as k, type StepFailEvent as l, type StepStartEvent as m, type JobHandle as n, type StepContext as o, type Database as p, type LogsTable as q, type RunsTable as r, type SchemaVersionsTable as s, type StepsTable as t, type Log as u, type Run as v, withLogPersistence as w, type RunFilter$1 as x, type Step as y };
|