snugkv-nest 0.2.0 → 0.3.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 +38 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -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 +64 -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 +68 -0
- package/dist/core/queue/job.js +221 -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 +346 -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 +307 -0
- package/dist/core/queue/worker.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 +4 -0
- package/dist/nest/module.js +8 -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 +15 -2
- package/dist/nest/runtime.js.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -95,6 +95,43 @@ 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
|
+
|
|
98
135
|
## Messaging (streams)
|
|
99
136
|
|
|
100
137
|
## Quick start
|
|
@@ -267,4 +304,4 @@ Tests that need a server are skipped when `SNUGKV_BIN` is not set.
|
|
|
267
304
|
|
|
268
305
|
## License
|
|
269
306
|
|
|
270
|
-
MIT
|
|
307
|
+
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
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"}
|
|
@@ -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,64 @@
|
|
|
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 client_1 = require("../client");
|
|
6
|
+
const resp_1 = require("../resp");
|
|
7
|
+
const util_1 = require("../util");
|
|
8
|
+
const queue_1 = require("./queue");
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
/**
|
|
11
|
+
* Adds a tree of jobs in one atomic step. A parent waits (state `waiting-children`) until all its
|
|
12
|
+
* children have finished, then runs; `job.getChildrenValues()` returns what the children returned.
|
|
13
|
+
*/
|
|
14
|
+
class FlowProducer {
|
|
15
|
+
client;
|
|
16
|
+
owns;
|
|
17
|
+
prefix;
|
|
18
|
+
queues = new Map();
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.owns = !options.client;
|
|
21
|
+
this.client = options.client ?? new client_1.SnugKvClient(options.connection ?? {});
|
|
22
|
+
this.prefix = options.prefix ?? 'snugq';
|
|
23
|
+
}
|
|
24
|
+
queue(name) {
|
|
25
|
+
let q = this.queues.get(name);
|
|
26
|
+
if (!q) {
|
|
27
|
+
q = new queue_1.Queue(name, { client: this.client, prefix: this.prefix });
|
|
28
|
+
this.queues.set(name, q);
|
|
29
|
+
}
|
|
30
|
+
return q;
|
|
31
|
+
}
|
|
32
|
+
async add(flow) {
|
|
33
|
+
return (await this.addBulk([flow]))[0];
|
|
34
|
+
}
|
|
35
|
+
async addBulk(flows) {
|
|
36
|
+
if (!this.client.isReady)
|
|
37
|
+
await this.client.connect(10_000);
|
|
38
|
+
const commands = [];
|
|
39
|
+
const nodes = [];
|
|
40
|
+
const walk = (f, parent) => {
|
|
41
|
+
const q = this.queue(f.queueName);
|
|
42
|
+
const id = f.opts?.jobId ?? (0, util_1.uuid)();
|
|
43
|
+
const opts = { ...f.opts, jobId: id, parent: undefined };
|
|
44
|
+
commands.push(['FCALL', 'snugq_add', 1, q.base, ...(0, types_1.jobArgs)(f.name, f.data ?? null, opts, { parentBase: parent?.base, parentId: parent?.id, waitChildren: (f.children?.length ?? 0) > 0 })]);
|
|
45
|
+
const node = { job: (0, queue_1.localJob)(q, id, f.name, f.data ?? null, opts, parent) };
|
|
46
|
+
if (f.children?.length)
|
|
47
|
+
node.children = f.children.map((c) => walk(c, { base: q.base, id }));
|
|
48
|
+
return node;
|
|
49
|
+
};
|
|
50
|
+
for (const f of flows)
|
|
51
|
+
nodes.push(walk(f));
|
|
52
|
+
const replies = await this.client.execBlock(commands);
|
|
53
|
+
const bad = replies.find((r) => r instanceof resp_1.RespError);
|
|
54
|
+
if (bad)
|
|
55
|
+
throw bad;
|
|
56
|
+
return nodes;
|
|
57
|
+
}
|
|
58
|
+
async close() {
|
|
59
|
+
if (this.owns)
|
|
60
|
+
this.client.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.FlowProducer = FlowProducer;
|
|
64
|
+
//# 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,68 @@
|
|
|
1
|
+
import type { QueueEvents } from './events';
|
|
2
|
+
import { JobsOptions, JobState, QueueLike } from './types';
|
|
3
|
+
/** A job as stored on the server. Fetched jobs are snapshots; call `getState()` or `Queue.getJob()` for fresh data. */
|
|
4
|
+
export declare class Job<D = any, R = any> {
|
|
5
|
+
readonly queue: QueueLike;
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
data: D;
|
|
9
|
+
readonly opts: JobsOptions;
|
|
10
|
+
readonly timestamp: number;
|
|
11
|
+
readonly delay: number;
|
|
12
|
+
readonly priority: number;
|
|
13
|
+
progress: any;
|
|
14
|
+
returnvalue: R | undefined;
|
|
15
|
+
failedReason: string | undefined;
|
|
16
|
+
stacktrace: string[];
|
|
17
|
+
attemptsMade: number;
|
|
18
|
+
stalledCounter: number;
|
|
19
|
+
processedOn: number | undefined;
|
|
20
|
+
finishedOn: number | undefined;
|
|
21
|
+
/** Lock token while a worker holds the job. */
|
|
22
|
+
token: string | undefined;
|
|
23
|
+
/** Scheduler that created this job. */
|
|
24
|
+
readonly schedulerId: string | undefined;
|
|
25
|
+
readonly parent: {
|
|
26
|
+
id: string;
|
|
27
|
+
queue: string;
|
|
28
|
+
} | undefined;
|
|
29
|
+
/** Children that have not finished. */
|
|
30
|
+
readonly pendingChildren: number;
|
|
31
|
+
/** Set by a worker. */
|
|
32
|
+
readonly raw: Record<string, string>;
|
|
33
|
+
constructor(queue: QueueLike, id: string, raw: Record<string, string>);
|
|
34
|
+
static fromFlat(queue: QueueLike, id: string, flat: any[]): Job;
|
|
35
|
+
/** Where this job is now. */
|
|
36
|
+
getState(): Promise<JobState>;
|
|
37
|
+
updateProgress(progress: number | string | boolean | object): Promise<void>;
|
|
38
|
+
/** Adds a line to the job log. Returns the log length. */
|
|
39
|
+
log(line: string): Promise<number>;
|
|
40
|
+
updateData(data: D): Promise<void>;
|
|
41
|
+
/** Removes the job and its unfinished children. Returns false if a worker holds it. */
|
|
42
|
+
remove(): Promise<boolean>;
|
|
43
|
+
/** Puts a failed (or completed) job back in the queue. */
|
|
44
|
+
retry(state?: 'failed' | 'completed', opts?: {
|
|
45
|
+
resetAttemptsMade?: boolean;
|
|
46
|
+
}): Promise<void>;
|
|
47
|
+
/** Runs a delayed job now. */
|
|
48
|
+
promote(): Promise<void>;
|
|
49
|
+
changeDelay(delayMs: number): Promise<void>;
|
|
50
|
+
changePriority(opts: {
|
|
51
|
+
priority: number;
|
|
52
|
+
lifo?: boolean;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* From a processor: delay this job until `timestamp` (ms since epoch), then throw `new DelayedError()`.
|
|
56
|
+
* It does not use up an attempt.
|
|
57
|
+
*/
|
|
58
|
+
moveToDelayed(timestamp: number, token?: string): Promise<void>;
|
|
59
|
+
/** Log lines. */
|
|
60
|
+
getLogs(start?: number, end?: number): Promise<string[]>;
|
|
61
|
+
/** Results of finished children, keyed by `queue:jobId`. */
|
|
62
|
+
getChildrenValues(): Promise<Record<string, any>>;
|
|
63
|
+
/** Reasons of children that failed, keyed by `queue:jobId`. */
|
|
64
|
+
getFailedChildrenValues(): Promise<Record<string, string>>;
|
|
65
|
+
/** Resolves with the return value when the job completes; rejects when it fails. Needs a started {@link QueueEvents}. */
|
|
66
|
+
waitUntilFinished(events: QueueEvents, ttlMs?: number): Promise<R>;
|
|
67
|
+
toJSON(): Record<string, unknown>;
|
|
68
|
+
}
|