semola 0.6.0 → 0.6.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.
Files changed (43) hide show
  1. package/README.md +17 -5
  2. package/dist/lib/api/index.cjs +1 -537
  3. package/dist/lib/api/index.d.cts +1 -271
  4. package/dist/lib/api/index.d.mts +80 -84
  5. package/dist/lib/api/index.mjs +1 -535
  6. package/dist/lib/cache/index.cjs +1 -99
  7. package/dist/lib/cache/index.d.cts +1 -27
  8. package/dist/lib/cache/index.mjs +1 -98
  9. package/dist/lib/cron/index.cjs +1 -735
  10. package/dist/lib/cron/index.d.cts +1 -146
  11. package/dist/lib/cron/index.d.mts +99 -36
  12. package/dist/lib/cron/index.mjs +1 -726
  13. package/dist/lib/errors/index.cjs +1 -30
  14. package/dist/lib/errors/index.d.cts +1 -13
  15. package/dist/lib/errors/index.mjs +1 -26
  16. package/dist/lib/i18n/index.cjs +1 -42
  17. package/dist/lib/i18n/index.d.cts +1 -28
  18. package/dist/lib/i18n/index.mjs +1 -41
  19. package/dist/lib/logging/index.cjs +1 -388
  20. package/dist/lib/logging/index.d.cts +1 -108
  21. package/dist/lib/logging/index.mjs +1 -374
  22. package/dist/lib/orm/index.cjs +1 -1642
  23. package/dist/lib/orm/index.d.cts +1 -402
  24. package/dist/lib/orm/index.d.mts +4 -2
  25. package/dist/lib/orm/index.mjs +1 -1630
  26. package/dist/lib/policy/index.cjs +1 -285
  27. package/dist/lib/policy/index.d.cts +1 -77
  28. package/dist/lib/policy/index.mjs +1 -265
  29. package/dist/lib/prompts/index.cjs +6 -769
  30. package/dist/lib/prompts/index.d.cts +1 -75
  31. package/dist/lib/prompts/index.mjs +6 -762
  32. package/dist/lib/pubsub/index.cjs +1 -141
  33. package/dist/lib/pubsub/index.d.cts +1 -26
  34. package/dist/lib/pubsub/index.mjs +1 -140
  35. package/dist/lib/queue/index.cjs +1 -212
  36. package/dist/lib/queue/index.d.cts +1 -81
  37. package/dist/lib/queue/index.mjs +1 -209
  38. package/dist/lib/workflow/index.cjs +1 -537
  39. package/dist/lib/workflow/index.d.cts +1 -150
  40. package/dist/lib/workflow/index.mjs +1 -527
  41. package/dist/rolldown-runtime-CMqjfN_6.cjs +1 -0
  42. package/package.json +7 -5
  43. package/dist/chunk-CKQMccvm.cjs +0 -28
@@ -1,527 +1 @@
1
- import { mightThrow, mightThrowSync } from "../errors/index.mjs";
2
- import assert from "node:assert";
3
- //#region src/lib/workflow/errors.ts
4
- var WorkflowError = class extends Error {
5
- constructor(message) {
6
- super(message);
7
- this.name = "WorkflowError";
8
- }
9
- };
10
- var NotFoundError = class extends Error {
11
- constructor(message) {
12
- super(message);
13
- this.name = "NotFoundError";
14
- }
15
- };
16
- var StateError = class extends Error {
17
- constructor(message) {
18
- super(message);
19
- this.name = "StateError";
20
- }
21
- };
22
- var SerializationError = class extends Error {
23
- constructor(message) {
24
- super(message);
25
- this.name = "SerializationError";
26
- }
27
- };
28
- var ExecutionError = class extends Error {
29
- constructor(message) {
30
- super(message);
31
- this.name = "ExecutionError";
32
- }
33
- };
34
- var LockError = class extends Error {
35
- constructor(message) {
36
- super(message);
37
- this.name = "LockError";
38
- }
39
- };
40
- var CancelledError = class extends Error {
41
- constructor(message) {
42
- super(message);
43
- this.name = "CancelledError";
44
- }
45
- };
46
- //#endregion
47
- //#region src/lib/workflow/index.ts
48
- const DEFAULT_LOCK_TTL = 300 * 1e3;
49
- const DEFAULT_RETRIES = 3;
50
- const DEFAULT_RETRY_BASE_DELAY = 1e3;
51
- const DEFAULT_RETRY_MULTIPLIER = 2;
52
- const DEFAULT_RETRY_MAX_DELAY = 3e4;
53
- const now = () => Date.now();
54
- const delay = async (ms, signal, isCancelled) => {
55
- if (signal.aborted) throw new CancelledError("Workflow execution was aborted during retry backoff");
56
- const deadline = now() + ms;
57
- const pollInterval = 50;
58
- while (now() < deadline) {
59
- if (signal.aborted) throw new CancelledError("Workflow execution was aborted during retry backoff");
60
- if (isCancelled) {
61
- if (await isCancelled()) throw new CancelledError("Workflow execution was cancelled during retry backoff");
62
- }
63
- const remaining = deadline - now();
64
- await new Promise((resolve) => {
65
- setTimeout(resolve, Math.min(pollInterval, remaining));
66
- });
67
- }
68
- };
69
- const envelopeSerialize = (value) => {
70
- return JSON.stringify({ value });
71
- };
72
- const envelopeDeserialize = (raw) => {
73
- const [parseError, parsed] = mightThrowSync(() => JSON.parse(raw));
74
- if (parseError) throw parseError;
75
- if (parsed === null) return;
76
- if (typeof parsed !== "object") return;
77
- if ("value" in parsed) return parsed.value;
78
- };
79
- const knownStatuses = [
80
- "pending",
81
- "running",
82
- "completed",
83
- "failed",
84
- "cancelled"
85
- ];
86
- var WorkflowDefinition = class {
87
- options;
88
- lockTTL;
89
- retries;
90
- retryBaseDelay;
91
- retryMultiplier;
92
- retryMaxDelay;
93
- constructor(options) {
94
- this.options = options;
95
- this.lockTTL = options.lockTTL ?? DEFAULT_LOCK_TTL;
96
- this.retries = options.retries ?? DEFAULT_RETRIES;
97
- this.retryBaseDelay = options.retryBackoff?.baseDelay ?? DEFAULT_RETRY_BASE_DELAY;
98
- this.retryMultiplier = options.retryBackoff?.multiplier ?? DEFAULT_RETRY_MULTIPLIER;
99
- this.retryMaxDelay = options.retryBackoff?.maxDelay ?? DEFAULT_RETRY_MAX_DELAY;
100
- assert.ok(Number.isFinite(this.retries) && this.retries >= 0, "Invalid retries: must be a non-negative finite number");
101
- assert.ok(Number.isFinite(this.retryBaseDelay) && this.retryBaseDelay > 0, "Invalid retryBackoff.baseDelay: must be a positive finite number");
102
- assert.ok(Number.isFinite(this.retryMultiplier) && this.retryMultiplier > 0, "Invalid retryBackoff.multiplier: must be a positive finite number");
103
- assert.ok(Number.isFinite(this.retryMaxDelay) && this.retryMaxDelay > 0, "Invalid retryBackoff.maxDelay: must be a positive finite number");
104
- }
105
- runHook(hook) {
106
- return mightThrow(Promise.resolve().then(() => hook()));
107
- }
108
- computeBackoffDelay(attempt) {
109
- return Math.min(this.retryBaseDelay * this.retryMultiplier ** (attempt - 1), this.retryMaxDelay);
110
- }
111
- async start(input, options) {
112
- const executionId = options?.executionId ?? crypto.randomUUID();
113
- await this.createExecution(executionId, input);
114
- return this.execute(executionId, input);
115
- }
116
- async run(input, options) {
117
- const startData = await this.start(input, options);
118
- if (startData.status === "cancelled") throw new CancelledError(`Workflow execution ${startData.executionId} was cancelled`);
119
- return (await this.get(startData.executionId)).result;
120
- }
121
- async resume(executionId) {
122
- const execution = await this.get(executionId);
123
- if (execution.status === "completed") return {
124
- executionId,
125
- status: execution.status
126
- };
127
- if (execution.status === "cancelled") return {
128
- executionId,
129
- status: execution.status
130
- };
131
- return this.execute(executionId, execution.input);
132
- }
133
- async get(executionId) {
134
- const status = await this.getMeta(executionId, "status");
135
- if (!status) throw new NotFoundError(`Workflow execution ${executionId} not found`);
136
- const normalizedStatus = this.normalizeStatus(status);
137
- if (!normalizedStatus) throw new StateError(`Workflow execution ${executionId} has invalid status ${status}`);
138
- const input = await this.readInput(executionId);
139
- const result = await this.readResult(executionId);
140
- const steps = await this.readStepSnapshots(executionId);
141
- const createdAt = await this.readNumberMeta(executionId, "createdAt");
142
- const updatedAt = await this.readNumberMeta(executionId, "updatedAt");
143
- const errorMessage = await this.getMeta(executionId, "error");
144
- const completedAt = await this.readNumberMeta(executionId, "completedAt");
145
- const failedAt = await this.readNumberMeta(executionId, "failedAt");
146
- const cancelledAt = await this.readNumberMeta(executionId, "cancelledAt");
147
- if (createdAt === null) throw new StateError(`Workflow execution ${executionId} is missing createdAt`);
148
- if (updatedAt === null) throw new StateError(`Workflow execution ${executionId} is missing updatedAt`);
149
- return {
150
- id: executionId,
151
- name: this.options.name,
152
- status: normalizedStatus,
153
- input,
154
- result,
155
- error: errorMessage,
156
- createdAt,
157
- updatedAt,
158
- completedAt,
159
- failedAt,
160
- cancelledAt,
161
- steps
162
- };
163
- }
164
- async cancel(executionId) {
165
- const execution = await this.get(executionId);
166
- if (execution.status === "completed") throw new StateError(`Workflow execution ${executionId} is already completed`);
167
- const timestamp = now();
168
- await this.setMeta(executionId, "status", "cancelled");
169
- await this.setMeta(executionId, "updatedAt", String(timestamp));
170
- await this.setMeta(executionId, "cancelledAt", String(timestamp));
171
- await this.setMeta(executionId, "error", "");
172
- await this.setMeta(executionId, "failedAt", "");
173
- return {
174
- executionId,
175
- createdAt: execution.createdAt,
176
- cancelledAt: timestamp,
177
- updatedAt: timestamp,
178
- status: "cancelled"
179
- };
180
- }
181
- executionKey(executionId) {
182
- return `workflow:${this.options.name}:execution:${executionId}`;
183
- }
184
- metaKey(executionId) {
185
- return `${this.executionKey(executionId)}:meta`;
186
- }
187
- stepsKey(executionId) {
188
- return `${this.executionKey(executionId)}:steps`;
189
- }
190
- lockKey(executionId) {
191
- return `${this.executionKey(executionId)}:lock`;
192
- }
193
- async createExecution(executionId, input) {
194
- const serializedInput = this.serializeInput(input);
195
- const timestamp = now();
196
- if (await this.getMeta(executionId, "status")) throw new StateError(`Workflow execution ${executionId} already exists`);
197
- const metadata = {
198
- status: "pending",
199
- input: serializedInput,
200
- result: "",
201
- error: "",
202
- createdAt: String(timestamp),
203
- updatedAt: String(timestamp),
204
- completedAt: "",
205
- failedAt: "",
206
- cancelledAt: "",
207
- steps: "[]"
208
- };
209
- const [writeError] = await mightThrow(this.options.redis.hset(this.metaKey(executionId), metadata));
210
- if (writeError) throw new WorkflowError(`Unable to persist metadata for execution ${executionId}`);
211
- }
212
- async execute(executionId, input) {
213
- const token = crypto.randomUUID();
214
- await this.acquireLock(executionId, token);
215
- if (await this.getMeta(executionId, "status") === "cancelled") {
216
- await this.releaseLock(executionId, token);
217
- throw new StateError(`Workflow execution ${executionId} was cancelled`);
218
- }
219
- const timestamp = now();
220
- await this.setMeta(executionId, "status", "running");
221
- await this.setMeta(executionId, "updatedAt", String(timestamp));
222
- const controller = new AbortController();
223
- const renewInterval = Math.floor(this.lockTTL / 3);
224
- let lockLost = false;
225
- const renewTimer = setInterval(async () => {
226
- const [renewError] = await mightThrow(this.extendLock(executionId, token));
227
- if (renewError) {
228
- lockLost = true;
229
- controller.abort();
230
- clearInterval(renewTimer);
231
- }
232
- }, renewInterval);
233
- if (this.options.hooks?.onStart) await this.runHook(() => this.options.hooks?.onStart?.({
234
- executionId,
235
- input
236
- }));
237
- const step = async (name, handler) => {
238
- await this.throwIfCancelled(executionId, () => {
239
- controller.abort();
240
- });
241
- const cachedStep = await this.readStepOutput(executionId, name);
242
- if (cachedStep.found) return cachedStep.value;
243
- return this.runStepWithRetries(executionId, input, name, handler, controller.signal, () => {
244
- controller.abort();
245
- });
246
- };
247
- const handlerOutcome = await mightThrow(Promise.resolve(this.options.handler({
248
- input,
249
- executionId,
250
- signal: controller.signal,
251
- step
252
- })));
253
- clearInterval(renewTimer);
254
- if (lockLost) {
255
- await this.releaseLock(executionId, token);
256
- throw new LockError(`Lock expired during execution ${executionId}`);
257
- }
258
- if (await this.isCancelled(executionId)) {
259
- const cancelledAt = now();
260
- await this.setMeta(executionId, "status", "cancelled");
261
- await this.setMeta(executionId, "updatedAt", String(cancelledAt));
262
- await this.setMeta(executionId, "cancelledAt", String(cancelledAt));
263
- if (this.options.hooks?.onCancel) await this.runHook(() => this.options.hooks?.onCancel?.({
264
- executionId,
265
- input
266
- }));
267
- await this.releaseLock(executionId, token);
268
- return {
269
- executionId,
270
- status: "cancelled"
271
- };
272
- }
273
- const [handlerError, result] = handlerOutcome;
274
- if (handlerError) {
275
- const failedAt = now();
276
- await this.setMeta(executionId, "status", "failed");
277
- await this.setMeta(executionId, "error", handlerError.message);
278
- await this.setMeta(executionId, "updatedAt", String(failedAt));
279
- await this.setMeta(executionId, "failedAt", String(failedAt));
280
- await this.releaseLock(executionId, token);
281
- throw new ExecutionError(`Workflow execution ${executionId} failed: ${handlerError.message}`);
282
- }
283
- const [serializeResultError, serializedResult] = mightThrowSync(() => this.serializeResult(result));
284
- if (serializeResultError) {
285
- const failedAt = now();
286
- await this.setMeta(executionId, "status", "failed");
287
- await this.setMeta(executionId, "error", serializeResultError.message);
288
- await this.setMeta(executionId, "updatedAt", String(failedAt));
289
- await this.setMeta(executionId, "failedAt", String(failedAt));
290
- await this.releaseLock(executionId, token);
291
- throw new SerializationError(`Unable to serialize workflow result for ${executionId}`);
292
- }
293
- const completedAt = now();
294
- await this.setMeta(executionId, "result", serializedResult);
295
- await this.setMeta(executionId, "status", "completed");
296
- await this.setMeta(executionId, "error", "");
297
- await this.setMeta(executionId, "failedAt", "");
298
- await this.setMeta(executionId, "updatedAt", String(completedAt));
299
- await this.setMeta(executionId, "completedAt", String(completedAt));
300
- if (this.options.hooks?.onComplete) await this.runHook(() => this.options.hooks?.onComplete?.({
301
- executionId,
302
- input,
303
- result
304
- }));
305
- await this.releaseLock(executionId, token);
306
- return {
307
- executionId,
308
- status: "completed"
309
- };
310
- }
311
- async throwIfCancelled(executionId, abort) {
312
- if (await this.isCancelled(executionId)) {
313
- abort();
314
- throw new CancelledError(`Workflow execution ${executionId} was cancelled`);
315
- }
316
- }
317
- async runStepWithRetries(executionId, input, stepName, handler, signal, abort) {
318
- let attempt = 1;
319
- const errorHistory = [];
320
- while (true) {
321
- await this.throwIfCancelled(executionId, abort);
322
- const [stepError, output] = await mightThrow(Promise.resolve(handler(input, signal)));
323
- if (!stepError) {
324
- await this.writeStepOutput(executionId, stepName, output);
325
- return output;
326
- }
327
- const errorMsg = stepError.message;
328
- errorHistory.push({
329
- attempt,
330
- error: errorMsg,
331
- timestamp: now()
332
- });
333
- if (attempt <= this.retries) {
334
- const nextRetryDelayMs = this.computeBackoffDelay(attempt);
335
- if (this.options.hooks?.onRetry) await this.runHook(() => this.options.hooks?.onRetry?.({
336
- executionId,
337
- input,
338
- stepName,
339
- error: errorMsg,
340
- attempt,
341
- nextRetryDelayMs,
342
- retriesRemaining: this.retries - attempt
343
- }));
344
- const [delayError] = await mightThrow(delay(nextRetryDelayMs, signal, () => this.isCancelled(executionId)));
345
- if (delayError) throw delayError;
346
- attempt++;
347
- continue;
348
- }
349
- if (this.options.hooks?.onError) await this.runHook(() => this.options.hooks?.onError?.({
350
- executionId,
351
- input,
352
- stepName,
353
- error: errorMsg,
354
- totalAttempts: attempt,
355
- errorHistory
356
- }));
357
- throw stepError;
358
- }
359
- }
360
- async acquireLock(executionId, token) {
361
- const [lockError, lockResult] = await mightThrow(this.options.redis.set(this.lockKey(executionId), token, "PX", String(this.lockTTL), "NX"));
362
- if (lockError) throw new LockError(`Unable to acquire lock for execution ${executionId}`);
363
- if (lockResult !== "OK") throw new LockError(`Workflow execution ${executionId} is already running`);
364
- }
365
- async releaseLock(executionId, token) {
366
- await mightThrow(this.options.redis.send("EVAL", [
367
- "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end",
368
- "1",
369
- this.lockKey(executionId),
370
- token
371
- ]));
372
- }
373
- async extendLock(executionId, token) {
374
- const [evalError, extendResult] = await mightThrow(this.options.redis.send("EVAL", [
375
- "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end",
376
- "1",
377
- this.lockKey(executionId),
378
- token,
379
- String(this.lockTTL)
380
- ]));
381
- if (evalError) throw new LockError(`Unable to extend lock for execution ${executionId}`);
382
- if (extendResult === 0) throw new LockError(`Lock ownership lost for execution ${executionId}`);
383
- }
384
- async isCancelled(executionId) {
385
- return await this.getMeta(executionId, "status") === "cancelled";
386
- }
387
- async setMeta(executionId, field, value) {
388
- const [writeError] = await mightThrow(this.options.redis.hset(this.metaKey(executionId), field, value));
389
- if (writeError) throw new WorkflowError(`Unable to persist ${field} for execution ${executionId}`);
390
- }
391
- async getMeta(executionId, field) {
392
- const [readError, value] = await mightThrow(this.options.redis.hget(this.metaKey(executionId), field));
393
- if (readError) throw new WorkflowError(`Unable to read ${field} for execution ${executionId}`);
394
- if (value === null || value === void 0) return null;
395
- if (typeof value !== "string") throw new StateError(`Invalid ${field} value for execution ${executionId}`);
396
- if (value.length === 0) return null;
397
- return value;
398
- }
399
- async readNumberMeta(executionId, field) {
400
- const value = await this.getMeta(executionId, field);
401
- if (!value) return null;
402
- const parsed = Number(value);
403
- if (!Number.isFinite(parsed)) throw new StateError(`Invalid ${field} value for execution ${executionId}`);
404
- return parsed;
405
- }
406
- runSerializer(value, serializer, label) {
407
- const [serializeError, serialized] = mightThrowSync(() => serializer(value));
408
- if (serializeError) throw new SerializationError(`Unable to serialize ${label}: ${serializeError.message}`);
409
- if (typeof serialized !== "string") throw new SerializationError(`${label} serializer must return a string`);
410
- return serialized;
411
- }
412
- runDeserializer(raw, deserializer, label) {
413
- const result = mightThrowSync(() => deserializer(raw));
414
- if (result[0]) throw new SerializationError(`Unable to deserialize ${label}: ${result[0].message}`);
415
- return result[1];
416
- }
417
- serializeInput(input) {
418
- return this.runSerializer(input, this.options.serializeInput ?? envelopeSerialize, "workflow input");
419
- }
420
- deserializeInput(raw) {
421
- const deserializer = this.options.deserializeInput ?? ((value) => envelopeDeserialize(value));
422
- return this.runDeserializer(raw, deserializer, "workflow input");
423
- }
424
- serializeResult(result) {
425
- if (result === null) return envelopeSerialize(null);
426
- return this.runSerializer(result, this.options.serializeResult ?? envelopeSerialize, "workflow result");
427
- }
428
- deserializeResult(raw) {
429
- const deserializer = this.options.deserializeResult ?? ((value) => envelopeDeserialize(value));
430
- return this.runDeserializer(raw, deserializer, "workflow result");
431
- }
432
- serializeStepOutput(output) {
433
- return this.runSerializer(output, this.options.serializeStepOutput ?? envelopeSerialize, "step output");
434
- }
435
- deserializeStepOutput(raw) {
436
- const deserializer = this.options.deserializeStepOutput ?? ((value) => envelopeDeserialize(value));
437
- return this.runDeserializer(raw, deserializer, "step output");
438
- }
439
- async readInput(executionId) {
440
- const raw = await this.getMeta(executionId, "input");
441
- if (!raw) throw new StateError(`Workflow execution ${executionId} input not found`);
442
- return this.deserializeInput(raw);
443
- }
444
- async readResult(executionId) {
445
- const raw = await this.getMeta(executionId, "result");
446
- if (!raw) return null;
447
- return this.deserializeResult(raw);
448
- }
449
- async writeStepOutput(executionId, stepName, output) {
450
- const payload = {
451
- output: this.serializeStepOutput(output),
452
- completedAt: now()
453
- };
454
- const [payloadError, payloadRaw] = mightThrowSync(() => JSON.stringify(payload));
455
- if (payloadError || typeof payloadRaw !== "string") throw new SerializationError(`Unable to persist step ${stepName} output`);
456
- const [writeError] = await mightThrow(this.options.redis.hset(this.stepsKey(executionId), stepName, payloadRaw));
457
- if (writeError) throw new WorkflowError(`Unable to persist step ${stepName} for execution ${executionId}`);
458
- const stepNames = await this.readStepNames(executionId);
459
- if (!stepNames.includes(stepName)) {
460
- const nextStepNames = [...stepNames, stepName];
461
- const [serializeStepsError, serializedSteps] = mightThrowSync(() => JSON.stringify(nextStepNames));
462
- if (serializeStepsError || typeof serializedSteps !== "string") throw new SerializationError(`Unable to persist step history for execution ${executionId}`);
463
- await this.setMeta(executionId, "steps", serializedSteps);
464
- }
465
- await this.setMeta(executionId, "updatedAt", String(now()));
466
- }
467
- async readStepOutput(executionId, stepName) {
468
- const [readError, payloadRaw] = await mightThrow(this.options.redis.hget(this.stepsKey(executionId), stepName));
469
- if (readError) throw new WorkflowError(`Unable to read step ${stepName} for execution ${executionId}`);
470
- if (!payloadRaw) return {
471
- found: false,
472
- value: null
473
- };
474
- if (typeof payloadRaw !== "string") throw new StateError(`Invalid step payload for ${stepName} in execution ${executionId}`);
475
- const [parseError, parsed] = mightThrowSync(() => JSON.parse(payloadRaw));
476
- if (parseError || parsed === null || typeof parsed !== "object") throw new StateError(`Invalid step payload for ${stepName} in execution ${executionId}`);
477
- if (typeof parsed.output !== "string") throw new StateError(`Invalid step output for ${stepName} in execution ${executionId}`);
478
- const outputRaw = parsed.output;
479
- return {
480
- found: true,
481
- value: this.deserializeStepOutput(outputRaw)
482
- };
483
- }
484
- async readStepNames(executionId) {
485
- const stepsRaw = await this.getMeta(executionId, "steps");
486
- if (!stepsRaw) return [];
487
- const [parseError, values] = mightThrowSync(() => JSON.parse(stepsRaw));
488
- if (parseError || !Array.isArray(values)) throw new StateError(`Invalid step index for execution ${executionId}`);
489
- const stepNames = [];
490
- for (const value of values) if (typeof value === "string") stepNames.push(value);
491
- return stepNames;
492
- }
493
- async readStepSnapshots(executionId) {
494
- const stepNames = await this.readStepNames(executionId);
495
- const steps = [];
496
- for (const stepName of stepNames) {
497
- const [readError, payloadRaw] = await mightThrow(this.options.redis.hget(this.stepsKey(executionId), stepName));
498
- if (readError) throw new WorkflowError(`Unable to read step ${stepName} for execution ${executionId}`);
499
- if (!payloadRaw) continue;
500
- if (typeof payloadRaw !== "string") throw new StateError(`Invalid step payload for ${stepName} in execution ${executionId}`);
501
- const [parseError, parsed] = mightThrowSync(() => JSON.parse(payloadRaw));
502
- if (parseError || parsed === null || typeof parsed !== "object") throw new StateError(`Invalid step payload for ${stepName} in execution ${executionId}`);
503
- if (typeof parsed.completedAt !== "number") throw new StateError(`Invalid step payload for ${stepName} in execution ${executionId}`);
504
- steps.push({
505
- name: stepName,
506
- completedAt: parsed.completedAt
507
- });
508
- }
509
- return steps;
510
- }
511
- normalizeStatus(value) {
512
- for (const status of knownStatuses) if (status === value) return status;
513
- return null;
514
- }
515
- };
516
- const defineWorkflow = (options) => {
517
- const workflow = new WorkflowDefinition(options);
518
- return {
519
- start: (input, startOptions) => workflow.start(input, startOptions),
520
- run: (input, startOptions) => workflow.run(input, startOptions),
521
- resume: (executionId) => workflow.resume(executionId),
522
- get: (executionId) => workflow.get(executionId),
523
- cancel: (executionId) => workflow.cancel(executionId)
524
- };
525
- };
526
- //#endregion
527
- export { CancelledError, ExecutionError, LockError, NotFoundError, SerializationError, StateError, WorkflowError, defineWorkflow };
1
+ import{mightThrow as e,mightThrowSync as t}from"../errors/index.mjs";import n from"node:assert";var r=class extends Error{constructor(e){super(e),this.name=`WorkflowError`}},i=class extends Error{constructor(e){super(e),this.name=`NotFoundError`}},a=class extends Error{constructor(e){super(e),this.name=`StateError`}},o=class extends Error{constructor(e){super(e),this.name=`SerializationError`}},s=class extends Error{constructor(e){super(e),this.name=`ExecutionError`}},c=class extends Error{constructor(e){super(e),this.name=`LockError`}},l=class extends Error{constructor(e){super(e),this.name=`CancelledError`}};const u=()=>Date.now(),d=async(e,t,n)=>{if(t.aborted)throw new l(`Workflow execution was aborted during retry backoff`);let r=u()+e;for(;u()<r;){if(t.aborted)throw new l(`Workflow execution was aborted during retry backoff`);if(n&&await n())throw new l(`Workflow execution was cancelled during retry backoff`);let e=r-u();await new Promise(t=>{setTimeout(t,Math.min(50,e))})}},f=e=>JSON.stringify({value:e}),p=e=>{let[n,r]=t(()=>JSON.parse(e));if(n)throw n;if(typeof r==`object`&&r&&`value`in r)return r.value},m=[`pending`,`running`,`completed`,`failed`,`cancelled`];var h=class{options;lockTTL;retries;retryBaseDelay;retryMultiplier;retryMaxDelay;constructor(e){this.options=e,this.lockTTL=e.lockTTL??3e5,this.retries=e.retries??3,this.retryBaseDelay=e.retryBackoff?.baseDelay??1e3,this.retryMultiplier=e.retryBackoff?.multiplier??2,this.retryMaxDelay=e.retryBackoff?.maxDelay??3e4,n.ok(Number.isFinite(this.retries)&&this.retries>=0,`Invalid retries: must be a non-negative finite number`),n.ok(Number.isFinite(this.retryBaseDelay)&&this.retryBaseDelay>0,`Invalid retryBackoff.baseDelay: must be a positive finite number`),n.ok(Number.isFinite(this.retryMultiplier)&&this.retryMultiplier>0,`Invalid retryBackoff.multiplier: must be a positive finite number`),n.ok(Number.isFinite(this.retryMaxDelay)&&this.retryMaxDelay>0,`Invalid retryBackoff.maxDelay: must be a positive finite number`)}runHook(t){return e(Promise.resolve().then(()=>t()))}computeBackoffDelay(e){return Math.min(this.retryBaseDelay*this.retryMultiplier**(e-1),this.retryMaxDelay)}async start(e,t){let n=t?.executionId??crypto.randomUUID();return await this.createExecution(n,e),this.execute(n,e)}async run(e,t){let n=await this.start(e,t);if(n.status===`cancelled`)throw new l(`Workflow execution ${n.executionId} was cancelled`);return(await this.get(n.executionId)).result}async resume(e){let t=await this.get(e);return t.status===`completed`||t.status===`cancelled`?{executionId:e,status:t.status}:this.execute(e,t.input)}async get(e){let t=await this.getMeta(e,`status`);if(!t)throw new i(`Workflow execution ${e} not found`);let n=this.normalizeStatus(t);if(!n)throw new a(`Workflow execution ${e} has invalid status ${t}`);let r=await this.readInput(e),o=await this.readResult(e),s=await this.readStepSnapshots(e),c=await this.readNumberMeta(e,`createdAt`),l=await this.readNumberMeta(e,`updatedAt`),u=await this.getMeta(e,`error`),d=await this.readNumberMeta(e,`completedAt`),f=await this.readNumberMeta(e,`failedAt`),p=await this.readNumberMeta(e,`cancelledAt`);if(c===null)throw new a(`Workflow execution ${e} is missing createdAt`);if(l===null)throw new a(`Workflow execution ${e} is missing updatedAt`);return{id:e,name:this.options.name,status:n,input:r,result:o,error:u,createdAt:c,updatedAt:l,completedAt:d,failedAt:f,cancelledAt:p,steps:s}}async cancel(e){let t=await this.get(e);if(t.status===`completed`)throw new a(`Workflow execution ${e} is already completed`);let n=u();return await this.setMeta(e,`status`,`cancelled`),await this.setMeta(e,`updatedAt`,String(n)),await this.setMeta(e,`cancelledAt`,String(n)),await this.setMeta(e,`error`,``),await this.setMeta(e,`failedAt`,``),{executionId:e,createdAt:t.createdAt,cancelledAt:n,updatedAt:n,status:`cancelled`}}executionKey(e){return`workflow:${this.options.name}:execution:${e}`}metaKey(e){return`${this.executionKey(e)}:meta`}stepsKey(e){return`${this.executionKey(e)}:steps`}lockKey(e){return`${this.executionKey(e)}:lock`}async createExecution(t,n){let i=this.serializeInput(n),o=u();if(await this.getMeta(t,`status`))throw new a(`Workflow execution ${t} already exists`);let s={status:`pending`,input:i,result:``,error:``,createdAt:String(o),updatedAt:String(o),completedAt:``,failedAt:``,cancelledAt:``,steps:`[]`},[c]=await e(this.options.redis.hset(this.metaKey(t),s));if(c)throw new r(`Unable to persist metadata for execution ${t}`)}async execute(n,r){let i=crypto.randomUUID();if(await this.acquireLock(n,i),await this.getMeta(n,`status`)===`cancelled`)throw await this.releaseLock(n,i),new a(`Workflow execution ${n} was cancelled`);let l=u();await this.setMeta(n,`status`,`running`),await this.setMeta(n,`updatedAt`,String(l));let d=new AbortController,f=Math.floor(this.lockTTL/3),p=!1,m=setInterval(async()=>{let[t]=await e(this.extendLock(n,i));t&&(p=!0,d.abort(),clearInterval(m))},f);this.options.hooks?.onStart&&await this.runHook(()=>this.options.hooks?.onStart?.({executionId:n,input:r}));let h=await e(Promise.resolve(this.options.handler({input:r,executionId:n,signal:d.signal,step:async(e,t)=>{await this.throwIfCancelled(n,()=>{d.abort()});let i=await this.readStepOutput(n,e);return i.found?i.value:this.runStepWithRetries(n,r,e,t,d.signal,()=>{d.abort()})}})));if(clearInterval(m),p)throw await this.releaseLock(n,i),new c(`Lock expired during execution ${n}`);if(await this.isCancelled(n)){let e=u();return await this.setMeta(n,`status`,`cancelled`),await this.setMeta(n,`updatedAt`,String(e)),await this.setMeta(n,`cancelledAt`,String(e)),this.options.hooks?.onCancel&&await this.runHook(()=>this.options.hooks?.onCancel?.({executionId:n,input:r})),await this.releaseLock(n,i),{executionId:n,status:`cancelled`}}let[g,_]=h;if(g){let e=u();throw await this.setMeta(n,`status`,`failed`),await this.setMeta(n,`error`,g.message),await this.setMeta(n,`updatedAt`,String(e)),await this.setMeta(n,`failedAt`,String(e)),await this.releaseLock(n,i),new s(`Workflow execution ${n} failed: ${g.message}`)}let[v,y]=t(()=>this.serializeResult(_));if(v){let e=u();throw await this.setMeta(n,`status`,`failed`),await this.setMeta(n,`error`,v.message),await this.setMeta(n,`updatedAt`,String(e)),await this.setMeta(n,`failedAt`,String(e)),await this.releaseLock(n,i),new o(`Unable to serialize workflow result for ${n}`)}let b=u();return await this.setMeta(n,`result`,y),await this.setMeta(n,`status`,`completed`),await this.setMeta(n,`error`,``),await this.setMeta(n,`failedAt`,``),await this.setMeta(n,`updatedAt`,String(b)),await this.setMeta(n,`completedAt`,String(b)),this.options.hooks?.onComplete&&await this.runHook(()=>this.options.hooks?.onComplete?.({executionId:n,input:r,result:_})),await this.releaseLock(n,i),{executionId:n,status:`completed`}}async throwIfCancelled(e,t){if(await this.isCancelled(e))throw t(),new l(`Workflow execution ${e} was cancelled`)}async runStepWithRetries(t,n,r,i,a,o){let s=1,c=[];for(;;){await this.throwIfCancelled(t,o);let[l,f]=await e(Promise.resolve(i(n,a)));if(!l)return await this.writeStepOutput(t,r,f),f;let p=l.message;if(c.push({attempt:s,error:p,timestamp:u()}),s<=this.retries){let i=this.computeBackoffDelay(s);this.options.hooks?.onRetry&&await this.runHook(()=>this.options.hooks?.onRetry?.({executionId:t,input:n,stepName:r,error:p,attempt:s,nextRetryDelayMs:i,retriesRemaining:this.retries-s}));let[o]=await e(d(i,a,()=>this.isCancelled(t)));if(o)throw o;s++;continue}throw this.options.hooks?.onError&&await this.runHook(()=>this.options.hooks?.onError?.({executionId:t,input:n,stepName:r,error:p,totalAttempts:s,errorHistory:c})),l}}async acquireLock(t,n){let[r,i]=await e(this.options.redis.set(this.lockKey(t),n,`PX`,String(this.lockTTL),`NX`));if(r)throw new c(`Unable to acquire lock for execution ${t}`);if(i!==`OK`)throw new c(`Workflow execution ${t} is already running`)}async releaseLock(t,n){await e(this.options.redis.send(`EVAL`,[`if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end`,`1`,this.lockKey(t),n]))}async extendLock(t,n){let[r,i]=await e(this.options.redis.send(`EVAL`,[`if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end`,`1`,this.lockKey(t),n,String(this.lockTTL)]));if(r)throw new c(`Unable to extend lock for execution ${t}`);if(i===0)throw new c(`Lock ownership lost for execution ${t}`)}async isCancelled(e){return await this.getMeta(e,`status`)===`cancelled`}async setMeta(t,n,i){let[a]=await e(this.options.redis.hset(this.metaKey(t),n,i));if(a)throw new r(`Unable to persist ${n} for execution ${t}`)}async getMeta(t,n){let[i,o]=await e(this.options.redis.hget(this.metaKey(t),n));if(i)throw new r(`Unable to read ${n} for execution ${t}`);if(o==null)return null;if(typeof o!=`string`)throw new a(`Invalid ${n} value for execution ${t}`);return o.length===0?null:o}async readNumberMeta(e,t){let n=await this.getMeta(e,t);if(!n)return null;let r=Number(n);if(!Number.isFinite(r))throw new a(`Invalid ${t} value for execution ${e}`);return r}runSerializer(e,n,r){let[i,a]=t(()=>n(e));if(i)throw new o(`Unable to serialize ${r}: ${i.message}`);if(typeof a!=`string`)throw new o(`${r} serializer must return a string`);return a}runDeserializer(e,n,r){let i=t(()=>n(e));if(i[0])throw new o(`Unable to deserialize ${r}: ${i[0].message}`);return i[1]}serializeInput(e){return this.runSerializer(e,this.options.serializeInput??f,`workflow input`)}deserializeInput(e){let t=this.options.deserializeInput??(e=>p(e));return this.runDeserializer(e,t,`workflow input`)}serializeResult(e){return e===null?f(null):this.runSerializer(e,this.options.serializeResult??f,`workflow result`)}deserializeResult(e){let t=this.options.deserializeResult??(e=>p(e));return this.runDeserializer(e,t,`workflow result`)}serializeStepOutput(e){return this.runSerializer(e,this.options.serializeStepOutput??f,`step output`)}deserializeStepOutput(e){let t=this.options.deserializeStepOutput??(e=>p(e));return this.runDeserializer(e,t,`step output`)}async readInput(e){let t=await this.getMeta(e,`input`);if(!t)throw new a(`Workflow execution ${e} input not found`);return this.deserializeInput(t)}async readResult(e){let t=await this.getMeta(e,`result`);return t?this.deserializeResult(t):null}async writeStepOutput(n,i,a){let s={output:this.serializeStepOutput(a),completedAt:u()},[c,l]=t(()=>JSON.stringify(s));if(c||typeof l!=`string`)throw new o(`Unable to persist step ${i} output`);let[d]=await e(this.options.redis.hset(this.stepsKey(n),i,l));if(d)throw new r(`Unable to persist step ${i} for execution ${n}`);let f=await this.readStepNames(n);if(!f.includes(i)){let e=[...f,i],[r,a]=t(()=>JSON.stringify(e));if(r||typeof a!=`string`)throw new o(`Unable to persist step history for execution ${n}`);await this.setMeta(n,`steps`,a)}await this.setMeta(n,`updatedAt`,String(u()))}async readStepOutput(n,i){let[o,s]=await e(this.options.redis.hget(this.stepsKey(n),i));if(o)throw new r(`Unable to read step ${i} for execution ${n}`);if(!s)return{found:!1,value:null};if(typeof s!=`string`)throw new a(`Invalid step payload for ${i} in execution ${n}`);let[c,l]=t(()=>JSON.parse(s));if(c||typeof l!=`object`||!l)throw new a(`Invalid step payload for ${i} in execution ${n}`);if(typeof l.output!=`string`)throw new a(`Invalid step output for ${i} in execution ${n}`);let u=l.output;return{found:!0,value:this.deserializeStepOutput(u)}}async readStepNames(e){let n=await this.getMeta(e,`steps`);if(!n)return[];let[r,i]=t(()=>JSON.parse(n));if(r||!Array.isArray(i))throw new a(`Invalid step index for execution ${e}`);let o=[];for(let e of i)typeof e==`string`&&o.push(e);return o}async readStepSnapshots(n){let i=await this.readStepNames(n),o=[];for(let s of i){let[i,c]=await e(this.options.redis.hget(this.stepsKey(n),s));if(i)throw new r(`Unable to read step ${s} for execution ${n}`);if(!c)continue;if(typeof c!=`string`)throw new a(`Invalid step payload for ${s} in execution ${n}`);let[l,u]=t(()=>JSON.parse(c));if(l||typeof u!=`object`||!u||typeof u.completedAt!=`number`)throw new a(`Invalid step payload for ${s} in execution ${n}`);o.push({name:s,completedAt:u.completedAt})}return o}normalizeStatus(e){for(let t of m)if(t===e)return t;return null}};const g=e=>{let t=new h(e);return{start:(e,n)=>t.start(e,n),run:(e,n)=>t.run(e,n),resume:e=>t.resume(e),get:e=>t.get(e),cancel:e=>t.cancel(e)}};export{l as CancelledError,s as ExecutionError,c as LockError,i as NotFoundError,o as SerializationError,a as StateError,r as WorkflowError,g as defineWorkflow};
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return s}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semola",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Zero-dependency TypeScript utilities for Bun. Type-safe APIs, Redis queues, pub/sub, i18n, caching & auth with tree-shakeable imports.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -179,12 +179,14 @@
179
179
  "devDependencies": {
180
180
  "@biomejs/biome": "2.3.10",
181
181
  "@standard-schema/spec": "^1.1.0",
182
+ "@standard-schema/utils": "^0.3.0",
182
183
  "@types/bun": "latest",
184
+ "@typescript/native-preview": "^7.0.0-dev.20260624.1",
185
+ "@valibot/to-json-schema": "^1.7.1",
186
+ "arktype": "^2.2.1",
183
187
  "openapi-types": "^12.1.3",
184
- "tsdown": "^0.21.7",
188
+ "tsdown": "^0.22.3",
189
+ "valibot": "^1.4.1",
185
190
  "zod": "^4.3.6"
186
- },
187
- "peerDependencies": {
188
- "typescript": "^5"
189
191
  }
190
192
  }
@@ -1,28 +0,0 @@
1
- //#region \0rolldown/runtime.js
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
- //#endregion
23
- Object.defineProperty(exports, "__toESM", {
24
- enumerable: true,
25
- get: function() {
26
- return __toESM;
27
- }
28
- });