@zizq-labs/zizq 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 CHANGED
@@ -17,9 +17,28 @@ This is the official Zizq client library for Node.js, written in TypeScript.
17
17
  * Scheduled jobs
18
18
  * Configurable backoff policies
19
19
  * Configurable job retention policies
20
+ * Recurring jobs (cron)
20
21
  * Job introspection and management APIs, with support for `jq` query filters
21
22
  * Unique jobs
22
23
 
24
+ ## Installation
25
+
26
+ > [!NOTE]
27
+ > If you have not yet installed the Zizq server, follow the
28
+ > [Getting Started](https://zizq.io/docs/getting-started) guide first.
29
+
30
+ Install it with your package manager of choice:
31
+
32
+ ``` shell
33
+ npm install @zizq-labs/zizq
34
+ ```
35
+
36
+ Or:
37
+
38
+ ```shell
39
+ yarn add @zizq-labs/zizq
40
+ ```
41
+
23
42
  ## Example
24
43
 
25
44
  > [!TIP]
package/dist/client.d.ts CHANGED
@@ -31,309 +31,12 @@
31
31
  * @module
32
32
  */
33
33
  import { type Dispatcher } from "undici";
34
- import { Job, JobPage, ErrorRecord, ErrorPage } from "./resources.ts";
34
+ import { Job, JobPage, ErrorRecord, ErrorPage, CronGroup, CronEntry } from "./resources.ts";
35
35
  import { JobQuery, type JobQueryOptions } from "./query.ts";
36
- /** Lifecycle status of a job. */
37
- export type JobStatus = "ready" | "in_flight" | "scheduled" | "completed" | "dead";
38
- /** Sort direction for paginated listings. */
39
- export type SortDirection = "asc" | "desc";
40
- /** Uniqueness scope for deduplication. */
41
- export type UniqueScope = "queued" | "active" | "exists";
42
- export { Job, JobPage, ErrorRecord, ErrorPage, type JobData, type ErrorRecordData } from "./resources.ts";
43
- /**
44
- * Backoff configuration for retry delays.
45
- *
46
- * This is used in the following formula:
47
- *
48
- * ```
49
- * t = baseMs + (attempts ** exponent) + (attempts * random() * jitterMs)
50
- * ```
51
- *
52
- * The random jitter is designed to ensure clusters of failed jobs do not all
53
- * retry at the same time but are instead randomly spread out.
54
- */
55
- export interface BackoffConfig {
56
- /** Base delay in milliseconds, applied to all retries. */
57
- baseMs: number;
58
- /** Backoff curve steepness (attempts ** exponent). */
59
- exponent: number;
60
- /** Maximum random jitter in milliseconds per attempt multiplier. */
61
- jitterMs: number;
62
- }
63
- /**
64
- * Retention configuration controlling how long jobs in terminal statuses are kept.
65
- *
66
- * The terminal statuses are "completed" and "dead".
67
- */
68
- export interface RetentionConfig {
69
- /** How long completed jobs remain visible (ms). `null` clears to server default. */
70
- completedMs?: number | null;
71
- /** How long dead jobs remain visible (ms). `null` clears to server default. */
72
- deadMs?: number | null;
73
- }
74
- /**
75
- * Options for enqueueing a single job.
76
- *
77
- * @example
78
- * ```ts
79
- * await client.enqueue({
80
- * type: "generate_report",
81
- * queue: "reports",
82
- * payload: { reportId: 42 },
83
- * priority: 100, // optional, lower = higher priority
84
- * readyAt: Date.now() + 60000, // optional, delay by 1 minute
85
- * });
86
- * ```
87
- */
88
- export interface EnqueueOptions {
89
- /** Job type identifier. */
90
- type: string;
91
- /**
92
- * Target queue name.
93
- *
94
- * Must be valid UTF-8 and must not contain any of the following reserved
95
- * characters: ",", "?", "*", "[", "]", "{", "}", "!".
96
- */
97
- queue: string;
98
- /**
99
- * Arbitrary payload delivered to the worker.
100
- */
101
- payload: unknown;
102
- /**
103
- * Optional priority (lower = higher priority).
104
- *
105
- * Valid range is 0 to 65536. Default: 32768.
106
- */
107
- priority?: number;
108
- /**
109
- * Optional timestamp (ms since epoch) when the job becomes eligible.
110
- *
111
- * When set to a future timestamp the job is created in the "scheduled"
112
- * status. Otherwise the job is created in the "ready" status.
113
- */
114
- readyAt?: number;
115
- /**
116
- * Optional per-job retry limit.
117
- *
118
- * When not set the server default value applies.
119
- */
120
- retryLimit?: number;
121
- /** Optional per-job backoff configuration. */
122
- backoff?: BackoffConfig;
123
- /** Optional per-job retention configuration. */
124
- retention?: RetentionConfig;
125
- /**
126
- * Optional unique key for enqueue-time deduplication.
127
- *
128
- * Requires a pro license on the server.
129
- *
130
- * The key is global across all queues and job types. Prefix with the job
131
- * type to make it unique per job type.
132
- */
133
- uniqueKey?: string;
134
- /**
135
- * Uniqueness scope. Only valid when `uniqueKey` is set.
136
- *
137
- * When set to "queued" other jobs with the same key will not be enqueued as
138
- * long as this job is in the "scheduled" or "ready" statuses.
139
- *
140
- * When set to "active" other jobs with the same key will not be enqueued
141
- * while this job is in the "scheduled", "ready" or "in_flight" statuses.
142
- *
143
- * When set to "exists" other jobs with the same key will not be enqueued
144
- * for as long as this job remains on the server (i.e. until it is eventually
145
- * reaped, based on the retention policy).
146
- */
147
- uniqueWhile?: UniqueScope;
148
- }
149
- /** Options for reporting a job failure. */
150
- export interface FailureOptions {
151
- /** Error message describing what went wrong. */
152
- message: string;
153
- /** Optional error class name, e.g. "TimeoutError". */
154
- errorType?: string;
155
- /** Optional stack trace from the worker. */
156
- backtrace?: string;
157
- /** Optional forced retry time (ms since epoch), bypassing backoff. */
158
- retryAt?: number;
159
- /**
160
- * Kill the job immediately regardless of retry limit.
161
- *
162
- * Note: passing false does nothing.
163
- */
164
- kill?: boolean;
165
- }
166
- /**
167
- * Options for the streaming take endpoint.
168
- *
169
- * Returns an async generator which never terminates as long as the connection
170
- * to the server remains open. Clients should use `break` to explicitly
171
- * disconnect from the endpoint and stop receiving jobs.
172
- *
173
- * When no jobs are available, the generator waits until new jobs are enqueued.
174
- *
175
- * @example
176
- * ```ts
177
- * // Take up to 10 jobs at a time from specific queues
178
- * for await (const job of client.take({ prefetch: 10, queues: ["emails", "webhooks"] })) {
179
- * // process job...
180
- * }
181
- * ```
182
- */
183
- export interface TakeOptions {
184
- /**
185
- * Maximum number of "in_flight", unacknowledged jobs the server will send.
186
- *
187
- * The default is 1, meaning the client must acknowledge or fail the job
188
- * before the server sends the next, and so on.
189
- */
190
- prefetch?: number;
191
- /** Only take jobs from these queues. Empty means all queues. */
192
- queues?: string[];
193
- /** AbortSignal to cancel the streaming connection. */
194
- signal?: AbortSignal;
195
- }
196
- /**
197
- * Options for listing jobs with cursor-based pagination.
198
- *
199
- * @example
200
- * ```ts
201
- * const page = await client.listJobs({
202
- * queue: "emails",
203
- * status: ["ready", "in_flight"],
204
- * limit: 20,
205
- * });
206
- * ```
207
- */
208
- export interface ListJobsOptions {
209
- /** Cursor: start after this job ID (exclusive). */
210
- from?: string;
211
- /** Sort order. Default: "asc" (oldest first). */
212
- order?: SortDirection;
213
- /** Maximum number of jobs per page (1–2000, default 50). */
214
- limit?: number;
215
- /** Filter by status. Accepts a single value or an array. */
216
- status?: JobStatus | JobStatus[];
217
- /** Filter by queue name. Accepts a single value or an array. */
218
- queue?: string | string[];
219
- /** Filter by job type. Accepts a single value or an array. */
220
- type?: string | string[];
221
- /** Filter by job ID. Accepts a single value or an array. */
222
- id?: string | string[];
223
- /** jq expression to filter jobs by payload. */
224
- filter?: string;
225
- }
226
- /**
227
- * Options for listing error records for a job.
228
- *
229
- * @example
230
- * ```ts
231
- * const page = await client.listErrors("job-id", { order: "desc", limit: 10 });
232
- * ```
233
- */
234
- export interface ListErrorsOptions {
235
- /** Cursor: start after this attempt number (exclusive). */
236
- from?: number;
237
- /** Sort order. Default: "asc" (oldest first). */
238
- order?: SortDirection;
239
- /** Maximum number of error records per page (1–2000, default 50). */
240
- limit?: number;
241
- }
242
- /**
243
- * Mutable fields for updating a job.
244
- *
245
- * Field semantics:
246
- * - **omitted or `undefined`** — leave unchanged
247
- * - **`null`** — clear the field (only valid for nullable fields)
248
- * - **a value** — update to that value
249
- *
250
- * Nullable fields: `readyAt`, `retryLimit`, `backoff`, `retention`.
251
- * Non-nullable fields: `queue`, `priority`.
252
- *
253
- * @example
254
- * ```ts
255
- * // Change priority and clear retry limit (use server default)
256
- * await client.updateJob("job-id", {
257
- * priority: 100,
258
- * retryLimit: null,
259
- * });
260
- * ```
261
- */
262
- export interface UpdateJobOptions {
263
- /** Move the job to a different queue. */
264
- queue?: string;
265
- /** Change the job's priority. */
266
- priority?: number;
267
- /** Change when the job becomes ready (ms since epoch). `null` clears to immediately ready. */
268
- readyAt?: number | null;
269
- /** Override the retry limit. `null` clears to server default. */
270
- retryLimit?: number | null;
271
- /** Override the backoff config. `null` clears to server default. */
272
- backoff?: BackoffConfig | null;
273
- /** Override the retention config. `null` clears to server default. */
274
- retention?: RetentionConfig | null;
275
- }
276
- /**
277
- * Options for bulk-updating jobs.
278
- *
279
- * Has two parts:
280
- * - `where` — filter selecting which jobs to update (same as `deleteAllJobs`)
281
- * - `apply` — fields to update on the matching jobs (same as `updateJob`)
282
- *
283
- * An empty array filter (e.g. `where.id: []`) short-circuits to 0 jobs updated.
284
- *
285
- * @example
286
- * ```ts
287
- * // Lower the priority of all dead jobs in the emails queue
288
- * const count = await client.updateAllJobs({
289
- * where: { queue: "emails", status: "dead" },
290
- * apply: { priority: 1000 },
291
- * });
292
- * ```
293
- */
294
- export interface UpdateAllJobsOptions {
295
- /** Filter selecting which jobs to update. */
296
- where?: JobFilter;
297
- /** Fields to update on the matching jobs. */
298
- apply: UpdateJobOptions;
299
- }
300
- /**
301
- * Filter for selecting jobs in bulk operations.
302
- *
303
- * Used by `deleteAllJobs` and `updateAllJobs` to scope which jobs are
304
- * affected. An empty filter selects all jobs.
305
- *
306
- * Multi-value fields accept either a single value or an array.
307
- */
308
- export interface JobFilter {
309
- /** Filter by job ID. Accepts a single value or an array. */
310
- id?: string | string[];
311
- /** Filter by status. Accepts a single value or an array. */
312
- status?: JobStatus | JobStatus[];
313
- /** Filter by queue name. Accepts a single value or an array. */
314
- queue?: string | string[];
315
- /** Filter by job type. Accepts a single value or an array. */
316
- type?: string | string[];
317
- /** jq expression to filter jobs by payload. */
318
- filter?: string;
319
- }
320
- /**
321
- * Options for bulk-deleting jobs.
322
- *
323
- * An empty `where` filter deletes all jobs.
324
- *
325
- * @example
326
- * ```ts
327
- * // Delete all dead jobs in the emails queue
328
- * const count = await client.deleteAllJobs({
329
- * where: { queue: "emails", status: "dead" },
330
- * });
331
- * ```
332
- */
333
- export interface DeleteAllJobsOptions {
334
- /** Filter selecting which jobs to delete. */
335
- where?: JobFilter;
336
- }
36
+ import { CronHandle } from "./cron.ts";
37
+ export type { JobStatus, SortDirection, UniqueScope, BackoffConfig, RetentionConfig, EnqueueOptions, FailureOptions, UpdateJobOptions, Format, TakeOptions, ListJobsOptions, ListErrorsOptions, UpdateAllJobsOptions, JobFilter, DeleteAllJobsOptions, CronEntryInput, ReplaceCronGroupOptions, } from "./types.ts";
38
+ import type { EnqueueOptions, FailureOptions, UpdateJobOptions, Format, TakeOptions, ListJobsOptions, ListErrorsOptions, UpdateAllJobsOptions, JobFilter, DeleteAllJobsOptions, CronEntryInput, ReplaceCronGroupOptions } from "./types.ts";
39
+ export { Job, JobPage, ErrorRecord, ErrorPage, CronGroup, CronEntry, type JobData, type ErrorRecordData, type CronGroupData, type CronEntryData, } from "./resources.ts";
337
40
  /** Response shape for `GET /health`. */
338
41
  interface HealthResponse {
339
42
  status: string;
@@ -367,7 +70,6 @@ export interface TlsOptions {
367
70
  key?: string | Buffer;
368
71
  }
369
72
  /** Serialization format for client-server communication. */
370
- export type Format = "json" | "msgpack";
371
73
  /** Options for constructing a {@link Client}. */
372
74
  export interface ClientOptions {
373
75
  /** Base URL of the Zizq server, e.g. "http://localhost:7890". */
@@ -522,7 +224,7 @@ export declare class Client {
522
224
  *
523
225
  * @param id - The job ID to fetch.
524
226
  * @returns The full job data including payload.
525
- * @throws {ZizqError} If the job is not found (404).
227
+ * @throws {NotFoundError} If the job is not found (404).
526
228
  */
527
229
  getJob(id: string): Promise<Job>;
528
230
  /**
@@ -693,6 +395,154 @@ export declare class Client {
693
395
  * @throws {NotFoundError} If the job or attempt is not found.
694
396
  */
695
397
  getError(id: string, attempt: number): Promise<ErrorRecord>;
398
+ /**
399
+ * List all cron group names.
400
+ *
401
+ * Requires a Pro license on the server.
402
+ *
403
+ * @example
404
+ * ```ts
405
+ * const groups = await client.listCronGroups();
406
+ * // ["default", "billing"]
407
+ * ```
408
+ */
409
+ listCronGroups(): Promise<string[]>;
410
+ /**
411
+ * Fetch a cron group and all its entries.
412
+ *
413
+ * Requires a Pro license on the server.
414
+ *
415
+ * @param name - The name of the cron group
416
+ * @returns The cron group record
417
+ * @throws {NotFoundError} If the cron group is not found.
418
+ */
419
+ getCronGroup(name: string): Promise<CronGroup>;
420
+ /**
421
+ * Create or replace an entire cron group.
422
+ *
423
+ * Entries absent from the request are removed. Entries with unchanged
424
+ * expressions preserve their scheduling state.
425
+ *
426
+ * Requires a Pro license on the server.
427
+ *
428
+ * @example
429
+ * ```ts
430
+ * const group = await client.replaceCronGroup("default", {
431
+ * entries: [
432
+ * { name: "send-reminders", expression: "0 9 * * *",
433
+ * job: { type: "send_reminders", queue: "emails", payload: {} } },
434
+ * ],
435
+ * });
436
+ * ```
437
+ */
438
+ replaceCronGroup(name: string, options: ReplaceCronGroupOptions): Promise<CronGroup>;
439
+ /**
440
+ * Update group-level fields.
441
+ *
442
+ * Currently only used for pause/resume.
443
+ *
444
+ * Requires a Pro license on the server.
445
+ *
446
+ * @param name - The name of the cron group
447
+ * @param options - The new state of the cron group
448
+ * @returns The cron group record
449
+ * @throws {NotFoundError} If the cron group is not found.
450
+ */
451
+ updateCronGroup(name: string, options: {
452
+ paused: boolean;
453
+ }): Promise<CronGroup>;
454
+ /**
455
+ * Delete a cron group and all its entries.
456
+ *
457
+ * Requires a Pro license on the server.
458
+ *
459
+ * @param name - The name of the cron group
460
+ * @throws {NotFoundError} If the cron group is not found.
461
+ */
462
+ deleteCronGroup(name: string): Promise<void>;
463
+ /**
464
+ * Fetch a single cron entry within a group.
465
+ *
466
+ * Requires a Pro license on the server.
467
+ *
468
+ * @param group - The name of the cron group
469
+ * @param entry - The name of the entry within the group
470
+ * @returns The cron entry record
471
+ * @throws {NotFoundError} If the cron entry is not found.
472
+ */
473
+ getCronEntry(group: string, entry: string): Promise<CronEntry>;
474
+ /**
475
+ * Add a single entry to a cron group.
476
+ *
477
+ * Creates the group if needed. Throws a 409 conflict error if an entry
478
+ * with the same name already exists.
479
+ *
480
+ * Requires a Pro license on the server.
481
+ *
482
+ * @example
483
+ * ```ts
484
+ * const entry = await client.addCronEntry("default", {
485
+ * name: "weekly-report",
486
+ * expression: "0 9 * * MON",
487
+ * job: { type: "generate_report", queue: "reports", payload: { format: "pdf" } },
488
+ * });
489
+ * ```
490
+ *
491
+ * @throws {ClientError} If the cron entry already exists (409)
492
+ */
493
+ addCronEntry(group: string, entry: CronEntryInput): Promise<CronEntry>;
494
+ /**
495
+ * Create or replace a single cron entry within a group.
496
+ *
497
+ * Creates the group if needed.
498
+ *
499
+ * Requires a Pro license on the server.
500
+ *
501
+ * @param group - The name of the cron group
502
+ * @param entry - The entry definition to create or replace
503
+ * @returns The cron entry record
504
+ */
505
+ replaceCronEntry(group: string, entryName: string, entry: Omit<CronEntryInput, "name">): Promise<CronEntry>;
506
+ /**
507
+ * Update cron entry-level fields.
508
+ *
509
+ * Currently only used for pause/resume.
510
+ *
511
+ * Requires a Pro license on the server.
512
+ *
513
+ * @param group - The name of the cron group
514
+ * @param entry - The name of the entry within the group
515
+ * @param options - The new state of the cron group
516
+ * @returns The cron entry record
517
+ * @throws {NotFoundError} If the cron entry is not found.
518
+ */
519
+ updateCronEntry(group: string, entry: string, options: {
520
+ paused: boolean;
521
+ }): Promise<CronEntry>;
522
+ /**
523
+ * Delete a single cron entry within a group.
524
+ *
525
+ * Requires a Pro license on the server.
526
+ *
527
+ * @param group - The name of the cron group
528
+ * @param entry - The name of the entry within the group
529
+ * @throws {NotFoundError} If the cron entry is not found.
530
+ */
531
+ deleteCronEntry(group: string, entry: string): Promise<void>;
532
+ /**
533
+ * Get a handle for a cron group (schedule).
534
+ *
535
+ * No API call is made until you invoke a method on the handle.
536
+ *
537
+ * @example
538
+ * ```ts
539
+ * const cron = client.cron("default");
540
+ * await cron.register({ entries: [...] });
541
+ * await cron.pause();
542
+ * await cron.entry("sync").pause();
543
+ * ```
544
+ */
545
+ cron(name: string): CronHandle;
696
546
  /**
697
547
  * Connect to the streaming take endpoint and return an async generator
698
548
  * of jobs.
@@ -746,6 +596,7 @@ export declare class Client {
746
596
  private request;
747
597
  private post;
748
598
  private patch;
599
+ private put;
749
600
  private requestWithBody;
750
601
  /** Encode a value in the configured format. */
751
602
  private encode;