flowfn 0.0.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 (59) hide show
  1. package/dist/index.d.mts +1305 -0
  2. package/dist/index.d.ts +1305 -0
  3. package/dist/index.js +3180 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +3088 -0
  6. package/dist/index.mjs.map +1 -0
  7. package/docs/API.md +801 -0
  8. package/docs/USAGE.md +619 -0
  9. package/package.json +75 -0
  10. package/src/adapters/base.ts +46 -0
  11. package/src/adapters/memory.ts +183 -0
  12. package/src/adapters/postgres/index.ts +383 -0
  13. package/src/adapters/postgres/postgres.test.ts +100 -0
  14. package/src/adapters/postgres/schema.ts +110 -0
  15. package/src/adapters/redis.test.ts +124 -0
  16. package/src/adapters/redis.ts +331 -0
  17. package/src/core/flow-fn.test.ts +70 -0
  18. package/src/core/flow-fn.ts +198 -0
  19. package/src/core/metrics.ts +198 -0
  20. package/src/core/scheduler.test.ts +80 -0
  21. package/src/core/scheduler.ts +154 -0
  22. package/src/index.ts +57 -0
  23. package/src/monitoring/health.ts +261 -0
  24. package/src/patterns/backoff.ts +30 -0
  25. package/src/patterns/batching.ts +248 -0
  26. package/src/patterns/circuit-breaker.test.ts +52 -0
  27. package/src/patterns/circuit-breaker.ts +52 -0
  28. package/src/patterns/priority.ts +146 -0
  29. package/src/patterns/rate-limit.ts +290 -0
  30. package/src/patterns/retry.test.ts +62 -0
  31. package/src/queue/batch.test.ts +35 -0
  32. package/src/queue/dependencies.test.ts +33 -0
  33. package/src/queue/dlq.ts +222 -0
  34. package/src/queue/job.ts +67 -0
  35. package/src/queue/queue.ts +243 -0
  36. package/src/queue/types.ts +153 -0
  37. package/src/queue/worker.ts +66 -0
  38. package/src/storage/event-log.ts +205 -0
  39. package/src/storage/job-storage.ts +206 -0
  40. package/src/storage/workflow-storage.ts +182 -0
  41. package/src/stream/stream.ts +194 -0
  42. package/src/stream/types.ts +81 -0
  43. package/src/utils/hashing.ts +29 -0
  44. package/src/utils/id-generator.ts +109 -0
  45. package/src/utils/serialization.ts +142 -0
  46. package/src/utils/time.ts +167 -0
  47. package/src/workflow/advanced.test.ts +43 -0
  48. package/src/workflow/events.test.ts +39 -0
  49. package/src/workflow/types.ts +132 -0
  50. package/src/workflow/workflow.test.ts +55 -0
  51. package/src/workflow/workflow.ts +422 -0
  52. package/tests/dlq.test.ts +205 -0
  53. package/tests/health.test.ts +228 -0
  54. package/tests/integration.test.ts +253 -0
  55. package/tests/stream.test.ts +233 -0
  56. package/tests/workflow.test.ts +286 -0
  57. package/tsconfig.json +17 -0
  58. package/tsup.config.ts +10 -0
  59. package/vitest.config.ts +15 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,3088 @@
1
+ // src/adapters/memory.ts
2
+ import { EventEmitter } from "eventemitter3";
3
+ var MemoryAdapter = class {
4
+ constructor() {
5
+ this.queues = /* @__PURE__ */ new Map();
6
+ this.allJobs = /* @__PURE__ */ new Map();
7
+ this.streams = /* @__PURE__ */ new Map();
8
+ this.streamEmitters = /* @__PURE__ */ new Map();
9
+ this.workflowStates = /* @__PURE__ */ new Map();
10
+ }
11
+ async enqueue(queueName, job) {
12
+ if (!this.queues.has(queueName)) {
13
+ this.queues.set(queueName, []);
14
+ }
15
+ const queue = this.queues.get(queueName);
16
+ queue.push(job);
17
+ this.allJobs.set(job.id, job);
18
+ return job.id;
19
+ }
20
+ async dequeue(queueName) {
21
+ const queue = this.queues.get(queueName);
22
+ if (!queue || queue.length === 0) return null;
23
+ return queue.shift() || null;
24
+ }
25
+ async ack(queue, jobId) {
26
+ const job = this.allJobs.get(jobId);
27
+ if (job) {
28
+ job.state = "completed";
29
+ job.finishedOn = Date.now();
30
+ }
31
+ }
32
+ async nack(queueName, jobId, requeue = true) {
33
+ const job = this.allJobs.get(jobId);
34
+ if (job && requeue) {
35
+ job.state = "waiting";
36
+ const queue = this.queues.get(queueName);
37
+ if (queue) queue.push(job);
38
+ }
39
+ }
40
+ async getJob(queueName, jobId) {
41
+ return this.allJobs.get(jobId) || null;
42
+ }
43
+ async getJobs(queue, status) {
44
+ return Array.from(this.allJobs.values()).filter(
45
+ (job) => job.state === status
46
+ );
47
+ }
48
+ async getAllJobs(queue) {
49
+ return Array.from(this.allJobs.values());
50
+ }
51
+ async cleanJobs(queue, grace, status) {
52
+ const now2 = Date.now();
53
+ const jobsToClean = [];
54
+ for (const [id, job] of this.allJobs.entries()) {
55
+ if (job.state === status) {
56
+ const timestamp2 = job.finishedOn || job.timestamp;
57
+ if (now2 - timestamp2 > grace) {
58
+ jobsToClean.push(id);
59
+ }
60
+ }
61
+ }
62
+ for (const id of jobsToClean) {
63
+ this.allJobs.delete(id);
64
+ }
65
+ return jobsToClean.length;
66
+ }
67
+ async publish(streamName, message) {
68
+ if (!this.streams.has(streamName)) {
69
+ this.streams.set(streamName, []);
70
+ this.streamEmitters.set(streamName, new EventEmitter());
71
+ }
72
+ const stream = this.streams.get(streamName);
73
+ stream.push(message);
74
+ this.streamEmitters.get(streamName).emit("message", message);
75
+ return message.id;
76
+ }
77
+ async subscribe(streamName, handler) {
78
+ if (!this.streamEmitters.has(streamName)) {
79
+ this.streamEmitters.set(streamName, new EventEmitter());
80
+ this.streams.set(streamName, []);
81
+ }
82
+ const emitter = this.streamEmitters.get(streamName);
83
+ const wrapper = (msg) => {
84
+ handler(msg).catch((err) => console.error("Stream handler error", err));
85
+ };
86
+ emitter.on("message", wrapper);
87
+ return {
88
+ unsubscribe: async () => {
89
+ emitter.off("message", wrapper);
90
+ }
91
+ };
92
+ }
93
+ async consume(stream, group, consumer, handler) {
94
+ return this.subscribe(stream, handler);
95
+ }
96
+ async createConsumerGroup(stream, group) {
97
+ }
98
+ async saveWorkflowState(workflowId, state) {
99
+ this.workflowStates.set(workflowId, state);
100
+ }
101
+ async loadWorkflowState(workflowId) {
102
+ return this.workflowStates.get(workflowId) || null;
103
+ }
104
+ async getQueueStats(queueName) {
105
+ const length = this.queues.get(queueName)?.length || 0;
106
+ const allInQueue = Array.from(this.allJobs.values()).filter(
107
+ (j) => j.state === "completed"
108
+ ).length;
109
+ return {
110
+ waiting: length,
111
+ active: 0,
112
+ completed: allInQueue,
113
+ failed: 0,
114
+ delayed: 0,
115
+ paused: 0
116
+ };
117
+ }
118
+ async getStreamInfo(streamName) {
119
+ const length = this.streams.get(streamName)?.length || 0;
120
+ return {
121
+ name: streamName,
122
+ length,
123
+ groups: 0
124
+ };
125
+ }
126
+ async cleanup() {
127
+ this.queues.clear();
128
+ this.allJobs.clear();
129
+ this.streams.clear();
130
+ this.workflowStates.clear();
131
+ this.streamEmitters.clear();
132
+ }
133
+ };
134
+
135
+ // src/queue/job.ts
136
+ import { v4 as uuidv4 } from "uuid";
137
+ var JobImpl = class {
138
+ constructor(name, data, opts) {
139
+ this.state = "waiting";
140
+ this.progress = 0;
141
+ this.delay = 0;
142
+ this.attemptsMade = 0;
143
+ this.id = opts?.jobId || uuidv4();
144
+ this.name = name;
145
+ this.data = data;
146
+ this.opts = opts || {};
147
+ this.timestamp = Date.now();
148
+ }
149
+ async update(data) {
150
+ this.data = { ...this.data, ...data };
151
+ }
152
+ async log(message) {
153
+ }
154
+ async updateProgress(progress) {
155
+ this.progress = progress;
156
+ }
157
+ async moveToCompleted(returnValue) {
158
+ this.state = "completed";
159
+ this.returnvalue = returnValue;
160
+ this.finishedOn = Date.now();
161
+ }
162
+ async moveToFailed(error) {
163
+ this.state = "failed";
164
+ this.failedReason = error.message;
165
+ this.finishedOn = Date.now();
166
+ }
167
+ async retry() {
168
+ }
169
+ async discard() {
170
+ }
171
+ async waitUntilFinished() {
172
+ return Promise.resolve();
173
+ }
174
+ };
175
+
176
+ // src/queue/queue.ts
177
+ import EventEmitter2 from "eventemitter3";
178
+
179
+ // src/patterns/backoff.ts
180
+ function calculateBackoff(attemptsMade, options) {
181
+ const { type, delay, maxDelay } = options;
182
+ let resultDelay;
183
+ switch (type) {
184
+ case "fixed":
185
+ resultDelay = delay;
186
+ break;
187
+ case "exponential":
188
+ resultDelay = delay * Math.pow(2, attemptsMade - 1);
189
+ break;
190
+ case "custom":
191
+ resultDelay = delay;
192
+ break;
193
+ default:
194
+ resultDelay = delay;
195
+ }
196
+ if (maxDelay && resultDelay > maxDelay) {
197
+ return maxDelay;
198
+ }
199
+ return resultDelay;
200
+ }
201
+
202
+ // src/queue/queue.ts
203
+ var QueueImpl = class extends EventEmitter2 {
204
+ constructor(name, adapter, options = {}) {
205
+ super();
206
+ this.isProcessing = false;
207
+ this.currentWorkers = /* @__PURE__ */ new Set();
208
+ this.name = name;
209
+ this.adapter = adapter;
210
+ this.options = options;
211
+ }
212
+ async add(name, data, opts) {
213
+ const job = new JobImpl(name, data, {
214
+ ...this.options.defaultJobOptions,
215
+ ...opts
216
+ });
217
+ await this.adapter.enqueue(this.name, job);
218
+ this.emit("waiting", job);
219
+ return job;
220
+ }
221
+ async addBulk(jobs2) {
222
+ return Promise.all(jobs2.map((j) => this.add(j.name, j.data, j.opts)));
223
+ }
224
+ process(arg1, arg2, arg3) {
225
+ let handler;
226
+ let concurrency = 1;
227
+ if (typeof arg1 === "number") {
228
+ concurrency = arg1;
229
+ handler = arg2;
230
+ } else if (typeof arg1 === "string") {
231
+ if (typeof arg2 === "number") {
232
+ concurrency = arg2;
233
+ handler = arg3;
234
+ } else {
235
+ handler = arg2;
236
+ }
237
+ } else {
238
+ handler = arg1;
239
+ }
240
+ this.resume();
241
+ for (let i = 0; i < concurrency; i++) {
242
+ const worker = this.startProcessing(handler);
243
+ this.currentWorkers.add(worker);
244
+ worker.finally(() => this.currentWorkers.delete(worker));
245
+ }
246
+ }
247
+ async startProcessing(handler) {
248
+ while (this.isProcessing) {
249
+ const jobData = await this.adapter.dequeue(this.name);
250
+ if (jobData) {
251
+ const job = Object.assign(
252
+ new JobImpl(jobData.name, jobData.data, jobData.opts),
253
+ jobData
254
+ );
255
+ if (job.opts.waitFor && job.opts.waitFor.length > 0) {
256
+ let allDone = true;
257
+ for (const depId of job.opts.waitFor) {
258
+ const depJob = await this.adapter.getJob(this.name, depId);
259
+ if (!depJob || depJob.state !== "completed") {
260
+ allDone = false;
261
+ break;
262
+ }
263
+ }
264
+ if (!allDone) {
265
+ await this.adapter.enqueue(this.name, job);
266
+ await new Promise((resolve) => setTimeout(resolve, 100));
267
+ continue;
268
+ }
269
+ }
270
+ try {
271
+ job.state = "active";
272
+ job.processedOn = Date.now();
273
+ this.emit("active", job);
274
+ const result = await handler(job);
275
+ await job.moveToCompleted(result);
276
+ await this.adapter.ack(this.name, job.id);
277
+ this.emit("completed", job, result);
278
+ } catch (err) {
279
+ job.attemptsMade++;
280
+ job.stacktrace = job.stacktrace || [];
281
+ job.stacktrace.push(err.stack);
282
+ const maxAttempts = job.opts.attempts || 1;
283
+ if (job.attemptsMade < maxAttempts) {
284
+ job.state = "delayed";
285
+ const backoff = job.opts.backoff ? calculateBackoff(job.attemptsMade, job.opts.backoff) : 0;
286
+ job.opts.delay = backoff;
287
+ await this.adapter.enqueue(this.name, job);
288
+ this.emit("failed", job, err);
289
+ } else {
290
+ await job.moveToFailed(err);
291
+ await this.adapter.ack(this.name, job.id);
292
+ this.emit("failed", job, err);
293
+ }
294
+ }
295
+ } else {
296
+ await new Promise((resolve) => setTimeout(resolve, 100));
297
+ }
298
+ }
299
+ }
300
+ processBatch(arg1, arg2, arg3) {
301
+ let handler;
302
+ let batchSize = 10;
303
+ let maxWait = 1e3;
304
+ if (typeof arg2 === "function") {
305
+ handler = arg2;
306
+ } else {
307
+ handler = arg3;
308
+ if (typeof arg2 === "number") {
309
+ batchSize = arg2;
310
+ } else {
311
+ batchSize = arg2.batchSize;
312
+ maxWait = arg2.maxWait || 1e3;
313
+ }
314
+ }
315
+ this.resume();
316
+ this.startBatchProcessing(handler, batchSize, maxWait);
317
+ }
318
+ async startBatchProcessing(handler, batchSize, maxWait) {
319
+ while (this.isProcessing) {
320
+ const batch2 = [];
321
+ const start = Date.now();
322
+ while (batch2.length < batchSize && Date.now() - start < maxWait) {
323
+ const jobData = await this.adapter.dequeue(this.name);
324
+ if (jobData) {
325
+ const job = Object.assign(
326
+ new JobImpl(jobData.name, jobData.data, jobData.opts),
327
+ jobData
328
+ );
329
+ batch2.push(job);
330
+ } else {
331
+ await new Promise((resolve) => setTimeout(resolve, 50));
332
+ }
333
+ }
334
+ if (batch2.length > 0) {
335
+ try {
336
+ for (const job of batch2) {
337
+ job.state = "active";
338
+ job.processedOn = Date.now();
339
+ }
340
+ const results = await handler(batch2);
341
+ for (let i = 0; i < batch2.length; i++) {
342
+ await batch2[i].moveToCompleted(results[i]);
343
+ await this.adapter.ack(this.name, batch2[i].id);
344
+ }
345
+ } catch (err) {
346
+ for (const job of batch2) {
347
+ await job.moveToFailed(err);
348
+ await this.adapter.ack(this.name, job.id);
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ async pause() {
355
+ this.isProcessing = false;
356
+ }
357
+ async resume() {
358
+ this.isProcessing = true;
359
+ }
360
+ async drain() {
361
+ await Promise.all(this.currentWorkers);
362
+ }
363
+ async clean(grace, status) {
364
+ return this.adapter.cleanJobs(this.name, grace, status);
365
+ }
366
+ async getJob(jobId) {
367
+ const job = await this.adapter.getJob(this.name, jobId);
368
+ return job;
369
+ }
370
+ async getJobs(status) {
371
+ const jobs2 = await this.adapter.getJobs(this.name, status);
372
+ return jobs2;
373
+ }
374
+ async getJobCounts() {
375
+ return this.adapter.getQueueStats(this.name);
376
+ }
377
+ async close() {
378
+ this.isProcessing = false;
379
+ await this.drain();
380
+ }
381
+ };
382
+
383
+ // src/stream/stream.ts
384
+ import { v4 as uuidv42 } from "uuid";
385
+ var StreamImpl = class {
386
+ constructor(name, adapter, options = {}) {
387
+ this.messages = /* @__PURE__ */ new Map();
388
+ this.name = name;
389
+ this.adapter = adapter;
390
+ this.options = options;
391
+ }
392
+ async publish(data, options) {
393
+ const message = {
394
+ id: uuidv42(),
395
+ stream: this.name,
396
+ data,
397
+ headers: options?.headers,
398
+ timestamp: Date.now(),
399
+ partition: options?.partition,
400
+ key: options?.key,
401
+ ack: async () => {
402
+ },
403
+ nack: async () => {
404
+ }
405
+ };
406
+ this.messages.set(message.id, message);
407
+ if (this.options.maxLength && this.messages.size > this.options.maxLength) {
408
+ await this.trim({ maxLength: this.options.maxLength });
409
+ }
410
+ return this.adapter.publish(this.name, message);
411
+ }
412
+ async publishBatch(messages2) {
413
+ return Promise.all(messages2.map((m) => this.publish(m.data, m.options)));
414
+ }
415
+ async subscribe(handler, options) {
416
+ return this.adapter.subscribe(this.name, handler);
417
+ }
418
+ createConsumer(consumerId, options) {
419
+ let subscription = null;
420
+ let paused = false;
421
+ return {
422
+ subscribe: async (handler) => {
423
+ if (options.fromBeginning && this.messages.size > 0) {
424
+ const sortedMessages = Array.from(this.messages.values()).sort(
425
+ (a, b) => a.timestamp - b.timestamp
426
+ );
427
+ for (const msg of sortedMessages) {
428
+ if (!paused) {
429
+ await handler(msg).catch(console.error);
430
+ }
431
+ }
432
+ }
433
+ subscription = await this.adapter.consume(
434
+ this.name,
435
+ options.groupId,
436
+ consumerId,
437
+ handler
438
+ );
439
+ },
440
+ pause: async () => {
441
+ paused = true;
442
+ },
443
+ resume: async () => {
444
+ paused = false;
445
+ },
446
+ close: async () => {
447
+ if (subscription) await subscription.unsubscribe();
448
+ }
449
+ };
450
+ }
451
+ async getInfo() {
452
+ const info = await this.adapter.getStreamInfo(this.name);
453
+ return {
454
+ ...info,
455
+ length: this.messages.size
456
+ };
457
+ }
458
+ async trim(strategy) {
459
+ const now2 = Date.now();
460
+ const messagesToDelete = [];
461
+ if (strategy.maxLength) {
462
+ const sortedMessages = Array.from(this.messages.entries()).sort(
463
+ (a, b) => b[1].timestamp - a[1].timestamp
464
+ );
465
+ if (sortedMessages.length > strategy.maxLength) {
466
+ const toRemove = sortedMessages.slice(strategy.maxLength);
467
+ messagesToDelete.push(...toRemove.map(([id]) => id));
468
+ }
469
+ }
470
+ if (strategy.maxAgeSeconds) {
471
+ const maxAge = strategy.maxAgeSeconds * 1e3;
472
+ for (const [id, message] of this.messages.entries()) {
473
+ if (now2 - message.timestamp > maxAge) {
474
+ messagesToDelete.push(id);
475
+ }
476
+ }
477
+ }
478
+ const uniqueToDelete = [...new Set(messagesToDelete)];
479
+ for (const id of uniqueToDelete) {
480
+ this.messages.delete(id);
481
+ }
482
+ return uniqueToDelete.length;
483
+ }
484
+ async getMessages(start, end, count) {
485
+ const allMessages = Array.from(this.messages.values()).sort(
486
+ (a, b) => a.timestamp - b.timestamp
487
+ );
488
+ let filtered = allMessages.filter((m) => m.id >= start && m.id <= end);
489
+ if (count !== void 0 && count > 0) {
490
+ filtered = filtered.slice(0, count);
491
+ }
492
+ return filtered;
493
+ }
494
+ /**
495
+ * Replay messages from a specific timestamp
496
+ */
497
+ async replay(fromTimestamp, handler) {
498
+ const messages2 = Array.from(this.messages.values()).filter((m) => m.timestamp >= fromTimestamp).sort((a, b) => a.timestamp - b.timestamp);
499
+ for (const message of messages2) {
500
+ await handler(message);
501
+ }
502
+ return messages2.length;
503
+ }
504
+ /**
505
+ * Get message count
506
+ */
507
+ getMessageCount() {
508
+ return this.messages.size;
509
+ }
510
+ async close() {
511
+ this.messages.clear();
512
+ }
513
+ };
514
+
515
+ // src/workflow/workflow.ts
516
+ import { v4 as uuidv43 } from "uuid";
517
+
518
+ // src/storage/workflow-storage.ts
519
+ var MemoryWorkflowStorage = class {
520
+ constructor() {
521
+ this.executions = /* @__PURE__ */ new Map();
522
+ this.workflowIndex = /* @__PURE__ */ new Map();
523
+ }
524
+ // workflowId -> executionIds
525
+ async save(workflowId, execution) {
526
+ this.executions.set(execution.id, { ...execution });
527
+ if (!this.workflowIndex.has(workflowId)) {
528
+ this.workflowIndex.set(workflowId, /* @__PURE__ */ new Set());
529
+ }
530
+ this.workflowIndex.get(workflowId).add(execution.id);
531
+ }
532
+ async get(executionId) {
533
+ return this.executions.get(executionId) || null;
534
+ }
535
+ async list(workflowId, options = {}) {
536
+ const executionIds = this.workflowIndex.get(workflowId) || /* @__PURE__ */ new Set();
537
+ let executions = [];
538
+ for (const id of executionIds) {
539
+ const execution = this.executions.get(id);
540
+ if (execution) {
541
+ if (!options.status || execution.status === options.status) {
542
+ executions.push(execution);
543
+ }
544
+ }
545
+ }
546
+ const sortBy = options.sortBy || "createdAt";
547
+ const sortOrder = options.sortOrder || "desc";
548
+ executions.sort((a, b) => {
549
+ const aVal = a[sortBy] || 0;
550
+ const bVal = b[sortBy] || 0;
551
+ return sortOrder === "asc" ? aVal - bVal : bVal - aVal;
552
+ });
553
+ const offset = options.offset || 0;
554
+ const limit = options.limit || executions.length;
555
+ return executions.slice(offset, offset + limit);
556
+ }
557
+ async updateStatus(executionId, status) {
558
+ const execution = this.executions.get(executionId);
559
+ if (execution) {
560
+ execution.status = status;
561
+ execution.updatedAt = Date.now();
562
+ if (status === "completed" || status === "failed" || status === "cancelled") {
563
+ execution.completedAt = Date.now();
564
+ }
565
+ }
566
+ }
567
+ async updateState(executionId, state) {
568
+ const execution = this.executions.get(executionId);
569
+ if (execution) {
570
+ execution.state = { ...execution.state, ...state };
571
+ execution.updatedAt = Date.now();
572
+ }
573
+ }
574
+ async delete(executionId) {
575
+ const execution = this.executions.get(executionId);
576
+ if (execution) {
577
+ const workflowId = execution.workflowId;
578
+ const executionIds = this.workflowIndex.get(workflowId);
579
+ if (executionIds) {
580
+ executionIds.delete(executionId);
581
+ if (executionIds.size === 0) {
582
+ this.workflowIndex.delete(workflowId);
583
+ }
584
+ }
585
+ this.executions.delete(executionId);
586
+ }
587
+ }
588
+ async clean(workflowId, grace) {
589
+ const now2 = Date.now();
590
+ const executionIds = this.workflowIndex.get(workflowId) || /* @__PURE__ */ new Set();
591
+ const toDelete = [];
592
+ for (const id of executionIds) {
593
+ const execution = this.executions.get(id);
594
+ if (execution && execution.completedAt) {
595
+ if (now2 - execution.completedAt > grace) {
596
+ toDelete.push(id);
597
+ }
598
+ }
599
+ }
600
+ for (const id of toDelete) {
601
+ await this.delete(id);
602
+ }
603
+ return toDelete.length;
604
+ }
605
+ /**
606
+ * Clear all executions
607
+ */
608
+ clear() {
609
+ this.executions.clear();
610
+ this.workflowIndex.clear();
611
+ }
612
+ };
613
+
614
+ // src/storage/event-log.ts
615
+ var MemoryEventLog = class {
616
+ constructor() {
617
+ this.events = [];
618
+ this.aggregateVersions = /* @__PURE__ */ new Map();
619
+ this.subscribers = [];
620
+ }
621
+ async append(event) {
622
+ const version = (this.aggregateVersions.get(event.aggregateId) || 0) + 1;
623
+ this.aggregateVersions.set(event.aggregateId, version);
624
+ const fullEvent = {
625
+ ...event,
626
+ id: `evt_${Date.now()}_${Math.random().toString(36).substring(7)}`,
627
+ timestamp: Date.now(),
628
+ version
629
+ };
630
+ this.events.push(fullEvent);
631
+ for (const subscriber of this.subscribers) {
632
+ if (this.matchesFilter(fullEvent, subscriber.filter)) {
633
+ Promise.resolve(subscriber.handler(fullEvent)).catch(
634
+ (err) => console.error("Event subscriber error:", err)
635
+ );
636
+ }
637
+ }
638
+ return fullEvent;
639
+ }
640
+ async getEvents(filter) {
641
+ let results = [...this.events];
642
+ if (filter.aggregateId) {
643
+ results = results.filter((e) => e.aggregateId === filter.aggregateId);
644
+ }
645
+ if (filter.aggregateType) {
646
+ results = results.filter((e) => e.aggregateType === filter.aggregateType);
647
+ }
648
+ if (filter.types && filter.types.length > 0) {
649
+ results = results.filter((e) => filter.types.includes(e.type));
650
+ }
651
+ if (filter.fromVersion !== void 0) {
652
+ results = results.filter((e) => e.version >= filter.fromVersion);
653
+ }
654
+ if (filter.toVersion !== void 0) {
655
+ results = results.filter((e) => e.version <= filter.toVersion);
656
+ }
657
+ if (filter.fromTimestamp !== void 0) {
658
+ results = results.filter((e) => e.timestamp >= filter.fromTimestamp);
659
+ }
660
+ if (filter.toTimestamp !== void 0) {
661
+ results = results.filter((e) => e.timestamp <= filter.toTimestamp);
662
+ }
663
+ results.sort((a, b) => a.version - b.version);
664
+ const offset = filter.offset || 0;
665
+ const limit = filter.limit || results.length;
666
+ return results.slice(offset, offset + limit);
667
+ }
668
+ async getAggregateEvents(aggregateId, fromVersion) {
669
+ return this.getEvents({
670
+ aggregateId,
671
+ fromVersion
672
+ });
673
+ }
674
+ async getLatestVersion(aggregateId) {
675
+ return this.aggregateVersions.get(aggregateId) || 0;
676
+ }
677
+ subscribe(handler, filter) {
678
+ const subscriber = { handler, filter };
679
+ this.subscribers.push(subscriber);
680
+ return () => {
681
+ const index2 = this.subscribers.indexOf(subscriber);
682
+ if (index2 > -1) {
683
+ this.subscribers.splice(index2, 1);
684
+ }
685
+ };
686
+ }
687
+ matchesFilter(event, filter) {
688
+ if (!filter) return true;
689
+ if (filter.aggregateId && event.aggregateId !== filter.aggregateId)
690
+ return false;
691
+ if (filter.aggregateType && event.aggregateType !== filter.aggregateType)
692
+ return false;
693
+ if (filter.types && !filter.types.includes(event.type)) return false;
694
+ if (filter.fromVersion !== void 0 && event.version < filter.fromVersion)
695
+ return false;
696
+ if (filter.toVersion !== void 0 && event.version > filter.toVersion)
697
+ return false;
698
+ if (filter.fromTimestamp !== void 0 && event.timestamp < filter.fromTimestamp)
699
+ return false;
700
+ if (filter.toTimestamp !== void 0 && event.timestamp > filter.toTimestamp)
701
+ return false;
702
+ return true;
703
+ }
704
+ /**
705
+ * Clear all events
706
+ */
707
+ clear() {
708
+ this.events = [];
709
+ this.aggregateVersions.clear();
710
+ this.subscribers = [];
711
+ }
712
+ };
713
+
714
+ // src/workflow/workflow.ts
715
+ var WorkflowBuilderImpl = class {
716
+ constructor(name, adapter) {
717
+ this.steps = [];
718
+ this.name = name;
719
+ this.adapter = adapter;
720
+ }
721
+ input() {
722
+ return this;
723
+ }
724
+ step(name, handler) {
725
+ this.steps.push({ type: "step", name, handler });
726
+ return this;
727
+ }
728
+ parallel(steps) {
729
+ this.steps.push({
730
+ type: "parallel",
731
+ steps: steps.map((h, i) => ({
732
+ type: "step",
733
+ name: `parallel-${i}`,
734
+ handler: h
735
+ }))
736
+ });
737
+ return this;
738
+ }
739
+ branch(options) {
740
+ const thenSteps = options.then.steps;
741
+ const elseSteps = options.else ? options.else.steps : void 0;
742
+ this.steps.push({
743
+ type: "branch",
744
+ condition: options.condition,
745
+ steps: thenSteps,
746
+ elseSteps
747
+ });
748
+ return this;
749
+ }
750
+ saga(name, saga) {
751
+ this.steps.push({
752
+ type: "saga",
753
+ name,
754
+ handler: saga.execute,
755
+ compensate: saga.compensate
756
+ });
757
+ return this;
758
+ }
759
+ delay(duration) {
760
+ this.steps.push({ type: "delay", options: duration });
761
+ return this;
762
+ }
763
+ delayUntil(condition) {
764
+ this.steps.push({ type: "delayUntil", condition });
765
+ return this;
766
+ }
767
+ waitForApproval(approver, options) {
768
+ this.steps.push({
769
+ type: "wait",
770
+ name: "approval",
771
+ options: { approver, ...options }
772
+ });
773
+ return this;
774
+ }
775
+ waitForEvent(event, options) {
776
+ this.steps.push({
777
+ type: "wait",
778
+ name: "event",
779
+ options: { event, ...options }
780
+ });
781
+ return this;
782
+ }
783
+ onError(step, handler) {
784
+ const s = this.steps.find((s2) => s2.name === step);
785
+ if (s) {
786
+ }
787
+ return this;
788
+ }
789
+ compensate(step, handler) {
790
+ const s = this.steps.find((s2) => s2.name === step);
791
+ if (s) {
792
+ s.compensate = handler;
793
+ }
794
+ return this;
795
+ }
796
+ build() {
797
+ return new WorkflowImpl(this.name, this.steps, this.adapter);
798
+ }
799
+ };
800
+ var WorkflowImpl = class {
801
+ constructor(name, steps, adapter) {
802
+ this.executionCount = 0;
803
+ this.successCount = 0;
804
+ this.totalDuration = 0;
805
+ this.id = uuidv43();
806
+ this.name = name;
807
+ this.steps = steps;
808
+ this.adapter = adapter;
809
+ this.storage = new MemoryWorkflowStorage();
810
+ this.eventLog = new MemoryEventLog();
811
+ }
812
+ async execute(input) {
813
+ const executionId = uuidv43();
814
+ const execution = {
815
+ id: executionId,
816
+ workflowId: this.id,
817
+ status: "running",
818
+ input,
819
+ state: {},
820
+ startedAt: Date.now(),
821
+ createdAt: Date.now(),
822
+ updatedAt: Date.now()
823
+ };
824
+ await this.storage.save(this.id, execution);
825
+ await this.logEvent(executionId, "execution.started", { input });
826
+ this.runExecution(execution).catch(console.error);
827
+ return execution;
828
+ }
829
+ async runExecution(execution) {
830
+ const context = {
831
+ workflowId: this.id,
832
+ executionId: execution.id,
833
+ input: execution.input,
834
+ state: execution.state || {},
835
+ metadata: {},
836
+ set: (k, v) => {
837
+ context.state[k] = v;
838
+ this.storage.updateState(execution.id, context.state).catch(console.error);
839
+ },
840
+ get: (k) => context.state[k],
841
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
842
+ waitForEvent: async (event, timeout2) => {
843
+ await new Promise((r) => setTimeout(r, timeout2 || 100));
844
+ }
845
+ };
846
+ const executedSagas = [];
847
+ try {
848
+ await this.executeSteps(this.steps, context, executedSagas);
849
+ execution.status = "completed";
850
+ execution.output = context.state;
851
+ execution.completedAt = Date.now();
852
+ await this.logEvent(execution.id, "execution.completed", {
853
+ output: execution.output
854
+ });
855
+ this.executionCount++;
856
+ this.successCount++;
857
+ this.totalDuration += execution.completedAt - execution.startedAt;
858
+ } catch (error) {
859
+ execution.status = "failed";
860
+ execution.error = error;
861
+ execution.completedAt = Date.now();
862
+ await this.logEvent(execution.id, "execution.failed", {
863
+ error: error.message
864
+ });
865
+ await this.compensate(executedSagas, context);
866
+ this.executionCount++;
867
+ this.totalDuration += execution.completedAt - execution.startedAt;
868
+ }
869
+ await this.storage.save(this.id, execution);
870
+ await this.adapter.saveWorkflowState(execution.id, execution);
871
+ }
872
+ async logEvent(executionId, type, data) {
873
+ await this.eventLog.append({
874
+ type,
875
+ aggregateId: executionId,
876
+ aggregateType: "workflow_execution",
877
+ data
878
+ });
879
+ }
880
+ async executeSteps(steps, context, executedSagas) {
881
+ for (const step of steps) {
882
+ if (step.type === "step" && step.handler) {
883
+ await this.logEvent(context.executionId, "step.started", {
884
+ step: step.name
885
+ });
886
+ await step.handler(context);
887
+ await this.logEvent(context.executionId, "step.completed", {
888
+ step: step.name
889
+ });
890
+ } else if (step.type === "saga" && step.handler) {
891
+ await this.logEvent(context.executionId, "saga.started", {
892
+ step: step.name
893
+ });
894
+ await step.handler(context);
895
+ executedSagas.push(step);
896
+ await this.logEvent(context.executionId, "saga.completed", {
897
+ step: step.name
898
+ });
899
+ } else if (step.type === "parallel" && step.steps) {
900
+ await this.logEvent(context.executionId, "parallel.started", {
901
+ count: step.steps.length
902
+ });
903
+ await Promise.all(
904
+ step.steps.map(
905
+ (s) => s.handler ? s.handler(context) : Promise.resolve()
906
+ )
907
+ );
908
+ await this.logEvent(context.executionId, "parallel.completed", {});
909
+ } else if (step.type === "branch" && step.condition) {
910
+ const conditionMet = await step.condition(context);
911
+ if (conditionMet && step.steps) {
912
+ await this.executeSteps(step.steps, context, executedSagas);
913
+ } else if (!conditionMet && step.elseSteps) {
914
+ await this.executeSteps(step.elseSteps, context, executedSagas);
915
+ }
916
+ } else if (step.type === "delay") {
917
+ const ms = step.options?.ms || 0;
918
+ await new Promise((r) => setTimeout(r, ms));
919
+ } else if (step.type === "delayUntil" && step.condition) {
920
+ const targetTime = await step.condition(context);
921
+ if (typeof targetTime === "number") {
922
+ const now2 = Date.now();
923
+ const delay = Math.max(0, targetTime - now2);
924
+ if (delay > 0) {
925
+ await new Promise((r) => setTimeout(r, delay));
926
+ }
927
+ }
928
+ } else if (step.type === "wait") {
929
+ if (step.options?.timeout) {
930
+ await new Promise((r) => setTimeout(r, step.options.timeout));
931
+ }
932
+ }
933
+ }
934
+ }
935
+ async compensate(executedSagas, context) {
936
+ for (let i = executedSagas.length - 1; i >= 0; i--) {
937
+ const step = executedSagas[i];
938
+ if (step.compensate) {
939
+ try {
940
+ await step.compensate(context);
941
+ } catch (err) {
942
+ console.error(`Compensation failed for step ${step.name}`, err);
943
+ }
944
+ }
945
+ }
946
+ }
947
+ async getExecution(executionId) {
948
+ const execution = await this.storage.get(executionId);
949
+ if (!execution) {
950
+ const adapterExecution = await this.adapter.loadWorkflowState(executionId);
951
+ if (!adapterExecution) {
952
+ throw new Error(`Execution ${executionId} not found`);
953
+ }
954
+ return adapterExecution;
955
+ }
956
+ return execution;
957
+ }
958
+ async listExecutions(options) {
959
+ return this.storage.list(this.id, options);
960
+ }
961
+ async cancelExecution(executionId) {
962
+ const execution = await this.storage.get(executionId);
963
+ if (!execution) {
964
+ throw new Error(`Execution ${executionId} not found`);
965
+ }
966
+ if (execution.status === "completed" || execution.status === "failed" || execution.status === "cancelled") {
967
+ throw new Error(`Cannot cancel execution in ${execution.status} state`);
968
+ }
969
+ await this.storage.updateStatus(executionId, "cancelled");
970
+ await this.logEvent(executionId, "execution.cancelled", {});
971
+ }
972
+ async retryExecution(executionId) {
973
+ const execution = await this.storage.get(executionId);
974
+ if (!execution) {
975
+ throw new Error(`Execution ${executionId} not found`);
976
+ }
977
+ if (execution.status !== "failed") {
978
+ throw new Error(
979
+ `Can only retry failed executions, current status: ${execution.status}`
980
+ );
981
+ }
982
+ return this.execute(execution.input);
983
+ }
984
+ async getExecutionHistory(executionId) {
985
+ const events = await this.eventLog.getAggregateEvents(executionId);
986
+ return events.map((e) => ({
987
+ timestamp: e.timestamp,
988
+ type: e.type,
989
+ step: e.data.step,
990
+ data: e.data
991
+ }));
992
+ }
993
+ async getMetrics() {
994
+ const successRate = this.executionCount > 0 ? this.successCount / this.executionCount * 100 : 0;
995
+ const avgDuration = this.executionCount > 0 ? this.totalDuration / this.executionCount : 0;
996
+ return {
997
+ totalExecutions: this.executionCount,
998
+ successRate,
999
+ avgDuration
1000
+ };
1001
+ }
1002
+ };
1003
+
1004
+ // src/core/scheduler.ts
1005
+ import parser from "cron-parser";
1006
+ var Scheduler = class {
1007
+ constructor() {
1008
+ this.tasks = /* @__PURE__ */ new Map();
1009
+ }
1010
+ async schedule(name, pattern, handler) {
1011
+ await this.cancel(name);
1012
+ const options = typeof pattern === "string" ? { pattern } : pattern;
1013
+ const now2 = Date.now();
1014
+ let nextRun = 0;
1015
+ if (options.pattern) {
1016
+ const interval = parser.parseExpression(options.pattern, {
1017
+ currentDate: options.startDate || /* @__PURE__ */ new Date(),
1018
+ tz: options.timezone
1019
+ });
1020
+ nextRun = interval.next().getTime();
1021
+ } else if (options.every) {
1022
+ nextRun = (options.startDate?.getTime() || now2) + options.every;
1023
+ }
1024
+ this.tasks.set(name, {
1025
+ pattern,
1026
+ handler,
1027
+ nextRun,
1028
+ count: 0,
1029
+ paused: false
1030
+ });
1031
+ this.plan(name);
1032
+ }
1033
+ async repeat(name, options, handler) {
1034
+ return this.schedule(name, options, handler);
1035
+ }
1036
+ plan(name) {
1037
+ const task = this.tasks.get(name);
1038
+ if (!task || task.paused) return;
1039
+ const now2 = Date.now();
1040
+ const delay = Math.max(0, task.nextRun - now2);
1041
+ if (task.timer) clearTimeout(task.timer);
1042
+ task.timer = setTimeout(async () => {
1043
+ if (task.paused) return;
1044
+ try {
1045
+ const options = typeof task.pattern === "string" ? { pattern: task.pattern } : task.pattern;
1046
+ await task.handler(options.data);
1047
+ task.count++;
1048
+ if (options.limit && task.count >= options.limit) {
1049
+ this.tasks.delete(name);
1050
+ return;
1051
+ }
1052
+ if (options.pattern) {
1053
+ const interval = parser.parseExpression(options.pattern, {
1054
+ currentDate: /* @__PURE__ */ new Date(),
1055
+ tz: options.timezone
1056
+ });
1057
+ task.nextRun = interval.next().getTime();
1058
+ } else if (options.every) {
1059
+ task.nextRun = Date.now() + options.every;
1060
+ }
1061
+ if (options.endDate && task.nextRun > options.endDate.getTime()) {
1062
+ this.tasks.delete(name);
1063
+ return;
1064
+ }
1065
+ this.plan(name);
1066
+ } catch (err) {
1067
+ console.error(`Scheduler task "${name}" failed:`, err);
1068
+ this.plan(name);
1069
+ }
1070
+ }, delay);
1071
+ }
1072
+ async list() {
1073
+ return Array.from(this.tasks.entries()).map(([name, task]) => ({
1074
+ name,
1075
+ nextRun: new Date(task.nextRun),
1076
+ count: task.count,
1077
+ paused: task.paused
1078
+ }));
1079
+ }
1080
+ async cancel(name) {
1081
+ const task = this.tasks.get(name);
1082
+ if (task) {
1083
+ if (task.timer) clearTimeout(task.timer);
1084
+ this.tasks.delete(name);
1085
+ }
1086
+ }
1087
+ async pause(name) {
1088
+ const task = this.tasks.get(name);
1089
+ if (task) {
1090
+ task.paused = true;
1091
+ if (task.timer) clearTimeout(task.timer);
1092
+ }
1093
+ }
1094
+ async resume(name) {
1095
+ const task = this.tasks.get(name);
1096
+ if (task && task.paused) {
1097
+ task.paused = false;
1098
+ const options = typeof task.pattern === "string" ? { pattern: task.pattern } : task.pattern;
1099
+ if (options.pattern) {
1100
+ const interval = parser.parseExpression(options.pattern, {
1101
+ currentDate: /* @__PURE__ */ new Date(),
1102
+ tz: options.timezone
1103
+ });
1104
+ task.nextRun = interval.next().getTime();
1105
+ } else if (options.every) {
1106
+ task.nextRun = Date.now() + options.every;
1107
+ }
1108
+ this.plan(name);
1109
+ }
1110
+ }
1111
+ async close() {
1112
+ for (const task of this.tasks.values()) {
1113
+ if (task.timer) clearTimeout(task.timer);
1114
+ }
1115
+ this.tasks.clear();
1116
+ }
1117
+ };
1118
+
1119
+ // src/core/metrics.ts
1120
+ var MetricsManager = class {
1121
+ constructor(adapter) {
1122
+ this.metrics = /* @__PURE__ */ new Map();
1123
+ this.maxDataPoints = 1e3;
1124
+ this.adapter = adapter;
1125
+ }
1126
+ /**
1127
+ * Record a metric data point
1128
+ */
1129
+ record(name, value, tags) {
1130
+ const key = this.getMetricKey(name, tags);
1131
+ if (!this.metrics.has(key)) {
1132
+ this.metrics.set(key, []);
1133
+ }
1134
+ const dataPoints = this.metrics.get(key);
1135
+ dataPoints.push({
1136
+ timestamp: Date.now(),
1137
+ value,
1138
+ tags
1139
+ });
1140
+ if (dataPoints.length > this.maxDataPoints) {
1141
+ this.metrics.set(key, dataPoints.slice(-this.maxDataPoints));
1142
+ }
1143
+ }
1144
+ /**
1145
+ * Get time series for a metric
1146
+ */
1147
+ getTimeSeries(name, options) {
1148
+ const key = this.getMetricKey(name, options?.tags);
1149
+ let dataPoints = this.metrics.get(key) || [];
1150
+ if (options?.since) {
1151
+ dataPoints = dataPoints.filter((dp) => dp.timestamp >= options.since);
1152
+ }
1153
+ if (options?.limit) {
1154
+ dataPoints = dataPoints.slice(-options.limit);
1155
+ }
1156
+ if (dataPoints.length === 0) {
1157
+ return null;
1158
+ }
1159
+ return this.calculateAggregations(dataPoints);
1160
+ }
1161
+ calculateAggregations(dataPoints) {
1162
+ const values = dataPoints.map((dp) => dp.value).sort((a, b) => a - b);
1163
+ const sum = values.reduce((a, b) => a + b, 0);
1164
+ const count = values.length;
1165
+ return {
1166
+ dataPoints,
1167
+ min: values[0],
1168
+ max: values[count - 1],
1169
+ avg: sum / count,
1170
+ sum,
1171
+ count,
1172
+ p50: this.percentile(values, 50),
1173
+ p95: this.percentile(values, 95),
1174
+ p99: this.percentile(values, 99)
1175
+ };
1176
+ }
1177
+ percentile(sorted, p) {
1178
+ const index2 = Math.ceil(sorted.length * p / 100) - 1;
1179
+ return sorted[Math.max(0, index2)];
1180
+ }
1181
+ getMetricKey(name, tags) {
1182
+ if (!tags) return name;
1183
+ const tagStr = Object.entries(tags).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${v}`).join(",");
1184
+ return `${name}{${tagStr}}`;
1185
+ }
1186
+ async getQueueMetrics(name) {
1187
+ const stats = await this.adapter.getQueueStats(name);
1188
+ const throughputMetrics = this.getTimeSeries("queue.throughput", {
1189
+ tags: { queue: name },
1190
+ since: Date.now() - 6e4
1191
+ // Last minute
1192
+ });
1193
+ const durationMetrics = this.getTimeSeries("queue.job.duration", {
1194
+ tags: { queue: name }
1195
+ });
1196
+ return {
1197
+ ...stats,
1198
+ throughput: throughputMetrics?.avg || 0,
1199
+ avgDuration: durationMetrics?.avg || 0,
1200
+ p95Duration: durationMetrics?.p95 || 0
1201
+ };
1202
+ }
1203
+ async getStreamMetrics(name) {
1204
+ const info = await this.adapter.getStreamInfo(name);
1205
+ const throughputMetrics = this.getTimeSeries("stream.throughput", {
1206
+ tags: { stream: name },
1207
+ since: Date.now() - 1e3
1208
+ // Last second
1209
+ });
1210
+ return {
1211
+ ...info,
1212
+ lag: 0,
1213
+ throughput: throughputMetrics?.avg || 0,
1214
+ avgLatency: 0
1215
+ };
1216
+ }
1217
+ async getWorkflowMetrics(name) {
1218
+ const metricsData = this.getTimeSeries("workflow.executions", {
1219
+ tags: { workflow: name }
1220
+ });
1221
+ return {
1222
+ totalExecutions: metricsData?.count || 0,
1223
+ running: 0,
1224
+ completed: 0,
1225
+ failed: 0,
1226
+ successRate: 0,
1227
+ avgDuration: metricsData?.avg || 0
1228
+ };
1229
+ }
1230
+ async getSystemMetrics() {
1231
+ const memUsage = typeof process !== "undefined" && process.memoryUsage ? process.memoryUsage().heapUsed : 0;
1232
+ return {
1233
+ queues: 0,
1234
+ streams: 0,
1235
+ workflows: 0,
1236
+ workers: 0,
1237
+ memoryUsage: memUsage
1238
+ };
1239
+ }
1240
+ /**
1241
+ * Clear old metrics
1242
+ */
1243
+ cleanup(maxAge) {
1244
+ const now2 = Date.now();
1245
+ for (const [key, dataPoints] of this.metrics.entries()) {
1246
+ const filtered = dataPoints.filter((dp) => now2 - dp.timestamp <= maxAge);
1247
+ if (filtered.length === 0) {
1248
+ this.metrics.delete(key);
1249
+ } else {
1250
+ this.metrics.set(key, filtered);
1251
+ }
1252
+ }
1253
+ }
1254
+ };
1255
+
1256
+ // src/monitoring/health.ts
1257
+ var HealthCheckerImpl = class {
1258
+ constructor() {
1259
+ this.checks = /* @__PURE__ */ new Map();
1260
+ this.startTime = Date.now();
1261
+ this.addCheck("uptime", async () => ({
1262
+ name: "uptime",
1263
+ status: "pass",
1264
+ responseTime: 0,
1265
+ details: {
1266
+ uptime: Date.now() - this.startTime,
1267
+ startTime: this.startTime
1268
+ }
1269
+ }));
1270
+ this.addCheck("memory", async () => {
1271
+ if (typeof process !== "undefined" && process.memoryUsage) {
1272
+ const mem = process.memoryUsage();
1273
+ const usedMB = mem.heapUsed / 1024 / 1024;
1274
+ const totalMB = mem.heapTotal / 1024 / 1024;
1275
+ const usagePercent = usedMB / totalMB * 100;
1276
+ return {
1277
+ name: "memory",
1278
+ status: usagePercent > 90 ? "warn" : "pass",
1279
+ message: usagePercent > 90 ? "High memory usage" : void 0,
1280
+ responseTime: 0,
1281
+ details: {
1282
+ heapUsedMB: Math.round(usedMB),
1283
+ heapTotalMB: Math.round(totalMB),
1284
+ usagePercent: Math.round(usagePercent)
1285
+ }
1286
+ };
1287
+ }
1288
+ return {
1289
+ name: "memory",
1290
+ status: "pass",
1291
+ responseTime: 0
1292
+ };
1293
+ });
1294
+ }
1295
+ addCheck(name, checker) {
1296
+ this.checks.set(name, checker);
1297
+ }
1298
+ removeCheck(name) {
1299
+ this.checks.delete(name);
1300
+ }
1301
+ async check() {
1302
+ const results = [];
1303
+ let allHealthy = true;
1304
+ for (const [name, checker] of this.checks.entries()) {
1305
+ try {
1306
+ const start = Date.now();
1307
+ const result = await checker();
1308
+ result.responseTime = Date.now() - start;
1309
+ results.push(result);
1310
+ if (result.status === "fail") {
1311
+ allHealthy = false;
1312
+ }
1313
+ } catch (error) {
1314
+ results.push({
1315
+ name,
1316
+ status: "fail",
1317
+ message: error.message,
1318
+ responseTime: 0
1319
+ });
1320
+ allHealthy = false;
1321
+ }
1322
+ }
1323
+ return {
1324
+ healthy: allHealthy,
1325
+ timestamp: Date.now(),
1326
+ checks: results
1327
+ };
1328
+ }
1329
+ };
1330
+ var MemoryEventTracker = class {
1331
+ constructor() {
1332
+ this.events = [];
1333
+ this.maxEvents = 1e4;
1334
+ }
1335
+ track(event) {
1336
+ const trackedEvent = {
1337
+ ...event,
1338
+ id: `evt_${Date.now()}_${Math.random().toString(36).substring(7)}`,
1339
+ timestamp: Date.now()
1340
+ };
1341
+ this.events.push(trackedEvent);
1342
+ if (this.events.length > this.maxEvents) {
1343
+ this.events = this.events.slice(-this.maxEvents);
1344
+ }
1345
+ }
1346
+ getEvents(filter) {
1347
+ let filtered = [...this.events];
1348
+ if (filter?.category) {
1349
+ filtered = filtered.filter((e) => e.category === filter.category);
1350
+ }
1351
+ if (filter?.severity) {
1352
+ filtered = filtered.filter((e) => e.severity === filter.severity);
1353
+ }
1354
+ if (filter?.since !== void 0) {
1355
+ filtered = filtered.filter((e) => e.timestamp >= filter.since);
1356
+ }
1357
+ if (filter?.limit) {
1358
+ filtered = filtered.slice(-filter.limit);
1359
+ }
1360
+ return filtered;
1361
+ }
1362
+ cleanup(maxAge) {
1363
+ const now2 = Date.now();
1364
+ const before = this.events.length;
1365
+ this.events = this.events.filter((e) => now2 - e.timestamp <= maxAge);
1366
+ return before - this.events.length;
1367
+ }
1368
+ clear() {
1369
+ this.events = [];
1370
+ }
1371
+ };
1372
+
1373
+ // src/core/flow-fn.ts
1374
+ import EventEmitter3 from "eventemitter3";
1375
+ var FlowFnImpl = class extends EventEmitter3 {
1376
+ constructor(config) {
1377
+ super();
1378
+ this.queues = /* @__PURE__ */ new Map();
1379
+ this.streams = /* @__PURE__ */ new Map();
1380
+ this.startTime = Date.now();
1381
+ if (config.adapter === "memory") {
1382
+ this.adapter = new MemoryAdapter();
1383
+ } else if (typeof config.adapter === "string") {
1384
+ throw new Error(
1385
+ `Adapter ${config.adapter} not automatically initialized yet. Pass an instance.`
1386
+ );
1387
+ } else {
1388
+ this.adapter = config.adapter;
1389
+ }
1390
+ this._metrics = new MetricsManager(this.adapter);
1391
+ this._scheduler = new Scheduler();
1392
+ this.healthChecker = new HealthCheckerImpl();
1393
+ this.eventTracker = new MemoryEventTracker();
1394
+ this.setupHealthChecks();
1395
+ }
1396
+ setupHealthChecks() {
1397
+ this.healthChecker.addCheck("queues", async () => {
1398
+ const queueCount = this.queues.size;
1399
+ return {
1400
+ name: "queues",
1401
+ status: "pass",
1402
+ details: {
1403
+ count: queueCount,
1404
+ active: Array.from(this.queues.keys())
1405
+ }
1406
+ };
1407
+ });
1408
+ this.healthChecker.addCheck("adapter", async () => {
1409
+ try {
1410
+ await this.adapter.getQueueStats("health-check");
1411
+ return {
1412
+ name: "adapter",
1413
+ status: "pass",
1414
+ message: "Adapter responding"
1415
+ };
1416
+ } catch (error) {
1417
+ return {
1418
+ name: "adapter",
1419
+ status: "fail",
1420
+ message: error.message
1421
+ };
1422
+ }
1423
+ });
1424
+ }
1425
+ queue(name, options) {
1426
+ if (!this.queues.has(name)) {
1427
+ this.queues.set(name, new QueueImpl(name, this.adapter, options));
1428
+ }
1429
+ return this.queues.get(name);
1430
+ }
1431
+ async listQueues() {
1432
+ return Array.from(this.queues.keys());
1433
+ }
1434
+ stream(name, options) {
1435
+ if (!this.streams.has(name)) {
1436
+ this.streams.set(name, new StreamImpl(name, this.adapter, options));
1437
+ }
1438
+ return this.streams.get(name);
1439
+ }
1440
+ async listStreams() {
1441
+ return Array.from(this.streams.keys());
1442
+ }
1443
+ workflow(name) {
1444
+ return new WorkflowBuilderImpl(name, this.adapter);
1445
+ }
1446
+ async listWorkflows() {
1447
+ return [];
1448
+ }
1449
+ scheduler() {
1450
+ return this._scheduler;
1451
+ }
1452
+ get metrics() {
1453
+ return this._metrics;
1454
+ }
1455
+ async healthCheck() {
1456
+ const health = await this.healthChecker.check();
1457
+ this.eventTracker.track({
1458
+ type: "health.check",
1459
+ category: "system",
1460
+ severity: health.healthy ? "info" : "warn",
1461
+ message: health.healthy ? "System healthy" : "System unhealthy",
1462
+ metadata: { checksCount: health.checks.length }
1463
+ });
1464
+ return health;
1465
+ }
1466
+ /**
1467
+ * Get event tracker
1468
+ */
1469
+ getEventTracker() {
1470
+ return this.eventTracker;
1471
+ }
1472
+ async close() {
1473
+ await this.adapter.cleanup();
1474
+ for (const q of this.queues.values()) await q.close();
1475
+ for (const s of this.streams.values()) await s.close();
1476
+ }
1477
+ };
1478
+ function createFlow(config) {
1479
+ return new FlowFnImpl(config);
1480
+ }
1481
+
1482
+ // src/queue/worker.ts
1483
+ import EventEmitter4 from "eventemitter3";
1484
+ var Worker = class extends EventEmitter4 {
1485
+ constructor(options) {
1486
+ super();
1487
+ this.queues = [];
1488
+ this.flow = options.flow;
1489
+ this.queueNames = options.queues;
1490
+ this.concurrency = options.concurrency || 1;
1491
+ }
1492
+ async run() {
1493
+ for (const name of this.queueNames) {
1494
+ const queue = this.flow.queue(name);
1495
+ this.queues.push(queue);
1496
+ const qConcurrency = typeof this.concurrency === "number" ? this.concurrency : this.concurrency[name] || 1;
1497
+ queue.on("active", (job) => this.emit("active", job));
1498
+ queue.on("completed", (job, result) => this.emit("completed", job, result));
1499
+ queue.on("failed", (job, err) => this.emit("failed", job, err));
1500
+ }
1501
+ this.emit("ready");
1502
+ }
1503
+ // Helper to register handlers
1504
+ register(queueName, handler) {
1505
+ const queue = this.flow.queue(queueName);
1506
+ const qConcurrency = typeof this.concurrency === "number" ? this.concurrency : this.concurrency[queueName] || 1;
1507
+ queue.process(qConcurrency, handler);
1508
+ }
1509
+ async close() {
1510
+ this.emit("closing");
1511
+ await Promise.all(this.queues.map((q) => q.close()));
1512
+ }
1513
+ };
1514
+ function createWorker(options) {
1515
+ return new Worker(options);
1516
+ }
1517
+
1518
+ // src/queue/dlq.ts
1519
+ var MemoryDLQManager = class {
1520
+ constructor(options = {}) {
1521
+ this.dlqJobs = /* @__PURE__ */ new Map();
1522
+ this.options = {
1523
+ queueName: "dlq",
1524
+ maxRetries: 3,
1525
+ ttl: 7 * 24 * 60 * 60 * 1e3,
1526
+ // 7 days
1527
+ ...options
1528
+ };
1529
+ }
1530
+ async moveToDLQ(job, reason) {
1531
+ const dlqJob = {
1532
+ ...job,
1533
+ originalQueue: job.name,
1534
+ dlqReason: reason,
1535
+ dlqTimestamp: Date.now(),
1536
+ errors: [...job.stacktrace || [], job.failedReason || reason].filter(
1537
+ Boolean
1538
+ )
1539
+ };
1540
+ this.dlqJobs.set(job.id, dlqJob);
1541
+ if (this.options.onDLQ) {
1542
+ await Promise.resolve(this.options.onDLQ(job, reason));
1543
+ }
1544
+ return dlqJob;
1545
+ }
1546
+ async getAll() {
1547
+ return Array.from(this.dlqJobs.values());
1548
+ }
1549
+ async getByQueue(queueName) {
1550
+ return Array.from(this.dlqJobs.values()).filter(
1551
+ (job) => job.originalQueue === queueName
1552
+ );
1553
+ }
1554
+ async retry(jobId) {
1555
+ const dlqJob = this.dlqJobs.get(jobId);
1556
+ if (!dlqJob) {
1557
+ throw new Error(`DLQ job ${jobId} not found`);
1558
+ }
1559
+ this.dlqJobs.delete(jobId);
1560
+ const retriedJob = {
1561
+ ...dlqJob,
1562
+ state: "waiting",
1563
+ attemptsMade: 0,
1564
+ failedReason: void 0,
1565
+ stacktrace: [],
1566
+ processedOn: void 0,
1567
+ finishedOn: void 0
1568
+ };
1569
+ return retriedJob;
1570
+ }
1571
+ async retryAll(queueName) {
1572
+ const jobs2 = await this.getByQueue(queueName);
1573
+ let count = 0;
1574
+ for (const job of jobs2) {
1575
+ try {
1576
+ await this.retry(job.id);
1577
+ count++;
1578
+ } catch (err) {
1579
+ console.error(`Failed to retry job ${job.id}:`, err);
1580
+ }
1581
+ }
1582
+ return count;
1583
+ }
1584
+ async delete(jobId) {
1585
+ this.dlqJobs.delete(jobId);
1586
+ }
1587
+ async clean(maxAge) {
1588
+ const now2 = Date.now();
1589
+ const toDelete = [];
1590
+ for (const [id, job] of this.dlqJobs.entries()) {
1591
+ if (now2 - job.dlqTimestamp > maxAge) {
1592
+ toDelete.push(id);
1593
+ }
1594
+ }
1595
+ for (const id of toDelete) {
1596
+ this.dlqJobs.delete(id);
1597
+ }
1598
+ return toDelete.length;
1599
+ }
1600
+ async getStats() {
1601
+ const byQueue = {};
1602
+ for (const job of this.dlqJobs.values()) {
1603
+ byQueue[job.originalQueue] = (byQueue[job.originalQueue] || 0) + 1;
1604
+ }
1605
+ return {
1606
+ total: this.dlqJobs.size,
1607
+ byQueue
1608
+ };
1609
+ }
1610
+ /**
1611
+ * Clear all DLQ jobs (for testing)
1612
+ */
1613
+ clear() {
1614
+ this.dlqJobs.clear();
1615
+ }
1616
+ };
1617
+
1618
+ // src/adapters/redis.ts
1619
+ import Redis from "ioredis";
1620
+ var RedisAdapter = class {
1621
+ constructor(options = {}) {
1622
+ this.subscriptions = /* @__PURE__ */ new Map();
1623
+ if (options.connection instanceof Redis) {
1624
+ this.redis = options.connection;
1625
+ } else {
1626
+ this.redis = new Redis(options.connection || {});
1627
+ }
1628
+ this.prefix = options.prefix || "flowfn:";
1629
+ }
1630
+ key(type, name) {
1631
+ return `${this.prefix}${type}:${name}`;
1632
+ }
1633
+ async enqueue(queueName, job) {
1634
+ const queueKey = this.key("queue", queueName);
1635
+ const jobKey = this.key("job", job.id);
1636
+ await this.redis.set(jobKey, JSON.stringify(job));
1637
+ if (job.opts.delay && job.opts.delay > 0) {
1638
+ const delayedKey = this.key("delayed", queueName);
1639
+ await this.redis.zadd(delayedKey, Date.now() + job.opts.delay, job.id);
1640
+ } else {
1641
+ await this.redis.lpush(queueKey, job.id);
1642
+ }
1643
+ return job.id;
1644
+ }
1645
+ async dequeue(queueName) {
1646
+ const queueKey = this.key("queue", queueName);
1647
+ const delayedKey = this.key("delayed", queueName);
1648
+ const now2 = Date.now();
1649
+ const delayed = await this.redis.zrangebyscore(
1650
+ delayedKey,
1651
+ 0,
1652
+ now2,
1653
+ "LIMIT",
1654
+ 0,
1655
+ 1
1656
+ );
1657
+ if (delayed.length > 0) {
1658
+ const jobId2 = delayed[0];
1659
+ await this.redis.zrem(delayedKey, jobId2);
1660
+ await this.redis.lpush(queueKey, jobId2);
1661
+ }
1662
+ const jobId = await this.redis.rpop(queueKey);
1663
+ if (!jobId) return null;
1664
+ const jobData = await this.redis.get(this.key("job", jobId));
1665
+ if (!jobData) return null;
1666
+ return JSON.parse(jobData);
1667
+ }
1668
+ async ack(queue, jobId) {
1669
+ await this.redis.del(this.key("job", jobId));
1670
+ }
1671
+ async nack(queueName, jobId, requeue = true) {
1672
+ if (requeue) {
1673
+ await this.redis.lpush(this.key("queue", queueName), jobId);
1674
+ }
1675
+ }
1676
+ async publish(streamName, message) {
1677
+ const streamKey = this.key("stream", streamName);
1678
+ const id = await this.redis.xadd(
1679
+ streamKey,
1680
+ "*",
1681
+ "data",
1682
+ JSON.stringify(message.data),
1683
+ "headers",
1684
+ JSON.stringify(message.headers || {})
1685
+ );
1686
+ return id || "";
1687
+ }
1688
+ async subscribe(streamName, handler) {
1689
+ const streamKey = this.key("stream", streamName);
1690
+ let active = true;
1691
+ let lastId = "$";
1692
+ const poll = async () => {
1693
+ if (!active) return;
1694
+ try {
1695
+ const results = await this.redis.xread(
1696
+ "STREAMS",
1697
+ streamKey,
1698
+ lastId,
1699
+ "BLOCK",
1700
+ 1e3
1701
+ );
1702
+ if (results) {
1703
+ for (const [stream, messages2] of results) {
1704
+ for (const [id, fields] of messages2) {
1705
+ lastId = id;
1706
+ const dataIdx = fields.indexOf("data");
1707
+ const headersIdx = fields.indexOf("headers");
1708
+ const data = dataIdx > -1 ? JSON.parse(fields[dataIdx + 1]) : {};
1709
+ const headers = headersIdx > -1 ? JSON.parse(fields[headersIdx + 1]) : {};
1710
+ const msg = {
1711
+ id,
1712
+ stream: streamName,
1713
+ data,
1714
+ headers,
1715
+ timestamp: parseInt(id.split("-")[0]),
1716
+ ack: async () => {
1717
+ },
1718
+ nack: async () => {
1719
+ }
1720
+ };
1721
+ handler(msg).catch(console.error);
1722
+ }
1723
+ }
1724
+ }
1725
+ } catch (err) {
1726
+ console.error("Redis stream poll error", err);
1727
+ await new Promise((r) => setTimeout(r, 1e3));
1728
+ }
1729
+ if (active) setTimeout(poll, 0);
1730
+ };
1731
+ setTimeout(poll, 0);
1732
+ return {
1733
+ unsubscribe: async () => {
1734
+ active = false;
1735
+ }
1736
+ };
1737
+ }
1738
+ async consume(streamName, group, consumer, handler) {
1739
+ const streamKey = this.key("stream", streamName);
1740
+ try {
1741
+ await this.redis.xgroup("CREATE", streamKey, group, "$", "MKSTREAM");
1742
+ } catch (e) {
1743
+ if (!e.message.includes("BUSYGROUP")) throw e;
1744
+ }
1745
+ let active = true;
1746
+ const poll = async () => {
1747
+ if (!active) return;
1748
+ try {
1749
+ const results = await this.redis.xreadgroup(
1750
+ "GROUP",
1751
+ group,
1752
+ consumer,
1753
+ "COUNT",
1754
+ 10,
1755
+ "BLOCK",
1756
+ 1e3,
1757
+ "STREAMS",
1758
+ streamKey,
1759
+ ">"
1760
+ );
1761
+ if (results) {
1762
+ for (const [stream, messages2] of results) {
1763
+ for (const [id, fields] of messages2) {
1764
+ const dataIdx = fields.indexOf("data");
1765
+ const headersIdx = fields.indexOf("headers");
1766
+ const data = dataIdx > -1 ? JSON.parse(fields[dataIdx + 1]) : {};
1767
+ const headers = headersIdx > -1 ? JSON.parse(fields[headersIdx + 1]) : {};
1768
+ const msg = {
1769
+ id,
1770
+ stream: streamName,
1771
+ data,
1772
+ headers,
1773
+ timestamp: parseInt(id.split("-")[0]),
1774
+ ack: async () => {
1775
+ await this.redis.xack(streamKey, group, id);
1776
+ },
1777
+ nack: async (requeue = true) => {
1778
+ }
1779
+ };
1780
+ handler(msg).catch(console.error);
1781
+ }
1782
+ }
1783
+ }
1784
+ } catch (err) {
1785
+ console.error("Redis consumer poll error", err);
1786
+ await new Promise((r) => setTimeout(r, 1e3));
1787
+ }
1788
+ if (active) setTimeout(poll, 0);
1789
+ };
1790
+ setTimeout(poll, 0);
1791
+ return {
1792
+ unsubscribe: async () => {
1793
+ active = false;
1794
+ }
1795
+ };
1796
+ }
1797
+ async createConsumerGroup(stream, group) {
1798
+ const streamKey = this.key("stream", stream);
1799
+ try {
1800
+ await this.redis.xgroup("CREATE", streamKey, group, "$", "MKSTREAM");
1801
+ } catch (e) {
1802
+ if (!e.message.includes("BUSYGROUP")) throw e;
1803
+ }
1804
+ }
1805
+ async saveWorkflowState(workflowId, state) {
1806
+ await this.redis.set(
1807
+ this.key("workflow", workflowId),
1808
+ JSON.stringify(state)
1809
+ );
1810
+ }
1811
+ async loadWorkflowState(workflowId) {
1812
+ const data = await this.redis.get(this.key("workflow", workflowId));
1813
+ return data ? JSON.parse(data) : null;
1814
+ }
1815
+ async getJob(queue, jobId) {
1816
+ const jobData = await this.redis.get(this.key("job", jobId));
1817
+ return jobData ? JSON.parse(jobData) : null;
1818
+ }
1819
+ async getJobs(queue, status) {
1820
+ return [];
1821
+ }
1822
+ async getAllJobs(queue) {
1823
+ return [];
1824
+ }
1825
+ async cleanJobs(queue, grace, status) {
1826
+ return 0;
1827
+ }
1828
+ async getQueueStats(queueName) {
1829
+ const length = await this.redis.llen(this.key("queue", queueName));
1830
+ const delayedLength = await this.redis.zcard(
1831
+ this.key("delayed", queueName)
1832
+ );
1833
+ return {
1834
+ waiting: length,
1835
+ active: 0,
1836
+ completed: 0,
1837
+ failed: 0,
1838
+ delayed: delayedLength,
1839
+ paused: 0
1840
+ };
1841
+ }
1842
+ async getStreamInfo(streamName) {
1843
+ return {
1844
+ name: streamName,
1845
+ length: 0,
1846
+ groups: 0
1847
+ };
1848
+ }
1849
+ async cleanup() {
1850
+ for (const subs of this.subscriptions.values()) {
1851
+ for (const sub of subs) {
1852
+ await sub.quit();
1853
+ }
1854
+ }
1855
+ this.subscriptions.clear();
1856
+ await this.redis.quit();
1857
+ }
1858
+ };
1859
+
1860
+ // src/adapters/postgres/schema.ts
1861
+ import { pgTable, varchar, text, jsonb, integer, bigint, timestamp, index, uniqueIndex } from "drizzle-orm/pg-core";
1862
+ var jobs = pgTable("flowfn_jobs", {
1863
+ id: varchar("id", { length: 255 }).primaryKey(),
1864
+ queue: varchar("queue", { length: 255 }).notNull(),
1865
+ name: varchar("name", { length: 255 }).notNull(),
1866
+ data: jsonb("data").notNull(),
1867
+ opts: jsonb("opts"),
1868
+ state: varchar("state", { length: 50 }).notNull(),
1869
+ // waiting, active, completed, failed, delayed, paused
1870
+ priority: integer("priority").default(0),
1871
+ progress: integer("progress").default(0),
1872
+ returnValue: jsonb("return_value"),
1873
+ timestamp: bigint("timestamp", { mode: "number" }).notNull(),
1874
+ processedOn: bigint("processed_on", { mode: "number" }),
1875
+ finishedOn: bigint("finished_on", { mode: "number" }),
1876
+ delay: bigint("delay", { mode: "number" }).default(0),
1877
+ attemptsMade: integer("attempts_made").default(0),
1878
+ failedReason: text("failed_reason"),
1879
+ stacktrace: jsonb("stacktrace"),
1880
+ createdAt: timestamp("created_at").defaultNow(),
1881
+ updatedAt: timestamp("updated_at").defaultNow()
1882
+ }, (table) => ({
1883
+ queueStateIdx: index("idx_queue_state").on(table.queue, table.state),
1884
+ queuePriorityIdx: index("idx_queue_priority").on(table.queue, table.priority),
1885
+ timestampIdx: index("idx_timestamp").on(table.timestamp)
1886
+ }));
1887
+ var messages = pgTable("flowfn_messages", {
1888
+ id: varchar("id", { length: 255 }).primaryKey(),
1889
+ stream: varchar("stream", { length: 255 }).notNull(),
1890
+ data: jsonb("data").notNull(),
1891
+ headers: jsonb("headers"),
1892
+ partition: integer("partition"),
1893
+ offset: bigint("offset", { mode: "number" }),
1894
+ key: varchar("key", { length: 255 }),
1895
+ timestamp: bigint("timestamp", { mode: "number" }).notNull(),
1896
+ createdAt: timestamp("created_at").defaultNow()
1897
+ }, (table) => ({
1898
+ streamTimestampIdx: index("idx_stream_timestamp").on(table.stream, table.timestamp)
1899
+ }));
1900
+ var consumerGroups = pgTable("flowfn_consumer_groups", {
1901
+ id: varchar("id", { length: 255 }).primaryKey(),
1902
+ stream: varchar("stream", { length: 255 }).notNull(),
1903
+ groupId: varchar("group_id", { length: 255 }).notNull(),
1904
+ consumerId: varchar("consumer_id", { length: 255 }).notNull(),
1905
+ lastMessageId: varchar("last_message_id", { length: 255 }),
1906
+ lastOffset: bigint("last_offset", { mode: "number" }),
1907
+ lag: integer("lag"),
1908
+ createdAt: timestamp("created_at").defaultNow(),
1909
+ updatedAt: timestamp("updated_at").defaultNow()
1910
+ }, (table) => ({
1911
+ streamGroupConsumerIdx: uniqueIndex("idx_stream_group_consumer").on(table.stream, table.groupId, table.consumerId)
1912
+ }));
1913
+ var workflows = pgTable("flowfn_workflows", {
1914
+ id: varchar("id", { length: 255 }).primaryKey(),
1915
+ name: varchar("name", { length: 255 }).notNull(),
1916
+ definition: jsonb("definition").notNull(),
1917
+ version: integer("version").default(1),
1918
+ status: varchar("status", { length: 50 }).notNull(),
1919
+ metadata: jsonb("metadata"),
1920
+ createdAt: timestamp("created_at").defaultNow(),
1921
+ updatedAt: timestamp("updated_at").defaultNow()
1922
+ });
1923
+ var workflowExecutions = pgTable("flowfn_workflow_executions", {
1924
+ id: varchar("id", { length: 255 }).primaryKey(),
1925
+ workflowId: varchar("workflow_id", { length: 255 }).notNull(),
1926
+ workflowName: varchar("workflow_name", { length: 255 }),
1927
+ status: varchar("status", { length: 50 }).notNull(),
1928
+ input: jsonb("input"),
1929
+ state: jsonb("state"),
1930
+ output: jsonb("output"),
1931
+ error: text("error"),
1932
+ currentStep: varchar("current_step", { length: 255 }),
1933
+ completedSteps: jsonb("completed_steps"),
1934
+ startedAt: bigint("started_at", { mode: "number" }).notNull(),
1935
+ updatedAt: bigint("updated_at", { mode: "number" }),
1936
+ completedAt: bigint("completed_at", { mode: "number" }),
1937
+ durationMs: integer("duration_ms")
1938
+ }, (table) => ({
1939
+ workflowIdx: index("idx_workflow").on(table.workflowId),
1940
+ statusIdx: index("idx_status").on(table.status)
1941
+ }));
1942
+ var workflowEvents = pgTable("flowfn_workflow_events", {
1943
+ id: varchar("id", { length: 255 }).primaryKey(),
1944
+ executionId: varchar("execution_id", { length: 255 }).notNull(),
1945
+ type: varchar("type", { length: 50 }).notNull(),
1946
+ step: varchar("step", { length: 255 }),
1947
+ timestamp: bigint("timestamp", { mode: "number" }).notNull(),
1948
+ data: jsonb("data")
1949
+ }, (table) => ({
1950
+ executionIdx: index("idx_execution").on(table.executionId)
1951
+ }));
1952
+
1953
+ // src/adapters/postgres/index.ts
1954
+ import { eq, and, asc, sql } from "drizzle-orm";
1955
+ import { v4 as uuidv44 } from "uuid";
1956
+ var PostgresAdapter = class {
1957
+ constructor(options) {
1958
+ this.activeSubscriptions = /* @__PURE__ */ new Map();
1959
+ this.db = options.db;
1960
+ this.pollInterval = options.pollInterval || 1e3;
1961
+ }
1962
+ async enqueue(queueName, job) {
1963
+ await this.db.insert(jobs).values({
1964
+ id: job.id,
1965
+ queue: queueName,
1966
+ name: job.name,
1967
+ data: job.data,
1968
+ opts: job.opts,
1969
+ state: job.opts.delay ? "delayed" : "waiting",
1970
+ timestamp: job.timestamp,
1971
+ delay: job.opts.delay || 0,
1972
+ priority: job.opts.priority || 0,
1973
+ attemptsMade: 0
1974
+ });
1975
+ return job.id;
1976
+ }
1977
+ async dequeue(queueName) {
1978
+ const now2 = Date.now();
1979
+ return await this.db.transaction(async (tx) => {
1980
+ const result = await tx.execute(sql`
1981
+ UPDATE flowfn_jobs
1982
+ SET state = 'active', processed_on = ${now2}
1983
+ WHERE id = (
1984
+ SELECT id
1985
+ FROM flowfn_jobs
1986
+ WHERE queue = ${queueName}
1987
+ AND (
1988
+ state = 'waiting'
1989
+ OR (state = 'delayed' AND (timestamp + delay) <= ${now2})
1990
+ )
1991
+ ORDER BY priority DESC, timestamp ASC
1992
+ FOR UPDATE SKIP LOCKED
1993
+ LIMIT 1
1994
+ )
1995
+ RETURNING *
1996
+ `);
1997
+ if (result.length === 0) return null;
1998
+ const row = result[0];
1999
+ return {
2000
+ id: row.id,
2001
+ name: row.name,
2002
+ data: row.data,
2003
+ opts: row.opts,
2004
+ state: row.state,
2005
+ progress: row.progress,
2006
+ returnvalue: row.return_value,
2007
+ timestamp: Number(row.timestamp),
2008
+ processedOn: Number(row.processed_on),
2009
+ finishedOn: Number(row.finished_on),
2010
+ delay: Number(row.delay),
2011
+ attemptsMade: row.attempts_made,
2012
+ failedReason: row.failed_reason,
2013
+ stacktrace: row.stacktrace
2014
+ // Re-bind methods in queue implementation
2015
+ };
2016
+ });
2017
+ }
2018
+ async ack(queue, jobId) {
2019
+ await this.db.update(jobs).set({ state: "completed", finishedOn: Date.now() }).where(eq(jobs.id, jobId));
2020
+ }
2021
+ async nack(queue, jobId, requeue = true) {
2022
+ if (requeue) {
2023
+ await this.db.update(jobs).set({ state: "waiting", processedOn: null }).where(eq(jobs.id, jobId));
2024
+ } else {
2025
+ }
2026
+ }
2027
+ async publish(streamName, message) {
2028
+ await this.db.insert(messages).values({
2029
+ id: message.id,
2030
+ stream: streamName,
2031
+ data: message.data,
2032
+ headers: message.headers,
2033
+ timestamp: message.timestamp,
2034
+ partition: message.partition,
2035
+ key: message.key
2036
+ });
2037
+ return message.id;
2038
+ }
2039
+ async subscribe(streamName, handler) {
2040
+ const subId = uuidv44();
2041
+ this.activeSubscriptions.set(subId, true);
2042
+ let lastTimestamp = Date.now();
2043
+ const poll = async () => {
2044
+ if (!this.activeSubscriptions.get(subId)) return;
2045
+ try {
2046
+ const msgs = await this.db.select().from(messages).where(
2047
+ and(
2048
+ eq(messages.stream, streamName),
2049
+ sql`${messages.timestamp} > ${lastTimestamp}`
2050
+ )
2051
+ ).orderBy(asc(messages.timestamp)).limit(100);
2052
+ for (const row of msgs) {
2053
+ lastTimestamp = Math.max(lastTimestamp, Number(row.timestamp));
2054
+ const msg = {
2055
+ id: row.id,
2056
+ stream: row.stream,
2057
+ data: row.data,
2058
+ headers: row.headers,
2059
+ timestamp: Number(row.timestamp),
2060
+ ack: async () => {
2061
+ },
2062
+ nack: async () => {
2063
+ }
2064
+ };
2065
+ handler(msg).catch(console.error);
2066
+ }
2067
+ } catch (e) {
2068
+ console.error("Postgres stream poll error", e);
2069
+ }
2070
+ if (this.activeSubscriptions.get(subId)) {
2071
+ setTimeout(poll, this.pollInterval);
2072
+ }
2073
+ };
2074
+ setTimeout(poll, 0);
2075
+ return {
2076
+ unsubscribe: async () => {
2077
+ this.activeSubscriptions.set(subId, false);
2078
+ }
2079
+ };
2080
+ }
2081
+ async createConsumerGroup(stream, group) {
2082
+ }
2083
+ async saveWorkflowState(workflowId, state) {
2084
+ await this.db.insert(workflowExecutions).values({
2085
+ id: state.id,
2086
+ workflowId: state.workflowId,
2087
+ status: state.status,
2088
+ input: state.input,
2089
+ output: state.output,
2090
+ error: state.error instanceof Error ? state.error.message : String(state.error),
2091
+ startedAt: state.startedAt,
2092
+ completedAt: state.completedAt
2093
+ }).onConflictDoUpdate({
2094
+ target: workflowExecutions.id,
2095
+ set: {
2096
+ status: state.status,
2097
+ output: state.output,
2098
+ error: state.error instanceof Error ? state.error.message : String(state.error),
2099
+ updatedAt: Date.now(),
2100
+ completedAt: state.completedAt
2101
+ }
2102
+ });
2103
+ }
2104
+ async loadWorkflowState(executionId) {
2105
+ const rows = await this.db.select().from(workflowExecutions).where(eq(workflowExecutions.id, executionId));
2106
+ if (rows.length === 0) return null;
2107
+ const row = rows[0];
2108
+ return {
2109
+ id: row.id,
2110
+ workflowId: row.workflowId,
2111
+ status: row.status,
2112
+ // Cast from DB string to union type
2113
+ input: row.input,
2114
+ output: row.output,
2115
+ error: row.error,
2116
+ startedAt: Number(row.startedAt),
2117
+ completedAt: row.completedAt ? Number(row.completedAt) : void 0
2118
+ };
2119
+ }
2120
+ async getJob(queue, jobId) {
2121
+ const rows = await this.db.select().from(jobs).where(and(eq(jobs.queue, queue), eq(jobs.id, jobId)));
2122
+ if (rows.length === 0) return null;
2123
+ const row = rows[0];
2124
+ return {
2125
+ id: row.id,
2126
+ name: row.name,
2127
+ data: row.data,
2128
+ opts: row.opts,
2129
+ state: row.state,
2130
+ timestamp: Number(row.timestamp),
2131
+ attemptsMade: row.attempts_made
2132
+ };
2133
+ }
2134
+ async getQueueStats(queueName) {
2135
+ const counts = await this.db.select({
2136
+ state: jobs.state,
2137
+ count: sql`count(*)`
2138
+ }).from(jobs).where(eq(jobs.queue, queueName)).groupBy(jobs.state);
2139
+ const stats = {
2140
+ waiting: 0,
2141
+ active: 0,
2142
+ completed: 0,
2143
+ failed: 0,
2144
+ delayed: 0,
2145
+ paused: 0
2146
+ };
2147
+ for (const c of counts) {
2148
+ if (c.state in stats) {
2149
+ stats[c.state] = Number(c.count);
2150
+ }
2151
+ }
2152
+ return stats;
2153
+ }
2154
+ async getStreamInfo(streamName) {
2155
+ return { name: streamName, length: 0, groups: 0 };
2156
+ }
2157
+ async consume(stream, group, consumer, handler) {
2158
+ return this.subscribe(stream, handler);
2159
+ }
2160
+ async getJobs(queue, status) {
2161
+ const rows = await this.db.select().from(jobs).where(and(eq(jobs.queue, queue), eq(jobs.state, status)));
2162
+ return rows.map(
2163
+ (row) => ({
2164
+ id: row.id,
2165
+ name: row.name,
2166
+ data: row.data,
2167
+ opts: row.opts,
2168
+ state: row.state,
2169
+ timestamp: Number(row.timestamp),
2170
+ attemptsMade: row.attempts_made
2171
+ })
2172
+ );
2173
+ }
2174
+ async getAllJobs(queue) {
2175
+ const rows = await this.db.select().from(jobs).where(eq(jobs.queue, queue));
2176
+ return rows.map(
2177
+ (row) => ({
2178
+ id: row.id,
2179
+ name: row.name,
2180
+ data: row.data,
2181
+ opts: row.opts,
2182
+ state: row.state,
2183
+ timestamp: Number(row.timestamp),
2184
+ attemptsMade: row.attempts_made
2185
+ })
2186
+ );
2187
+ }
2188
+ async cleanJobs(queue, grace, status) {
2189
+ const now2 = Date.now();
2190
+ const result = await this.db.delete(jobs).where(
2191
+ and(
2192
+ eq(jobs.queue, queue),
2193
+ eq(jobs.state, status),
2194
+ sql`${jobs.finishedOn} < ${now2 - grace}`
2195
+ )
2196
+ );
2197
+ return result.rowCount || 0;
2198
+ }
2199
+ async cleanup() {
2200
+ this.activeSubscriptions.clear();
2201
+ }
2202
+ };
2203
+
2204
+ // src/patterns/circuit-breaker.ts
2205
+ function circuitBreaker(options, handler) {
2206
+ let state = "CLOSED";
2207
+ let failures = 0;
2208
+ let lastFailureTime = 0;
2209
+ let lastSuccessTime = 0;
2210
+ return async (job) => {
2211
+ const now2 = Date.now();
2212
+ if (state === "OPEN") {
2213
+ if (now2 - lastFailureTime > options.timeout) {
2214
+ state = "HALF_OPEN";
2215
+ } else {
2216
+ throw new Error("Circuit Breaker is OPEN");
2217
+ }
2218
+ }
2219
+ try {
2220
+ const result = await handler(job);
2221
+ if (state === "HALF_OPEN") {
2222
+ state = "CLOSED";
2223
+ failures = 0;
2224
+ }
2225
+ lastSuccessTime = now2;
2226
+ return result;
2227
+ } catch (err) {
2228
+ failures++;
2229
+ lastFailureTime = now2;
2230
+ if (state === "HALF_OPEN" || failures >= options.threshold) {
2231
+ state = "OPEN";
2232
+ }
2233
+ throw err;
2234
+ }
2235
+ };
2236
+ }
2237
+
2238
+ // src/patterns/rate-limit.ts
2239
+ var RateLimiter = class {
2240
+ constructor(options) {
2241
+ this.counters = /* @__PURE__ */ new Map();
2242
+ this.options = {
2243
+ strategy: "throw",
2244
+ ...options
2245
+ };
2246
+ }
2247
+ /**
2248
+ * Check if request is allowed
2249
+ */
2250
+ async check(key = "default") {
2251
+ const now2 = Date.now();
2252
+ let counter = this.counters.get(key);
2253
+ if (!counter || now2 >= counter.resetAt) {
2254
+ counter = {
2255
+ count: 0,
2256
+ resetAt: now2 + this.options.window
2257
+ };
2258
+ this.counters.set(key, counter);
2259
+ }
2260
+ const allowed = counter.count < this.options.limit;
2261
+ const remaining = Math.max(0, this.options.limit - counter.count);
2262
+ const retryAfter = allowed ? void 0 : counter.resetAt - now2;
2263
+ if (allowed) {
2264
+ counter.count++;
2265
+ }
2266
+ return {
2267
+ allowed,
2268
+ remaining,
2269
+ resetAt: counter.resetAt,
2270
+ retryAfter
2271
+ };
2272
+ }
2273
+ /**
2274
+ * Execute a function with rate limiting
2275
+ */
2276
+ async execute(fn, key = "default") {
2277
+ const result = await this.check(key);
2278
+ if (!result.allowed) {
2279
+ switch (this.options.strategy) {
2280
+ case "throw":
2281
+ throw new Error(
2282
+ `Rate limit exceeded. Retry after ${result.retryAfter}ms`
2283
+ );
2284
+ case "delay":
2285
+ await new Promise(
2286
+ (resolve) => setTimeout(resolve, result.retryAfter)
2287
+ );
2288
+ return this.execute(fn, key);
2289
+ case "drop":
2290
+ throw new Error("Request dropped due to rate limit");
2291
+ default:
2292
+ throw new Error("Unknown rate limit strategy");
2293
+ }
2294
+ }
2295
+ return fn();
2296
+ }
2297
+ /**
2298
+ * Clear all rate limit counters
2299
+ */
2300
+ reset() {
2301
+ this.counters.clear();
2302
+ }
2303
+ /**
2304
+ * Clear rate limit counter for specific key
2305
+ */
2306
+ resetKey(key) {
2307
+ this.counters.delete(key);
2308
+ }
2309
+ /**
2310
+ * Get current limit info for a key
2311
+ */
2312
+ getInfo(key = "default") {
2313
+ const now2 = Date.now();
2314
+ const counter = this.counters.get(key);
2315
+ if (!counter || now2 >= counter.resetAt) {
2316
+ return {
2317
+ allowed: true,
2318
+ remaining: this.options.limit,
2319
+ resetAt: now2 + this.options.window
2320
+ };
2321
+ }
2322
+ const allowed = counter.count < this.options.limit;
2323
+ const remaining = Math.max(0, this.options.limit - counter.count);
2324
+ return {
2325
+ allowed,
2326
+ remaining,
2327
+ resetAt: counter.resetAt,
2328
+ retryAfter: allowed ? void 0 : counter.resetAt - now2
2329
+ };
2330
+ }
2331
+ };
2332
+ function createRateLimiter(options) {
2333
+ return new RateLimiter(options);
2334
+ }
2335
+ var SlidingWindowRateLimiter = class {
2336
+ constructor(options) {
2337
+ this.requests = /* @__PURE__ */ new Map();
2338
+ this.options = options;
2339
+ }
2340
+ async check(key = "default") {
2341
+ const now2 = Date.now();
2342
+ const windowStart = now2 - this.options.window;
2343
+ let requests = this.requests.get(key) || [];
2344
+ requests = requests.filter((timestamp2) => timestamp2 > windowStart);
2345
+ this.requests.set(key, requests);
2346
+ const allowed = requests.length < this.options.limit;
2347
+ const remaining = Math.max(0, this.options.limit - requests.length);
2348
+ if (allowed) {
2349
+ requests.push(now2);
2350
+ }
2351
+ const oldestRequest = requests[0];
2352
+ const resetAt = oldestRequest ? oldestRequest + this.options.window : now2 + this.options.window;
2353
+ const retryAfter = allowed ? void 0 : resetAt - now2;
2354
+ return {
2355
+ allowed,
2356
+ remaining,
2357
+ resetAt,
2358
+ retryAfter
2359
+ };
2360
+ }
2361
+ async execute(fn, key = "default") {
2362
+ const result = await this.check(key);
2363
+ if (!result.allowed) {
2364
+ switch (this.options.strategy || "throw") {
2365
+ case "throw":
2366
+ throw new Error(
2367
+ `Rate limit exceeded. Retry after ${result.retryAfter}ms`
2368
+ );
2369
+ case "delay":
2370
+ await new Promise(
2371
+ (resolve) => setTimeout(resolve, result.retryAfter)
2372
+ );
2373
+ return this.execute(fn, key);
2374
+ case "drop":
2375
+ throw new Error("Request dropped due to rate limit");
2376
+ }
2377
+ }
2378
+ return fn();
2379
+ }
2380
+ reset() {
2381
+ this.requests.clear();
2382
+ }
2383
+ };
2384
+ var TokenBucketRateLimiter = class {
2385
+ constructor(options) {
2386
+ this.buckets = /* @__PURE__ */ new Map();
2387
+ this.capacity = options.capacity;
2388
+ this.refillRate = options.refillRate;
2389
+ this.refillInterval = options.refillInterval || 1e3;
2390
+ }
2391
+ refill(key) {
2392
+ const now2 = Date.now();
2393
+ let bucket = this.buckets.get(key);
2394
+ if (!bucket) {
2395
+ bucket = { tokens: this.capacity, lastRefill: now2 };
2396
+ this.buckets.set(key, bucket);
2397
+ return;
2398
+ }
2399
+ const timePassed = now2 - bucket.lastRefill;
2400
+ const intervals = timePassed / this.refillInterval;
2401
+ const tokensToAdd = intervals * this.refillRate;
2402
+ bucket.tokens = Math.min(this.capacity, bucket.tokens + tokensToAdd);
2403
+ bucket.lastRefill = now2;
2404
+ }
2405
+ async check(key = "default", cost = 1) {
2406
+ this.refill(key);
2407
+ const bucket = this.buckets.get(key);
2408
+ const allowed = bucket.tokens >= cost;
2409
+ if (allowed) {
2410
+ bucket.tokens -= cost;
2411
+ }
2412
+ return {
2413
+ allowed,
2414
+ remaining: Math.floor(bucket.tokens),
2415
+ resetAt: bucket.lastRefill + this.refillInterval,
2416
+ retryAfter: allowed ? void 0 : Math.ceil(
2417
+ (cost - bucket.tokens) / this.refillRate * this.refillInterval
2418
+ )
2419
+ };
2420
+ }
2421
+ reset() {
2422
+ this.buckets.clear();
2423
+ }
2424
+ };
2425
+
2426
+ // src/patterns/batching.ts
2427
+ var BatchAccumulator = class {
2428
+ constructor(processor, options) {
2429
+ this.batch = [];
2430
+ this.timer = null;
2431
+ this.pending = [];
2432
+ this.processor = processor;
2433
+ this.options = {
2434
+ minSize: 1,
2435
+ maxWait: 1e3,
2436
+ ...options
2437
+ };
2438
+ }
2439
+ /**
2440
+ * Add an item to the batch
2441
+ */
2442
+ async add(item) {
2443
+ return new Promise((resolve, reject) => {
2444
+ this.batch.push(item);
2445
+ this.pending.push({ resolve, reject });
2446
+ if (this.batch.length >= this.options.maxSize) {
2447
+ this.flush();
2448
+ } else if (!this.timer) {
2449
+ this.timer = setTimeout(() => {
2450
+ this.flush();
2451
+ }, this.options.maxWait);
2452
+ }
2453
+ });
2454
+ }
2455
+ /**
2456
+ * Manually flush the current batch
2457
+ */
2458
+ async flush() {
2459
+ if (this.timer) {
2460
+ clearTimeout(this.timer);
2461
+ this.timer = null;
2462
+ }
2463
+ if (this.batch.length < this.options.minSize) {
2464
+ return;
2465
+ }
2466
+ const currentBatch = this.batch;
2467
+ const currentPending = this.pending;
2468
+ this.batch = [];
2469
+ this.pending = [];
2470
+ try {
2471
+ const results = await this.processor(currentBatch);
2472
+ for (let i = 0; i < currentPending.length; i++) {
2473
+ currentPending[i].resolve(results[i]);
2474
+ }
2475
+ } catch (error) {
2476
+ for (const pending of currentPending) {
2477
+ pending.reject(error);
2478
+ }
2479
+ }
2480
+ }
2481
+ /**
2482
+ * Get current batch size
2483
+ */
2484
+ size() {
2485
+ return this.batch.length;
2486
+ }
2487
+ /**
2488
+ * Clear the batch without processing
2489
+ */
2490
+ clear() {
2491
+ if (this.timer) {
2492
+ clearTimeout(this.timer);
2493
+ this.timer = null;
2494
+ }
2495
+ this.batch = [];
2496
+ for (const pending of this.pending) {
2497
+ pending.reject(new Error("Batch cleared"));
2498
+ }
2499
+ this.pending = [];
2500
+ }
2501
+ };
2502
+ function batch(fn, options) {
2503
+ const accumulator = new BatchAccumulator(fn, options);
2504
+ return (item) => accumulator.add(item);
2505
+ }
2506
+ function chunk(array, size) {
2507
+ const chunks = [];
2508
+ for (let i = 0; i < array.length; i += size) {
2509
+ chunks.push(array.slice(i, i + size));
2510
+ }
2511
+ return chunks;
2512
+ }
2513
+ async function processBatches(items, processor, options) {
2514
+ const batches = chunk(items, options.batchSize);
2515
+ const results = [];
2516
+ for (let i = 0; i < batches.length; i++) {
2517
+ const batchResults = await processor(batches[i]);
2518
+ results.push(...batchResults);
2519
+ if (i < batches.length - 1 && options.delayMs) {
2520
+ await new Promise((resolve) => setTimeout(resolve, options.delayMs));
2521
+ }
2522
+ }
2523
+ return results;
2524
+ }
2525
+ var BatchWriter = class {
2526
+ constructor(writer, options) {
2527
+ this.accumulator = new BatchAccumulator(async (items) => {
2528
+ await writer(items);
2529
+ return new Array(items.length).fill(void 0);
2530
+ }, options);
2531
+ }
2532
+ async write(item) {
2533
+ return this.accumulator.add(item);
2534
+ }
2535
+ async flush() {
2536
+ return this.accumulator.flush();
2537
+ }
2538
+ size() {
2539
+ return this.accumulator.size();
2540
+ }
2541
+ clear() {
2542
+ this.accumulator.clear();
2543
+ }
2544
+ };
2545
+ async function batchByKey(items, keyFn, processor, options) {
2546
+ const grouped = /* @__PURE__ */ new Map();
2547
+ for (const item of items) {
2548
+ const key = keyFn(item);
2549
+ if (!grouped.has(key)) {
2550
+ grouped.set(key, []);
2551
+ }
2552
+ grouped.get(key).push(item);
2553
+ }
2554
+ const results = [];
2555
+ const entries = Array.from(grouped.entries());
2556
+ if (options?.concurrency) {
2557
+ for (let i = 0; i < entries.length; i += options.concurrency) {
2558
+ const batch2 = entries.slice(i, i + options.concurrency);
2559
+ const batchResults = await Promise.all(
2560
+ batch2.map(([key, items2]) => processor(key, items2))
2561
+ );
2562
+ results.push(...batchResults.flat());
2563
+ }
2564
+ } else {
2565
+ for (const [key, keyItems] of entries) {
2566
+ const batchResults = await processor(key, keyItems);
2567
+ results.push(...batchResults);
2568
+ }
2569
+ }
2570
+ return results;
2571
+ }
2572
+
2573
+ // src/patterns/priority.ts
2574
+ var PriorityQueue = class {
2575
+ constructor(mode = "max") {
2576
+ this.heap = [];
2577
+ this.compareFn = mode === "max" ? (a, b) => a - b : (a, b) => b - a;
2578
+ }
2579
+ /**
2580
+ * Add item with priority
2581
+ */
2582
+ enqueue(value, priority) {
2583
+ this.heap.push({ value, priority });
2584
+ this.bubbleUp(this.heap.length - 1);
2585
+ }
2586
+ /**
2587
+ * Remove and return highest priority item
2588
+ */
2589
+ dequeue() {
2590
+ if (this.heap.length === 0) return void 0;
2591
+ if (this.heap.length === 1) return this.heap.pop().value;
2592
+ const result = this.heap[0];
2593
+ this.heap[0] = this.heap.pop();
2594
+ this.bubbleDown(0);
2595
+ return result.value;
2596
+ }
2597
+ /**
2598
+ * Peek at highest priority item without removing
2599
+ */
2600
+ peek() {
2601
+ return this.heap[0]?.value;
2602
+ }
2603
+ /**
2604
+ * Get queue size
2605
+ */
2606
+ size() {
2607
+ return this.heap.length;
2608
+ }
2609
+ /**
2610
+ * Check if queue is empty
2611
+ */
2612
+ isEmpty() {
2613
+ return this.heap.length === 0;
2614
+ }
2615
+ /**
2616
+ * Clear the queue
2617
+ */
2618
+ clear() {
2619
+ this.heap = [];
2620
+ }
2621
+ /**
2622
+ * Convert to array (sorted by priority)
2623
+ */
2624
+ toArray() {
2625
+ const copy = [...this.heap];
2626
+ const result = [];
2627
+ while (this.heap.length > 0) {
2628
+ result.push(this.dequeue());
2629
+ }
2630
+ this.heap = copy;
2631
+ return result;
2632
+ }
2633
+ bubbleUp(index2) {
2634
+ while (index2 > 0) {
2635
+ const parentIndex = Math.floor((index2 - 1) / 2);
2636
+ if (this.compareFn(
2637
+ this.heap[index2].priority,
2638
+ this.heap[parentIndex].priority
2639
+ ) <= 0) {
2640
+ break;
2641
+ }
2642
+ [this.heap[index2], this.heap[parentIndex]] = [
2643
+ this.heap[parentIndex],
2644
+ this.heap[index2]
2645
+ ];
2646
+ index2 = parentIndex;
2647
+ }
2648
+ }
2649
+ bubbleDown(index2) {
2650
+ while (true) {
2651
+ const leftChild = 2 * index2 + 1;
2652
+ const rightChild = 2 * index2 + 2;
2653
+ let largest = index2;
2654
+ if (leftChild < this.heap.length && this.compareFn(
2655
+ this.heap[leftChild].priority,
2656
+ this.heap[largest].priority
2657
+ ) > 0) {
2658
+ largest = leftChild;
2659
+ }
2660
+ if (rightChild < this.heap.length && this.compareFn(
2661
+ this.heap[rightChild].priority,
2662
+ this.heap[largest].priority
2663
+ ) > 0) {
2664
+ largest = rightChild;
2665
+ }
2666
+ if (largest === index2) break;
2667
+ [this.heap[index2], this.heap[largest]] = [
2668
+ this.heap[largest],
2669
+ this.heap[index2]
2670
+ ];
2671
+ index2 = largest;
2672
+ }
2673
+ }
2674
+ };
2675
+
2676
+ // src/utils/hashing.ts
2677
+ import { createHash } from "crypto";
2678
+ function hashJob(data, options) {
2679
+ const algorithm = options?.algorithm || "sha256";
2680
+ const hash = createHash(algorithm);
2681
+ const normalized = JSON.stringify(data, Object.keys(data).sort());
2682
+ hash.update(normalized);
2683
+ return hash.digest("hex");
2684
+ }
2685
+ function generateDeduplicationKey(name, data) {
2686
+ return `${name}:${hashJob(data)}`;
2687
+ }
2688
+ function areJobsEquivalent(job1, job2) {
2689
+ return hashJob(job1) === hashJob(job2);
2690
+ }
2691
+
2692
+ // src/utils/id-generator.ts
2693
+ import { v4 as uuidv45, v5 as uuidv5, v1 as uuidv1 } from "uuid";
2694
+ import { createHash as createHash2, randomBytes } from "crypto";
2695
+ function generateId(options = {}) {
2696
+ const {
2697
+ strategy = "uuid-v4",
2698
+ namespace,
2699
+ prefix = "",
2700
+ customGenerator
2701
+ } = options;
2702
+ let id;
2703
+ switch (strategy) {
2704
+ case "uuid-v4":
2705
+ id = uuidv45();
2706
+ break;
2707
+ case "uuid-v5":
2708
+ if (!namespace) {
2709
+ throw new Error("UUID v5 requires a namespace");
2710
+ }
2711
+ const name = `${Date.now()}-${randomBytes(8).toString("hex")}`;
2712
+ id = uuidv5(name, namespace);
2713
+ break;
2714
+ case "uuid-v1":
2715
+ id = uuidv1();
2716
+ break;
2717
+ case "nanoid":
2718
+ id = randomBytes(16).toString("base64url");
2719
+ break;
2720
+ case "incremental":
2721
+ id = `${Date.now()}-${randomBytes(4).toString("hex")}`;
2722
+ break;
2723
+ case "custom":
2724
+ if (!customGenerator) {
2725
+ throw new Error("Custom strategy requires customGenerator function");
2726
+ }
2727
+ id = customGenerator();
2728
+ break;
2729
+ default:
2730
+ id = uuidv45();
2731
+ }
2732
+ return prefix ? `${prefix}${id}` : id;
2733
+ }
2734
+ function generateJobId(prefix) {
2735
+ return generateId({ prefix: prefix || "job_" });
2736
+ }
2737
+ function generateExecutionId(prefix) {
2738
+ return generateId({ prefix: prefix || "exec_" });
2739
+ }
2740
+ function generateMessageId(prefix) {
2741
+ return generateId({ prefix: prefix || "msg_" });
2742
+ }
2743
+ function generateDeterministicId(content, prefix) {
2744
+ const hash = createHash2("sha256");
2745
+ const data = typeof content === "string" ? content : JSON.stringify(content);
2746
+ hash.update(data);
2747
+ const id = hash.digest("hex").slice(0, 32);
2748
+ return prefix ? `${prefix}${id}` : id;
2749
+ }
2750
+
2751
+ // src/utils/time.ts
2752
+ function toMilliseconds(duration) {
2753
+ let ms = 0;
2754
+ if (duration.milliseconds) ms += duration.milliseconds;
2755
+ if (duration.seconds) ms += duration.seconds * 1e3;
2756
+ if (duration.minutes) ms += duration.minutes * 60 * 1e3;
2757
+ if (duration.hours) ms += duration.hours * 60 * 60 * 1e3;
2758
+ if (duration.days) ms += duration.days * 24 * 60 * 60 * 1e3;
2759
+ if (duration.weeks) ms += duration.weeks * 7 * 24 * 60 * 60 * 1e3;
2760
+ return ms;
2761
+ }
2762
+ function fromMilliseconds(ms) {
2763
+ const weeks = Math.floor(ms / (7 * 24 * 60 * 60 * 1e3));
2764
+ ms %= 7 * 24 * 60 * 60 * 1e3;
2765
+ const days = Math.floor(ms / (24 * 60 * 60 * 1e3));
2766
+ ms %= 24 * 60 * 60 * 1e3;
2767
+ const hours = Math.floor(ms / (60 * 60 * 1e3));
2768
+ ms %= 60 * 60 * 1e3;
2769
+ const minutes = Math.floor(ms / (60 * 1e3));
2770
+ ms %= 60 * 1e3;
2771
+ const seconds = Math.floor(ms / 1e3);
2772
+ const milliseconds = ms % 1e3;
2773
+ return { weeks, days, hours, minutes, seconds, milliseconds };
2774
+ }
2775
+ function formatDuration(duration) {
2776
+ const parts = [];
2777
+ if (duration.weeks) parts.push(`${duration.weeks}w`);
2778
+ if (duration.days) parts.push(`${duration.days}d`);
2779
+ if (duration.hours) parts.push(`${duration.hours}h`);
2780
+ if (duration.minutes) parts.push(`${duration.minutes}m`);
2781
+ if (duration.seconds) parts.push(`${duration.seconds}s`);
2782
+ if (duration.milliseconds) parts.push(`${duration.milliseconds}ms`);
2783
+ return parts.join(" ") || "0ms";
2784
+ }
2785
+ function sleep(ms) {
2786
+ return new Promise((resolve) => setTimeout(resolve, ms));
2787
+ }
2788
+ async function sleepDuration(duration) {
2789
+ return sleep(toMilliseconds(duration));
2790
+ }
2791
+ function timeout(promise, ms, message) {
2792
+ return Promise.race([
2793
+ promise,
2794
+ new Promise(
2795
+ (_, reject) => setTimeout(
2796
+ () => reject(new Error(message || `Timeout after ${ms}ms`)),
2797
+ ms
2798
+ )
2799
+ )
2800
+ ]);
2801
+ }
2802
+ function now() {
2803
+ return Date.now();
2804
+ }
2805
+ function delayUntil(timestamp2) {
2806
+ return Math.max(0, timestamp2 - Date.now());
2807
+ }
2808
+ function isPast(timestamp2) {
2809
+ return timestamp2 < Date.now();
2810
+ }
2811
+ function isFuture(timestamp2) {
2812
+ return timestamp2 > Date.now();
2813
+ }
2814
+ function addDuration(timestamp2, duration) {
2815
+ return timestamp2 + toMilliseconds(duration);
2816
+ }
2817
+ function parseDuration(str) {
2818
+ const match = str.match(/^(\d+)(ms|s|m|h|d|w)$/);
2819
+ if (!match) {
2820
+ throw new Error(`Invalid duration string: ${str}`);
2821
+ }
2822
+ const value = parseInt(match[1], 10);
2823
+ const unit = match[2];
2824
+ switch (unit) {
2825
+ case "ms":
2826
+ return value;
2827
+ case "s":
2828
+ return value * 1e3;
2829
+ case "m":
2830
+ return value * 60 * 1e3;
2831
+ case "h":
2832
+ return value * 60 * 60 * 1e3;
2833
+ case "d":
2834
+ return value * 24 * 60 * 60 * 1e3;
2835
+ case "w":
2836
+ return value * 7 * 24 * 60 * 60 * 1e3;
2837
+ default:
2838
+ throw new Error(`Unknown unit: ${unit}`);
2839
+ }
2840
+ }
2841
+
2842
+ // src/utils/serialization.ts
2843
+ function serialize(data, options = {}) {
2844
+ const { pretty = false, dateFormat = "iso" } = options;
2845
+ const replacer = (key, value) => {
2846
+ if (value instanceof Date) {
2847
+ return dateFormat === "timestamp" ? value.getTime() : value.toISOString();
2848
+ }
2849
+ if (value === void 0 && !options.includeUndefined) {
2850
+ return null;
2851
+ }
2852
+ if (typeof value === "function") {
2853
+ return void 0;
2854
+ }
2855
+ if (typeof value === "bigint") {
2856
+ return value.toString();
2857
+ }
2858
+ return value;
2859
+ };
2860
+ return JSON.stringify(data, replacer, pretty ? 2 : void 0);
2861
+ }
2862
+ function deserialize(json) {
2863
+ return JSON.parse(json, (key, value) => {
2864
+ if (typeof value === "string") {
2865
+ const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
2866
+ if (isoDateRegex.test(value)) {
2867
+ return new Date(value);
2868
+ }
2869
+ }
2870
+ return value;
2871
+ });
2872
+ }
2873
+ function serializeSafe(data, options = {}) {
2874
+ const seen = /* @__PURE__ */ new WeakSet();
2875
+ const { pretty = false } = options;
2876
+ const replacer = (key, value) => {
2877
+ if (typeof value === "object" && value !== null) {
2878
+ if (seen.has(value)) {
2879
+ return "[Circular]";
2880
+ }
2881
+ seen.add(value);
2882
+ }
2883
+ if (value instanceof Date) {
2884
+ return value.toISOString();
2885
+ }
2886
+ if (typeof value === "function") {
2887
+ return "[Function]";
2888
+ }
2889
+ if (typeof value === "bigint") {
2890
+ return value.toString();
2891
+ }
2892
+ return value;
2893
+ };
2894
+ return JSON.stringify(data, replacer, pretty ? 2 : void 0);
2895
+ }
2896
+ function cloneViaSerialization(obj) {
2897
+ return deserialize(serialize(obj));
2898
+ }
2899
+ function serializeCompressed(data) {
2900
+ const json = serialize(data);
2901
+ return Buffer.from(json).toString("base64");
2902
+ }
2903
+ function deserializeCompressed(compressed) {
2904
+ const json = Buffer.from(compressed, "base64").toString("utf-8");
2905
+ return deserialize(json);
2906
+ }
2907
+ function isSerializable(value) {
2908
+ try {
2909
+ serialize(value);
2910
+ return true;
2911
+ } catch {
2912
+ return false;
2913
+ }
2914
+ }
2915
+ function getSerializedSize(data) {
2916
+ return Buffer.byteLength(serialize(data), "utf-8");
2917
+ }
2918
+
2919
+ // src/storage/job-storage.ts
2920
+ var MemoryJobStorage = class {
2921
+ constructor() {
2922
+ this.jobs = /* @__PURE__ */ new Map();
2923
+ this.deduplicationKeys = /* @__PURE__ */ new Map();
2924
+ }
2925
+ // key -> jobId
2926
+ async save(queue, job) {
2927
+ const key = `${queue}:${job.id}`;
2928
+ this.jobs.set(key, { ...job });
2929
+ if (job.opts.deduplicationKey) {
2930
+ const dedupKey = `${queue}:${job.opts.deduplicationKey}`;
2931
+ this.deduplicationKeys.set(dedupKey, job.id);
2932
+ }
2933
+ }
2934
+ async get(queue, jobId) {
2935
+ const key = `${queue}:${jobId}`;
2936
+ return this.jobs.get(key) || null;
2937
+ }
2938
+ async getByStatus(queue, status) {
2939
+ const result = [];
2940
+ const prefix = `${queue}:`;
2941
+ for (const [key, job] of this.jobs.entries()) {
2942
+ if (key.startsWith(prefix) && job.state === status) {
2943
+ result.push(job);
2944
+ }
2945
+ }
2946
+ return result;
2947
+ }
2948
+ async getAll(queue) {
2949
+ const result = [];
2950
+ const prefix = `${queue}:`;
2951
+ for (const [key, job] of this.jobs.entries()) {
2952
+ if (key.startsWith(prefix)) {
2953
+ result.push(job);
2954
+ }
2955
+ }
2956
+ return result;
2957
+ }
2958
+ async updateStatus(queue, jobId, status) {
2959
+ const key = `${queue}:${jobId}`;
2960
+ const job = this.jobs.get(key);
2961
+ if (job) {
2962
+ job.state = status;
2963
+ if (status === "completed" || status === "failed") {
2964
+ job.finishedOn = Date.now();
2965
+ }
2966
+ }
2967
+ }
2968
+ async update(queue, jobId, updates) {
2969
+ const key = `${queue}:${jobId}`;
2970
+ const job = this.jobs.get(key);
2971
+ if (job) {
2972
+ Object.assign(job, updates);
2973
+ }
2974
+ }
2975
+ async delete(queue, jobId) {
2976
+ const key = `${queue}:${jobId}`;
2977
+ const job = this.jobs.get(key);
2978
+ if (job?.opts.deduplicationKey) {
2979
+ const dedupKey = `${queue}:${job.opts.deduplicationKey}`;
2980
+ this.deduplicationKeys.delete(dedupKey);
2981
+ }
2982
+ this.jobs.delete(key);
2983
+ }
2984
+ async clean(queue, grace, status) {
2985
+ const now2 = Date.now();
2986
+ const prefix = `${queue}:`;
2987
+ const toDelete = [];
2988
+ for (const [key, job] of this.jobs.entries()) {
2989
+ if (key.startsWith(prefix) && job.state === status) {
2990
+ const timestamp2 = job.finishedOn || job.timestamp;
2991
+ if (now2 - timestamp2 > grace) {
2992
+ toDelete.push(key);
2993
+ }
2994
+ }
2995
+ }
2996
+ for (const key of toDelete) {
2997
+ const job = this.jobs.get(key);
2998
+ if (job?.opts.deduplicationKey) {
2999
+ const dedupKey = `${queue}:${job.opts.deduplicationKey}`;
3000
+ this.deduplicationKeys.delete(dedupKey);
3001
+ }
3002
+ this.jobs.delete(key);
3003
+ }
3004
+ return toDelete.length;
3005
+ }
3006
+ async count(queue, status) {
3007
+ const prefix = `${queue}:`;
3008
+ let count = 0;
3009
+ for (const [key, job] of this.jobs.entries()) {
3010
+ if (key.startsWith(prefix)) {
3011
+ if (!status || job.state === status) {
3012
+ count++;
3013
+ }
3014
+ }
3015
+ }
3016
+ return count;
3017
+ }
3018
+ async existsByDeduplicationKey(queue, key) {
3019
+ const dedupKey = `${queue}:${key}`;
3020
+ return this.deduplicationKeys.has(dedupKey);
3021
+ }
3022
+ /**
3023
+ * Clear all jobs
3024
+ */
3025
+ clear() {
3026
+ this.jobs.clear();
3027
+ this.deduplicationKeys.clear();
3028
+ }
3029
+ };
3030
+ export {
3031
+ BatchAccumulator,
3032
+ BatchWriter,
3033
+ FlowFnImpl,
3034
+ HealthCheckerImpl,
3035
+ MemoryAdapter,
3036
+ MemoryDLQManager,
3037
+ MemoryEventLog,
3038
+ MemoryEventTracker,
3039
+ MemoryJobStorage,
3040
+ MemoryWorkflowStorage,
3041
+ MetricsManager,
3042
+ PostgresAdapter,
3043
+ PriorityQueue,
3044
+ RateLimiter,
3045
+ RedisAdapter,
3046
+ Scheduler,
3047
+ SlidingWindowRateLimiter,
3048
+ TokenBucketRateLimiter,
3049
+ Worker,
3050
+ addDuration,
3051
+ areJobsEquivalent,
3052
+ batch,
3053
+ batchByKey,
3054
+ calculateBackoff,
3055
+ chunk,
3056
+ circuitBreaker,
3057
+ cloneViaSerialization,
3058
+ createFlow,
3059
+ createRateLimiter,
3060
+ createWorker,
3061
+ delayUntil,
3062
+ deserialize,
3063
+ deserializeCompressed,
3064
+ formatDuration,
3065
+ fromMilliseconds,
3066
+ generateDeduplicationKey,
3067
+ generateDeterministicId,
3068
+ generateExecutionId,
3069
+ generateId,
3070
+ generateJobId,
3071
+ generateMessageId,
3072
+ getSerializedSize,
3073
+ hashJob,
3074
+ isFuture,
3075
+ isPast,
3076
+ isSerializable,
3077
+ now,
3078
+ parseDuration,
3079
+ processBatches,
3080
+ serialize,
3081
+ serializeCompressed,
3082
+ serializeSafe,
3083
+ sleep,
3084
+ sleepDuration,
3085
+ timeout,
3086
+ toMilliseconds
3087
+ };
3088
+ //# sourceMappingURL=index.mjs.map