@zucker-framework/task 1.0.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/LICENSE +201 -0
- package/NOTICE +22 -0
- package/THIRD_PARTY_NOTICES +4 -0
- package/assets/task-events.postgresql.sql +106 -0
- package/assets/task-runtime.prisma +118 -0
- package/dist/index.d.mts +604 -0
- package/dist/index.d.ts +604 -0
- package/dist/index.js +1933 -0
- package/dist/index.mjs +1881 -0
- package/package.json +53 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
declare const TASK_STATUSES: readonly ["QUEUED", "PROCESSING", "WAITING_PROVIDER", "RETRYING", "COMPLETED", "FAILED", "CANCELLED"];
|
|
2
|
+
type TaskStatus = (typeof TASK_STATUSES)[number];
|
|
3
|
+
declare class InvalidTaskTransitionError extends Error {
|
|
4
|
+
readonly from: TaskStatus;
|
|
5
|
+
readonly to: TaskStatus;
|
|
6
|
+
constructor(from: TaskStatus, to: TaskStatus);
|
|
7
|
+
}
|
|
8
|
+
declare function canTransitionTask(from: TaskStatus, to: TaskStatus): boolean;
|
|
9
|
+
declare function assertTaskTransition(from: TaskStatus, to: TaskStatus): void;
|
|
10
|
+
declare function isTerminalTaskStatus(status: TaskStatus): boolean;
|
|
11
|
+
declare function requiresTaskLease(status: TaskStatus): boolean;
|
|
12
|
+
|
|
13
|
+
/** Produces a deterministic SHA-256 fingerprint for a JSON-compatible task request. */
|
|
14
|
+
declare function createTaskFingerprint(value: unknown): string;
|
|
15
|
+
|
|
16
|
+
interface TaskResourceRef {
|
|
17
|
+
type: string;
|
|
18
|
+
id: string;
|
|
19
|
+
}
|
|
20
|
+
type TaskFailureCategory = 'RATE_LIMIT' | 'NETWORK' | 'PROVIDER_5XX' | 'PROVIDER_TIMEOUT' | 'INTERNAL' | 'VALIDATION' | 'AUTHENTICATION' | 'AUTHORIZATION' | 'CONTENT_POLICY' | 'CANCELLED' | 'UNKNOWN';
|
|
21
|
+
interface TaskErrorSummary {
|
|
22
|
+
code: string;
|
|
23
|
+
category: TaskFailureCategory;
|
|
24
|
+
summary: string;
|
|
25
|
+
retryable: boolean;
|
|
26
|
+
metadata?: Readonly<Record<string, string | number | boolean | null>>;
|
|
27
|
+
}
|
|
28
|
+
interface TaskRecord<TPayload = unknown, TResult = unknown> {
|
|
29
|
+
id: string;
|
|
30
|
+
kind: string;
|
|
31
|
+
resource: TaskResourceRef;
|
|
32
|
+
userId?: string;
|
|
33
|
+
orderId?: string;
|
|
34
|
+
status: TaskStatus;
|
|
35
|
+
stage?: string;
|
|
36
|
+
progress: number;
|
|
37
|
+
attempt: number;
|
|
38
|
+
maxAttempts: number;
|
|
39
|
+
idempotencyKey: string;
|
|
40
|
+
idempotencyFingerprint: string;
|
|
41
|
+
handlerVersion: string;
|
|
42
|
+
payload: TPayload;
|
|
43
|
+
result?: TResult;
|
|
44
|
+
error?: TaskErrorSummary;
|
|
45
|
+
providerRequestId?: string;
|
|
46
|
+
workerId?: string;
|
|
47
|
+
leaseToken?: string;
|
|
48
|
+
leaseExpiresAt?: Date;
|
|
49
|
+
heartbeatAt?: Date;
|
|
50
|
+
availableAt: Date;
|
|
51
|
+
createdAt: Date;
|
|
52
|
+
startedAt?: Date;
|
|
53
|
+
completedAt?: Date;
|
|
54
|
+
updatedAt: Date;
|
|
55
|
+
version: number;
|
|
56
|
+
}
|
|
57
|
+
interface CreateTaskInput<TPayload> {
|
|
58
|
+
id: string;
|
|
59
|
+
kind: string;
|
|
60
|
+
resource: TaskResourceRef;
|
|
61
|
+
userId?: string;
|
|
62
|
+
orderId?: string;
|
|
63
|
+
idempotencyKey: string;
|
|
64
|
+
idempotencyFingerprint: string;
|
|
65
|
+
handlerVersion: string;
|
|
66
|
+
payload: TPayload;
|
|
67
|
+
maxAttempts: number;
|
|
68
|
+
availableAt?: Date;
|
|
69
|
+
now: Date;
|
|
70
|
+
}
|
|
71
|
+
interface CreateTaskResult<TPayload, TResult> {
|
|
72
|
+
task: TaskRecord<TPayload, TResult>;
|
|
73
|
+
created: boolean;
|
|
74
|
+
}
|
|
75
|
+
interface ClaimTaskInput {
|
|
76
|
+
id: string;
|
|
77
|
+
workerId: string;
|
|
78
|
+
leaseToken: string;
|
|
79
|
+
leaseExpiresAt: Date;
|
|
80
|
+
now: Date;
|
|
81
|
+
}
|
|
82
|
+
interface HeartbeatTaskInput {
|
|
83
|
+
id: string;
|
|
84
|
+
leaseToken: string;
|
|
85
|
+
leaseExpiresAt: Date;
|
|
86
|
+
now: Date;
|
|
87
|
+
}
|
|
88
|
+
interface ReportTaskProgressInput {
|
|
89
|
+
id: string;
|
|
90
|
+
leaseToken: string;
|
|
91
|
+
stage?: string;
|
|
92
|
+
progress: number;
|
|
93
|
+
now: Date;
|
|
94
|
+
}
|
|
95
|
+
interface TransitionTaskInput<TResult> {
|
|
96
|
+
id: string;
|
|
97
|
+
from: TaskStatus;
|
|
98
|
+
to: TaskStatus;
|
|
99
|
+
leaseToken?: string;
|
|
100
|
+
stage?: string;
|
|
101
|
+
progress?: number;
|
|
102
|
+
result?: TResult;
|
|
103
|
+
error?: TaskErrorSummary;
|
|
104
|
+
providerRequestId?: string;
|
|
105
|
+
availableAt?: Date;
|
|
106
|
+
now: Date;
|
|
107
|
+
}
|
|
108
|
+
interface FindExpiredTaskLeasesInput {
|
|
109
|
+
now: Date;
|
|
110
|
+
limit: number;
|
|
111
|
+
}
|
|
112
|
+
interface FindReadyQueuedTasksInput {
|
|
113
|
+
now: Date;
|
|
114
|
+
limit: number;
|
|
115
|
+
offset?: number;
|
|
116
|
+
}
|
|
117
|
+
interface RecoverExpiredTaskLeaseInput {
|
|
118
|
+
id: string;
|
|
119
|
+
leaseToken: string;
|
|
120
|
+
now: Date;
|
|
121
|
+
availableAt?: Date;
|
|
122
|
+
error: TaskErrorSummary;
|
|
123
|
+
}
|
|
124
|
+
interface TaskRepository<TPayload = unknown, TResult = unknown> {
|
|
125
|
+
create(input: CreateTaskInput<TPayload>): Promise<CreateTaskResult<TPayload, TResult>>;
|
|
126
|
+
findById(id: string): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
127
|
+
findByIdempotencyKey(key: string): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
128
|
+
/** The only operation allowed to start a queued attempt and issue its lease. */
|
|
129
|
+
claim(input: ClaimTaskInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
130
|
+
heartbeat(input: HeartbeatTaskInput): Promise<boolean>;
|
|
131
|
+
/** Reports monotonic progress only while the current attempt lease is valid. */
|
|
132
|
+
reportProgress(input: ReportTaskProgressInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
133
|
+
/** Applies lifecycle transitions other than QUEUED -> PROCESSING, which must use claim(). */
|
|
134
|
+
transition(input: TransitionTaskInput<TResult>): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
135
|
+
/** Returns durable work that should have a corresponding transport message. */
|
|
136
|
+
findReadyQueued(input: FindReadyQueuedTasksInput): Promise<TaskRecord<TPayload, TResult>[]>;
|
|
137
|
+
findExpiredLeases(input: FindExpiredTaskLeasesInput): Promise<TaskRecord<TPayload, TResult>[]>;
|
|
138
|
+
recoverExpiredLease(input: RecoverExpiredTaskLeaseInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
139
|
+
cancel(id: string, now: Date): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
140
|
+
}
|
|
141
|
+
declare class TaskIdempotencyConflictError extends Error {
|
|
142
|
+
readonly idempotencyKey: string;
|
|
143
|
+
constructor(idempotencyKey: string);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Reference implementation for contract tests and local development only.
|
|
148
|
+
* Production applications must use a repository with durable storage and atomic conditional updates.
|
|
149
|
+
*/
|
|
150
|
+
declare class InMemoryTaskRepository<TPayload = unknown, TResult = unknown> implements TaskRepository<TPayload, TResult> {
|
|
151
|
+
private readonly tasks;
|
|
152
|
+
private readonly idempotencyIndex;
|
|
153
|
+
create(input: CreateTaskInput<TPayload>): Promise<CreateTaskResult<TPayload, TResult>>;
|
|
154
|
+
findById(id: string): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
155
|
+
findByIdempotencyKey(key: string): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
156
|
+
claim(input: ClaimTaskInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
157
|
+
heartbeat(input: HeartbeatTaskInput): Promise<boolean>;
|
|
158
|
+
reportProgress(input: ReportTaskProgressInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
159
|
+
transition(input: TransitionTaskInput<TResult>): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
160
|
+
findExpiredLeases(input: FindExpiredTaskLeasesInput): Promise<TaskRecord<TPayload, TResult>[]>;
|
|
161
|
+
findReadyQueued(input: FindReadyQueuedTasksInput): Promise<TaskRecord<TPayload, TResult>[]>;
|
|
162
|
+
recoverExpiredLease(input: RecoverExpiredTaskLeaseInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
163
|
+
cancel(id: string, now: Date): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface PostgresQueryResult<TRow> {
|
|
167
|
+
rows: TRow[];
|
|
168
|
+
}
|
|
169
|
+
/** Minimal port implemented by pg clients, PGlite, and thin Prisma raw-query adapters. */
|
|
170
|
+
interface PostgresTaskClient {
|
|
171
|
+
query<TRow>(sql: string, parameters?: readonly unknown[]): Promise<PostgresQueryResult<TRow>>;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* PostgreSQL reference repository for the published Prisma schema fragment.
|
|
175
|
+
* All ownership-changing operations use conditional UPDATE ... RETURNING statements.
|
|
176
|
+
*/
|
|
177
|
+
declare class PostgresTaskRepository<TPayload = unknown, TResult = unknown> implements TaskRepository<TPayload, TResult> {
|
|
178
|
+
private readonly client;
|
|
179
|
+
constructor(client: PostgresTaskClient);
|
|
180
|
+
create(input: CreateTaskInput<TPayload>): Promise<CreateTaskResult<TPayload, TResult>>;
|
|
181
|
+
findById(id: string): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
182
|
+
findByIdempotencyKey(key: string): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
183
|
+
claim(input: ClaimTaskInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
184
|
+
heartbeat(input: HeartbeatTaskInput): Promise<boolean>;
|
|
185
|
+
reportProgress(input: ReportTaskProgressInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
186
|
+
transition(input: TransitionTaskInput<TResult>): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
187
|
+
findExpiredLeases(input: FindExpiredTaskLeasesInput): Promise<TaskRecord<TPayload, TResult>[]>;
|
|
188
|
+
findReadyQueued(input: FindReadyQueuedTasksInput): Promise<TaskRecord<TPayload, TResult>[]>;
|
|
189
|
+
recoverExpiredLease(input: RecoverExpiredTaskLeaseInput): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
190
|
+
cancel(id: string, now: Date): Promise<TaskRecord<TPayload, TResult> | null>;
|
|
191
|
+
private toTask;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
interface TaskSummaryError {
|
|
195
|
+
code: string;
|
|
196
|
+
category: TaskFailureCategory;
|
|
197
|
+
summary: string;
|
|
198
|
+
retryable: boolean;
|
|
199
|
+
}
|
|
200
|
+
interface TaskSummary {
|
|
201
|
+
id: string;
|
|
202
|
+
kind: string;
|
|
203
|
+
resource: TaskResourceRef;
|
|
204
|
+
status: TaskStatus;
|
|
205
|
+
stage?: string;
|
|
206
|
+
progress: number;
|
|
207
|
+
attempt: number;
|
|
208
|
+
maxAttempts: number;
|
|
209
|
+
error?: TaskSummaryError;
|
|
210
|
+
createdAt: Date;
|
|
211
|
+
startedAt?: Date;
|
|
212
|
+
completedAt?: Date;
|
|
213
|
+
updatedAt: Date;
|
|
214
|
+
version: number;
|
|
215
|
+
}
|
|
216
|
+
interface ListOwnedTaskSummariesInput {
|
|
217
|
+
userId: string;
|
|
218
|
+
statuses?: readonly TaskStatus[];
|
|
219
|
+
limit?: number;
|
|
220
|
+
}
|
|
221
|
+
/** Read-only, owner-scoped task projection that never loads task payloads or results. */
|
|
222
|
+
declare class PostgresTaskSummaryReader {
|
|
223
|
+
private readonly client;
|
|
224
|
+
constructor(client: PostgresTaskClient);
|
|
225
|
+
findOwnedById(taskId: string, userId: string): Promise<TaskSummary | null>;
|
|
226
|
+
listOwned(input: ListOwnedTaskSummariesInput): Promise<TaskSummary[]>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
declare const TASK_TIMELINE_EVENT_TYPES: readonly ["SNAPSHOT", "CREATED", "CLAIMED", "PROGRESS", "RETRY_SCHEDULED", "REQUEUED", "LEASE_RECOVERED", "MANUAL_REDRIVE", "COMPLETED", "FAILED", "CANCELLED", "STATUS_CHANGED"];
|
|
230
|
+
type TaskTimelineEventType = (typeof TASK_TIMELINE_EVENT_TYPES)[number];
|
|
231
|
+
interface OwnedTaskTimelineEvent {
|
|
232
|
+
id: string;
|
|
233
|
+
taskId: string;
|
|
234
|
+
sequence: number;
|
|
235
|
+
type: TaskTimelineEventType;
|
|
236
|
+
fromStatus?: TaskStatus;
|
|
237
|
+
toStatus: TaskStatus;
|
|
238
|
+
stage?: string;
|
|
239
|
+
progress: number;
|
|
240
|
+
attempt: number;
|
|
241
|
+
maxAttempts: number;
|
|
242
|
+
error?: {
|
|
243
|
+
code: string;
|
|
244
|
+
category: TaskFailureCategory;
|
|
245
|
+
summary: string;
|
|
246
|
+
retryable: boolean;
|
|
247
|
+
};
|
|
248
|
+
createdAt: Date;
|
|
249
|
+
}
|
|
250
|
+
interface TaskTimelineEvent extends OwnedTaskTimelineEvent {
|
|
251
|
+
workerId?: string;
|
|
252
|
+
providerRequestId?: string;
|
|
253
|
+
}
|
|
254
|
+
/** Payload-free timeline reader with separate owner and operator projections. */
|
|
255
|
+
declare class PostgresTaskTimelineReader {
|
|
256
|
+
private readonly client;
|
|
257
|
+
constructor(client: PostgresTaskClient);
|
|
258
|
+
listOwned(taskId: string, userId: string, limit?: number): Promise<OwnedTaskTimelineEvent[]>;
|
|
259
|
+
listByTaskId(taskId: string, limit?: number): Promise<TaskTimelineEvent[]>;
|
|
260
|
+
private normalizeLimit;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
interface RedriveFailedTaskInput {
|
|
264
|
+
taskId: string;
|
|
265
|
+
now: Date;
|
|
266
|
+
additionalAttempts?: number;
|
|
267
|
+
stage?: string;
|
|
268
|
+
}
|
|
269
|
+
interface TaskRedriveResult {
|
|
270
|
+
taskId: string;
|
|
271
|
+
attempt: number;
|
|
272
|
+
maxAttempts: number;
|
|
273
|
+
version: number;
|
|
274
|
+
availableAt: Date;
|
|
275
|
+
}
|
|
276
|
+
/** Generic terminal-failure redrive primitive; domain projections and outbox stay application-owned. */
|
|
277
|
+
declare class PostgresTaskRedriver {
|
|
278
|
+
private readonly client;
|
|
279
|
+
constructor(client: PostgresTaskClient);
|
|
280
|
+
redriveFailed(input: RedriveFailedTaskInput): Promise<TaskRedriveResult | null>;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
type TaskOutboxStatus = 'PENDING' | 'PUBLISHING' | 'PUBLISHED';
|
|
284
|
+
interface TaskOutboxRecord<TPayload = unknown> {
|
|
285
|
+
id: string;
|
|
286
|
+
taskId: string;
|
|
287
|
+
topic: string;
|
|
288
|
+
payload: TPayload;
|
|
289
|
+
status: TaskOutboxStatus;
|
|
290
|
+
attempt: number;
|
|
291
|
+
availableAt: Date;
|
|
292
|
+
workerId?: string;
|
|
293
|
+
leaseToken?: string;
|
|
294
|
+
leaseExpiresAt?: Date;
|
|
295
|
+
createdAt: Date;
|
|
296
|
+
updatedAt: Date;
|
|
297
|
+
publishedAt?: Date;
|
|
298
|
+
lastError?: string;
|
|
299
|
+
}
|
|
300
|
+
interface AppendTaskOutboxInput<TPayload> {
|
|
301
|
+
id: string;
|
|
302
|
+
taskId: string;
|
|
303
|
+
topic: string;
|
|
304
|
+
payload: TPayload;
|
|
305
|
+
availableAt?: Date;
|
|
306
|
+
now: Date;
|
|
307
|
+
}
|
|
308
|
+
interface ClaimTaskOutboxInput {
|
|
309
|
+
workerId: string;
|
|
310
|
+
leaseToken: string;
|
|
311
|
+
leaseExpiresAt: Date;
|
|
312
|
+
limit: number;
|
|
313
|
+
now: Date;
|
|
314
|
+
}
|
|
315
|
+
interface TaskOutboxRepository<TPayload = unknown> {
|
|
316
|
+
append(input: AppendTaskOutboxInput<TPayload>): Promise<{
|
|
317
|
+
record: TaskOutboxRecord<TPayload>;
|
|
318
|
+
created: boolean;
|
|
319
|
+
}>;
|
|
320
|
+
claimBatch(input: ClaimTaskOutboxInput): Promise<TaskOutboxRecord<TPayload>[]>;
|
|
321
|
+
markPublished(id: string, leaseToken: string, now: Date): Promise<boolean>;
|
|
322
|
+
reschedule(id: string, leaseToken: string, availableAt: Date, errorSummary: string, now: Date): Promise<boolean>;
|
|
323
|
+
}
|
|
324
|
+
interface TaskQueuePublisher<TPayload = unknown> {
|
|
325
|
+
publish(message: TaskOutboxRecord<TPayload>): Promise<void>;
|
|
326
|
+
}
|
|
327
|
+
declare class TaskOutboxConflictError extends Error {
|
|
328
|
+
readonly eventId: string;
|
|
329
|
+
constructor(eventId: string);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
interface PostgresTaskTransactionRunner {
|
|
333
|
+
runInTransaction<T>(work: (client: PostgresTaskClient) => Promise<T>): Promise<T>;
|
|
334
|
+
}
|
|
335
|
+
interface SubmitTaskOutboxInput<TPayload> {
|
|
336
|
+
id: string;
|
|
337
|
+
topic: string;
|
|
338
|
+
payload: TPayload;
|
|
339
|
+
availableAt?: Date;
|
|
340
|
+
}
|
|
341
|
+
interface SubmitTaskInput<TTaskPayload, TOutboxPayload> {
|
|
342
|
+
task: CreateTaskInput<TTaskPayload>;
|
|
343
|
+
outbox: SubmitTaskOutboxInput<TOutboxPayload>;
|
|
344
|
+
}
|
|
345
|
+
interface SubmitTaskResult<TTaskPayload, TTaskResult, TOutboxPayload> {
|
|
346
|
+
task: CreateTaskResult<TTaskPayload, TTaskResult>;
|
|
347
|
+
outbox: {
|
|
348
|
+
record: TaskOutboxRecord<TOutboxPayload>;
|
|
349
|
+
created: boolean;
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
interface SubmitTaskOptions<TTaskPayload, TTaskResult, TOutboxPayload> {
|
|
353
|
+
/**
|
|
354
|
+
* Persists an application-owned projection in the same transaction as the
|
|
355
|
+
* durable task and outbox intent. The callback can run again for an
|
|
356
|
+
* idempotent submission, so application writes must also be idempotent.
|
|
357
|
+
*/
|
|
358
|
+
transactionalWork?: (client: PostgresTaskClient, result: SubmitTaskResult<TTaskPayload, TTaskResult, TOutboxPayload>) => Promise<void>;
|
|
359
|
+
}
|
|
360
|
+
interface SubmitTaskBatchOptions<TTaskPayload, TTaskResult, TOutboxPayload> {
|
|
361
|
+
/**
|
|
362
|
+
* Persists application-owned batch projections in the same transaction as
|
|
363
|
+
* every task and outbox intent. The callback receives results in input order
|
|
364
|
+
* and must remain idempotent when a batch is submitted again.
|
|
365
|
+
*/
|
|
366
|
+
transactionalWork?: (client: PostgresTaskClient, results: SubmitTaskResult<TTaskPayload, TTaskResult, TOutboxPayload>[]) => Promise<void>;
|
|
367
|
+
}
|
|
368
|
+
/** Creates the durable task and its queue intent in one database transaction. */
|
|
369
|
+
declare class PostgresTaskSubmitter<TTaskPayload = unknown, TTaskResult = unknown, TOutboxPayload = unknown> {
|
|
370
|
+
private readonly transactions;
|
|
371
|
+
constructor(transactions: PostgresTaskTransactionRunner);
|
|
372
|
+
submit(input: SubmitTaskInput<TTaskPayload, TOutboxPayload>, options?: SubmitTaskOptions<TTaskPayload, TTaskResult, TOutboxPayload>): Promise<SubmitTaskResult<TTaskPayload, TTaskResult, TOutboxPayload>>;
|
|
373
|
+
submitBatch(inputs: readonly SubmitTaskInput<TTaskPayload, TOutboxPayload>[], options?: SubmitTaskBatchOptions<TTaskPayload, TTaskResult, TOutboxPayload>): Promise<SubmitTaskResult<TTaskPayload, TTaskResult, TOutboxPayload>[]>;
|
|
374
|
+
private submitWithClient;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
interface TaskLeaseReconcilerOptions {
|
|
378
|
+
batchSize?: number;
|
|
379
|
+
retryDelayMs?: number;
|
|
380
|
+
now?: () => Date;
|
|
381
|
+
}
|
|
382
|
+
interface TaskLeaseReconcileResult {
|
|
383
|
+
found: number;
|
|
384
|
+
requeued: number;
|
|
385
|
+
failed: number;
|
|
386
|
+
conflicts: number;
|
|
387
|
+
}
|
|
388
|
+
declare class TaskLeaseReconciler<TPayload = unknown, TResult = unknown> {
|
|
389
|
+
private readonly repository;
|
|
390
|
+
private readonly batchSize;
|
|
391
|
+
private readonly retryDelayMs;
|
|
392
|
+
private readonly now;
|
|
393
|
+
constructor(repository: TaskRepository<TPayload, TResult>, options?: TaskLeaseReconcilerOptions);
|
|
394
|
+
runOnce(): Promise<TaskLeaseReconcileResult>;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
interface TaskQueueReconcilerOptions {
|
|
398
|
+
batchSize?: number;
|
|
399
|
+
now?: () => Date;
|
|
400
|
+
}
|
|
401
|
+
interface TaskQueueReconcileResult {
|
|
402
|
+
found: number;
|
|
403
|
+
ensured: number;
|
|
404
|
+
failed: number;
|
|
405
|
+
}
|
|
406
|
+
interface ReadyTaskPublisher<TPayload = unknown, TResult = unknown> {
|
|
407
|
+
ensure(task: TaskRecord<TPayload, TResult>): Promise<void>;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Repairs drift between the durable task store and an at-least-once queue.
|
|
411
|
+
* A publish failure leaves the task QUEUED so a later pass can retry safely.
|
|
412
|
+
*/
|
|
413
|
+
declare class TaskQueueReconciler<TPayload = unknown, TResult = unknown> {
|
|
414
|
+
private readonly repository;
|
|
415
|
+
private readonly publisher;
|
|
416
|
+
private readonly batchSize;
|
|
417
|
+
private readonly now;
|
|
418
|
+
private offset;
|
|
419
|
+
constructor(repository: TaskRepository<TPayload, TResult>, publisher: ReadyTaskPublisher<TPayload, TResult>, options?: TaskQueueReconcilerOptions);
|
|
420
|
+
runOnce(): Promise<TaskQueueReconcileResult>;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
interface TaskRetryContext {
|
|
424
|
+
error: TaskErrorSummary;
|
|
425
|
+
attempt: number;
|
|
426
|
+
maxAttempts: number;
|
|
427
|
+
retryAfterMs?: number;
|
|
428
|
+
}
|
|
429
|
+
type TaskRetryDecision = {
|
|
430
|
+
retry: false;
|
|
431
|
+
reason: 'NON_RETRYABLE' | 'ATTEMPTS_EXHAUSTED';
|
|
432
|
+
} | {
|
|
433
|
+
retry: true;
|
|
434
|
+
delayMs: number;
|
|
435
|
+
};
|
|
436
|
+
interface TaskRetryPolicy {
|
|
437
|
+
decide(context: TaskRetryContext): TaskRetryDecision;
|
|
438
|
+
}
|
|
439
|
+
interface ExponentialTaskRetryPolicyOptions {
|
|
440
|
+
baseDelayMs?: number;
|
|
441
|
+
maxDelayMs?: number;
|
|
442
|
+
jitterRatio?: number;
|
|
443
|
+
retryableCategories?: readonly TaskFailureCategory[];
|
|
444
|
+
random?: () => number;
|
|
445
|
+
}
|
|
446
|
+
declare class ExponentialTaskRetryPolicy implements TaskRetryPolicy {
|
|
447
|
+
private readonly baseDelayMs;
|
|
448
|
+
private readonly maxDelayMs;
|
|
449
|
+
private readonly jitterRatio;
|
|
450
|
+
private readonly retryableCategories;
|
|
451
|
+
private readonly random;
|
|
452
|
+
constructor(options?: ExponentialTaskRetryPolicyOptions);
|
|
453
|
+
decide(context: TaskRetryContext): TaskRetryDecision;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
interface DurableTaskAttemptContext<TPayload = unknown, TResult = unknown> {
|
|
457
|
+
readonly task: TaskRecord<TPayload, TResult>;
|
|
458
|
+
readonly leaseToken: string;
|
|
459
|
+
readonly signal: AbortSignal;
|
|
460
|
+
now(): Date;
|
|
461
|
+
assertActive(): Promise<void>;
|
|
462
|
+
reportProgress(stage: string | undefined, progress: number): Promise<TaskRecord<TPayload, TResult>>;
|
|
463
|
+
}
|
|
464
|
+
interface DurableTaskHandler<TPayload = unknown, TResult = unknown> {
|
|
465
|
+
execute(context: DurableTaskAttemptContext<TPayload, TResult>): Promise<TResult>;
|
|
466
|
+
/**
|
|
467
|
+
* Override when task completion and a business projection must commit atomically.
|
|
468
|
+
* Returning false means the current lease no longer owns the commit.
|
|
469
|
+
*/
|
|
470
|
+
complete?(context: DurableTaskAttemptContext<TPayload, TResult>, result: TResult): Promise<boolean>;
|
|
471
|
+
/** Override when retry state and a business projection must commit atomically. */
|
|
472
|
+
retry?(context: DurableTaskAttemptContext<TPayload, TResult>, error: TaskErrorSummary, retryAt: Date): Promise<boolean>;
|
|
473
|
+
/** Override when failure state and a business projection must commit atomically. */
|
|
474
|
+
fail?(context: DurableTaskAttemptContext<TPayload, TResult>, error: TaskErrorSummary): Promise<boolean>;
|
|
475
|
+
onCancelled?(context: DurableTaskAttemptContext<TPayload, TResult>): Promise<void> | void;
|
|
476
|
+
cleanup?(context: DurableTaskAttemptContext<TPayload, TResult>): Promise<void> | void;
|
|
477
|
+
}
|
|
478
|
+
interface DurableWorkerRuntimeLogger {
|
|
479
|
+
debug?(message: string, context: Readonly<Record<string, unknown>>): void;
|
|
480
|
+
warn?(message: string, context: Readonly<Record<string, unknown>>): void;
|
|
481
|
+
error?(message: string, context: Readonly<Record<string, unknown>>): void;
|
|
482
|
+
}
|
|
483
|
+
interface DurableWorkerHeartbeatScheduler {
|
|
484
|
+
start(tick: () => Promise<void>, intervalMs: number): unknown;
|
|
485
|
+
stop(handle: unknown): void;
|
|
486
|
+
}
|
|
487
|
+
interface DurableWorkerRuntimeOptions<TPayload = unknown, TResult = unknown> {
|
|
488
|
+
repository: TaskRepository<TPayload, TResult>;
|
|
489
|
+
resolveHandler(task: TaskRecord<TPayload, TResult>): DurableTaskHandler<TPayload, TResult> | null | undefined;
|
|
490
|
+
retryPolicy: TaskRetryPolicy;
|
|
491
|
+
classifyError(error: unknown, task: TaskRecord<TPayload, TResult>): TaskErrorSummary;
|
|
492
|
+
workerId: string;
|
|
493
|
+
leaseDurationMs?: number;
|
|
494
|
+
heartbeatIntervalMs?: number;
|
|
495
|
+
maxConsecutiveHeartbeatFailures?: number;
|
|
496
|
+
now?: () => Date;
|
|
497
|
+
createLeaseToken?: () => string;
|
|
498
|
+
retryAfterMs?(error: unknown, task: TaskRecord<TPayload, TResult>): number | undefined;
|
|
499
|
+
heartbeatScheduler?: DurableWorkerHeartbeatScheduler;
|
|
500
|
+
logger?: DurableWorkerRuntimeLogger;
|
|
501
|
+
onAbort?(task: TaskRecord<TPayload, TResult>, reason: DurableTaskLeaseLostError): Promise<void> | void;
|
|
502
|
+
}
|
|
503
|
+
interface DurableWorkerRunResult {
|
|
504
|
+
taskId: string;
|
|
505
|
+
kind: string;
|
|
506
|
+
status: TaskStatus;
|
|
507
|
+
}
|
|
508
|
+
declare class DurableTaskNotFoundError extends Error {
|
|
509
|
+
readonly taskId: string;
|
|
510
|
+
constructor(taskId: string);
|
|
511
|
+
}
|
|
512
|
+
declare class UnsupportedDurableTaskKindError extends Error {
|
|
513
|
+
readonly taskId: string;
|
|
514
|
+
readonly kind: string;
|
|
515
|
+
constructor(taskId: string, kind: string);
|
|
516
|
+
}
|
|
517
|
+
declare class DurableTaskNotClaimableError extends Error {
|
|
518
|
+
readonly taskId: string;
|
|
519
|
+
readonly status: TaskStatus;
|
|
520
|
+
constructor(taskId: string, status: TaskStatus);
|
|
521
|
+
}
|
|
522
|
+
declare class DurableTaskLeaseLostError extends Error {
|
|
523
|
+
readonly taskId: string;
|
|
524
|
+
readonly operation: string;
|
|
525
|
+
constructor(taskId: string, operation: string);
|
|
526
|
+
}
|
|
527
|
+
declare class DurableTaskRetryScheduledError extends Error {
|
|
528
|
+
readonly taskId: string;
|
|
529
|
+
readonly taskError: TaskErrorSummary;
|
|
530
|
+
readonly retryAt: Date;
|
|
531
|
+
constructor(taskId: string, taskError: TaskErrorSummary, retryAt: Date);
|
|
532
|
+
}
|
|
533
|
+
declare class DurableWorkerRuntime<TPayload = unknown, TResult = unknown> {
|
|
534
|
+
private readonly options;
|
|
535
|
+
private readonly repository;
|
|
536
|
+
private readonly retryPolicy;
|
|
537
|
+
private readonly workerId;
|
|
538
|
+
private readonly leaseDurationMs;
|
|
539
|
+
private readonly heartbeatIntervalMs;
|
|
540
|
+
private readonly maxConsecutiveHeartbeatFailures;
|
|
541
|
+
private readonly now;
|
|
542
|
+
private readonly createLeaseToken;
|
|
543
|
+
private readonly heartbeatScheduler;
|
|
544
|
+
constructor(options: DurableWorkerRuntimeOptions<TPayload, TResult>);
|
|
545
|
+
run(taskId: string): Promise<DurableWorkerRunResult>;
|
|
546
|
+
private leaseExpiry;
|
|
547
|
+
private retry;
|
|
548
|
+
private resolveRejectedCommit;
|
|
549
|
+
private findLatestBestEffort;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** Reference outbox for contract tests and local development only. */
|
|
553
|
+
declare class InMemoryTaskOutboxRepository<TPayload = unknown> implements TaskOutboxRepository<TPayload> {
|
|
554
|
+
private readonly records;
|
|
555
|
+
append(input: AppendTaskOutboxInput<TPayload>): Promise<{
|
|
556
|
+
record: TaskOutboxRecord<TPayload>;
|
|
557
|
+
created: boolean;
|
|
558
|
+
}>;
|
|
559
|
+
claimBatch(input: ClaimTaskOutboxInput): Promise<TaskOutboxRecord<TPayload>[]>;
|
|
560
|
+
markPublished(id: string, leaseToken: string, now: Date): Promise<boolean>;
|
|
561
|
+
reschedule(id: string, leaseToken: string, availableAt: Date, errorSummary: string, now: Date): Promise<boolean>;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** PostgreSQL outbox repository with atomic multi-dispatcher claims. */
|
|
565
|
+
declare class PostgresTaskOutboxRepository<TPayload = unknown> implements TaskOutboxRepository<TPayload> {
|
|
566
|
+
private readonly client;
|
|
567
|
+
constructor(client: PostgresTaskClient);
|
|
568
|
+
append(input: AppendTaskOutboxInput<TPayload>): Promise<{
|
|
569
|
+
record: TaskOutboxRecord<TPayload>;
|
|
570
|
+
created: boolean;
|
|
571
|
+
}>;
|
|
572
|
+
claimBatch(input: ClaimTaskOutboxInput): Promise<TaskOutboxRecord<TPayload>[]>;
|
|
573
|
+
markPublished(id: string, leaseToken: string, now: Date): Promise<boolean>;
|
|
574
|
+
reschedule(id: string, leaseToken: string, availableAt: Date, errorSummary: string, now: Date): Promise<boolean>;
|
|
575
|
+
private toRecord;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
interface TaskOutboxDispatcherOptions {
|
|
579
|
+
workerId: string;
|
|
580
|
+
createLeaseToken: () => string;
|
|
581
|
+
batchSize?: number;
|
|
582
|
+
leaseDurationMs?: number;
|
|
583
|
+
retryDelayMs?: (attempt: number) => number;
|
|
584
|
+
now?: () => Date;
|
|
585
|
+
}
|
|
586
|
+
interface TaskOutboxDispatchResult {
|
|
587
|
+
claimed: number;
|
|
588
|
+
published: number;
|
|
589
|
+
rescheduled: number;
|
|
590
|
+
conflicts: number;
|
|
591
|
+
}
|
|
592
|
+
declare class TaskOutboxDispatcher<TPayload = unknown> {
|
|
593
|
+
private readonly repository;
|
|
594
|
+
private readonly publisher;
|
|
595
|
+
private readonly options;
|
|
596
|
+
private readonly batchSize;
|
|
597
|
+
private readonly leaseDurationMs;
|
|
598
|
+
private readonly retryDelayMs;
|
|
599
|
+
private readonly now;
|
|
600
|
+
constructor(repository: TaskOutboxRepository<TPayload>, publisher: TaskQueuePublisher<TPayload>, options: TaskOutboxDispatcherOptions);
|
|
601
|
+
runOnce(): Promise<TaskOutboxDispatchResult>;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export { type AppendTaskOutboxInput, type ClaimTaskInput, type ClaimTaskOutboxInput, type CreateTaskInput, type CreateTaskResult, type DurableTaskAttemptContext, type DurableTaskHandler, DurableTaskLeaseLostError, DurableTaskNotClaimableError, DurableTaskNotFoundError, DurableTaskRetryScheduledError, type DurableWorkerHeartbeatScheduler, type DurableWorkerRunResult, DurableWorkerRuntime, type DurableWorkerRuntimeLogger, type DurableWorkerRuntimeOptions, ExponentialTaskRetryPolicy, type ExponentialTaskRetryPolicyOptions, type FindExpiredTaskLeasesInput, type FindReadyQueuedTasksInput, type HeartbeatTaskInput, InMemoryTaskOutboxRepository, InMemoryTaskRepository, InvalidTaskTransitionError, type ListOwnedTaskSummariesInput, type OwnedTaskTimelineEvent, type PostgresQueryResult, type PostgresTaskClient, PostgresTaskOutboxRepository, PostgresTaskRedriver, PostgresTaskRepository, PostgresTaskSubmitter, PostgresTaskSummaryReader, PostgresTaskTimelineReader, type PostgresTaskTransactionRunner, type ReadyTaskPublisher, type RecoverExpiredTaskLeaseInput, type RedriveFailedTaskInput, type ReportTaskProgressInput, type SubmitTaskBatchOptions, type SubmitTaskInput, type SubmitTaskOptions, type SubmitTaskOutboxInput, type SubmitTaskResult, TASK_STATUSES, TASK_TIMELINE_EVENT_TYPES, type TaskErrorSummary, type TaskFailureCategory, TaskIdempotencyConflictError, type TaskLeaseReconcileResult, TaskLeaseReconciler, type TaskLeaseReconcilerOptions, TaskOutboxConflictError, type TaskOutboxDispatchResult, TaskOutboxDispatcher, type TaskOutboxDispatcherOptions, type TaskOutboxRecord, type TaskOutboxRepository, type TaskOutboxStatus, type TaskQueuePublisher, type TaskQueueReconcileResult, TaskQueueReconciler, type TaskQueueReconcilerOptions, type TaskRecord, type TaskRedriveResult, type TaskRepository, type TaskResourceRef, type TaskRetryContext, type TaskRetryDecision, type TaskRetryPolicy, type TaskStatus, type TaskSummary, type TaskSummaryError, type TaskTimelineEvent, type TaskTimelineEventType, type TransitionTaskInput, UnsupportedDurableTaskKindError, assertTaskTransition, canTransitionTask, createTaskFingerprint, isTerminalTaskStatus, requiresTaskLease };
|