glide-mq 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +190 -0
  2. package/dist/connection.d.ts +36 -0
  3. package/dist/connection.d.ts.map +1 -0
  4. package/dist/connection.js +100 -0
  5. package/dist/connection.js.map +1 -0
  6. package/dist/errors.d.ts +10 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +25 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/flow-producer.d.ts +36 -0
  11. package/dist/flow-producer.d.ts.map +1 -0
  12. package/dist/flow-producer.js +185 -0
  13. package/dist/flow-producer.js.map +1 -0
  14. package/dist/functions/index.d.ts +136 -0
  15. package/dist/functions/index.d.ts.map +1 -0
  16. package/dist/functions/index.js +1062 -0
  17. package/dist/functions/index.js.map +1 -0
  18. package/dist/graceful-shutdown.d.ts +17 -0
  19. package/dist/graceful-shutdown.d.ts.map +1 -0
  20. package/dist/graceful-shutdown.js +27 -0
  21. package/dist/graceful-shutdown.js.map +1 -0
  22. package/dist/index.d.ts +13 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +27 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/job.d.ts +106 -0
  27. package/dist/job.d.ts.map +1 -0
  28. package/dist/job.js +252 -0
  29. package/dist/job.js.map +1 -0
  30. package/dist/queue-events.d.ts +33 -0
  31. package/dist/queue-events.d.ts.map +1 -0
  32. package/dist/queue-events.js +138 -0
  33. package/dist/queue-events.js.map +1 -0
  34. package/dist/queue.d.ts +140 -0
  35. package/dist/queue.d.ts.map +1 -0
  36. package/dist/queue.js +483 -0
  37. package/dist/queue.js.map +1 -0
  38. package/dist/scheduler.d.ts +48 -0
  39. package/dist/scheduler.d.ts.map +1 -0
  40. package/dist/scheduler.js +140 -0
  41. package/dist/scheduler.js.map +1 -0
  42. package/dist/telemetry.d.ts +29 -0
  43. package/dist/telemetry.d.ts.map +1 -0
  44. package/dist/telemetry.js +90 -0
  45. package/dist/telemetry.js.map +1 -0
  46. package/dist/types.d.ts +125 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +3 -0
  49. package/dist/types.js.map +1 -0
  50. package/dist/utils.d.ts +65 -0
  51. package/dist/utils.d.ts.map +1 -0
  52. package/dist/utils.js +217 -0
  53. package/dist/utils.js.map +1 -0
  54. package/dist/worker.d.ts +138 -0
  55. package/dist/worker.d.ts.map +1 -0
  56. package/dist/worker.js +574 -0
  57. package/dist/worker.js.map +1 -0
  58. package/dist/workflows.d.ts +34 -0
  59. package/dist/workflows.d.ts.map +1 -0
  60. package/dist/workflows.js +117 -0
  61. package/dist/workflows.js.map +1 -0
  62. package/package.json +56 -0
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # glide-mq
2
+
3
+ High-performance message queue for Node.js, built on Valkey/Redis Streams with [Valkey GLIDE](https://github.com/valkey-io/valkey-glide) native NAPI bindings.
4
+
5
+ ## Performance
6
+
7
+ | Concurrency | Throughput |
8
+ |-------------|-----------|
9
+ | c=1 | 4,376 jobs/s |
10
+ | c=10 | 20,979 jobs/s |
11
+ | c=50 | 44,643 jobs/s |
12
+
13
+ No-op processor, Valkey 8.0, single node.
14
+
15
+ ## Why
16
+
17
+ - **Streams-first** - uses Redis Streams + consumer groups + PEL instead of Lists + BRPOPLPUSH. Fewer moving parts, built-in at-least-once delivery.
18
+ - **Server Functions** - single `FUNCTION LOAD` instead of dozens of EVAL scripts. Persistent across restarts, no NOSCRIPT cache-miss errors.
19
+ - **1 RTT per job** - `completeAndFetchNext` combines job completion + next job fetch + activation in a single FCALL round trip.
20
+ - **Cluster-native** - hash-tagged keys from day one. No afterthought `{braces}` requirement.
21
+ - **Native bindings** - built on [@glidemq/speedkey](https://github.com/avifenesh/speedkey) (valkey-glide with Rust core + NAPI).
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm install glide-mq
27
+ ```
28
+
29
+ Requires Node.js 20+ and a running Valkey (7.2+) or Redis (6.2+) instance.
30
+
31
+ ## Quick Start
32
+
33
+ ```typescript
34
+ import { Queue, Worker } from 'glide-mq';
35
+
36
+ const connection = { addresses: [{ host: 'localhost', port: 6379 }] };
37
+
38
+ // Producer
39
+ const queue = new Queue('tasks', { connection });
40
+ await queue.add('send-email', { to: 'user@example.com', subject: 'Hello' });
41
+
42
+ // Consumer
43
+ const worker = new Worker('tasks', async (job) => {
44
+ console.log(`Processing ${job.name}: ${JSON.stringify(job.data)}`);
45
+ return { sent: true };
46
+ }, { connection, concurrency: 10 });
47
+
48
+ worker.on('completed', (job) => console.log(`Job ${job.id} completed`));
49
+ worker.on('failed', (job, err) => console.log(`Job ${job.id} failed: ${err.message}`));
50
+ ```
51
+
52
+ ## Features
53
+
54
+ ### Core
55
+ - **Queue** - add, addBulk, getJob, getJobs, pause, resume, obliterate, drain
56
+ - **Worker** - concurrent processing, XREADGROUP polling, graceful shutdown
57
+ - **Job** - progress, updateData, retry, remove, log, state queries, waitUntilFinished
58
+
59
+ ### Reliability
60
+ - **Retries** - fixed, exponential, jitter backoff + custom strategy functions
61
+ - **Stalled recovery** - XAUTOCLAIM with heartbeat-based lock renewal
62
+ - **Per-job timeout** - automatic failure if processor exceeds timeout
63
+ - **Dead letter queue** - permanently failed jobs routed to a separate queue
64
+
65
+ ### Advanced
66
+ - **Deduplication** - simple, throttle, debounce modes
67
+ - **Rate limiting** - sliding window, per-queue
68
+ - **Global concurrency** - limit active jobs across all workers
69
+ - **Job retention** - removeOnComplete/removeOnFail (count, age-based)
70
+ - **Priorities** - encoded in sorted set scores, FIFO within same priority
71
+
72
+ ### Workflows
73
+ - **FlowProducer** - atomic parent-child job trees with nested flows
74
+ - **chain(queue, jobs)** - sequential pipeline, each job receives previous result
75
+ - **group(queue, jobs)** - parallel execution, parent completes when all children done
76
+ - **chord(queue, group, callback)** - run group, then callback with all results
77
+
78
+ ### Observability
79
+ - **QueueEvents** - stream-based event subscription (added, completed, failed, stalled, etc.)
80
+ - **Job schedulers** - cron patterns and fixed intervals for repeatable jobs
81
+ - **Metrics** - getJobCounts, getMetrics
82
+ - **OpenTelemetry** - optional spans for Queue.add, Worker.process, FlowProducer.add
83
+
84
+ ### Operations
85
+ - **Graceful shutdown** - SIGTERM/SIGINT handler, waits for active jobs
86
+ - **Connection recovery** - exponential backoff reconnect with function library reload
87
+ - **Job revocation** - cooperative cancellation via AbortSignal
88
+ - **Cluster mode** - all features work in Valkey Cluster (pass `clusterMode: true`)
89
+
90
+ ## API
91
+
92
+ ### Queue
93
+
94
+ ```typescript
95
+ const queue = new Queue('name', {
96
+ connection: { addresses: [{ host, port }], clusterMode: false },
97
+ prefix: 'glide', // key prefix (default: 'glide')
98
+ });
99
+
100
+ await queue.add('jobName', data, {
101
+ delay: 5000, // delayed job (ms)
102
+ priority: 1, // lower = higher priority
103
+ attempts: 3, // max retry attempts
104
+ backoff: { type: 'exponential', delay: 1000 },
105
+ timeout: 30000, // per-job timeout (ms)
106
+ removeOnComplete: true, // or count, or { age, count }
107
+ deduplication: { id: 'unique-key', mode: 'simple' },
108
+ });
109
+
110
+ await queue.pause();
111
+ await queue.resume();
112
+ await queue.getJobCounts(); // { waiting, active, delayed, completed, failed }
113
+ await queue.obliterate({ force: true });
114
+ await queue.close();
115
+ ```
116
+
117
+ ### Worker
118
+
119
+ ```typescript
120
+ const worker = new Worker('name', async (job) => {
121
+ await job.updateProgress(50);
122
+ return result;
123
+ }, {
124
+ connection,
125
+ concurrency: 10,
126
+ blockTimeout: 5000,
127
+ stalledInterval: 30000,
128
+ lockDuration: 30000,
129
+ limiter: { max: 100, duration: 60000 },
130
+ });
131
+
132
+ worker.on('completed', (job, result) => {});
133
+ worker.on('failed', (job, error) => {});
134
+ await worker.close();
135
+ ```
136
+
137
+ ### Flows
138
+
139
+ ```typescript
140
+ import { FlowProducer, chain, group, chord } from 'glide-mq';
141
+
142
+ // Parent-child
143
+ const flow = new FlowProducer({ connection });
144
+ await flow.add({
145
+ name: 'parent', queueName: 'tasks', data: {},
146
+ children: [
147
+ { name: 'child1', queueName: 'tasks', data: {} },
148
+ { name: 'child2', queueName: 'tasks', data: {} },
149
+ ],
150
+ });
151
+
152
+ // Chain: A -> B -> C (sequential)
153
+ await chain(connection, 'tasks', [
154
+ { name: 'step1', data: {} },
155
+ { name: 'step2', data: {} },
156
+ ]);
157
+
158
+ // Group: A + B + C (parallel)
159
+ await group(connection, 'tasks', [
160
+ { name: 'task1', data: {} },
161
+ { name: 'task2', data: {} },
162
+ ]);
163
+ ```
164
+
165
+ ### Events
166
+
167
+ ```typescript
168
+ import { QueueEvents } from 'glide-mq';
169
+
170
+ const events = new QueueEvents('tasks', { connection });
171
+ events.on('completed', ({ jobId, returnvalue }) => {});
172
+ events.on('failed', ({ jobId, failedReason }) => {});
173
+ events.on('stalled', ({ jobId }) => {});
174
+ ```
175
+
176
+ ## Cluster Mode
177
+
178
+ ```typescript
179
+ const connection = {
180
+ addresses: [{ host: 'cluster-node', port: 7000 }],
181
+ clusterMode: true,
182
+ };
183
+
184
+ // Everything works the same - keys are hash-tagged automatically
185
+ const queue = new Queue('tasks', { connection });
186
+ ```
187
+
188
+ ## License
189
+
190
+ Apache-2.0
@@ -0,0 +1,36 @@
1
+ import type { ConnectionOptions, Client } from './types';
2
+ /**
3
+ * Create a GlideClient (standalone) or GlideClusterClient (cluster) based on options.
4
+ */
5
+ export declare function createClient(opts: ConnectionOptions): Promise<Client>;
6
+ /**
7
+ * Create a dedicated client for XREADGROUP BLOCK / XREAD BLOCK.
8
+ * Must not be shared with non-blocking commands.
9
+ */
10
+ export declare function createBlockingClient(opts: ConnectionOptions): Promise<Client>;
11
+ /**
12
+ * Ensure the glidemq function library is loaded and up-to-date on the server.
13
+ *
14
+ * Strategy:
15
+ * 1. Try FCALL glidemq_version with empty keys/args
16
+ * 2. If succeeds and version matches, return (already loaded)
17
+ * 3. If fails (function not found) or version mismatch, call functionLoad with replace
18
+ * 4. For cluster clients, load to all primaries via route option
19
+ *
20
+ * @param client - The Valkey client
21
+ * @param librarySource - The Lua library source code to load
22
+ * @param clusterMode - Whether the client is a cluster client (determines routing for FUNCTION LOAD)
23
+ */
24
+ export declare function ensureFunctionLibrary(client: Client, librarySource?: string, clusterMode?: boolean): Promise<void>;
25
+ /**
26
+ * Create a consumer group for the given stream key.
27
+ * Uses XGROUP CREATE with mkStream to auto-create the stream if it doesn't exist.
28
+ * Handles BUSYGROUP error (group already exists) gracefully.
29
+ *
30
+ * @param client - The Valkey client
31
+ * @param streamKey - The stream key to create the group on
32
+ * @param groupName - The consumer group name
33
+ * @param startId - The ID to start reading from (default: '0' = beginning)
34
+ */
35
+ export declare function createConsumerGroup(client: Client, streamKey: string, groupName: string, startId?: string): Promise<void>;
36
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAIzD;;GAEG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB3E;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,aAAa,GAAE,MAAuB,EACtC,WAAW,GAAE,OAAe,GAC3B,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,MAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAYf"}
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createClient = createClient;
4
+ exports.createBlockingClient = createBlockingClient;
5
+ exports.ensureFunctionLibrary = ensureFunctionLibrary;
6
+ exports.createConsumerGroup = createConsumerGroup;
7
+ const speedkey_1 = require("@glidemq/speedkey");
8
+ const errors_1 = require("./errors");
9
+ const index_1 = require("./functions/index");
10
+ /**
11
+ * Create a GlideClient (standalone) or GlideClusterClient (cluster) based on options.
12
+ */
13
+ async function createClient(opts) {
14
+ const config = {
15
+ addresses: opts.addresses,
16
+ useTLS: opts.useTLS,
17
+ credentials: opts.credentials,
18
+ };
19
+ try {
20
+ if (opts.clusterMode) {
21
+ return await speedkey_1.GlideClusterClient.createClient(config);
22
+ }
23
+ return await speedkey_1.GlideClient.createClient(config);
24
+ }
25
+ catch (err) {
26
+ throw new errors_1.ConnectionError(`Failed to create ${opts.clusterMode ? 'cluster' : 'standalone'} client: ${err instanceof Error ? err.message : String(err)}`);
27
+ }
28
+ }
29
+ /**
30
+ * Create a dedicated client for XREADGROUP BLOCK / XREAD BLOCK.
31
+ * Must not be shared with non-blocking commands.
32
+ */
33
+ async function createBlockingClient(opts) {
34
+ return createClient(opts);
35
+ }
36
+ /**
37
+ * Ensure the glidemq function library is loaded and up-to-date on the server.
38
+ *
39
+ * Strategy:
40
+ * 1. Try FCALL glidemq_version with empty keys/args
41
+ * 2. If succeeds and version matches, return (already loaded)
42
+ * 3. If fails (function not found) or version mismatch, call functionLoad with replace
43
+ * 4. For cluster clients, load to all primaries via route option
44
+ *
45
+ * @param client - The Valkey client
46
+ * @param librarySource - The Lua library source code to load
47
+ * @param clusterMode - Whether the client is a cluster client (determines routing for FUNCTION LOAD)
48
+ */
49
+ async function ensureFunctionLibrary(client, librarySource = index_1.LIBRARY_SOURCE, clusterMode = false) {
50
+ if (clusterMode) {
51
+ // In cluster mode, always load with REPLACE to all primaries.
52
+ // FUNCTION LOAD REPLACE is idempotent - safe to call every time.
53
+ // Skipping the version check avoids FCALL routing to replicas (empty keys = random node).
54
+ await client.functionLoad(librarySource, {
55
+ replace: true,
56
+ route: 'allPrimaries',
57
+ });
58
+ return;
59
+ }
60
+ // Standalone: check version first to avoid unnecessary reload
61
+ try {
62
+ const result = await client.fcall('glidemq_version', [], []);
63
+ if (String(result) === index_1.LIBRARY_VERSION)
64
+ return;
65
+ }
66
+ catch (err) {
67
+ const msg = err instanceof Error ? err.message : String(err);
68
+ if (!msg.includes('Function not loaded') && !msg.includes('Function not found') && !msg.includes('No matching script') && !msg.includes('NOSCRIPT')) {
69
+ throw err;
70
+ }
71
+ }
72
+ await client.functionLoad(librarySource, {
73
+ replace: true,
74
+ });
75
+ }
76
+ /**
77
+ * Create a consumer group for the given stream key.
78
+ * Uses XGROUP CREATE with mkStream to auto-create the stream if it doesn't exist.
79
+ * Handles BUSYGROUP error (group already exists) gracefully.
80
+ *
81
+ * @param client - The Valkey client
82
+ * @param streamKey - The stream key to create the group on
83
+ * @param groupName - The consumer group name
84
+ * @param startId - The ID to start reading from (default: '0' = beginning)
85
+ */
86
+ async function createConsumerGroup(client, streamKey, groupName, startId = '0') {
87
+ try {
88
+ await client.xgroupCreate(streamKey, groupName, startId, {
89
+ mkStream: true,
90
+ });
91
+ }
92
+ catch (err) {
93
+ // BUSYGROUP means the group already exists - that's fine
94
+ if (err instanceof Error && err.message.includes('BUSYGROUP')) {
95
+ return;
96
+ }
97
+ throw err;
98
+ }
99
+ }
100
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare class GlideMQError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class ConnectionError extends GlideMQError {
5
+ constructor(message: string);
6
+ }
7
+ export declare class ScriptError extends GlideMQError {
8
+ constructor(message: string);
9
+ }
10
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,eAAgB,SAAQ,YAAY;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,WAAY,SAAQ,YAAY;gBAC/B,OAAO,EAAE,MAAM;CAI5B"}
package/dist/errors.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScriptError = exports.ConnectionError = exports.GlideMQError = void 0;
4
+ class GlideMQError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'GlideMQError';
8
+ }
9
+ }
10
+ exports.GlideMQError = GlideMQError;
11
+ class ConnectionError extends GlideMQError {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = 'ConnectionError';
15
+ }
16
+ }
17
+ exports.ConnectionError = ConnectionError;
18
+ class ScriptError extends GlideMQError {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = 'ScriptError';
22
+ }
23
+ }
24
+ exports.ScriptError = ScriptError;
25
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";;;AAAA,MAAa,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AALD,oCAKC;AAED,MAAa,eAAgB,SAAQ,YAAY;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AALD,0CAKC;AAED,MAAa,WAAY,SAAQ,YAAY;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AALD,kCAKC"}
@@ -0,0 +1,36 @@
1
+ import type { FlowProducerOptions, FlowJob } from './types';
2
+ import { Job } from './job';
3
+ export interface JobNode {
4
+ job: Job;
5
+ children?: JobNode[];
6
+ }
7
+ export declare class FlowProducer {
8
+ private opts;
9
+ private client;
10
+ private closing;
11
+ constructor(opts: FlowProducerOptions);
12
+ /** @internal */
13
+ private getClient;
14
+ /**
15
+ * Add a flow (parent with children) atomically.
16
+ * Children can have their own children (recursive flows), which are flattened
17
+ * into multiple addFlow calls (one per level with children).
18
+ */
19
+ add(flow: FlowJob): Promise<JobNode>;
20
+ /**
21
+ * Add multiple independent flows.
22
+ */
23
+ addBulk(flows: FlowJob[]): Promise<JobNode[]>;
24
+ /**
25
+ * Recursively add a flow. If children themselves have children,
26
+ * those sub-flows are added first (bottom-up), and the resulting
27
+ * child jobs are used as direct children of the current parent.
28
+ */
29
+ private addFlowRecursive;
30
+ /**
31
+ * Close the FlowProducer and release the underlying client connection.
32
+ * Idempotent: safe to call multiple times.
33
+ */
34
+ close(): Promise<void>;
35
+ }
36
+ //# sourceMappingURL=flow-producer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-producer.d.ts","sourceRoot":"","sources":["../src/flow-producer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAU,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAM5B,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,mBAAmB;IAIrC,gBAAgB;YACF,SAAS;IAYvB;;;;OAIG;IACG,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAe1C;;OAEG;IACG,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IASnD;;;;OAIG;YACW,gBAAgB;IAyI9B;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ7B"}
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FlowProducer = void 0;
37
+ const job_1 = require("./job");
38
+ const utils_1 = require("./utils");
39
+ const connection_1 = require("./connection");
40
+ const index_1 = require("./functions/index");
41
+ const telemetry_1 = require("./telemetry");
42
+ class FlowProducer {
43
+ opts;
44
+ client = null;
45
+ closing = false;
46
+ constructor(opts) {
47
+ this.opts = opts;
48
+ }
49
+ /** @internal */
50
+ async getClient() {
51
+ if (!this.client) {
52
+ this.client = await (0, connection_1.createClient)(this.opts.connection);
53
+ await (0, connection_1.ensureFunctionLibrary)(this.client, index_1.LIBRARY_SOURCE, this.opts.connection.clusterMode ?? false);
54
+ }
55
+ return this.client;
56
+ }
57
+ /**
58
+ * Add a flow (parent with children) atomically.
59
+ * Children can have their own children (recursive flows), which are flattened
60
+ * into multiple addFlow calls (one per level with children).
61
+ */
62
+ async add(flow) {
63
+ return (0, telemetry_1.withSpan)('glide-mq.flow.add', {
64
+ 'glide-mq.queue': flow.queueName,
65
+ 'glide-mq.flow.name': flow.name,
66
+ 'glide-mq.flow.childCount': flow.children?.length ?? 0,
67
+ }, async () => {
68
+ const client = await this.getClient();
69
+ return this.addFlowRecursive(client, flow);
70
+ });
71
+ }
72
+ /**
73
+ * Add multiple independent flows.
74
+ */
75
+ async addBulk(flows) {
76
+ const client = await this.getClient();
77
+ const results = [];
78
+ for (const flow of flows) {
79
+ results.push(await this.addFlowRecursive(client, flow));
80
+ }
81
+ return results;
82
+ }
83
+ /**
84
+ * Recursively add a flow. If children themselves have children,
85
+ * those sub-flows are added first (bottom-up), and the resulting
86
+ * child jobs are used as direct children of the current parent.
87
+ */
88
+ async addFlowRecursive(client, flow) {
89
+ const prefix = this.opts.prefix ?? 'glide';
90
+ const parentQueueName = flow.queueName;
91
+ const parentKeys = (0, utils_1.buildKeys)(parentQueueName, prefix);
92
+ // If no children, this is a leaf - add as a regular job (not a flow)
93
+ if (!flow.children || flow.children.length === 0) {
94
+ const { addJob } = await Promise.resolve().then(() => __importStar(require('./functions/index')));
95
+ const timestamp = Date.now();
96
+ const opts = flow.opts ?? {};
97
+ const jobId = await addJob(client, parentKeys, flow.name, JSON.stringify(flow.data), JSON.stringify(opts), timestamp, opts.delay ?? 0, opts.priority ?? 0, '', opts.attempts ?? 0);
98
+ const job = new job_1.Job(client, parentKeys, String(jobId), flow.name, flow.data, opts);
99
+ job.timestamp = timestamp;
100
+ return { job };
101
+ }
102
+ // Recursively process children that themselves have children (bottom-up).
103
+ const childNodeMap = new Map();
104
+ for (let i = 0; i < flow.children.length; i++) {
105
+ const child = flow.children[i];
106
+ if (child.children && child.children.length > 0) {
107
+ childNodeMap.set(i, await this.addFlowRecursive(client, child));
108
+ }
109
+ }
110
+ // Build leaf children data for addFlow (children without sub-children)
111
+ const childrenForLua = flow.children
112
+ .filter((_, i) => !childNodeMap.has(i))
113
+ .map((child) => {
114
+ const childOpts = child.opts ?? {};
115
+ return {
116
+ name: child.name,
117
+ data: JSON.stringify(child.data),
118
+ opts: JSON.stringify(childOpts),
119
+ delay: childOpts.delay ?? 0,
120
+ priority: childOpts.priority ?? 0,
121
+ maxAttempts: childOpts.attempts ?? 0,
122
+ keys: (0, utils_1.buildKeys)(child.queueName, prefix),
123
+ queuePrefix: (0, utils_1.keyPrefix)(prefix, child.queueName),
124
+ parentQueueName: parentQueueName,
125
+ };
126
+ });
127
+ // Build extra deps for sub-flow children (already created recursively)
128
+ const extraDeps = [];
129
+ for (const [i, subNode] of childNodeMap.entries()) {
130
+ const child = flow.children[i];
131
+ extraDeps.push(`${(0, utils_1.keyPrefix)(prefix, child.queueName)}:${subNode.job.id}`);
132
+ }
133
+ const timestamp = Date.now();
134
+ const parentOpts = flow.opts ?? {};
135
+ const ids = await (0, index_1.addFlow)(client, parentKeys, flow.name, JSON.stringify(flow.data), JSON.stringify(parentOpts), timestamp, parentOpts.delay ?? 0, parentOpts.priority ?? 0, parentOpts.attempts ?? 0, childrenForLua, extraDeps);
136
+ const parentId = ids[0];
137
+ // Set parentId and parentQueue on pre-existing sub-flow children
138
+ for (const [i, subNode] of childNodeMap.entries()) {
139
+ const child = flow.children[i];
140
+ const childKeys = (0, utils_1.buildKeys)(child.queueName, prefix);
141
+ await client.hset(childKeys.job(subNode.job.id), {
142
+ parentId: parentId,
143
+ parentQueue: parentQueueName,
144
+ });
145
+ subNode.job.parentId = parentId;
146
+ subNode.job.parentQueue = parentQueueName;
147
+ }
148
+ // Build JobNode tree - interleave sub-flow and leaf child nodes in original order
149
+ const childNodes = [];
150
+ let leafIdx = 0;
151
+ for (let i = 0; i < flow.children.length; i++) {
152
+ if (childNodeMap.has(i)) {
153
+ childNodes.push(childNodeMap.get(i));
154
+ }
155
+ else {
156
+ const childId = ids[1 + leafIdx];
157
+ const child = flow.children[i];
158
+ const childJob = new job_1.Job(client, (0, utils_1.buildKeys)(child.queueName, prefix), childId, child.name, child.data, child.opts ?? {});
159
+ childJob.timestamp = timestamp;
160
+ childJob.parentId = parentId;
161
+ childJob.parentQueue = parentQueueName;
162
+ childNodes.push({ job: childJob });
163
+ leafIdx++;
164
+ }
165
+ }
166
+ const parentJob = new job_1.Job(client, parentKeys, parentId, flow.name, flow.data, parentOpts);
167
+ parentJob.timestamp = timestamp;
168
+ return { job: parentJob, children: childNodes };
169
+ }
170
+ /**
171
+ * Close the FlowProducer and release the underlying client connection.
172
+ * Idempotent: safe to call multiple times.
173
+ */
174
+ async close() {
175
+ if (this.closing)
176
+ return;
177
+ this.closing = true;
178
+ if (this.client) {
179
+ this.client.close();
180
+ this.client = null;
181
+ }
182
+ }
183
+ }
184
+ exports.FlowProducer = FlowProducer;
185
+ //# sourceMappingURL=flow-producer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow-producer.js","sourceRoot":"","sources":["../src/flow-producer.ts"],"names":[],"mappings":"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"}