snugkv-nest 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/queue/cron.d.ts +21 -0
- package/dist/core/queue/cron.js +144 -0
- package/dist/core/queue/cron.js.map +1 -0
- package/dist/core/queue/events.d.ts +32 -0
- package/dist/core/queue/events.js +98 -0
- package/dist/core/queue/events.js.map +1 -0
- package/dist/core/queue/flow.d.ts +35 -0
- package/dist/core/queue/flow.js +65 -0
- package/dist/core/queue/flow.js.map +1 -0
- package/dist/core/queue/index.d.ts +7 -0
- package/dist/core/queue/index.js +27 -0
- package/dist/core/queue/index.js.map +1 -0
- package/dist/core/queue/job.d.ts +71 -0
- package/dist/core/queue/job.js +225 -0
- package/dist/core/queue/job.js.map +1 -0
- package/dist/core/queue/queue.d.ts +152 -0
- package/dist/core/queue/queue.js +355 -0
- package/dist/core/queue/queue.js.map +1 -0
- package/dist/core/queue/types.d.ts +79 -0
- package/dist/core/queue/types.js +85 -0
- package/dist/core/queue/types.js.map +1 -0
- package/dist/core/queue/worker.d.ts +87 -0
- package/dist/core/queue/worker.js +311 -0
- package/dist/core/queue/worker.js.map +1 -0
- package/dist/core/trace.d.ts +113 -0
- package/dist/core/trace.js +235 -0
- package/dist/core/trace.js.map +1 -0
- package/dist/nest/explorer.d.ts +13 -0
- package/dist/nest/explorer.js +51 -0
- package/dist/nest/explorer.js.map +1 -1
- package/dist/nest/index.d.ts +3 -1
- package/dist/nest/index.js +4 -1
- package/dist/nest/index.js.map +1 -1
- package/dist/nest/module.d.ts +11 -0
- package/dist/nest/module.js +32 -2
- package/dist/nest/module.js.map +1 -1
- package/dist/nest/queue-decorators.d.ts +34 -0
- package/dist/nest/queue-decorators.js +56 -0
- package/dist/nest/queue-decorators.js.map +1 -0
- package/dist/nest/queue-module.d.ts +22 -0
- package/dist/nest/queue-module.js +41 -0
- package/dist/nest/queue-module.js.map +1 -0
- package/dist/nest/runtime.d.ts +1 -0
- package/dist/nest/runtime.js +21 -2
- package/dist/nest/runtime.js.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -95,6 +95,88 @@ Subscriptions are restored after a reconnect; messages sent while disconnected a
|
|
|
95
95
|
- `SnugKvLocks`: `acquire(name, { ttlMs, waitMs })`, `run(name, fn)`. A lock expires on its own, and only its holder can release or extend it. It protects against crashed holders, not against the server itself failing, so keep critical sections idempotent.
|
|
96
96
|
- `SnugKvRateLimiter`: `hit(key, limit, windowMs)` is a fixed window; it returns `{ allowed, count, remaining, resetMs }`.
|
|
97
97
|
|
|
98
|
+
## Job queues
|
|
99
|
+
|
|
100
|
+
BullMQ-style queues that run inside SnugKV (the `snugq` function library, loaded by the server by default). Needs a server version that includes it.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { Queue, Worker, QueueEvents, FlowProducer } from 'snugkv-nest';
|
|
104
|
+
|
|
105
|
+
const emails = new Queue('emails', { connection: { host: '127.0.0.1', port: 6379 } });
|
|
106
|
+
await emails.add('welcome', { to: 'a@b.c' }, { attempts: 3, backoff: { type: 'exponential', delay: 1000 }, removeOnComplete: 100 });
|
|
107
|
+
|
|
108
|
+
const worker = new Worker('emails', async (job) => {
|
|
109
|
+
await job.updateProgress(50);
|
|
110
|
+
return send(job.data);
|
|
111
|
+
}, { connection: { host: '127.0.0.1', port: 6379 }, concurrency: 5 });
|
|
112
|
+
worker.on('failed', (job, err) => console.error(job.id, err.message));
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
With Nest, register queues and mark processors; workers start and stop with the app:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
SnugKvQueueModule.registerQueue({ name: 'emails' })
|
|
119
|
+
|
|
120
|
+
@SnugProcessor('emails', { concurrency: 5 })
|
|
121
|
+
export class EmailProcessor extends WorkerHost {
|
|
122
|
+
async process(job: Job<{ to: string }>) { /* … */ }
|
|
123
|
+
|
|
124
|
+
@OnSnugWorkerEvent('failed')
|
|
125
|
+
onFailed(job: Job, err: Error) { /* … */ }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
constructor(@InjectSnugQueue('emails') private readonly emails: Queue) {}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Use `@SnugProcess('jobName')` on methods instead of `process()` to route by job name.
|
|
132
|
+
|
|
133
|
+
Supported: priorities, LIFO, delayed jobs, attempts with fixed, exponential or custom backoff (`UnrecoverableError` skips retries), locks with stalled-job recovery, pause/resume, global concurrency, queue rate limits (`limiter`, `queue.setRateLimit`, `worker.rateLimit` + `RateLimitError`), custom ids and deduplication, `removeOnComplete`/`removeOnFail`, progress and logs, `QueueEvents` and `job.waitUntilFinished`, `drain`/`clean`/`obliterate`/`retryJobs`/`promoteJobs`, repeatable jobs (`upsertJobScheduler` with `every` or a cron `pattern` and `tz`), and parent/child flows (`FlowProducer`, `job.getChildrenValues()`, `failParentOnFailure`, `ignoreDependencyOnFailure`). Job data must be JSON-serialisable. Cron patterns are computed by workers, so keep one running. See the server's `docs/QUEUES.md` for the storage layout.
|
|
134
|
+
|
|
135
|
+
## Flow tracing
|
|
136
|
+
|
|
137
|
+
Turn on `tracing` and every activity becomes one trace you can follow across
|
|
138
|
+
services: a webhook, the jobs it queues, the worker in another service that
|
|
139
|
+
handles them, and every step you mark in between.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
SnugKvModule.forRoot({ url: 'snugkv://localhost:6383', service: 'chain-ingest', tracing: true });
|
|
143
|
+
|
|
144
|
+
@Injectable()
|
|
145
|
+
export class WebhooksService {
|
|
146
|
+
// Injectable when `tracing` is on (null otherwise).
|
|
147
|
+
constructor(private readonly tracer: Tracer, @InjectSnugQueue('chain-events') private readonly q: Queue) {}
|
|
148
|
+
|
|
149
|
+
async onWebhook(body: AlchemyEvent) {
|
|
150
|
+
// A step with no trace around it starts a new trace.
|
|
151
|
+
return this.tracer.step('webhook.alchemy', { network: body.event.network }, async (span) => {
|
|
152
|
+
await this.tracer.step('dedup', {}, () => this.checkDuplicate(body));
|
|
153
|
+
await this.q.add('deposit.detected', { txHash, address }); // carries the trace
|
|
154
|
+
span.set({ transfers: body.event.activity.length });
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The worker that processes `deposit.detected` (in any service with tracing on)
|
|
161
|
+
runs inside a span of the same trace. Inside it, `tracer.step()` nests further
|
|
162
|
+
and `tracer.event('push.sent', { ticket })` records a step that already
|
|
163
|
+
happened. A step that throws is recorded with its error and stack, and the
|
|
164
|
+
error is rethrown unchanged.
|
|
165
|
+
|
|
166
|
+
Spans are written in batches to the `snug:traces` stream (capped at about
|
|
167
|
+
100,000 entries). Each entry has `traceId`, `spanId`, `parentId`, `service`,
|
|
168
|
+
`name`, `status` (`ok` / `error`), `start`, `durationMs`, `attrs` (JSON) and,
|
|
169
|
+
for failures, `error` and `stack`. Attribute keys that look like credentials
|
|
170
|
+
(`token`, `secret`, `signature`, `privateKey`, `seed`, ...) are stored as
|
|
171
|
+
`[redacted]`, and long strings are cut. Tracing never throws into your code:
|
|
172
|
+
when the server is unreachable, spans are buffered and written later.
|
|
173
|
+
|
|
174
|
+
The trace travels inside job data as a `__trace` field (plain-object data
|
|
175
|
+
only) and is removed before your processor sees `job.data`.
|
|
176
|
+
|
|
177
|
+
Options: `tracing: { stream, maxLen, batchSize, flushMs, maxBuffer, redact, maxAttrLength }`.
|
|
178
|
+
Without Nest: `const tracer = new Tracer(client, { service: 'api' }); setTracer(tracer);`
|
|
179
|
+
|
|
98
180
|
## Messaging (streams)
|
|
99
181
|
|
|
100
182
|
## Quick start
|
|
@@ -267,4 +349,4 @@ Tests that need a server are skipped when `SNUGKV_BIN` is not set.
|
|
|
267
349
|
|
|
268
350
|
## License
|
|
269
351
|
|
|
270
|
-
MIT
|
|
352
|
+
MIT. This package is only a client. The SnugKV server it talks to is a separate project, licensed under AGPL-3.0 with a commercial licence option.
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/index.js
CHANGED
|
@@ -31,4 +31,6 @@ __exportStar(require("./tx"), exports);
|
|
|
31
31
|
__exportStar(require("./cache"), exports);
|
|
32
32
|
__exportStar(require("./lock"), exports);
|
|
33
33
|
__exportStar(require("./ratelimit"), exports);
|
|
34
|
+
__exportStar(require("./queue"), exports);
|
|
35
|
+
__exportStar(require("./trace"), exports);
|
|
34
36
|
//# sourceMappingURL=index.js.map
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,yCAAuB;AACvB,2CAA8E;AAArE,wGAAA,UAAU,OAAA;AAAE,6GAAA,eAAe,OAAA;AACpC,2CAAyB;AACzB,6CAA2B;AAC3B,8CAA4B;AAC5B,6CAA2B;AAC3B,wCAAsB;AACtB,+BAA6C;AAAzB,gGAAA,QAAQ,OAAA;AAC5B,2CAAyB;AACzB,uCAAqB;AACrB,0CAAwB;AACxB,yCAAuB;AACvB,8CAA4B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,yCAAuB;AACvB,2CAA8E;AAArE,wGAAA,UAAU,OAAA;AAAE,6GAAA,eAAe,OAAA;AACpC,2CAAyB;AACzB,6CAA2B;AAC3B,8CAA4B;AAC5B,6CAA2B;AAC3B,wCAAsB;AACtB,+BAA6C;AAAzB,gGAAA,QAAQ,OAAA;AAC5B,2CAAyB;AACzB,uCAAqB;AACrB,0CAAwB;AACxB,yCAAuB;AACvB,8CAA4B;AAC5B,0CAAwB;AACxB,0CAAwB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interface Field {
|
|
2
|
+
set: Set<number>;
|
|
3
|
+
star: boolean;
|
|
4
|
+
}
|
|
5
|
+
interface Parsed {
|
|
6
|
+
sec: Field;
|
|
7
|
+
min: Field;
|
|
8
|
+
hour: Field;
|
|
9
|
+
dom: Field;
|
|
10
|
+
month: Field;
|
|
11
|
+
dow: Field;
|
|
12
|
+
hasSeconds: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Parses 5 fields (minute hour day-of-month month day-of-week) or 6 with leading seconds. */
|
|
15
|
+
export declare function parseCron(pattern: string): Parsed;
|
|
16
|
+
/**
|
|
17
|
+
* The first time after `from` (ms since epoch) that matches the pattern, in the given IANA time zone
|
|
18
|
+
* (default UTC). Throws on a bad pattern or when nothing matches within 8 years.
|
|
19
|
+
*/
|
|
20
|
+
export declare function nextCronTime(pattern: string, from: number, tz?: string): number;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseCron = parseCron;
|
|
4
|
+
exports.nextCronTime = nextCronTime;
|
|
5
|
+
const MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
6
|
+
const DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
|
|
7
|
+
function parseField(text, min, max, names, nameBase = 0) {
|
|
8
|
+
const set = new Set();
|
|
9
|
+
let star = false;
|
|
10
|
+
const value = (v) => {
|
|
11
|
+
const i = names ? names.indexOf(v.toLowerCase()) : -1;
|
|
12
|
+
const n = i >= 0 ? i + nameBase : Number(v);
|
|
13
|
+
if (!Number.isInteger(n))
|
|
14
|
+
throw new Error(`bad cron value "${v}"`);
|
|
15
|
+
return n;
|
|
16
|
+
};
|
|
17
|
+
for (const part of text.split(',')) {
|
|
18
|
+
const [range, stepText] = part.split('/');
|
|
19
|
+
const step = stepText === undefined ? 1 : Number(stepText);
|
|
20
|
+
if (!Number.isInteger(step) || step < 1)
|
|
21
|
+
throw new Error(`bad cron step in "${part}"`);
|
|
22
|
+
let lo;
|
|
23
|
+
let hi;
|
|
24
|
+
if (range === '*' || range === '?') {
|
|
25
|
+
lo = min;
|
|
26
|
+
hi = max;
|
|
27
|
+
if (stepText === undefined)
|
|
28
|
+
star = true;
|
|
29
|
+
}
|
|
30
|
+
else if (range.includes('-')) {
|
|
31
|
+
const [a, b] = range.split('-');
|
|
32
|
+
lo = value(a);
|
|
33
|
+
hi = value(b);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
lo = value(range);
|
|
37
|
+
hi = stepText === undefined ? lo : max;
|
|
38
|
+
}
|
|
39
|
+
if (lo < min || hi > max + (max === 6 ? 1 : 0) || lo > hi)
|
|
40
|
+
throw new Error(`cron value out of range in "${part}"`);
|
|
41
|
+
for (let v = lo; v <= hi; v += step)
|
|
42
|
+
set.add(v);
|
|
43
|
+
}
|
|
44
|
+
return { set, star };
|
|
45
|
+
}
|
|
46
|
+
/** Parses 5 fields (minute hour day-of-month month day-of-week) or 6 with leading seconds. */
|
|
47
|
+
function parseCron(pattern) {
|
|
48
|
+
const parts = pattern.trim().split(/\s+/);
|
|
49
|
+
if (parts.length !== 5 && parts.length !== 6)
|
|
50
|
+
throw new Error(`cron pattern needs 5 or 6 fields: "${pattern}"`);
|
|
51
|
+
const hasSeconds = parts.length === 6;
|
|
52
|
+
const f = hasSeconds ? parts : ['0', ...parts];
|
|
53
|
+
const dow = parseField(f[5], 0, 6, DAYS);
|
|
54
|
+
if (dow.set.has(7)) {
|
|
55
|
+
dow.set.delete(7);
|
|
56
|
+
dow.set.add(0);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
sec: parseField(f[0], 0, 59),
|
|
60
|
+
min: parseField(f[1], 0, 59),
|
|
61
|
+
hour: parseField(f[2], 0, 23),
|
|
62
|
+
dom: parseField(f[3], 1, 31),
|
|
63
|
+
month: parseField(f[4], 1, 12, MONTHS, 1),
|
|
64
|
+
dow,
|
|
65
|
+
hasSeconds,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const formatters = new Map();
|
|
69
|
+
function offsetMs(instant, tz) {
|
|
70
|
+
if (tz === 'UTC')
|
|
71
|
+
return 0;
|
|
72
|
+
let f = formatters.get(tz);
|
|
73
|
+
if (!f) {
|
|
74
|
+
f = new Intl.DateTimeFormat('en-US', {
|
|
75
|
+
timeZone: tz,
|
|
76
|
+
hourCycle: 'h23',
|
|
77
|
+
year: 'numeric',
|
|
78
|
+
month: 'numeric',
|
|
79
|
+
day: 'numeric',
|
|
80
|
+
hour: 'numeric',
|
|
81
|
+
minute: 'numeric',
|
|
82
|
+
second: 'numeric',
|
|
83
|
+
});
|
|
84
|
+
formatters.set(tz, f);
|
|
85
|
+
}
|
|
86
|
+
const p = {};
|
|
87
|
+
for (const part of f.formatToParts(new Date(instant)))
|
|
88
|
+
if (part.type !== 'literal')
|
|
89
|
+
p[part.type] = Number(part.value);
|
|
90
|
+
const asUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour % 24, p.minute, p.second);
|
|
91
|
+
return asUtc - Math.floor(instant / 1000) * 1000;
|
|
92
|
+
}
|
|
93
|
+
/** Local wall-clock time of `instant` in `tz`, expressed as if it were UTC. */
|
|
94
|
+
function toNaive(instant, tz) {
|
|
95
|
+
return instant + offsetMs(instant, tz);
|
|
96
|
+
}
|
|
97
|
+
function fromNaive(naive, tz) {
|
|
98
|
+
if (tz === 'UTC')
|
|
99
|
+
return naive;
|
|
100
|
+
const guess = naive - offsetMs(naive, tz);
|
|
101
|
+
return naive - offsetMs(guess, tz);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The first time after `from` (ms since epoch) that matches the pattern, in the given IANA time zone
|
|
105
|
+
* (default UTC). Throws on a bad pattern or when nothing matches within 8 years.
|
|
106
|
+
*/
|
|
107
|
+
function nextCronTime(pattern, from, tz = 'UTC') {
|
|
108
|
+
const c = parseCron(pattern);
|
|
109
|
+
const step = c.hasSeconds ? 1000 : 60_000;
|
|
110
|
+
let t = Math.floor(toNaive(from, tz) / step) * step + step;
|
|
111
|
+
const limit = t + 8 * 366 * 86_400_000;
|
|
112
|
+
while (t < limit) {
|
|
113
|
+
const d = new Date(t);
|
|
114
|
+
if (!c.month.set.has(d.getUTCMonth() + 1)) {
|
|
115
|
+
t = Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 1);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const domOk = c.dom.set.has(d.getUTCDate());
|
|
119
|
+
const dowOk = c.dow.set.has(d.getUTCDay());
|
|
120
|
+
const dayOk = !c.dom.star && !c.dow.star ? domOk || dowOk : domOk && dowOk;
|
|
121
|
+
if (!dayOk) {
|
|
122
|
+
t = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() + 1);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!c.hour.set.has(d.getUTCHours())) {
|
|
126
|
+
t = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours() + 1);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (!c.min.set.has(d.getUTCMinutes())) {
|
|
130
|
+
t = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes() + 1);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (!c.sec.set.has(d.getUTCSeconds())) {
|
|
134
|
+
t += 1000;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const result = fromNaive(t, tz);
|
|
138
|
+
if (result > from)
|
|
139
|
+
return result;
|
|
140
|
+
t += step;
|
|
141
|
+
}
|
|
142
|
+
throw new Error(`cron pattern "${pattern}" never matches`);
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=cron.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cron.js","sourceRoot":"","sources":["../../../src/core/queue/cron.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { SnugKvClient, SnugKvClientOptions } from '../client';
|
|
3
|
+
export interface QueueEventsOptions {
|
|
4
|
+
client?: SnugKvClient;
|
|
5
|
+
connection?: SnugKvClientOptions;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
/** Where to start reading: `$` (default) only new events, or a stream id. */
|
|
8
|
+
lastEventId?: string;
|
|
9
|
+
/** How long one blocking read waits, ms. Default 10000. */
|
|
10
|
+
blockingTimeout?: number;
|
|
11
|
+
autorun?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Listens to a queue's event stream: `added`, `waiting`, `delayed`, `active`, `progress`, `completed`,
|
|
15
|
+
* `failed`, `retries`, `stalled`, `removed`, `paused`, `resumed`, `duplicated`, `deduplicated`,
|
|
16
|
+
* `waiting-children`. Listeners get `(args, eventId)`; `args.jobId` names the job. Uses its own connection.
|
|
17
|
+
*/
|
|
18
|
+
export declare class QueueEvents extends EventEmitter {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
private readonly options;
|
|
21
|
+
readonly key: string;
|
|
22
|
+
private readonly read;
|
|
23
|
+
private closing;
|
|
24
|
+
private readyPromise?;
|
|
25
|
+
constructor(name: string, options?: QueueEventsOptions);
|
|
26
|
+
/** Resolves when events are being read. */
|
|
27
|
+
waitUntilReady(): Promise<void>;
|
|
28
|
+
run(): Promise<void>;
|
|
29
|
+
private start;
|
|
30
|
+
private pump;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueueEvents = void 0;
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
const node_events_1 = require("node:events");
|
|
6
|
+
const client_1 = require("../client");
|
|
7
|
+
const types_1 = require("./types");
|
|
8
|
+
/**
|
|
9
|
+
* Listens to a queue's event stream: `added`, `waiting`, `delayed`, `active`, `progress`, `completed`,
|
|
10
|
+
* `failed`, `retries`, `stalled`, `removed`, `paused`, `resumed`, `duplicated`, `deduplicated`,
|
|
11
|
+
* `waiting-children`. Listeners get `(args, eventId)`; `args.jobId` names the job. Uses its own connection.
|
|
12
|
+
*/
|
|
13
|
+
class QueueEvents extends node_events_1.EventEmitter {
|
|
14
|
+
name;
|
|
15
|
+
options;
|
|
16
|
+
key;
|
|
17
|
+
read;
|
|
18
|
+
closing = false;
|
|
19
|
+
readyPromise;
|
|
20
|
+
constructor(name, options = {}) {
|
|
21
|
+
super();
|
|
22
|
+
this.name = name;
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.key = `${options.prefix ?? 'snugq'}:{${name}}:events`;
|
|
25
|
+
this.read = options.client ? options.client.duplicate({ name: `queue-events:${name}` }) : new client_1.SnugKvClient({ ...options.connection, name: `queue-events:${name}` });
|
|
26
|
+
this.read.on('error', (e) => {
|
|
27
|
+
if (this.listenerCount('error') > 0)
|
|
28
|
+
this.emit('error', e);
|
|
29
|
+
});
|
|
30
|
+
if (options.autorun !== false)
|
|
31
|
+
void this.run().catch(() => undefined);
|
|
32
|
+
}
|
|
33
|
+
/** Resolves when events are being read. */
|
|
34
|
+
waitUntilReady() {
|
|
35
|
+
return this.run();
|
|
36
|
+
}
|
|
37
|
+
run() {
|
|
38
|
+
this.readyPromise ??= this.start();
|
|
39
|
+
return this.readyPromise;
|
|
40
|
+
}
|
|
41
|
+
async start() {
|
|
42
|
+
await this.read.connect(10_000);
|
|
43
|
+
let last = this.options.lastEventId ?? '$';
|
|
44
|
+
if (last === '$') {
|
|
45
|
+
const tail = (0, client_1.toEntries)(await this.read.call('XREVRANGE', this.key, '+', '-', 'COUNT', 1));
|
|
46
|
+
last = tail[0]?.id ?? '0-0';
|
|
47
|
+
}
|
|
48
|
+
void this.pump(last);
|
|
49
|
+
}
|
|
50
|
+
async pump(from) {
|
|
51
|
+
let last = from;
|
|
52
|
+
const block = this.options.blockingTimeout ?? 10_000;
|
|
53
|
+
while (!this.closing) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = await this.read.call('XREAD', 'COUNT', 100, 'BLOCK', block, 'STREAMS', this.key, last);
|
|
56
|
+
if (!Array.isArray(raw))
|
|
57
|
+
continue;
|
|
58
|
+
for (const stream of raw) {
|
|
59
|
+
if (!Array.isArray(stream))
|
|
60
|
+
continue;
|
|
61
|
+
for (const e of (0, client_1.toEntries)(stream[1] ?? null)) {
|
|
62
|
+
last = e.id;
|
|
63
|
+
const { event, ...rest } = e.fields;
|
|
64
|
+
if (!event)
|
|
65
|
+
continue;
|
|
66
|
+
this.emit(event, convert(event, rest), e.id);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (this.closing)
|
|
72
|
+
return;
|
|
73
|
+
if (this.listenerCount('error') > 0)
|
|
74
|
+
this.emit('error', err);
|
|
75
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async close() {
|
|
80
|
+
this.closing = true;
|
|
81
|
+
this.read.close();
|
|
82
|
+
this.removeAllListeners();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.QueueEvents = QueueEvents;
|
|
86
|
+
function convert(event, f) {
|
|
87
|
+
const out = { ...f };
|
|
88
|
+
if (event === 'completed')
|
|
89
|
+
out.returnvalue = (0, types_1.parseJson)(f.returnvalue);
|
|
90
|
+
if (event === 'progress')
|
|
91
|
+
out.data = (0, types_1.parseJson)(f.data);
|
|
92
|
+
if (f.delay !== undefined)
|
|
93
|
+
out.delay = Number(f.delay);
|
|
94
|
+
if (f.attemptsMade !== undefined)
|
|
95
|
+
out.attemptsMade = Number(f.attemptsMade);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/core/queue/events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { SnugKvClient, SnugKvClientOptions } from '../client';
|
|
2
|
+
import type { Job } from './job';
|
|
3
|
+
import { JobsOptions } from './types';
|
|
4
|
+
export interface FlowJob<D = any> {
|
|
5
|
+
name: string;
|
|
6
|
+
queueName: string;
|
|
7
|
+
data?: D;
|
|
8
|
+
opts?: JobsOptions;
|
|
9
|
+
/** Jobs that must finish before this one runs. */
|
|
10
|
+
children?: FlowJob[];
|
|
11
|
+
}
|
|
12
|
+
export interface JobNode {
|
|
13
|
+
job: Job;
|
|
14
|
+
children?: JobNode[];
|
|
15
|
+
}
|
|
16
|
+
export interface FlowProducerOptions {
|
|
17
|
+
client?: SnugKvClient;
|
|
18
|
+
connection?: SnugKvClientOptions;
|
|
19
|
+
prefix?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Adds a tree of jobs in one atomic step. A parent waits (state `waiting-children`) until all its
|
|
23
|
+
* children have finished, then runs; `job.getChildrenValues()` returns what the children returned.
|
|
24
|
+
*/
|
|
25
|
+
export declare class FlowProducer {
|
|
26
|
+
private readonly client;
|
|
27
|
+
private readonly owns;
|
|
28
|
+
private readonly prefix;
|
|
29
|
+
private readonly queues;
|
|
30
|
+
constructor(options?: FlowProducerOptions);
|
|
31
|
+
private queue;
|
|
32
|
+
add(flow: FlowJob): Promise<JobNode>;
|
|
33
|
+
addBulk(flows: FlowJob[]): Promise<JobNode[]>;
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FlowProducer = void 0;
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
const trace_1 = require("../trace");
|
|
6
|
+
const client_1 = require("../client");
|
|
7
|
+
const resp_1 = require("../resp");
|
|
8
|
+
const util_1 = require("../util");
|
|
9
|
+
const queue_1 = require("./queue");
|
|
10
|
+
const types_1 = require("./types");
|
|
11
|
+
/**
|
|
12
|
+
* Adds a tree of jobs in one atomic step. A parent waits (state `waiting-children`) until all its
|
|
13
|
+
* children have finished, then runs; `job.getChildrenValues()` returns what the children returned.
|
|
14
|
+
*/
|
|
15
|
+
class FlowProducer {
|
|
16
|
+
client;
|
|
17
|
+
owns;
|
|
18
|
+
prefix;
|
|
19
|
+
queues = new Map();
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.owns = !options.client;
|
|
22
|
+
this.client = options.client ?? new client_1.SnugKvClient(options.connection ?? {});
|
|
23
|
+
this.prefix = options.prefix ?? 'snugq';
|
|
24
|
+
}
|
|
25
|
+
queue(name) {
|
|
26
|
+
let q = this.queues.get(name);
|
|
27
|
+
if (!q) {
|
|
28
|
+
q = new queue_1.Queue(name, { client: this.client, prefix: this.prefix });
|
|
29
|
+
this.queues.set(name, q);
|
|
30
|
+
}
|
|
31
|
+
return q;
|
|
32
|
+
}
|
|
33
|
+
async add(flow) {
|
|
34
|
+
return (await this.addBulk([flow]))[0];
|
|
35
|
+
}
|
|
36
|
+
async addBulk(flows) {
|
|
37
|
+
if (!this.client.isReady)
|
|
38
|
+
await this.client.connect(10_000);
|
|
39
|
+
const commands = [];
|
|
40
|
+
const nodes = [];
|
|
41
|
+
const walk = (f, parent) => {
|
|
42
|
+
const q = this.queue(f.queueName);
|
|
43
|
+
const id = f.opts?.jobId ?? (0, util_1.uuid)();
|
|
44
|
+
const opts = { ...f.opts, jobId: id, parent: undefined };
|
|
45
|
+
commands.push(['FCALL', 'snugq_add', 1, q.base, ...(0, types_1.jobArgs)(f.name, (0, trace_1.withTrace)(f.data ?? null), opts, { parentBase: parent?.base, parentId: parent?.id, waitChildren: (f.children?.length ?? 0) > 0 })]);
|
|
46
|
+
const node = { job: (0, queue_1.localJob)(q, id, f.name, f.data ?? null, opts, parent) };
|
|
47
|
+
if (f.children?.length)
|
|
48
|
+
node.children = f.children.map((c) => walk(c, { base: q.base, id }));
|
|
49
|
+
return node;
|
|
50
|
+
};
|
|
51
|
+
for (const f of flows)
|
|
52
|
+
nodes.push(walk(f));
|
|
53
|
+
const replies = await this.client.execBlock(commands);
|
|
54
|
+
const bad = replies.find((r) => r instanceof resp_1.RespError);
|
|
55
|
+
if (bad)
|
|
56
|
+
throw bad;
|
|
57
|
+
return nodes;
|
|
58
|
+
}
|
|
59
|
+
async close() {
|
|
60
|
+
if (this.owns)
|
|
61
|
+
this.client.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.FlowProducer = FlowProducer;
|
|
65
|
+
//# sourceMappingURL=flow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow.js","sourceRoot":"","sources":["../../../src/core/queue/flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.parseCron = exports.nextCronTime = void 0;
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
__exportStar(require("./job"), exports);
|
|
20
|
+
__exportStar(require("./queue"), exports);
|
|
21
|
+
__exportStar(require("./worker"), exports);
|
|
22
|
+
__exportStar(require("./events"), exports);
|
|
23
|
+
__exportStar(require("./flow"), exports);
|
|
24
|
+
var cron_1 = require("./cron");
|
|
25
|
+
Object.defineProperty(exports, "nextCronTime", { enumerable: true, get: function () { return cron_1.nextCronTime; } });
|
|
26
|
+
Object.defineProperty(exports, "parseCron", { enumerable: true, get: function () { return cron_1.parseCron; } });
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/queue/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,wCAAsB;AACtB,0CAAwB;AACxB,2CAAyB;AACzB,2CAAyB;AACzB,yCAAuB;AACvB,+BAAiD;AAAxC,oGAAA,YAAY,OAAA;AAAE,iGAAA,SAAS,OAAA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { QueueEvents } from './events';
|
|
2
|
+
import { TraceContext } from '../trace';
|
|
3
|
+
import { JobsOptions, JobState, QueueLike } from './types';
|
|
4
|
+
/** A job as stored on the server. Fetched jobs are snapshots; call `getState()` or `Queue.getJob()` for fresh data. */
|
|
5
|
+
export declare class Job<D = any, R = any> {
|
|
6
|
+
readonly queue: QueueLike;
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
data: D;
|
|
10
|
+
readonly opts: JobsOptions;
|
|
11
|
+
readonly timestamp: number;
|
|
12
|
+
readonly delay: number;
|
|
13
|
+
readonly priority: number;
|
|
14
|
+
progress: any;
|
|
15
|
+
returnvalue: R | undefined;
|
|
16
|
+
failedReason: string | undefined;
|
|
17
|
+
stacktrace: string[];
|
|
18
|
+
attemptsMade: number;
|
|
19
|
+
stalledCounter: number;
|
|
20
|
+
processedOn: number | undefined;
|
|
21
|
+
finishedOn: number | undefined;
|
|
22
|
+
/** Lock token while a worker holds the job. */
|
|
23
|
+
token: string | undefined;
|
|
24
|
+
/** Scheduler that created this job. */
|
|
25
|
+
readonly schedulerId: string | undefined;
|
|
26
|
+
readonly parent: {
|
|
27
|
+
id: string;
|
|
28
|
+
queue: string;
|
|
29
|
+
} | undefined;
|
|
30
|
+
/** Children that have not finished. */
|
|
31
|
+
readonly pendingChildren: number;
|
|
32
|
+
/** Set by a worker. */
|
|
33
|
+
readonly raw: Record<string, string>;
|
|
34
|
+
/** Trace the job was added in, when tracing is on. */
|
|
35
|
+
readonly trace: TraceContext | undefined;
|
|
36
|
+
constructor(queue: QueueLike, id: string, raw: Record<string, string>);
|
|
37
|
+
static fromFlat(queue: QueueLike, id: string, flat: any[]): Job;
|
|
38
|
+
/** Where this job is now. */
|
|
39
|
+
getState(): Promise<JobState>;
|
|
40
|
+
updateProgress(progress: number | string | boolean | object): Promise<void>;
|
|
41
|
+
/** Adds a line to the job log. Returns the log length. */
|
|
42
|
+
log(line: string): Promise<number>;
|
|
43
|
+
updateData(data: D): Promise<void>;
|
|
44
|
+
/** Removes the job and its unfinished children. Returns false if a worker holds it. */
|
|
45
|
+
remove(): Promise<boolean>;
|
|
46
|
+
/** Puts a failed (or completed) job back in the queue. */
|
|
47
|
+
retry(state?: 'failed' | 'completed', opts?: {
|
|
48
|
+
resetAttemptsMade?: boolean;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
/** Runs a delayed job now. */
|
|
51
|
+
promote(): Promise<void>;
|
|
52
|
+
changeDelay(delayMs: number): Promise<void>;
|
|
53
|
+
changePriority(opts: {
|
|
54
|
+
priority: number;
|
|
55
|
+
lifo?: boolean;
|
|
56
|
+
}): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* From a processor: delay this job until `timestamp` (ms since epoch), then throw `new DelayedError()`.
|
|
59
|
+
* It does not use up an attempt.
|
|
60
|
+
*/
|
|
61
|
+
moveToDelayed(timestamp: number, token?: string): Promise<void>;
|
|
62
|
+
/** Log lines. */
|
|
63
|
+
getLogs(start?: number, end?: number): Promise<string[]>;
|
|
64
|
+
/** Results of finished children, keyed by `queue:jobId`. */
|
|
65
|
+
getChildrenValues(): Promise<Record<string, any>>;
|
|
66
|
+
/** Reasons of children that failed, keyed by `queue:jobId`. */
|
|
67
|
+
getFailedChildrenValues(): Promise<Record<string, string>>;
|
|
68
|
+
/** Resolves with the return value when the job completes; rejects when it fails. Needs a started {@link QueueEvents}. */
|
|
69
|
+
waitUntilFinished(events: QueueEvents, ttlMs?: number): Promise<R>;
|
|
70
|
+
toJSON(): Record<string, unknown>;
|
|
71
|
+
}
|