@zizq-labs/zizq 0.3.2 → 0.4.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.
- package/README.md +28 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.js +42 -0
- package/dist/handler.js +7 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/router.d.ts +102 -0
- package/dist/router.js +106 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -153,6 +153,34 @@ await client.enqueue({
|
|
|
153
153
|
});
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
For cross-language workflows or when you want explicit `type -> handler`
|
|
157
|
+
registration with optional fallback, use `Router`. Routes match by job
|
|
158
|
+
type (a string the producer agrees on with the consumer), and an
|
|
159
|
+
optional `fallback` catches anything unmatched:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { Router } from "@zizq-labs/zizq";
|
|
163
|
+
|
|
164
|
+
const router = new Router()
|
|
165
|
+
.route("send_email", async (payload, job) => {
|
|
166
|
+
await mailer.send(payload.to, payload.subject);
|
|
167
|
+
})
|
|
168
|
+
.route("generate_report", async (payload) => {
|
|
169
|
+
await reports.generate(payload.id);
|
|
170
|
+
})
|
|
171
|
+
.fallback(async (job) => {
|
|
172
|
+
console.warn(`Unhandled job type: ${job.type}`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const worker = new Worker({ client, handler: router.build() });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Routes overwrite on re-registration, which makes it natural to compose
|
|
179
|
+
routers — e.g. start from one that supplies defaults and selectively
|
|
180
|
+
override individual routes. If no route matches and no fallback is
|
|
181
|
+
registered, the router throws `UnknownJobTypeError`, which the worker
|
|
182
|
+
treats like any other handler failure (retries, eventually dead-lettered).
|
|
183
|
+
|
|
156
184
|
### Running a worker
|
|
157
185
|
|
|
158
186
|
A `Worker` streams jobs from the server, dispatches them through your
|
package/dist/client.d.ts
CHANGED
|
@@ -309,6 +309,27 @@ export declare class Client {
|
|
|
309
309
|
* ```
|
|
310
310
|
*/
|
|
311
311
|
deleteAllJobs(options?: DeleteAllJobsOptions): Promise<number>;
|
|
312
|
+
/**
|
|
313
|
+
* Wipe *every* cron group and *every* job on the server.
|
|
314
|
+
*
|
|
315
|
+
* Equivalent to calling {@link deleteAllCrons} followed by
|
|
316
|
+
* {@link deleteAllJobs} (no filter), but in a single request. Useful
|
|
317
|
+
* primarily as a setup/teardown step in tests where you want a
|
|
318
|
+
* known-empty server between scenarios.
|
|
319
|
+
*
|
|
320
|
+
* **Destructive.** No filters, no escape hatch, no confirmation —
|
|
321
|
+
* the server-side operation simply returns once everything is gone.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* await client.reset();
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
reset(): Promise<void>;
|
|
329
|
+
/**
|
|
330
|
+
* Alias for {@link reset}.
|
|
331
|
+
*/
|
|
332
|
+
eraseAllData(): Promise<void>;
|
|
312
333
|
/**
|
|
313
334
|
* Count jobs matching the given filters.
|
|
314
335
|
*
|
|
@@ -522,6 +543,17 @@ export declare class Client {
|
|
|
522
543
|
* @throws {NotFoundError} If the cron group is not found.
|
|
523
544
|
*/
|
|
524
545
|
deleteCronGroup(name: string): Promise<void>;
|
|
546
|
+
/**
|
|
547
|
+
* Delete every cron group on the server in a single call.
|
|
548
|
+
*
|
|
549
|
+
* Returns the number of cron groups removed.
|
|
550
|
+
*
|
|
551
|
+
* **Destructive.** This deletes *every cron group on the server*. For
|
|
552
|
+
* granular deletes, use {@link deleteCronGroup} with a specific name.
|
|
553
|
+
*
|
|
554
|
+
* Requires a Pro license on the server.
|
|
555
|
+
*/
|
|
556
|
+
deleteAllCrons(): Promise<number>;
|
|
525
557
|
/**
|
|
526
558
|
* Fetch a single cron entry within a group.
|
|
527
559
|
*
|
package/dist/client.js
CHANGED
|
@@ -367,6 +367,34 @@ export class Client {
|
|
|
367
367
|
const data = await this.handleResponse(await this.request("DELETE", path));
|
|
368
368
|
return data.deleted;
|
|
369
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Wipe *every* cron group and *every* job on the server.
|
|
372
|
+
*
|
|
373
|
+
* Equivalent to calling {@link deleteAllCrons} followed by
|
|
374
|
+
* {@link deleteAllJobs} (no filter), but in a single request. Useful
|
|
375
|
+
* primarily as a setup/teardown step in tests where you want a
|
|
376
|
+
* known-empty server between scenarios.
|
|
377
|
+
*
|
|
378
|
+
* **Destructive.** No filters, no escape hatch, no confirmation —
|
|
379
|
+
* the server-side operation simply returns once everything is gone.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* ```ts
|
|
383
|
+
* await client.reset();
|
|
384
|
+
* ```
|
|
385
|
+
*/
|
|
386
|
+
async reset() {
|
|
387
|
+
const res = await this.request("POST", "/reset");
|
|
388
|
+
if (res.statusCode !== 204) {
|
|
389
|
+
await this.throwOnError(res);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Alias for {@link reset}.
|
|
394
|
+
*/
|
|
395
|
+
async eraseAllData() {
|
|
396
|
+
return this.reset();
|
|
397
|
+
}
|
|
370
398
|
/**
|
|
371
399
|
* Count jobs matching the given filters.
|
|
372
400
|
*
|
|
@@ -670,6 +698,20 @@ export class Client {
|
|
|
670
698
|
await this.throwOnError(res);
|
|
671
699
|
}
|
|
672
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Delete every cron group on the server in a single call.
|
|
703
|
+
*
|
|
704
|
+
* Returns the number of cron groups removed.
|
|
705
|
+
*
|
|
706
|
+
* **Destructive.** This deletes *every cron group on the server*. For
|
|
707
|
+
* granular deletes, use {@link deleteCronGroup} with a specific name.
|
|
708
|
+
*
|
|
709
|
+
* Requires a Pro license on the server.
|
|
710
|
+
*/
|
|
711
|
+
async deleteAllCrons() {
|
|
712
|
+
const data = await this.handleResponse(await this.request("DELETE", "/crons"));
|
|
713
|
+
return data.deleted;
|
|
714
|
+
}
|
|
673
715
|
/**
|
|
674
716
|
* Fetch a single cron entry within a group.
|
|
675
717
|
*
|
package/dist/handler.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
|
|
2
2
|
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
import { Router } from "./router.js";
|
|
3
4
|
/**
|
|
4
5
|
* Build a `JobHandler` that dispatches incoming jobs to the matching
|
|
5
6
|
* function from an array, looking up by `zizqOptions.type` (or `.name`).
|
|
@@ -24,22 +25,18 @@
|
|
|
24
25
|
* functions resolve to the same type.
|
|
25
26
|
*/
|
|
26
27
|
export function buildHandler(jobs) {
|
|
27
|
-
const
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
const router = new Router();
|
|
28
30
|
for (const fn of jobs) {
|
|
29
31
|
const typeName = fn.zizqOptions?.type ?? fn.name;
|
|
30
32
|
if (!typeName) {
|
|
31
33
|
throw new Error("Job function must have a name or zizqOptions.type");
|
|
32
34
|
}
|
|
33
|
-
if (
|
|
35
|
+
if (seen.has(typeName)) {
|
|
34
36
|
throw new Error(`Duplicate job type registered: "${typeName}"`);
|
|
35
37
|
}
|
|
36
|
-
|
|
38
|
+
seen.add(typeName);
|
|
39
|
+
router.route(typeName, fn);
|
|
37
40
|
}
|
|
38
|
-
return
|
|
39
|
-
const fn = handlers.get(job.type);
|
|
40
|
-
if (!fn) {
|
|
41
|
-
throw new Error(`No handler registered for job type: ${job.type}`);
|
|
42
|
-
}
|
|
43
|
-
await fn(job.payload, job);
|
|
44
|
-
};
|
|
41
|
+
return router.build();
|
|
45
42
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export { Worker } from "./worker.ts";
|
|
|
6
6
|
export type { WorkerOptions, Logger, RequestRetryOptions } from "./worker.ts";
|
|
7
7
|
export { buildHandler } from "./handler.ts";
|
|
8
8
|
export type { JobFunction, JobHandler, ZizqOptions, EnqueueTransform, } from "./handler.ts";
|
|
9
|
+
export { Router, UnknownJobTypeError } from "./router.ts";
|
|
10
|
+
export type { RouteHandler } from "./router.ts";
|
|
9
11
|
export { Lazy, ErrorQuery, JobQuery } from "./query.ts";
|
|
10
12
|
export type { ErrorQueryOptions, JobQueryOptions } from "./query.ts";
|
|
11
13
|
export { enqueue, enqueueBulk } from "./enqueue.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
export { Client, Job, JobPage, ErrorRecord, ErrorPage, CronGroup, CronEntry, ZizqError, ConnectionError, ResponseError, ClientError, NotFoundError, ServerError, } from "./client.js";
|
|
4
4
|
export { Worker } from "./worker.js";
|
|
5
5
|
export { buildHandler } from "./handler.js";
|
|
6
|
+
export { Router, UnknownJobTypeError } from "./router.js";
|
|
6
7
|
export { Lazy, ErrorQuery, JobQuery } from "./query.js";
|
|
7
8
|
export { enqueue, enqueueBulk } from "./enqueue.js";
|
|
8
9
|
export { CronHandle, CronEntryHandle } from "./cron.js";
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-based job dispatch.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { Job } from "./resources.ts";
|
|
7
|
+
import type { JobHandler } from "./handler.ts";
|
|
8
|
+
/**
|
|
9
|
+
* Handler for a specific job type.
|
|
10
|
+
*
|
|
11
|
+
* Receives the unwrapped `payload` (the JSON body of the job) along with the
|
|
12
|
+
* full `Job` for access to metadata like `id`, `type`, or `attempts`. Most
|
|
13
|
+
* handlers will only need the payload — JS lets you omit the second arg.
|
|
14
|
+
*/
|
|
15
|
+
export type RouteHandler = (
|
|
16
|
+
/**
|
|
17
|
+
* The payload of the job to be performed, equivalent to `job.payload`.
|
|
18
|
+
*/
|
|
19
|
+
payload: unknown,
|
|
20
|
+
/**
|
|
21
|
+
* The full `Job` resource received from the server.
|
|
22
|
+
*/
|
|
23
|
+
job: Job) => Promise<void> | void;
|
|
24
|
+
/**
|
|
25
|
+
* Thrown when a job arrives with no registered route and no fallback.
|
|
26
|
+
*
|
|
27
|
+
* Caught by the worker's normal failure path: the job is nacked for retry,
|
|
28
|
+
* and eventually dead-lettered once the retry limit is hit.
|
|
29
|
+
*/
|
|
30
|
+
export declare class UnknownJobTypeError extends Error {
|
|
31
|
+
/**
|
|
32
|
+
* The name of the unhandled job type.
|
|
33
|
+
*/
|
|
34
|
+
readonly type: string;
|
|
35
|
+
constructor(type: string);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Dispatch jobs by `type` string to registered handler functions.
|
|
39
|
+
*
|
|
40
|
+
* Designed for cross-language workflows: payloads are plain JSON values
|
|
41
|
+
* (objects / arrays / strings / numbers), `type` is a string the producer
|
|
42
|
+
* agrees on with the consumer, and routes are registered explicitly.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* import { Worker, Router } from "@zizq-labs/zizq";
|
|
47
|
+
*
|
|
48
|
+
* const router = new Router()
|
|
49
|
+
* .route("send_email", async (payload, job) => {
|
|
50
|
+
* await mailer.send(payload.to);
|
|
51
|
+
* })
|
|
52
|
+
* .route("generate_report", async (payload) => {
|
|
53
|
+
* await reports.generate(payload.id);
|
|
54
|
+
* })
|
|
55
|
+
* .fallback(async (job) => {
|
|
56
|
+
* console.warn(`Unhandled job type: ${job.type}`);
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* const worker = new Worker({ client, handler: router.build() });
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* If no route matches and no fallback is registered, the dispatcher throws
|
|
63
|
+
* `UnknownJobTypeError`, which the worker treats as a normal job failure
|
|
64
|
+
* (retried per the backoff policy, eventually dead-lettered).
|
|
65
|
+
*/
|
|
66
|
+
export declare class Router {
|
|
67
|
+
private readonly routes;
|
|
68
|
+
private fallbackHandler;
|
|
69
|
+
/**
|
|
70
|
+
* Register `handler` for jobs whose `type` matches.
|
|
71
|
+
*
|
|
72
|
+
* Returns `this` for chaining.
|
|
73
|
+
*
|
|
74
|
+
* Re-registering a type replaces the previous handler. This supports
|
|
75
|
+
* builder-style composition — e.g. starting from a router that supplies
|
|
76
|
+
* defaults and selectively overriding individual routes.
|
|
77
|
+
*/
|
|
78
|
+
route(type: string, handler: RouteHandler): this;
|
|
79
|
+
/**
|
|
80
|
+
* Register a fallback handler invoked when no route matches.
|
|
81
|
+
*
|
|
82
|
+
* Receives the full `Job` (not split into a payload/job pair). Since a
|
|
83
|
+
* fallback has the same signature as a `JobHandler`, you can delegate
|
|
84
|
+
* straight to another router's compiled handler:
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* router.fallback(otherRouter.build());
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* Returns `this` for chaining. Calling `fallback` again replaces the
|
|
91
|
+
* previous handler.
|
|
92
|
+
*/
|
|
93
|
+
fallback(handler: JobHandler): this;
|
|
94
|
+
/**
|
|
95
|
+
* Compile this router into a `JobHandler` suitable for the worker.
|
|
96
|
+
*
|
|
97
|
+
* The returned function dispatches each incoming job: routes match by
|
|
98
|
+
* `job.type`, then the fallback (if any), otherwise throws
|
|
99
|
+
* `UnknownJobTypeError`.
|
|
100
|
+
*/
|
|
101
|
+
build(): JobHandler;
|
|
102
|
+
}
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Copyright (c) 2026 Chris Corbyn <chris@zizq.io>
|
|
2
|
+
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
|
+
/**
|
|
4
|
+
* Thrown when a job arrives with no registered route and no fallback.
|
|
5
|
+
*
|
|
6
|
+
* Caught by the worker's normal failure path: the job is nacked for retry,
|
|
7
|
+
* and eventually dead-lettered once the retry limit is hit.
|
|
8
|
+
*/
|
|
9
|
+
export class UnknownJobTypeError extends Error {
|
|
10
|
+
/**
|
|
11
|
+
* The name of the unhandled job type.
|
|
12
|
+
*/
|
|
13
|
+
type;
|
|
14
|
+
constructor(type) {
|
|
15
|
+
super(`No handler registered for job type "${type}"`);
|
|
16
|
+
this.name = "UnknownJobTypeError";
|
|
17
|
+
this.type = type;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Dispatch jobs by `type` string to registered handler functions.
|
|
22
|
+
*
|
|
23
|
+
* Designed for cross-language workflows: payloads are plain JSON values
|
|
24
|
+
* (objects / arrays / strings / numbers), `type` is a string the producer
|
|
25
|
+
* agrees on with the consumer, and routes are registered explicitly.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* import { Worker, Router } from "@zizq-labs/zizq";
|
|
30
|
+
*
|
|
31
|
+
* const router = new Router()
|
|
32
|
+
* .route("send_email", async (payload, job) => {
|
|
33
|
+
* await mailer.send(payload.to);
|
|
34
|
+
* })
|
|
35
|
+
* .route("generate_report", async (payload) => {
|
|
36
|
+
* await reports.generate(payload.id);
|
|
37
|
+
* })
|
|
38
|
+
* .fallback(async (job) => {
|
|
39
|
+
* console.warn(`Unhandled job type: ${job.type}`);
|
|
40
|
+
* });
|
|
41
|
+
*
|
|
42
|
+
* const worker = new Worker({ client, handler: router.build() });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* If no route matches and no fallback is registered, the dispatcher throws
|
|
46
|
+
* `UnknownJobTypeError`, which the worker treats as a normal job failure
|
|
47
|
+
* (retried per the backoff policy, eventually dead-lettered).
|
|
48
|
+
*/
|
|
49
|
+
export class Router {
|
|
50
|
+
routes = new Map();
|
|
51
|
+
fallbackHandler = null;
|
|
52
|
+
/**
|
|
53
|
+
* Register `handler` for jobs whose `type` matches.
|
|
54
|
+
*
|
|
55
|
+
* Returns `this` for chaining.
|
|
56
|
+
*
|
|
57
|
+
* Re-registering a type replaces the previous handler. This supports
|
|
58
|
+
* builder-style composition — e.g. starting from a router that supplies
|
|
59
|
+
* defaults and selectively overriding individual routes.
|
|
60
|
+
*/
|
|
61
|
+
route(type, handler) {
|
|
62
|
+
this.routes.set(type, handler);
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Register a fallback handler invoked when no route matches.
|
|
67
|
+
*
|
|
68
|
+
* Receives the full `Job` (not split into a payload/job pair). Since a
|
|
69
|
+
* fallback has the same signature as a `JobHandler`, you can delegate
|
|
70
|
+
* straight to another router's compiled handler:
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* router.fallback(otherRouter.build());
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* Returns `this` for chaining. Calling `fallback` again replaces the
|
|
77
|
+
* previous handler.
|
|
78
|
+
*/
|
|
79
|
+
fallback(handler) {
|
|
80
|
+
this.fallbackHandler = handler;
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Compile this router into a `JobHandler` suitable for the worker.
|
|
85
|
+
*
|
|
86
|
+
* The returned function dispatches each incoming job: routes match by
|
|
87
|
+
* `job.type`, then the fallback (if any), otherwise throws
|
|
88
|
+
* `UnknownJobTypeError`.
|
|
89
|
+
*/
|
|
90
|
+
build() {
|
|
91
|
+
const routes = this.routes;
|
|
92
|
+
const fallback = this.fallbackHandler;
|
|
93
|
+
return async (job) => {
|
|
94
|
+
const handler = routes.get(job.type);
|
|
95
|
+
if (handler) {
|
|
96
|
+
await handler(job.payload, job);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (fallback) {
|
|
100
|
+
await fallback(job);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
throw new UnknownJobTypeError(job.type);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|