@zizq-labs/zizq 0.4.1 → 0.5.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/dist/client.d.ts CHANGED
@@ -34,7 +34,7 @@ import { type Dispatcher } from "undici";
34
34
  import { Job, JobPage, ErrorRecord, ErrorPage, CronGroup, CronEntry } from "./resources.ts";
35
35
  import { JobQuery, type JobQueryOptions } from "./query.ts";
36
36
  import { CronHandle } from "./cron.ts";
37
- export type { JobStatus, SortDirection, UniqueScope, BackoffConfig, RetentionConfig, EnqueueOptions, EnqueueInput, FailureOptions, UpdateJobOptions, Format, TakeOptions, ListJobsOptions, ListErrorsOptions, UpdateAllJobsOptions, JobFilter, DeleteAllJobsOptions, CronEntryInput, ReplaceCronGroupOptions, } from "./types.ts";
37
+ export type { JobStatus, SortDirection, UniqueScope, BackoffConfig, RetentionConfig, EnqueueOptions, EnqueueInput, FailureOptions, UpdateJobOptions, Format, TakeOptions, ListJobsOptions, ListErrorsOptions, UpdateAllJobsOptions, JobFilter, DeleteAllJobsOptions, CronEntryInput, RangeBounds, RangeFilter, ReplaceCronGroupOptions, } from "./types.ts";
38
38
  import type { EnqueueOptions, EnqueueInput, FailureOptions, UpdateJobOptions, Format, TakeOptions, ListJobsOptions, ListErrorsOptions, UpdateAllJobsOptions, JobFilter, DeleteAllJobsOptions, CronEntryInput, ReplaceCronGroupOptions } from "./types.ts";
39
39
  export { Job, JobPage, ErrorRecord, ErrorPage, CronGroup, CronEntry, type JobData, type ErrorRecordData, type CronGroupData, type CronEntryData, } from "./resources.ts";
40
40
  /** Response shape for `GET /health`. */
@@ -111,6 +111,20 @@ export interface ClientOptions {
111
111
  * Default: 30000 (30s).
112
112
  */
113
113
  streamIdleTimeout?: number;
114
+ /**
115
+ * Maximum number of concurrent HTTP/2 streams per connection that
116
+ * undici will request from the server via the
117
+ * `SETTINGS_MAX_CONCURRENT_STREAMS` frame. The effective ceiling
118
+ * is the minimum of this value and what the server advertises in
119
+ * its own SETTINGS — so raising this client-side only helps if
120
+ * the server is also configured to allow more streams.
121
+ *
122
+ * Applies to the request/response pool only. The long-lived
123
+ * `take()` stream uses HTTP/1.1 and is unaffected.
124
+ *
125
+ * Default: 1024.
126
+ */
127
+ maxConcurrentStreams?: number;
114
128
  /** @internal For testing — override the HTTP dispatcher. */
115
129
  dispatcher?: Dispatcher;
116
130
  }
package/dist/client.js CHANGED
@@ -168,12 +168,24 @@ export class Client {
168
168
  } : {}),
169
169
  timeout: connectTimeout,
170
170
  };
171
- // HTTP/2 for request/response traffic (multiplexed acks, enqueues).
171
+ // HTTP/2 for request/response traffic (multiplexed acks,
172
+ // enqueues, queries). `useH2c: true` lets undici speak HTTP/2
173
+ // over cleartext via prior knowledge, so http:// URLs get h2
174
+ // multiplexing too — not just https://. The Zizq server's hyper
175
+ // stack already accepts h2 prior-knowledge on any connection
176
+ // via its auto-protocol detector.
177
+ //
172
178
  // `bodyTimeout` is per-chunk inactivity — reset on each byte —
173
179
  // so it acts as the read timeout; `headersTimeout` caps
174
180
  // "server accepted the request but didn't respond" cases.
175
181
  this.http = new Pool(this.url, {
176
182
  allowH2: true,
183
+ // `useH2c` is supported at runtime in undici >=8 but isn't
184
+ // present in the public type definitions yet. Remove the
185
+ // expect-error once undici ships the type.
186
+ // @ts-expect-error -- runtime-supported, untyped
187
+ useH2c: true,
188
+ maxConcurrentStreams: options.maxConcurrentStreams ?? 1024,
177
189
  connect: connectOpts,
178
190
  headersTimeout: readTimeout,
179
191
  bodyTimeout: readTimeout,
@@ -355,7 +367,7 @@ export class Client {
355
367
  async deleteAllJobs(options = {}) {
356
368
  assertOnlyKeys("deleteAllJobs", options, ["where"]);
357
369
  const where = options.where ?? {};
358
- assertOnlyKeys("deleteAllJobs.where", where, ["id", "status", "queue", "type", "filter"]);
370
+ assertOnlyKeys("deleteAllJobs.where", where, ["id", "status", "queue", "type", "filter", "priority", "readyAt", "attempts"]);
359
371
  // Build the filter params, then short-circuit if any array filter resolved
360
372
  // to empty — an empty filter matches nothing, and we don't want to
361
373
  // accidentally delete everything.
@@ -405,7 +417,7 @@ export class Client {
405
417
  * ```
406
418
  */
407
419
  async countJobs(where = {}) {
408
- assertOnlyKeys("countJobs", where, ["id", "status", "queue", "type", "filter"]);
420
+ assertOnlyKeys("countJobs", where, ["id", "status", "queue", "type", "filter", "priority", "readyAt", "attempts"]);
409
421
  const params = buildJobFilter(where);
410
422
  if (isEmptyFilter(params))
411
423
  return 0;
@@ -456,7 +468,7 @@ export class Client {
456
468
  async updateAllJobs(options) {
457
469
  assertOnlyKeys("updateAllJobs", options, ["where", "apply"]);
458
470
  const where = options.where ?? {};
459
- assertOnlyKeys("updateAllJobs.where", where, ["id", "status", "queue", "type", "filter"]);
471
+ assertOnlyKeys("updateAllJobs.where", where, ["id", "status", "queue", "type", "filter", "priority", "readyAt", "attempts"]);
460
472
  // Same empty-filter short-circuit as deleteAllJobs.
461
473
  const params = buildJobFilter(where);
462
474
  if (isEmptyFilter(params))
@@ -1047,6 +1059,42 @@ function isEmptyFilter(params) {
1047
1059
  params.get("queue") === "" ||
1048
1060
  params.get("type") === "");
1049
1061
  }
1062
+ /**
1063
+ * Encode a {@link RangeFilter} value into the server's query syntax.
1064
+ *
1065
+ * The four accepted shapes:
1066
+ *
1067
+ * - `number` (e.g. `50`) -> `"50"`
1068
+ * - `{ min, max }` -> `"min..max"`
1069
+ * - `{ min }` -> `"min.."`
1070
+ * - `{ max }` -> `"..max"`
1071
+ *
1072
+ * Returns `undefined` for `null`/`undefined` so the caller can skip
1073
+ * setting the param. Throws `TypeError` for any other shape (e.g. arrays,
1074
+ * strings, objects with unknown keys).
1075
+ */
1076
+ function encodeRange(value) {
1077
+ if (value == null)
1078
+ return undefined;
1079
+ if (typeof value === "number") {
1080
+ if (!Number.isFinite(value)) {
1081
+ throw new TypeError(`range filter value must be a finite number, got ${value}`);
1082
+ }
1083
+ return String(value);
1084
+ }
1085
+ if (typeof value !== "object" || Array.isArray(value)) {
1086
+ throw new TypeError(`range filter must be a number or { min?, max? } object, got ${typeof value}`);
1087
+ }
1088
+ const { min, max } = value;
1089
+ for (const [key, bound] of [["min", min], ["max", max]]) {
1090
+ if (bound != null && (typeof bound !== "number" || !Number.isFinite(bound))) {
1091
+ throw new TypeError(`range filter "${key}" must be a finite number, got ${typeof bound}`);
1092
+ }
1093
+ }
1094
+ const lo = min != null ? String(min) : "";
1095
+ const hi = max != null ? String(max) : "";
1096
+ return `${lo}..${hi}`;
1097
+ }
1050
1098
  /**
1051
1099
  * Populate a {@link URLSearchParams} with the standard job filter fields.
1052
1100
  */
@@ -1061,6 +1109,15 @@ function buildJobFilter(where, params = new URLSearchParams()) {
1061
1109
  params.set("type", toCommaList(where.type));
1062
1110
  if (where.filter != null)
1063
1111
  params.set("filter", where.filter);
1112
+ const priority = encodeRange(where.priority);
1113
+ if (priority != null)
1114
+ params.set("priority", priority);
1115
+ const readyAt = encodeRange(where.readyAt);
1116
+ if (readyAt != null)
1117
+ params.set("ready_at", readyAt);
1118
+ const attempts = encodeRange(where.attempts);
1119
+ if (attempts != null)
1120
+ params.set("attempts", attempts);
1064
1121
  return params;
1065
1122
  }
1066
1123
  /**
package/dist/query.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  *
20
20
  * @module
21
21
  */
22
- import type { Client, JobStatus, SortDirection, UpdateJobOptions } from "./client.ts";
22
+ import type { Client, JobStatus, RangeFilter, SortDirection, UpdateJobOptions } from "./client.ts";
23
23
  import type { Job, JobPage, ErrorRecord, ErrorPage } from "./resources.ts";
24
24
  /**
25
25
  * Base class for lazy, async-iterable collections.
@@ -73,6 +73,12 @@ export interface JobQueryOptions {
73
73
  status?: JobStatus | JobStatus[];
74
74
  /** jq expression applied to the payload. */
75
75
  jqFilter?: string;
76
+ /** Priority filter — exact value or `{min, max}` inclusive range. */
77
+ priority?: RangeFilter;
78
+ /** `readyAt` filter (ms since epoch) — exact value or `{min, max}` range. */
79
+ readyAt?: RangeFilter;
80
+ /** Failure count filter — exact value or `{min, max}` inclusive range. */
81
+ attempts?: RangeFilter;
76
82
  /** Sort order. Default: "asc" (oldest first). */
77
83
  order?: SortDirection;
78
84
  /** Maximum total number of jobs to return across all pages. */
@@ -145,6 +151,9 @@ export declare class JobQuery extends Lazy<Job> {
145
151
  private _type?;
146
152
  private _status?;
147
153
  private _jqFilter?;
154
+ private _priority?;
155
+ private _readyAt?;
156
+ private _attempts?;
148
157
  private _order?;
149
158
  private _limit?;
150
159
  private _pageSize?;
@@ -172,6 +181,47 @@ export declare class JobQuery extends Lazy<Job> {
172
181
  * Add a jq payload filter. Combines with any existing filter via `and`.
173
182
  */
174
183
  addJqFilter(jqFilter: string): JobQuery;
184
+ /**
185
+ * Filter by priority. Replaces any existing priority filter.
186
+ *
187
+ * Accepts an exact value (`50`) or a `{min, max}` range with inclusive
188
+ * bounds. Omit either side for an unbounded end. Lower numbers are
189
+ * higher priority.
190
+ *
191
+ * @example
192
+ * client.jobs().byPriority(0); // exact match
193
+ * client.jobs().byPriority({ min: 0, max: 100 }); // bounded range
194
+ * client.jobs().byPriority({ min: 100 }); // 100 or higher
195
+ * client.jobs().byPriority({ max: 100 }); // 100 or lower
196
+ */
197
+ byPriority(priority: RangeFilter | undefined): JobQuery;
198
+ /**
199
+ * Filter by `readyAt` (ms since epoch). Replaces any existing filter.
200
+ *
201
+ * Accepts an exact value or a `{min, max}` range with inclusive bounds.
202
+ * Use `Date.prototype.getTime()` to derive ms from a `Date` instance.
203
+ *
204
+ * @example
205
+ * // Eligible to run by now.
206
+ * client.jobs().byReadyAt({ max: Date.now() });
207
+ *
208
+ * // Within the next hour.
209
+ * client.jobs().byReadyAt({ min: Date.now(), max: Date.now() + 3_600_000 });
210
+ */
211
+ byReadyAt(readyAt: RangeFilter | undefined): JobQuery;
212
+ /**
213
+ * Filter by failure count. Replaces any existing attempts filter.
214
+ *
215
+ * Accepts an exact value or a `{min, max}` range with inclusive bounds.
216
+ * `0` selects jobs that have never failed; `{ min: 1 }` selects anything
217
+ * that has failed at least once.
218
+ *
219
+ * @example
220
+ * client.jobs().byAttempts(0); // never failed
221
+ * client.jobs().byAttempts({ min: 1 }); // has failed
222
+ * client.jobs().byAttempts({ min: 1, max: 3 }); // 1, 2, or 3 failures
223
+ */
224
+ byAttempts(attempts: RangeFilter | undefined): JobQuery;
175
225
  /**
176
226
  * Constrain results by an exact payload match.
177
227
  *
package/dist/query.js CHANGED
@@ -161,6 +161,9 @@ export class JobQuery extends Lazy {
161
161
  _type;
162
162
  _status;
163
163
  _jqFilter;
164
+ _priority;
165
+ _readyAt;
166
+ _attempts;
164
167
  _order;
165
168
  _limit;
166
169
  _pageSize;
@@ -173,6 +176,9 @@ export class JobQuery extends Lazy {
173
176
  this._type = options.type;
174
177
  this._status = options.status;
175
178
  this._jqFilter = options.jqFilter;
179
+ this._priority = options.priority;
180
+ this._readyAt = options.readyAt;
181
+ this._attempts = options.attempts;
176
182
  this._order = options.order;
177
183
  this._limit = options.limit;
178
184
  this._pageSize = options.pageSize;
@@ -222,6 +228,53 @@ export class JobQuery extends Lazy {
222
228
  jqFilter: concat(this._jqFilter, `(${jqFilter})`).join(" and "),
223
229
  });
224
230
  }
231
+ /**
232
+ * Filter by priority. Replaces any existing priority filter.
233
+ *
234
+ * Accepts an exact value (`50`) or a `{min, max}` range with inclusive
235
+ * bounds. Omit either side for an unbounded end. Lower numbers are
236
+ * higher priority.
237
+ *
238
+ * @example
239
+ * client.jobs().byPriority(0); // exact match
240
+ * client.jobs().byPriority({ min: 0, max: 100 }); // bounded range
241
+ * client.jobs().byPriority({ min: 100 }); // 100 or higher
242
+ * client.jobs().byPriority({ max: 100 }); // 100 or lower
243
+ */
244
+ byPriority(priority) {
245
+ return this.rebuild({ priority });
246
+ }
247
+ /**
248
+ * Filter by `readyAt` (ms since epoch). Replaces any existing filter.
249
+ *
250
+ * Accepts an exact value or a `{min, max}` range with inclusive bounds.
251
+ * Use `Date.prototype.getTime()` to derive ms from a `Date` instance.
252
+ *
253
+ * @example
254
+ * // Eligible to run by now.
255
+ * client.jobs().byReadyAt({ max: Date.now() });
256
+ *
257
+ * // Within the next hour.
258
+ * client.jobs().byReadyAt({ min: Date.now(), max: Date.now() + 3_600_000 });
259
+ */
260
+ byReadyAt(readyAt) {
261
+ return this.rebuild({ readyAt });
262
+ }
263
+ /**
264
+ * Filter by failure count. Replaces any existing attempts filter.
265
+ *
266
+ * Accepts an exact value or a `{min, max}` range with inclusive bounds.
267
+ * `0` selects jobs that have never failed; `{ min: 1 }` selects anything
268
+ * that has failed at least once.
269
+ *
270
+ * @example
271
+ * client.jobs().byAttempts(0); // never failed
272
+ * client.jobs().byAttempts({ min: 1 }); // has failed
273
+ * client.jobs().byAttempts({ min: 1, max: 3 }); // 1, 2, or 3 failures
274
+ */
275
+ byAttempts(attempts) {
276
+ return this.rebuild({ attempts });
277
+ }
225
278
  /**
226
279
  * Constrain results by an exact payload match.
227
280
  *
@@ -453,6 +506,9 @@ export class JobQuery extends Lazy {
453
506
  type: this._type,
454
507
  status: this._status,
455
508
  filter: this._jqFilter,
509
+ priority: this._priority,
510
+ readyAt: this._readyAt,
511
+ attempts: this._attempts,
456
512
  order: this._order,
457
513
  limit: effectivePageSize,
458
514
  });
@@ -474,6 +530,9 @@ export class JobQuery extends Lazy {
474
530
  type: this._type,
475
531
  status: this._status,
476
532
  filter: this._jqFilter,
533
+ priority: this._priority,
534
+ readyAt: this._readyAt,
535
+ attempts: this._attempts,
477
536
  };
478
537
  }
479
538
  rebuild(overrides) {
@@ -483,6 +542,9 @@ export class JobQuery extends Lazy {
483
542
  type: this._type,
484
543
  status: this._status,
485
544
  jqFilter: this._jqFilter,
545
+ priority: this._priority,
546
+ readyAt: this._readyAt,
547
+ attempts: this._attempts,
486
548
  order: this._order,
487
549
  limit: this._limit,
488
550
  pageSize: this._pageSize,
package/dist/types.d.ts CHANGED
@@ -161,6 +161,32 @@ export interface TakeOptions {
161
161
  /** AbortSignal to cancel the streaming connection. */
162
162
  signal?: AbortSignal;
163
163
  }
164
+ /**
165
+ * A range with inclusive bounds. Omit either side for an unbounded end.
166
+ *
167
+ * Used by the `priority`, `readyAt`, and `attempts` filter fields. An
168
+ * empty object (`{}`) is treated as a fully unbounded range.
169
+ */
170
+ export interface RangeBounds {
171
+ /** Lower bound, inclusive. Omit for no lower bound. */
172
+ min?: number;
173
+ /** Upper bound, inclusive. Omit for no upper bound. */
174
+ max?: number;
175
+ }
176
+ /**
177
+ * A range filter that matches either a single value or a span of values.
178
+ *
179
+ * Pass a bare `number` for an exact match. Pass an object with `min` and/or
180
+ * `max` for a range — both bounds are **inclusive**. Omit either side for
181
+ * an unbounded end.
182
+ *
183
+ * @example
184
+ * priority: 50 // exactly 50
185
+ * priority: { min: 0, max: 100 } // 0..100 inclusive
186
+ * priority: { min: 50 } // 50 or higher
187
+ * priority: { max: 100 } // 100 or lower
188
+ */
189
+ export type RangeFilter = number | RangeBounds;
164
190
  /** Options for listing jobs with cursor-based pagination. */
165
191
  export interface ListJobsOptions {
166
192
  /** Cursor: start after this job ID (exclusive). */
@@ -177,6 +203,21 @@ export interface ListJobsOptions {
177
203
  type?: string | string[];
178
204
  /** Filter by job ID. Accepts a single value or an array. */
179
205
  id?: string | string[];
206
+ /**
207
+ * Filter by priority. Accepts an exact value or a `{min, max}` range
208
+ * with inclusive bounds. Lower numbers are higher priority.
209
+ */
210
+ priority?: RangeFilter;
211
+ /**
212
+ * Filter by `readyAt` (milliseconds since the Unix epoch). Accepts an
213
+ * exact value or a `{min, max}` range with inclusive bounds.
214
+ */
215
+ readyAt?: RangeFilter;
216
+ /**
217
+ * Filter by failure count. Accepts an exact value or a `{min, max}`
218
+ * range with inclusive bounds. `0` selects jobs that have never failed.
219
+ */
220
+ attempts?: RangeFilter;
180
221
  /** jq expression to filter jobs by payload. */
181
222
  filter?: string;
182
223
  }
@@ -211,6 +252,21 @@ export interface JobFilter {
211
252
  queue?: string | string[];
212
253
  /** Filter by job type. Accepts a single value or an array. */
213
254
  type?: string | string[];
255
+ /**
256
+ * Filter by priority. Accepts an exact value or a `{min, max}` range
257
+ * with inclusive bounds. Lower numbers are higher priority.
258
+ */
259
+ priority?: RangeFilter;
260
+ /**
261
+ * Filter by `readyAt` (milliseconds since the Unix epoch). Accepts an
262
+ * exact value or a `{min, max}` range with inclusive bounds.
263
+ */
264
+ readyAt?: RangeFilter;
265
+ /**
266
+ * Filter by failure count. Accepts an exact value or a `{min, max}`
267
+ * range with inclusive bounds. `0` selects jobs that have never failed.
268
+ */
269
+ attempts?: RangeFilter;
214
270
  /** jq expression to filter jobs by payload. */
215
271
  filter?: string;
216
272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zizq-labs/zizq",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Node.js client for the Zizq job queue server",
5
5
  "homepage": "https://zizq.io",
6
6
  "repository": {
@@ -46,6 +46,6 @@
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^25.5.2",
49
- "typescript": "^5.8.0"
49
+ "typescript": "^6.0.3"
50
50
  }
51
51
  }