@coji/durably 0.0.1 → 0.1.1
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/LICENSE +21 -0
- package/README.md +45 -25
- package/dist/index.d.ts +498 -44
- package/dist/index.js +886 -11
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.js.map +1 -1
- package/package.json +39 -13
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 coji
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,54 +1,74 @@
|
|
|
1
|
-
# durably
|
|
1
|
+
# @coji/durably
|
|
2
2
|
|
|
3
3
|
Step-oriented resumable batch execution for Node.js and browsers using SQLite.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**[Documentation](https://coji.github.io/durably/)** | **[GitHub](https://github.com/coji/durably)** | **[Live Demo](https://durably-demo.vercel.app)**
|
|
6
6
|
|
|
7
|
-
## Features
|
|
7
|
+
## Features
|
|
8
8
|
|
|
9
9
|
- Resumable batch processing with step-level persistence
|
|
10
10
|
- Works in both Node.js and browsers
|
|
11
|
-
- Uses SQLite for state management (better-sqlite3
|
|
12
|
-
- Minimal dependencies - just Kysely as
|
|
11
|
+
- Uses SQLite for state management (better-sqlite3/libsql for Node.js, SQLite WASM for browsers)
|
|
12
|
+
- Minimal dependencies - just Kysely and Zod as peer dependencies
|
|
13
13
|
- Event system for monitoring and extensibility
|
|
14
|
-
-
|
|
14
|
+
- Type-safe input/output with Zod schemas
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
19
|
+
# Node.js with better-sqlite3
|
|
20
|
+
npm install @coji/durably kysely zod better-sqlite3
|
|
21
|
+
|
|
22
|
+
# Node.js with libsql
|
|
23
|
+
npm install @coji/durably kysely zod @libsql/client @libsql/kysely-libsql
|
|
24
|
+
|
|
25
|
+
# Browser with SQLocal
|
|
26
|
+
npm install @coji/durably kysely zod sqlocal
|
|
20
27
|
```
|
|
21
28
|
|
|
22
|
-
## Usage
|
|
29
|
+
## Usage
|
|
23
30
|
|
|
24
31
|
```ts
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
32
|
+
import { createDurably } from '@coji/durably'
|
|
33
|
+
import SQLite from 'better-sqlite3'
|
|
34
|
+
import { SqliteDialect } from 'kysely'
|
|
35
|
+
import { z } from 'zod'
|
|
28
36
|
|
|
29
|
-
const dialect = new
|
|
30
|
-
database: new
|
|
37
|
+
const dialect = new SqliteDialect({
|
|
38
|
+
database: new SQLite('local.db'),
|
|
31
39
|
})
|
|
32
40
|
|
|
33
|
-
const
|
|
41
|
+
const durably = createDurably({ dialect })
|
|
34
42
|
|
|
35
|
-
const syncUsers = defineJob(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
const syncUsers = durably.defineJob(
|
|
44
|
+
{
|
|
45
|
+
name: 'sync-users',
|
|
46
|
+
input: z.object({ orgId: z.string() }),
|
|
47
|
+
output: z.object({ syncedCount: z.number() }),
|
|
48
|
+
},
|
|
49
|
+
async (context, payload) => {
|
|
50
|
+
const users = await context.run('fetch-users', async () => {
|
|
51
|
+
return api.fetchUsers(payload.orgId)
|
|
52
|
+
})
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
})
|
|
54
|
+
await context.run('save-to-db', async () => {
|
|
55
|
+
await db.upsertUsers(users)
|
|
56
|
+
})
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
return { syncedCount: users.length }
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
await durably.migrate()
|
|
63
|
+
durably.start()
|
|
48
64
|
|
|
49
65
|
await syncUsers.trigger({ orgId: 'org_123' })
|
|
50
66
|
```
|
|
51
67
|
|
|
68
|
+
## Documentation
|
|
69
|
+
|
|
70
|
+
For full documentation, visit [coji.github.io/durably](https://coji.github.io/durably/).
|
|
71
|
+
|
|
52
72
|
## License
|
|
53
73
|
|
|
54
74
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,55 +1,509 @@
|
|
|
1
|
+
import { Dialect, Kysely } from 'kysely';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* This package is under development. See https://github.com/coji/durably for updates.
|
|
5
|
+
* Base event interface
|
|
5
6
|
*/
|
|
6
|
-
interface
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
interface BaseEvent {
|
|
8
|
+
type: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
sequence: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Run start event
|
|
14
|
+
*/
|
|
15
|
+
interface RunStartEvent extends BaseEvent {
|
|
16
|
+
type: 'run:start';
|
|
17
|
+
runId: string;
|
|
18
|
+
jobName: string;
|
|
19
|
+
payload: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run complete event
|
|
23
|
+
*/
|
|
24
|
+
interface RunCompleteEvent extends BaseEvent {
|
|
25
|
+
type: 'run:complete';
|
|
26
|
+
runId: string;
|
|
27
|
+
jobName: string;
|
|
28
|
+
output: unknown;
|
|
29
|
+
duration: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Run fail event
|
|
33
|
+
*/
|
|
34
|
+
interface RunFailEvent extends BaseEvent {
|
|
35
|
+
type: 'run:fail';
|
|
36
|
+
runId: string;
|
|
37
|
+
jobName: string;
|
|
38
|
+
error: string;
|
|
39
|
+
failedStepName: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Step start event
|
|
43
|
+
*/
|
|
44
|
+
interface StepStartEvent extends BaseEvent {
|
|
45
|
+
type: 'step:start';
|
|
46
|
+
runId: string;
|
|
47
|
+
jobName: string;
|
|
48
|
+
stepName: string;
|
|
49
|
+
stepIndex: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Step complete event
|
|
53
|
+
*/
|
|
54
|
+
interface StepCompleteEvent extends BaseEvent {
|
|
55
|
+
type: 'step:complete';
|
|
56
|
+
runId: string;
|
|
57
|
+
jobName: string;
|
|
58
|
+
stepName: string;
|
|
59
|
+
stepIndex: number;
|
|
60
|
+
output: unknown;
|
|
61
|
+
duration: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Step fail event
|
|
65
|
+
*/
|
|
66
|
+
interface StepFailEvent extends BaseEvent {
|
|
67
|
+
type: 'step:fail';
|
|
68
|
+
runId: string;
|
|
69
|
+
jobName: string;
|
|
70
|
+
stepName: string;
|
|
71
|
+
stepIndex: number;
|
|
72
|
+
error: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Log write event
|
|
76
|
+
*/
|
|
77
|
+
interface LogWriteEvent extends BaseEvent {
|
|
78
|
+
type: 'log:write';
|
|
79
|
+
runId: string;
|
|
80
|
+
stepName: string | null;
|
|
81
|
+
level: 'info' | 'warn' | 'error';
|
|
82
|
+
message: string;
|
|
83
|
+
data: unknown;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Worker error event (internal errors like heartbeat failures)
|
|
87
|
+
*/
|
|
88
|
+
interface WorkerErrorEvent extends BaseEvent {
|
|
89
|
+
type: 'worker:error';
|
|
90
|
+
error: string;
|
|
91
|
+
context: string;
|
|
92
|
+
runId?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* All event types as discriminated union
|
|
96
|
+
*/
|
|
97
|
+
type DurablyEvent = RunStartEvent | RunCompleteEvent | RunFailEvent | StepStartEvent | StepCompleteEvent | StepFailEvent | LogWriteEvent | WorkerErrorEvent;
|
|
98
|
+
/**
|
|
99
|
+
* Event types for type-safe event names
|
|
100
|
+
*/
|
|
101
|
+
type EventType = DurablyEvent['type'];
|
|
102
|
+
/**
|
|
103
|
+
* Extract event by type
|
|
104
|
+
*/
|
|
105
|
+
type EventByType<T extends EventType> = Extract<DurablyEvent, {
|
|
106
|
+
type: T;
|
|
107
|
+
}>;
|
|
108
|
+
/**
|
|
109
|
+
* Event input (without auto-generated fields)
|
|
110
|
+
*/
|
|
111
|
+
type EventInput<T extends EventType> = Omit<EventByType<T>, 'timestamp' | 'sequence'>;
|
|
112
|
+
/**
|
|
113
|
+
* All possible event inputs as a union (properly distributed)
|
|
114
|
+
*/
|
|
115
|
+
type AnyEventInput = EventInput<'run:start'> | EventInput<'run:complete'> | EventInput<'run:fail'> | EventInput<'step:start'> | EventInput<'step:complete'> | EventInput<'step:fail'> | EventInput<'log:write'> | EventInput<'worker:error'>;
|
|
116
|
+
/**
|
|
117
|
+
* Event listener function
|
|
118
|
+
*/
|
|
119
|
+
type EventListener<T extends EventType> = (event: EventByType<T>) => void;
|
|
120
|
+
/**
|
|
121
|
+
* Unsubscribe function returned by on()
|
|
122
|
+
*/
|
|
123
|
+
type Unsubscribe = () => void;
|
|
124
|
+
/**
|
|
125
|
+
* Error handler function for listener exceptions
|
|
126
|
+
*/
|
|
127
|
+
type ErrorHandler = (error: Error, event: DurablyEvent) => void;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Database schema types for Durably
|
|
131
|
+
*/
|
|
132
|
+
interface RunsTable {
|
|
133
|
+
id: string;
|
|
134
|
+
job_name: string;
|
|
135
|
+
payload: string;
|
|
136
|
+
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
137
|
+
idempotency_key: string | null;
|
|
138
|
+
concurrency_key: string | null;
|
|
139
|
+
current_step_index: number;
|
|
140
|
+
progress: string | null;
|
|
141
|
+
output: string | null;
|
|
142
|
+
error: string | null;
|
|
143
|
+
heartbeat_at: string;
|
|
144
|
+
created_at: string;
|
|
145
|
+
updated_at: string;
|
|
146
|
+
}
|
|
147
|
+
interface StepsTable {
|
|
148
|
+
id: string;
|
|
149
|
+
run_id: string;
|
|
150
|
+
name: string;
|
|
151
|
+
index: number;
|
|
152
|
+
status: 'completed' | 'failed';
|
|
153
|
+
output: string | null;
|
|
154
|
+
error: string | null;
|
|
155
|
+
started_at: string;
|
|
156
|
+
completed_at: string | null;
|
|
157
|
+
}
|
|
158
|
+
interface LogsTable {
|
|
159
|
+
id: string;
|
|
160
|
+
run_id: string;
|
|
161
|
+
step_name: string | null;
|
|
162
|
+
level: 'info' | 'warn' | 'error';
|
|
163
|
+
message: string;
|
|
164
|
+
data: string | null;
|
|
165
|
+
created_at: string;
|
|
166
|
+
}
|
|
167
|
+
interface SchemaVersionsTable {
|
|
168
|
+
version: number;
|
|
169
|
+
applied_at: string;
|
|
170
|
+
}
|
|
171
|
+
interface Database {
|
|
172
|
+
durably_runs: RunsTable;
|
|
173
|
+
durably_steps: StepsTable;
|
|
174
|
+
durably_logs: LogsTable;
|
|
175
|
+
durably_schema_versions: SchemaVersionsTable;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Run data for creating a new run
|
|
180
|
+
*/
|
|
181
|
+
interface CreateRunInput {
|
|
182
|
+
jobName: string;
|
|
183
|
+
payload: unknown;
|
|
184
|
+
idempotencyKey?: string;
|
|
185
|
+
concurrencyKey?: string;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Run data returned from storage
|
|
189
|
+
*/
|
|
190
|
+
interface Run {
|
|
191
|
+
id: string;
|
|
192
|
+
jobName: string;
|
|
193
|
+
payload: unknown;
|
|
194
|
+
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
195
|
+
idempotencyKey: string | null;
|
|
196
|
+
concurrencyKey: string | null;
|
|
197
|
+
currentStepIndex: number;
|
|
198
|
+
progress: {
|
|
199
|
+
current: number;
|
|
200
|
+
total?: number;
|
|
201
|
+
message?: string;
|
|
202
|
+
} | null;
|
|
203
|
+
output: unknown | null;
|
|
204
|
+
error: string | null;
|
|
205
|
+
heartbeatAt: string;
|
|
206
|
+
createdAt: string;
|
|
207
|
+
updatedAt: string;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Run update data
|
|
211
|
+
*/
|
|
212
|
+
interface UpdateRunInput {
|
|
213
|
+
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
214
|
+
currentStepIndex?: number;
|
|
215
|
+
progress?: {
|
|
216
|
+
current: number;
|
|
217
|
+
total?: number;
|
|
218
|
+
message?: string;
|
|
219
|
+
} | null;
|
|
220
|
+
output?: unknown;
|
|
221
|
+
error?: string | null;
|
|
222
|
+
heartbeatAt?: string;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Run filter options
|
|
226
|
+
*/
|
|
227
|
+
interface RunFilter$1 {
|
|
228
|
+
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
229
|
+
jobName?: string;
|
|
230
|
+
/** Maximum number of runs to return */
|
|
231
|
+
limit?: number;
|
|
232
|
+
/** Number of runs to skip (for pagination) */
|
|
233
|
+
offset?: number;
|
|
11
234
|
}
|
|
12
|
-
|
|
13
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Step data for creating a new step
|
|
237
|
+
*/
|
|
238
|
+
interface CreateStepInput {
|
|
239
|
+
runId: string;
|
|
240
|
+
name: string;
|
|
241
|
+
index: number;
|
|
242
|
+
status: 'completed' | 'failed';
|
|
243
|
+
output?: unknown;
|
|
244
|
+
error?: string;
|
|
245
|
+
startedAt: string;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Step data returned from storage
|
|
249
|
+
*/
|
|
250
|
+
interface Step {
|
|
251
|
+
id: string;
|
|
252
|
+
runId: string;
|
|
253
|
+
name: string;
|
|
254
|
+
index: number;
|
|
255
|
+
status: 'completed' | 'failed';
|
|
256
|
+
output: unknown | null;
|
|
257
|
+
error: string | null;
|
|
258
|
+
startedAt: string;
|
|
259
|
+
completedAt: string | null;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Log data for creating a new log
|
|
263
|
+
*/
|
|
264
|
+
interface CreateLogInput {
|
|
265
|
+
runId: string;
|
|
266
|
+
stepName: string | null;
|
|
267
|
+
level: 'info' | 'warn' | 'error';
|
|
268
|
+
message: string;
|
|
269
|
+
data?: unknown;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Log data returned from storage
|
|
273
|
+
*/
|
|
274
|
+
interface Log {
|
|
275
|
+
id: string;
|
|
276
|
+
runId: string;
|
|
277
|
+
stepName: string | null;
|
|
278
|
+
level: 'info' | 'warn' | 'error';
|
|
279
|
+
message: string;
|
|
280
|
+
data: unknown | null;
|
|
281
|
+
createdAt: string;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Storage interface for database operations
|
|
285
|
+
*/
|
|
286
|
+
interface Storage {
|
|
287
|
+
createRun(input: CreateRunInput): Promise<Run>;
|
|
288
|
+
batchCreateRuns(inputs: CreateRunInput[]): Promise<Run[]>;
|
|
289
|
+
updateRun(runId: string, data: UpdateRunInput): Promise<void>;
|
|
290
|
+
deleteRun(runId: string): Promise<void>;
|
|
291
|
+
getRun(runId: string): Promise<Run | null>;
|
|
292
|
+
getRuns(filter?: RunFilter$1): Promise<Run[]>;
|
|
293
|
+
getNextPendingRun(excludeConcurrencyKeys: string[]): Promise<Run | null>;
|
|
294
|
+
createStep(input: CreateStepInput): Promise<Step>;
|
|
295
|
+
getSteps(runId: string): Promise<Step[]>;
|
|
296
|
+
getCompletedStep(runId: string, name: string): Promise<Step | null>;
|
|
297
|
+
createLog(input: CreateLogInput): Promise<Log>;
|
|
298
|
+
getLogs(runId: string): Promise<Log[]>;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Job context passed to the job function
|
|
303
|
+
*/
|
|
304
|
+
interface JobContext {
|
|
305
|
+
/**
|
|
306
|
+
* The ID of the current run
|
|
307
|
+
*/
|
|
308
|
+
readonly runId: string;
|
|
309
|
+
/**
|
|
310
|
+
* Execute a step with automatic persistence and replay
|
|
311
|
+
*/
|
|
312
|
+
run<T>(name: string, fn: () => T | Promise<T>): Promise<T>;
|
|
313
|
+
/**
|
|
314
|
+
* Report progress for the current run
|
|
315
|
+
*/
|
|
316
|
+
progress(current: number, total?: number, message?: string): void;
|
|
317
|
+
/**
|
|
318
|
+
* Log a message
|
|
319
|
+
*/
|
|
14
320
|
log: {
|
|
15
|
-
info(message: string, data?:
|
|
16
|
-
warn(message: string, data?:
|
|
17
|
-
error(message: string, data?:
|
|
321
|
+
info(message: string, data?: unknown): void;
|
|
322
|
+
warn(message: string, data?: unknown): void;
|
|
323
|
+
error(message: string, data?: unknown): void;
|
|
18
324
|
};
|
|
19
325
|
}
|
|
20
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Job function type
|
|
328
|
+
*/
|
|
329
|
+
type JobFunction<TInput, TOutput> = (context: JobContext, payload: TInput) => Promise<TOutput>;
|
|
330
|
+
/**
|
|
331
|
+
* Job definition options
|
|
332
|
+
*/
|
|
333
|
+
interface JobDefinition<TName extends string, TInputSchema extends z.ZodType, TOutputSchema extends z.ZodType | undefined> {
|
|
334
|
+
name: TName;
|
|
335
|
+
input: TInputSchema;
|
|
336
|
+
output?: TOutputSchema;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Trigger options
|
|
340
|
+
*/
|
|
341
|
+
interface TriggerOptions {
|
|
342
|
+
idempotencyKey?: string;
|
|
343
|
+
concurrencyKey?: string;
|
|
344
|
+
/** Timeout in milliseconds for triggerAndWait() */
|
|
345
|
+
timeout?: number;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Run filter options
|
|
349
|
+
*/
|
|
350
|
+
interface RunFilter {
|
|
351
|
+
status?: 'pending' | 'running' | 'completed' | 'failed';
|
|
352
|
+
jobName?: string;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Typed run with output type
|
|
356
|
+
*/
|
|
357
|
+
interface TypedRun<TOutput> extends Omit<Run, 'output'> {
|
|
358
|
+
output: TOutput | null;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Batch trigger input - either just the input or input with options
|
|
362
|
+
*/
|
|
363
|
+
type BatchTriggerInput<TInput> = TInput | {
|
|
364
|
+
input: TInput;
|
|
365
|
+
options?: TriggerOptions;
|
|
366
|
+
};
|
|
367
|
+
/**
|
|
368
|
+
* Result of triggerAndWait
|
|
369
|
+
*/
|
|
370
|
+
interface TriggerAndWaitResult<TOutput> {
|
|
371
|
+
id: string;
|
|
372
|
+
output: TOutput;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Job handle returned by defineJob
|
|
376
|
+
*/
|
|
377
|
+
interface JobHandle<TName extends string, TInput, TOutput> {
|
|
378
|
+
readonly name: TName;
|
|
379
|
+
/**
|
|
380
|
+
* Trigger a new run
|
|
381
|
+
*/
|
|
382
|
+
trigger(input: TInput, options?: TriggerOptions): Promise<TypedRun<TOutput>>;
|
|
383
|
+
/**
|
|
384
|
+
* Trigger a new run and wait for completion
|
|
385
|
+
* Returns the output directly, throws if the run fails
|
|
386
|
+
*/
|
|
387
|
+
triggerAndWait(input: TInput, options?: TriggerOptions): Promise<TriggerAndWaitResult<TOutput>>;
|
|
388
|
+
/**
|
|
389
|
+
* Trigger multiple runs in a batch
|
|
390
|
+
* All inputs are validated before any runs are created
|
|
391
|
+
*/
|
|
392
|
+
batchTrigger(inputs: BatchTriggerInput<TInput>[]): Promise<TypedRun<TOutput>[]>;
|
|
393
|
+
/**
|
|
394
|
+
* Get a run by ID
|
|
395
|
+
*/
|
|
396
|
+
getRun(id: string): Promise<TypedRun<TOutput> | null>;
|
|
397
|
+
/**
|
|
398
|
+
* Get runs with optional filter
|
|
399
|
+
*/
|
|
400
|
+
getRuns(filter?: Omit<RunFilter, 'jobName'>): Promise<TypedRun<TOutput>[]>;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Options for creating a Durably instance
|
|
405
|
+
*/
|
|
406
|
+
interface DurablyOptions {
|
|
407
|
+
dialect: Dialect;
|
|
408
|
+
pollingInterval?: number;
|
|
409
|
+
heartbeatInterval?: number;
|
|
410
|
+
staleThreshold?: number;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Plugin interface for extending Durably
|
|
414
|
+
*/
|
|
415
|
+
interface DurablyPlugin {
|
|
21
416
|
name: string;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
concurrencyKey?: string;
|
|
33
|
-
};
|
|
34
|
-
}>): Promise<Array<{
|
|
35
|
-
runId: string;
|
|
36
|
-
}>>;
|
|
37
|
-
}
|
|
38
|
-
interface Client {
|
|
39
|
-
register<TPayload>(job: Job<TPayload>): void;
|
|
417
|
+
install(durably: Durably): void;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Durably instance
|
|
421
|
+
*/
|
|
422
|
+
interface Durably {
|
|
423
|
+
/**
|
|
424
|
+
* Run database migrations
|
|
425
|
+
* This is idempotent and safe to call multiple times
|
|
426
|
+
*/
|
|
40
427
|
migrate(): Promise<void>;
|
|
428
|
+
/**
|
|
429
|
+
* Get the underlying Kysely database instance
|
|
430
|
+
* Useful for testing and advanced use cases
|
|
431
|
+
*/
|
|
432
|
+
readonly db: Kysely<Database>;
|
|
433
|
+
/**
|
|
434
|
+
* Storage layer for database operations
|
|
435
|
+
*/
|
|
436
|
+
readonly storage: Storage;
|
|
437
|
+
/**
|
|
438
|
+
* Register an event listener
|
|
439
|
+
* @returns Unsubscribe function
|
|
440
|
+
*/
|
|
441
|
+
on<T extends EventType>(type: T, listener: EventListener<T>): Unsubscribe;
|
|
442
|
+
/**
|
|
443
|
+
* Emit an event (auto-assigns timestamp and sequence)
|
|
444
|
+
*/
|
|
445
|
+
emit(event: AnyEventInput): void;
|
|
446
|
+
/**
|
|
447
|
+
* Register an error handler for listener exceptions
|
|
448
|
+
*/
|
|
449
|
+
onError(handler: ErrorHandler): void;
|
|
450
|
+
/**
|
|
451
|
+
* Define a job
|
|
452
|
+
*/
|
|
453
|
+
defineJob<TName extends string, TInputSchema extends z.ZodType, TOutputSchema extends z.ZodType | undefined = undefined>(definition: JobDefinition<TName, TInputSchema, TOutputSchema>, fn: JobFunction<z.infer<TInputSchema>, TOutputSchema extends z.ZodType ? z.infer<TOutputSchema> : void>): JobHandle<TName, z.infer<TInputSchema>, TOutputSchema extends z.ZodType ? z.infer<TOutputSchema> : void>;
|
|
454
|
+
/**
|
|
455
|
+
* Start the worker polling loop
|
|
456
|
+
*/
|
|
41
457
|
start(): void;
|
|
458
|
+
/**
|
|
459
|
+
* Stop the worker after current run completes
|
|
460
|
+
*/
|
|
42
461
|
stop(): Promise<void>;
|
|
462
|
+
/**
|
|
463
|
+
* Retry a failed run by resetting it to pending
|
|
464
|
+
* @throws Error if run is not in failed status
|
|
465
|
+
*/
|
|
43
466
|
retry(runId: string): Promise<void>;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
467
|
+
/**
|
|
468
|
+
* Cancel a pending or running run
|
|
469
|
+
* @throws Error if run is already completed, failed, or cancelled
|
|
470
|
+
*/
|
|
471
|
+
cancel(runId: string): Promise<void>;
|
|
472
|
+
/**
|
|
473
|
+
* Delete a completed, failed, or cancelled run and its associated steps and logs
|
|
474
|
+
* @throws Error if run is pending or running, or does not exist
|
|
475
|
+
*/
|
|
476
|
+
deleteRun(runId: string): Promise<void>;
|
|
477
|
+
/**
|
|
478
|
+
* Get a run by ID (returns unknown output type)
|
|
479
|
+
*/
|
|
480
|
+
getRun(runId: string): Promise<Run | null>;
|
|
481
|
+
/**
|
|
482
|
+
* Get runs with optional filtering
|
|
483
|
+
*/
|
|
484
|
+
getRuns(filter?: RunFilter$1): Promise<Run[]>;
|
|
485
|
+
/**
|
|
486
|
+
* Register a plugin
|
|
487
|
+
*/
|
|
488
|
+
use(plugin: DurablyPlugin): void;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Create a Durably instance
|
|
492
|
+
*/
|
|
493
|
+
declare function createDurably(options: DurablyOptions): Durably;
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Plugin that persists log events to the database
|
|
497
|
+
*/
|
|
498
|
+
declare function withLogPersistence(): DurablyPlugin;
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Error thrown when a run is cancelled during execution.
|
|
502
|
+
* The worker catches this error and treats it specially - it does not
|
|
503
|
+
* mark the run as failed, as the run status is already 'cancelled'.
|
|
504
|
+
*/
|
|
505
|
+
declare class CancelledError extends Error {
|
|
506
|
+
constructor(runId: string);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export { CancelledError, type Database, type Durably, type DurablyEvent, type DurablyOptions, type DurablyPlugin, type ErrorHandler, type EventType, type JobContext, type JobHandle, type Log, type LogWriteEvent, type LogsTable, type Run, type RunCompleteEvent, type RunFailEvent, type RunFilter$1 as RunFilter, type RunStartEvent, type RunsTable, type SchemaVersionsTable, type Step, type StepCompleteEvent, type StepFailEvent, type StepStartEvent, type StepsTable, type TriggerAndWaitResult, type WorkerErrorEvent, createDurably, withLogPersistence };
|