@owlmeans/queue 0.1.18-rc.22 → 0.1.18-rc.24

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
@@ -6,8 +6,8 @@ than about five seconds, accepted work must survive a process restart, or the wo
6
6
  against a third party (payment capture, provider sync, bulk mail, report generation, model
7
7
  pipelines). A request that finishes in under about two seconds and makes no external call should
8
8
  not queue: do it inline. Check for a smaller fix first, such as an index, a batch write or a cached
9
- aggregate. The framework has no scheduler, so recurring work is triggered by a platform cron that
10
- calls an entrypoint or enqueues a job.
9
+ aggregate. Recurring work is a declared schedule: the broker produces a job on an interval or a
10
+ cron pattern and a registered processor runs it.
11
11
 
12
12
  This package is contracts only and carries no broker code. A driver implements them
13
13
  (`@owlmeans/redis-queue` does, over BullMQ). Depend on this package from a shared contract package,
@@ -16,7 +16,7 @@ and on the driver only where the application wires itself up.
16
16
  ## Installation
17
17
 
18
18
  ```bash
19
- bun add @owlmeans/queue@^0.1.18-rc.22
19
+ bun add @owlmeans/queue@^0.1.18-rc.24
20
20
  ```
21
21
 
22
22
  ## Concepts
@@ -26,8 +26,8 @@ bun add @owlmeans/queue@^0.1.18-rc.22
26
26
  address, not a deployment.
27
27
  - **Listen**: `listenQueues(cfg, ...names)` in one process's own config says which queues *that
28
28
  process* consumes. A process that listens to nothing is a producer only.
29
- - **Queued protocol**: an immutable entrypoint protocol whose route uses `job()` instead of
30
- `backend()` (`RouteProtocols.QUEUE`). Callers use `ctx.entrypoint(protocol).call(...)` unchanged,
29
+ - **Queued protocol**: an immutable backend entrypoint protocol whose route uses `job()` from this
30
+ package (`QUEUE_PROTOCOL`). Callers use `ctx.entrypoint(protocol).call(...)` unchanged,
31
31
  and the QUEUE transport carries the call through the broker.
32
32
  - **Job record**: `ctx.jobs(queue)` returns a `QueueResource`, the ordinary `Resource<JobRecord>`
33
33
  contract plus `wait`, `flow`, `counts` and `close`. `create` enqueues, `list` inspects and `take`
@@ -36,6 +36,9 @@ bun add @owlmeans/queue@^0.1.18-rc.22
36
36
  a `JobContext`. It must be safe to run twice and must `touch()` in long loops.
37
37
  - **Single-flight id**: a `JobOptions.id` derived from what the job is about (`develop:<storyId>`).
38
38
  A duplicate enqueue then returns the existing job instead of a second one.
39
+ - **Schedule**: `declareSchedule(cfg, { id, queue, name, every | pattern, … })` in the shared config.
40
+ Every process that listens to the queue reconciles the broker with the declared schedules when its
41
+ worker starts; each run is an ordinary job, and `JobContext.scheduled` names the schedule.
39
42
 
40
43
  ## Usage
41
44
 
@@ -77,7 +80,8 @@ appendRedisQueue(context, { hooks: billingQueueHooks(context) })
77
80
 
78
81
  ```ts
79
82
  import { contract, protocol, typed } from '@owlmeans/entrypoint'
80
- import { backend, job, route } from '@owlmeans/route'
83
+ import { job } from '@owlmeans/queue'
84
+ import { backend, route } from '@owlmeans/route'
81
85
 
82
86
  const aliases = { base: 'app:report', build: 'app:report:build' } as const
83
87
  const reportBase = protocol(route(aliases.base, '/reports', backend({ service: APP_WORKER })), contract())
@@ -155,6 +159,41 @@ await context.jobs(APP_OPS).flow({
155
159
  })
156
160
  ```
157
161
 
162
+ ### Recurring jobs
163
+
164
+ ```ts
165
+ import { declareQueue, declareSchedule } from '@owlmeans/queue'
166
+
167
+ // Shared config: the queue first, then its schedules.
168
+ declareQueue(cfg, APP_MAINTENANCE, ['app:maintenance:reconcile', 'app:maintenance:purge'], {
169
+ worker: { concurrency: 1, lockDuration: 60_000 }
170
+ })
171
+ declareSchedule(cfg, {
172
+ id: 'app-nightly-reconcile', queue: APP_MAINTENANCE, name: 'app:maintenance:reconcile',
173
+ pattern: '17 3 * * *', tz: 'UTC', data: {},
174
+ })
175
+ declareSchedule(cfg, {
176
+ id: 'app-purge', queue: APP_MAINTENANCE, name: 'app:maintenance:purge', every: 15 * 60_000,
177
+ })
178
+ ```
179
+
180
+ The process that `listenQueues(cfg, APP_MAINTENANCE)` registers the processors; nothing else is
181
+ wired. A scheduled run carries no caller and no credentials, so it goes to a processor, never to a
182
+ guarded entrypoint:
183
+
184
+ ```ts
185
+ worker.process(APP_MAINTENANCE, 'app:maintenance:reconcile', async job => {
186
+ for await (const page of organizations()) {
187
+ await job.touch()
188
+ await reconcile(page) // safe to run twice: a restart re-runs the whole sweep
189
+ }
190
+ return { scheduled: job.scheduled } // 'app-nightly-reconcile'
191
+ })
192
+ ```
193
+
194
+ Renaming a schedule id removes the old scheduler and creates a new one; deleting a declaration
195
+ removes its scheduler at the next worker start.
196
+
158
197
  ### Jobs are records
159
198
 
160
199
  ```ts
@@ -197,6 +236,9 @@ not the job, so read the job back to learn what became of it.
197
236
  | `declareQueue(cfg, name, jobs, opts?)` | function | Declare a queue and the job names it accepts; re-declaring replaces |
198
237
  | `listenQueues(cfg, ...names)` | function | Name the queues this process consumes |
199
238
  | `queueOf(cfg, name)`, `queueOfJob(cfg, job)`, `isListening(cfg, name)` | function | Read declarations back; `queueOf` throws `UnknownQueue` |
239
+ | `declareSchedule(cfg, schedule)` | function | Declare a recurring job after checking it; re-declaring an id replaces |
240
+ | `schedulesOf(cfg, queue?)` | function | The declared schedules, of one queue when named |
241
+ | `assertSchedule(cfg, schedule)`, `assertSchedules(cfg)` | function | Check one schedule, or every declared one plus duplicate ids |
200
242
  | `enqueueProtocol(ctx, protocol, request, options?)` | function | Enqueue a QUEUE protocol with `JobOptions`, typed by the protocol |
201
243
  | `waitForProtocol(ctx, protocol, job, { timeout }?)` | function | Wait for that job and unwrap the typed reply |
202
244
  | `queueJobOf(request)` | function | `QueueJobMeta` of the broker job behind a handler's request, or `null` |
@@ -204,11 +246,11 @@ not the job, so read the job back to learn what became of it.
204
246
  | `queueWorkerMiddleware(alias?)` | function | Start the worker at the Ready stage, only in a process that listens |
205
247
  | `handleJob(ctx, job)`, `entrypointProcessor(ctx)`, `servedJobs(ctx)` | function | The bridge a driver dispatches entrypoint jobs through |
206
248
  | `requestOf(envelope)`, `assertFresh(envelope, ttl?)` | function | Rebuild a request from an envelope; enforce `envelopeTtl` |
207
- | `QueueConfig`, `QueueDeclaration`, `QueueWorkerOptions`, `JobOptions` | type | Configuration shapes |
249
+ | `QueueConfig`, `QueueDeclaration`, `QueueWorkerOptions`, `JobOptions`, `ScheduleDeclaration` | type | Configuration shapes |
208
250
  | `QueueResource<D, R>` | type | A queue as a resource, plus `queue`, `wait`, `flow`, `counts`, `close` |
209
251
  | `JobRecord<D, R>`, `JobEvent<R>`, `FlowSpec<D>` | type | A job as a record, a lifecycle event, a graph node |
210
252
  | `QueueWorkerService` | type | `process`, `start`, `stop`, `listening`, `hooks` |
211
- | `JobContext<D>`, `JobProcessor<D, R>` | type | What a processor receives; the processor signature |
253
+ | `JobContext<D>`, `JobProcessor<D, R>` | type | What a processor receives (`scheduled` names the schedule of a scheduled run); the processor signature |
212
254
  | `QueueHooks` | type | `wrapHandler`, `onJobResult`, `onJobStalled`, `onJobDead` |
213
255
  | `QueueJobMeta`, `JobEnvelope`, `JobReply<T>` | type | Broker identity for a handler, the call envelope, the reply shape |
214
256
  | `QueueAppend`, `QueueDriver` | type | The `ctx.jobs(queue?)` mixin a driver installs; what a driver supplies |
@@ -216,7 +258,7 @@ not the job, so read the job back to learn what became of it.
216
258
  | `JobState`, `JobEventType` | enum | Job lifecycle states and event types |
217
259
  | `isSettled(state?)` | function | Whether a state is final |
218
260
  | `DEFAULT_ALIAS`, `DEFAULT_JOB_TIMEOUT`, `DEFAULT_ATTEMPTS` | const | `'queue'`, `60_000` ms, `1` |
219
- | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired` | class | Error family |
261
+ | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired`, `ScheduleMisdeclared` | class | Error family |
220
262
 
221
263
  ## Common pitfalls
222
264
 
@@ -233,13 +275,19 @@ not the job, so read the job back to learn what became of it.
233
275
  - **Long loops without `touch()`**, or processors that are not safe to run twice.
234
276
  - **Throwing from a child for a domain failure.** Return `{ ok: false, error }` instead.
235
277
  - **Equal child ids across queues in one flow.** `children()` is keyed by bare id and collapses them.
236
- - **A self-re-enqueueing job as a cron.** Use a platform `CronJob`. `delay` defers a single job once.
278
+ - **A self-re-enqueueing job as a cron.** Declare a schedule. `delay` defers a single job once.
279
+ - **A schedule on a queue nothing consumes.** Only a listening worker creates the scheduler, and the
280
+ broker produces each next run when the previous one starts.
281
+ - **Loading different schedule lists in two processes that listen to one queue.** Each start removes
282
+ the schedules its own list does not name.
283
+ - **Declaring a schedule before its queue.** `declareSchedule` checks the queue and the job name and
284
+ throws `UnknownQueue` / `UnknownJobName`.
237
285
  - **Calling `ctx.jobs()` with no name once a second queue exists.** It throws `UnknownQueue`.
238
286
 
239
287
  ## Related packages
240
288
 
241
289
  - [`@owlmeans/redis-queue`](../redis-queue): the BullMQ driver, and where queue integration tests live
242
- - [`@owlmeans/route`](../route): `RouteProtocols.QUEUE` and the `job()` builder
290
+ - [`@owlmeans/route`](../route): transport-neutral route declarations used by the queue-owned `job()` builder
243
291
  - [`@owlmeans/entrypoint`](../entrypoint): protocol declarations and the transport seam
244
292
  - [`@owlmeans/resource`](../resource): the `Resource` contract jobs are read through
245
293
  - [`@owlmeans/server-job`](../server-job) / [`@owlmeans/client-job`](../client-job): listing,
@@ -253,7 +301,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
253
301
  your project's skill store (`.agents/skills/`):
254
302
 
255
303
  ```sh
256
- npx @owlmeans/agent-skills@^0.1.18-rc.27
304
+ npx @owlmeans/agent-skills@^0.1.18-rc.30
257
305
  ```
258
306
 
259
307
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,10 +1,17 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/queue",
4
- "version": "0.1.18-rc.22",
5
- "generatedAt": "2026-09-15T13:19:14.375Z",
4
+ "version": "0.1.18-rc.24",
5
+ "generatedAt": "2026-09-19T13:42:56.226Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
+ {
9
+ "kind": "skill",
10
+ "name": "scheduled-jobs",
11
+ "category": "multi-package",
12
+ "file": "skills/scheduled-jobs/SKILL.md",
13
+ "canonicalPath": ".agents/skills/scheduled-jobs/SKILL.md"
14
+ },
8
15
  {
9
16
  "kind": "skill",
10
17
  "name": "queue",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: queue
3
- description: How to use @owlmeans/queue — protocol-object enqueue/wait, job queues as resources, QUEUE declarations, producer/consumer split, single-flight job ids, lifecycle hooks, and processor rules. Auto-invoked when importing queue types or declaring a job protocol.
3
+ description: How to use @owlmeans/queue — protocol-object enqueue/wait, job queues as resources, QUEUE declarations, producer/consumer split, single-flight job ids, recurring schedules, lifecycle hooks, and processor rules. Auto-invoked when importing queue types, declaring a job protocol, or declaring a schedule.
4
4
  user-invocable: false
5
5
  ---
6
6
  <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
@@ -8,19 +8,20 @@ user-invocable: false
8
8
  # @owlmeans/queue
9
9
 
10
10
  **Layer:** Infra
11
- **Install:** `"@owlmeans/queue": "^0.1.18-rc.22"` in `dependencies`
11
+ **Install:** `"@owlmeans/queue": "^0.1.18-rc.24"` in `dependencies`
12
12
 
13
- Contracts only. It carries no broker code — a driver package (`@owlmeans/redis-queue`) implements
14
- them. Depend on this one from a shared contract package; depend on the driver only where the
13
+ Backend transport contracts only. It carries no broker code — a driver package
14
+ (`@owlmeans/redis-queue`) implements them. Depend on this one from a shared backend package;
15
+ generic common and frontend packages must not import it. Depend on the driver only where the
15
16
  application wires itself up.
16
17
 
17
18
  ## The one idea
18
19
 
19
- A queued call is an immutable entrypoint protocol whose route names `RouteProtocols.QUEUE`.
20
+ A queued call is an immutable entrypoint protocol whose route names `QUEUE_PROTOCOL`.
20
21
  Ordinary calls use `ctx.entrypoint(protocol).call(request)`. Code that needs broker options or a
21
22
  job handle uses `enqueueProtocol(ctx, protocol, request, options)` and
22
23
  `waitForProtocol(ctx, protocol, job)`. Both derive queue and job name from the protocol object and
23
- preserve its exact request/reply pair; raw strings and non-QUEUE declarations are rejected.
24
+ preserve its exact request/reply pair; raw strings and non-queue declarations are rejected.
24
25
 
25
26
  That is what makes it worth having: moving a service-to-service call onto the broker is a change
26
27
  to a declaration, not to the places that call it, and the broker holds the work across a restart of
@@ -60,6 +61,9 @@ protocol(
60
61
  )
61
62
  ```
62
63
 
64
+ Import `job`, `QUEUE_PROTOCOL`, `isQueueRoute` and `queueRouteOptions` from `@owlmeans/queue`.
65
+ The generic route package deliberately knows none of the queue option shape.
66
+
63
67
  The alias appears in `declareQueue(...jobs)` only because the broker is a string-keyed adapter.
64
68
  Application producers and handlers keep the protocol object.
65
69
 
@@ -142,6 +146,43 @@ run twice — skip work already recorded, delete the rows a previous attempt cre
142
146
  regenerating over a baseline. Decide this per job and say so in a comment; there is no way to make
143
147
  it automatic.
144
148
 
149
+ ## Recurring jobs
150
+
151
+ A recurring sweep is a SCHEDULE on a queue, run by a registered processor:
152
+
153
+ ```typescript
154
+ declareQueue(cfg, APP_MAINTENANCE, ['app:maintenance:reconcile'], {
155
+ worker: { concurrency: 1, lockDuration: 60_000 }
156
+ })
157
+ declareSchedule(cfg, {
158
+ id: 'app-nightly-reconcile', queue: APP_MAINTENANCE, name: 'app:maintenance:reconcile',
159
+ pattern: '17 3 * * *', tz: 'UTC', data: {},
160
+ })
161
+ ```
162
+
163
+ - **Declare it in the shared package, after its queue.** `declareSchedule` runs `assertSchedule`
164
+ first: the queue must be declared (`UnknownQueue`) and must accept the job name
165
+ (`UnknownJobName`), exactly one of `every` (a positive integer of milliseconds) and `pattern`
166
+ (cron), `tz` and `immediately` only with a pattern, never `immediately` with `startDate`, and
167
+ `opts.id` / `opts.delay` refused — the broker names and places every run
168
+ (`ScheduleMisdeclared`, message `<id>:<reason>`). Re-declaring an id replaces it.
169
+ - **The id is the identity.** Changing any other field updates the schedule in place; renaming the
170
+ id is one schedule removed and another created.
171
+ - **Every process that listens to the queue reconciles it at worker start** — the driver creates
172
+ and updates what is declared and removes the schedules it owns that are not. Two listeners of one
173
+ queue must therefore load the same schedule list, which is why it lives in the shared package.
174
+ A producer creates nothing.
175
+ - **A scheduled run is a processor job, never a guarded entrypoint.** Nobody's credentials travel
176
+ with it. `job.scheduled` carries the schedule id; it is absent on a job enqueued any other way.
177
+ - **It needs a consumer.** The broker produces the next run when the previous one starts
178
+ processing, so a schedule on a queue nothing consumes stops producing.
179
+ - **The processor obeys every rule above** — `touch()` through the sweep, and safe to run twice,
180
+ because a restart mid-sweep re-runs it from the start.
181
+
182
+ `schedulesOf(cfg, queue?)` reads the declarations back; `assertSchedules(cfg)` checks all of them,
183
+ including one written into `cfg.queue.schedules` directly, and refuses a duplicate id.
184
+ Which recurring work belongs on a schedule at all is `scheduled-jobs`.
185
+
145
186
  ## A queued call is still a guarded call
146
187
 
147
188
  `handleJob` rebuilds the request from the envelope and runs the entrypoint as the HTTP boundary
@@ -183,34 +224,36 @@ listens to, grouped by queue, which is what a driver binds.
183
224
  | `enqueueProtocol` / `waitForProtocol` | Typed protocol-object enqueue and result wait; validates protocol kind and queue configuration. |
184
225
  | `queueJobOf(request)` / `QueueJobMeta` | Explicit broker identity boundary for a protocol handler, including `touch()` for long work. |
185
226
  | `declareQueue` / `listenQueues` | Configuration — what exists, and what this process consumes |
227
+ | `declareSchedule` / `schedulesOf` / `assertSchedule` / `assertSchedules` | Recurring jobs — declare, read back, check |
186
228
  | `queueOf` / `queueOfJob` / `isListening` | Reading it back; `queueOf` throws `UnknownQueue` |
187
- | `QueueConfig` / `QueueDeclaration` / `QueueWorkerOptions` / `JobOptions` | The configuration shapes |
229
+ | `QueueConfig` / `QueueDeclaration` / `QueueWorkerOptions` / `JobOptions` / `ScheduleDeclaration` | The configuration shapes |
188
230
  | `QueueResource<D, R>` | A queue addressed as a resource, plus `wait` / `flow` / `counts` / `close` |
189
231
  | `JobRecord<D, R>` / `JobState` / `isSettled` / `JobEvent` / `JobEventType` | The record shape and lifecycle |
190
232
  | `FlowSpec<D>` | A graph node — `name`, optional `queue`, `data`, `opts`, `children` |
191
233
  | `QueueWorkerService` | `process(queue, name, fn)`, `start`, `stop`, `listening`, `hooks` |
192
- | `JobContext<D>` | What a processor gets: `id`, `name`, `queue`, `attempt`, `data`, `signal`, `touch`, `progress`, `children`, `failedChildren` |
234
+ | `JobContext<D>` | What a processor gets: `id`, `name`, `queue`, `attempt`, `data`, `signal`, `scheduled?`, `touch`, `progress`, `children`, `failedChildren` |
193
235
  | `QueueHooks` | `wrapHandler`, `onJobResult`, `onJobStalled`, `onJobDead` — the compensation seam |
194
236
  | `QueueAppend` | The `ctx.jobs(queue?)` mixin a driver installs; `QueueDriver` is what a driver supplies |
195
237
  | `appendQueueTransport` / `makeQueueTransport` | Binds the QUEUE protocol so `call()` routes through the broker |
196
238
  | `queueWorkerMiddleware` | Starts the worker at Ready stage |
197
239
  | `handleJob` / `servedJobs` / `entrypointProcessor` | The bridge a driver dispatches through |
198
240
  | `requestOf` / `assertFresh` / `JobEnvelope` / `JobReply` | The envelope, and rebuilding a request from it |
199
- | Errors | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired` |
241
+ | Errors | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired`, `ScheduleMisdeclared` |
200
242
  | Constants | `DEFAULT_ALIAS` (`queue`), `DEFAULT_JOB_TIMEOUT` (60 000 ms), `DEFAULT_ATTEMPTS` (1) |
201
243
 
202
244
  ## Depends On
203
245
 
204
246
  - `@owlmeans/context` — service registration and the middleware stage
205
247
  - `@owlmeans/entrypoint` — the transport seam and the served-entrypoint dispatch
206
- - `@owlmeans/route` — `RouteProtocols.QUEUE` and the `job()` builder
248
+ - `@owlmeans/route` — the generic declaration model that `QUEUE_PROTOCOL` and `job()` extend
207
249
  - `@owlmeans/resource` — the `Resource` contract jobs are read through
208
250
  - `@owlmeans/auth` / `@owlmeans/auth-common` — guarding a queued call and attaching its entity
209
251
  - `@owlmeans/error` — marshalling a refusal across the hop
210
252
 
211
253
  ## Related
212
254
 
213
- - `redis-queue` — the BullMQ driver; integration tests for queue behaviour live there
255
+ - `redis-queue` — the BullMQ driver; how schedules are reconciled; integration tests live there
256
+ - `scheduled-jobs` — when recurring work is a schedule, and how to write the sweep it runs
214
257
  - `server-job` / `client-job` — exposing a queue's jobs to an application's UI (list, cancel,
215
258
  progress over a socket), without touching the contracts here
216
259
  - `entrypoint` — protocol declarations, bindings, and the transport lookup
@@ -0,0 +1,149 @@
1
+ ---
2
+ name: scheduled-jobs
3
+ description: Recurring background work on OwlMeans queues — declareSchedule in @owlmeans/queue, reconciled into BullMQ job schedulers by @owlmeans/redis-queue when a listening worker starts. Covers interval vs cron schedules, the processor a sweep runs, overlap and restart semantics, renaming and removing a schedule, multi-replica and rolling-deploy behaviour, and testing. Use when adding a nightly or periodic job, replacing a platform CronJob or a setInterval with a schedule, or diagnosing a schedule that does not run.
4
+ user-invocable: false
5
+ ---
6
+ <!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
7
+
8
+ # Scheduled jobs
9
+
10
+ **Layer:** Infra · **Packages:** `@owlmeans/queue` (lead — the declaration), `@owlmeans/redis-queue`
11
+ (the reconciliation)
12
+
13
+ A schedule is a declaration that makes the broker produce an ordinary job on an interval or a cron
14
+ pattern. A worker that consumes the queue runs it with a processor. There is no second scheduler
15
+ process, no cron table and no timer in application code.
16
+
17
+ ## When a schedule is the answer
18
+
19
+ | Situation | Answer |
20
+ |---|---|
21
+ | Periodic sweep, reconciliation, digest, retention delete, rollup | A schedule on a queue a worker consumes |
22
+ | "Remind this user in 20 minutes" | `JobOptions.delay` on one job — a schedule is not per record |
23
+ | A far-future or cancellable per-record deadline | A row with a due date, swept by one schedule |
24
+ | A deployment that runs no worker | A platform `CronJob` calling a guarded entrypoint |
25
+ | A `setInterval` inside a request handler or a long-lived service | A schedule — the interval dies with the process and runs once per replica |
26
+
27
+ Whether the work belongs on a queue at all is `resource-choice`.
28
+
29
+ ## Declaring
30
+
31
+ In the SHARED package every process loads, after the queue:
32
+
33
+ ```typescript
34
+ declareQueue(cfg, APP_MAINTENANCE, ['app:maintenance:reconcile', 'app:maintenance:purge'], {
35
+ worker: { concurrency: 1, lockDuration: 60_000 }
36
+ })
37
+ declareSchedule(cfg, {
38
+ id: 'app-nightly-reconcile', queue: APP_MAINTENANCE, name: 'app:maintenance:reconcile',
39
+ pattern: '17 3 * * *', tz: 'UTC', data: {},
40
+ })
41
+ declareSchedule(cfg, {
42
+ id: 'app-purge-expired', queue: APP_MAINTENANCE, name: 'app:maintenance:purge', every: 15 * 60_000,
43
+ })
44
+ ```
45
+
46
+ And only in the process that runs them: `listenQueues(cfg, APP_MAINTENANCE)` plus a processor per
47
+ job name, registered while the context is wired.
48
+
49
+ - **`pattern` for the calendar, `every` for a frequency.** A nightly job is a pattern with an
50
+ explicit `tz`; "every quarter hour, whenever" is `every`. Pick an odd minute (`17 3 * * *`) so
51
+ many applications' sweeps do not stampede one Redis at the top of the hour.
52
+ - **`every` runs at once** when the scheduler is created (unless `startDate` says otherwise); a
53
+ pattern waits for its next match unless `immediately: true`, which fires only when the scheduler
54
+ is created or its declaration changes — never on an unchanged restart.
55
+ - **The id is permanent.** It is the scheduler's identity in the broker: any other field changes in
56
+ place, a renamed id is a removed schedule plus a new one. Prefix it with the application
57
+ (`app-…`) — ids share one namespace per queue.
58
+ - **Declaration errors surface at boot.** `declareSchedule` throws `UnknownQueue`,
59
+ `UnknownJobName` or `ScheduleMisdeclared` (`<id>:<reason>`) rather than letting the broker fail
60
+ on every tick.
61
+ - **`opts` sets retries and retention for every run; `id` and `delay` are refused** — the broker
62
+ names each run and places it on the schedule.
63
+
64
+ ## Writing the processor
65
+
66
+ ```typescript
67
+ worker.process<Record<string, never>, SweepReport>(APP_MAINTENANCE, 'app:maintenance:reconcile',
68
+ async job => {
69
+ const report = { scanned: 0, repaired: 0, failed: 0 }
70
+ for await (const page of organizations({ size: 200 })) {
71
+ await job.touch()
72
+ for (const organization of page) {
73
+ try {
74
+ report.repaired += await reconcile(organization.entityId) ? 1 : 0
75
+ } catch (error) {
76
+ report.failed++
77
+ console.error(`reconcile ${organization.entityId}`, error)
78
+ }
79
+ report.scanned++
80
+ }
81
+ }
82
+ console.info(`[maintenance] ${job.scheduled}`, report)
83
+ return report
84
+ })
85
+ ```
86
+
87
+ - **A scheduled run has no caller.** No credentials, no entity, no request: it is a processor job,
88
+ never a guarded entrypoint. A step that must go through another service's guard calls that
89
+ service with service credentials.
90
+ - **Idempotent per item, not per run.** A worker rolled out mid-sweep, a stalled lock or a manual
91
+ re-run repeats the whole sweep; each item's repair must be a no-op the second time.
92
+ - **One item's failure is logged and counted, never thrown.** A thrown sweep is retried from the
93
+ start, or dropped after its attempts — either way the other items pay for one bad record.
94
+ - **`touch()` per page.** A sweep over every organization outlives `lockDuration` easily; silence
95
+ hands the job to another worker while this one is still going.
96
+ - **Return a small report.** It lands on the completed job, where an operator reads it.
97
+
98
+ ## Overlap, replicas and deploys
99
+
100
+ - **One job per tick, whatever the replica count.** The broker produces one job; one worker takes
101
+ it. Several replicas listening to the queue do not multiply runs.
102
+ - **Runs can overlap on a concurrent queue.** The next run is produced when the previous one
103
+ starts, so a run that outlasts the interval meets its successor. Put sweeps on their own queue
104
+ with `concurrency: 1`, apart from interactive lanes.
105
+ - **A schedule needs a consumer.** Only a listening worker creates the scheduler, and the next run
106
+ is produced only when the previous one starts processing. A queue nothing consumes stops.
107
+ - **Every listener reconciles at start, from its own configuration.** It creates or updates what it
108
+ declares and removes the schedules it owns (`owlmeans:` scheduler ids) that it does not. Every
109
+ process listening to one queue must load the same schedule list; during a rolling deploy the
110
+ newest process to start wins, and the old pods leave the broker alone after their own start.
111
+ - **Removing a schedule is deleting its declaration.** The next worker start removes the scheduler.
112
+ So is a declaration whose job name the queue no longer accepts, or whose `endDate` has passed.
113
+ - **Scheduling never costs consumption.** A refused declaration or a broker failure while
114
+ reconciling is logged and skipped; the worker keeps consuming. Look for
115
+ `redis-queue-schedules:<queue>` in the worker's log when a schedule does not appear.
116
+
117
+ ## Diagnosing "it never ran"
118
+
119
+ 1. Is some process listening to the queue (`listenQueues`) and is its worker up?
120
+ 2. Did that process's start log a `redis-queue-schedules:<queue>` refusal?
121
+ 3. Does `new Queue(queue, { connection, prefix }).getJobSchedulers()` list `owlmeans:<id>` with the
122
+ expected `pattern`/`every`, `tz` and `next`?
123
+ 4. Is a processor registered for the job name? A run without one fails once as `UnknownJobName`
124
+ and shows in the failed state.
125
+ 5. Is a previous run still active on a `concurrency: 1` queue?
126
+
127
+ ## Testing
128
+
129
+ - **The declaration** is category A in the application's own shared package: build the config,
130
+ `assertSchedules(cfg)`, and pin ids and job names against the queue declaration.
131
+ - **The processor** is tested as a function over a fake `JobContext` or a real context — it is
132
+ ordinary code with a `touch`.
133
+ - **Against a broker** (Redis-gated): the worker's Ready-stage start is not awaited by
134
+ `context.init()`, so poll the broker (`getJobSchedulers`) or wait for the first run instead of
135
+ asserting straight after boot. A queue's schedulers go with `obliterate()`.
136
+
137
+ ## External docs
138
+
139
+ - https://docs.bullmq.io/guide/job-schedulers — `upsertJobScheduler(id, { every | pattern, tz,
140
+ startDate, endDate, limit, immediately }, { name, data, opts })` creates or updates one scheduler
141
+ per id and replaces its pending run; `getJobSchedulers()` / `removeJobScheduler(id)`. The next job
142
+ is added when the previous one starts processing, so a worker must be consuming. Produced jobs
143
+ carry `repeatJobKey` = the scheduler id.
144
+
145
+ ## Related
146
+
147
+ - `queue` — `declareSchedule`, `JobContext.scheduled`, processor rules
148
+ - `redis-queue` — `syncSchedules`, the `owlmeans:` ownership prefix, the reconciliation rules
149
+ - `resource-choice` — whether recurring work belongs on a queue at all
package/build/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Config, JobOptions, QueueDeclaration, QueueWorkerOptions } from './types.js';
1
+ import type { Config, JobOptions, QueueDeclaration, QueueWorkerOptions, ScheduleDeclaration } from './types.js';
2
2
  /**
3
3
  * Add a queue to the shared configuration. Every process in the monorepo loads the same
4
4
  * declarations — a producer needs them to know a queue exists and what it accepts, a worker needs
@@ -19,4 +19,32 @@ export declare const listenQueues: <C extends Config>(cfg: C, ...names: string[]
19
19
  export declare const queueOf: <C extends Config>(cfg: C, name: string) => QueueDeclaration;
20
20
  export declare const queueOfJob: <C extends Config>(cfg: C, job: string) => QueueDeclaration | undefined;
21
21
  export declare const isListening: <C extends Config>(cfg: C, name: string) => boolean;
22
+ /**
23
+ * Whether the broker can carry a schedule out as written. Nothing is recorded or changed.
24
+ *
25
+ * @throws {ScheduleMisdeclared} naming the schedule id and the reason.
26
+ * @throws {UnknownQueue} when the schedule's queue is not declared.
27
+ * @throws {UnknownJobName} when that queue does not accept the schedule's job name — a scheduled
28
+ * run of a name nothing declares would fail on every tick instead of once, at declaration.
29
+ */
30
+ export declare const assertSchedule: <C extends Config>(cfg: C, schedule: ScheduleDeclaration) => void;
31
+ /**
32
+ * Add a recurring job to the shared configuration. Declare the queue first — a schedule is checked
33
+ * against it here. Declaring an id twice replaces, like `declareQueue`.
34
+ *
35
+ * Declaring is all this does: the driver creates, updates and removes the broker's schedulers when
36
+ * a worker that listens to the queue starts.
37
+ *
38
+ * @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} — see `assertSchedule`.
39
+ */
40
+ export declare const declareSchedule: <C extends Config>(cfg: C, schedule: ScheduleDeclaration) => C;
41
+ /** The declared schedules, of one queue when it is named. */
42
+ export declare const schedulesOf: <C extends Config>(cfg: C, queue?: string) => ScheduleDeclaration[];
43
+ /**
44
+ * Check every declared schedule, including any written into the configuration without
45
+ * `declareSchedule`, and refuse two under one id.
46
+ *
47
+ * @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} for the first one that fails.
48
+ */
49
+ export declare const assertSchedules: <C extends Config>(cfg: C) => void;
22
50
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAG1F;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,OACtC,CAAC,QAAQ,MAAM,QAAQ,MAAM,EAAE,SAAS;IAAE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAAC,QAAQ,CAAC,EAAE,UAAU,CAAA;CAAE,KAClG,CAcF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,YAAY,MAAM,EAAE,KAAG,CAM3E,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,QAAQ,MAAM,KAAG,gBAQhE,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,OAAO,MAAM,KAAG,gBAAgB,GAAG,SACpB,CAAA;AAElE,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,QAAQ,MAAM,KAAG,OACzB,CAAA"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,EAC9E,MAAM,YAAY,CAAA;AAGnB;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,OACtC,CAAC,QAAQ,MAAM,QAAQ,MAAM,EAAE,SAAS;IAAE,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAAC,QAAQ,CAAC,EAAE,UAAU,CAAA;CAAE,KAClG,CAcF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,YAAY,MAAM,EAAE,KAAG,CAM3E,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,QAAQ,MAAM,KAAG,gBAQhE,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,OAAO,MAAM,KAAG,gBAAgB,GAAG,SACpB,CAAA;AAElE,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,QAAQ,MAAM,KAAG,OACzB,CAAA;AAM5C;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,YAAY,mBAAmB,KAAG,IAoDxF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,YAAY,mBAAmB,KAAG,CAezF,CAAA;AAED,6DAA6D;AAC7D,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,UAAU,MAAM,KAAG,mBAAmB,EACE,CAAA;AAE5F;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,KAAG,IAS1D,CAAA"}
package/build/config.js CHANGED
@@ -1,4 +1,4 @@
1
- import { UnknownQueue } from './errors.js';
1
+ import { ScheduleMisdeclared, UnknownJobName, UnknownQueue } from './errors.js';
2
2
  /**
3
3
  * Add a queue to the shared configuration. Every process in the monorepo loads the same
4
4
  * declarations — a producer needs them to know a queue exists and what it accepts, a worker needs
@@ -38,4 +38,104 @@ export const queueOf = (cfg, name) => {
38
38
  };
39
39
  export const queueOfJob = (cfg, job) => cfg.queue?.queues?.find(declared => declared.jobs.includes(job));
40
40
  export const isListening = (cfg, name) => cfg.queue?.listen?.includes(name) === true;
41
+ /** A date option the broker can read — a `Date`, epoch milliseconds or a parsable string. */
42
+ const isInstant = (value) => Number.isFinite((value instanceof Date ? value : new Date(value)).getTime());
43
+ /**
44
+ * Whether the broker can carry a schedule out as written. Nothing is recorded or changed.
45
+ *
46
+ * @throws {ScheduleMisdeclared} naming the schedule id and the reason.
47
+ * @throws {UnknownQueue} when the schedule's queue is not declared.
48
+ * @throws {UnknownJobName} when that queue does not accept the schedule's job name — a scheduled
49
+ * run of a name nothing declares would fail on every tick instead of once, at declaration.
50
+ */
51
+ export const assertSchedule = (cfg, schedule) => {
52
+ const { id } = schedule;
53
+ if (typeof id !== 'string' || id.trim() === '') {
54
+ throw new ScheduleMisdeclared('(empty):id');
55
+ }
56
+ const queue = queueOf(cfg, schedule.queue);
57
+ if (!queue.jobs.includes(schedule.name)) {
58
+ throw new UnknownJobName(`${schedule.queue}:${schedule.name}`);
59
+ }
60
+ const every = schedule.every != null;
61
+ const pattern = schedule.pattern != null;
62
+ if (every === pattern) {
63
+ throw new ScheduleMisdeclared(`${id}:every-or-pattern`);
64
+ }
65
+ if (every && !(Number.isSafeInteger(schedule.every) && schedule.every > 0)) {
66
+ throw new ScheduleMisdeclared(`${id}:every`);
67
+ }
68
+ if (pattern && (typeof schedule.pattern !== 'string' || schedule.pattern.trim() === '')) {
69
+ throw new ScheduleMisdeclared(`${id}:pattern`);
70
+ }
71
+ // Both only mean something to a cron pattern: an interval has no wall clock to read in a time
72
+ // zone, and its first run is immediate already.
73
+ if (!pattern && schedule.tz != null) {
74
+ throw new ScheduleMisdeclared(`${id}:tz`);
75
+ }
76
+ if (!pattern && schedule.immediately === true) {
77
+ throw new ScheduleMisdeclared(`${id}:immediately`);
78
+ }
79
+ if (schedule.immediately === true && schedule.startDate != null) {
80
+ throw new ScheduleMisdeclared(`${id}:immediately-start-date`);
81
+ }
82
+ if (schedule.limit != null && !(Number.isSafeInteger(schedule.limit) && schedule.limit > 0)) {
83
+ throw new ScheduleMisdeclared(`${id}:limit`);
84
+ }
85
+ if (schedule.startDate != null && !isInstant(schedule.startDate)) {
86
+ throw new ScheduleMisdeclared(`${id}:start-date`);
87
+ }
88
+ if (schedule.endDate != null && !isInstant(schedule.endDate)) {
89
+ throw new ScheduleMisdeclared(`${id}:end-date`);
90
+ }
91
+ // The type already omits both; a declaration assembled at runtime still reaches this check. The
92
+ // broker names every run itself and places it on the schedule, so either would be overridden.
93
+ const opts = schedule.opts;
94
+ if (opts?.id != null) {
95
+ throw new ScheduleMisdeclared(`${id}:opts-id`);
96
+ }
97
+ if (opts?.delay != null) {
98
+ throw new ScheduleMisdeclared(`${id}:opts-delay`);
99
+ }
100
+ };
101
+ /**
102
+ * Add a recurring job to the shared configuration. Declare the queue first — a schedule is checked
103
+ * against it here. Declaring an id twice replaces, like `declareQueue`.
104
+ *
105
+ * Declaring is all this does: the driver creates, updates and removes the broker's schedulers when
106
+ * a worker that listens to the queue starts.
107
+ *
108
+ * @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} — see `assertSchedule`.
109
+ */
110
+ export const declareSchedule = (cfg, schedule) => {
111
+ assertSchedule(cfg, schedule);
112
+ const schedules = cfg.queue?.schedules ?? [];
113
+ const existing = schedules.findIndex(declared => declared.id === schedule.id);
114
+ if (existing < 0) {
115
+ schedules.push(schedule);
116
+ }
117
+ else {
118
+ schedules[existing] = schedule;
119
+ }
120
+ cfg.queue = { ...cfg.queue, schedules };
121
+ return cfg;
122
+ };
123
+ /** The declared schedules, of one queue when it is named. */
124
+ export const schedulesOf = (cfg, queue) => (cfg.queue?.schedules ?? []).filter(schedule => queue == null || schedule.queue === queue);
125
+ /**
126
+ * Check every declared schedule, including any written into the configuration without
127
+ * `declareSchedule`, and refuse two under one id.
128
+ *
129
+ * @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} for the first one that fails.
130
+ */
131
+ export const assertSchedules = (cfg) => {
132
+ const seen = new Set();
133
+ for (const schedule of schedulesOf(cfg)) {
134
+ assertSchedule(cfg, schedule);
135
+ if (seen.has(schedule.id)) {
136
+ throw new ScheduleMisdeclared(`${schedule.id}:duplicate`);
137
+ }
138
+ seen.add(schedule.id);
139
+ }
140
+ };
41
141
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,GAAM,EAAE,IAAY,EAAE,IAAc,EAAE,IAA6D,EAChG,EAAE;IACL,MAAM,KAAK,GAAqB,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAAA;IACvD,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,EAAE,CAAA;IACtC,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;IAErE,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACpB,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAA;IAC1B,CAAC;IAED,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;IAEpC,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAmB,GAAM,EAAE,GAAG,KAAe,EAAK,EAAE;IAC9E,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK,CAAC,CAAC,CAAA;IAEhE,GAAG,CAAC,KAAK,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,EAAE,CAAA;IAEjD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAmB,GAAM,EAAE,IAAY,EAAoB,EAAE;IAClF,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;IAEzE,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;QAClB,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,CAAmB,GAAM,EAAE,GAAW,EAAgC,EAAE,CAChG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;AAElE,MAAM,CAAC,MAAM,WAAW,GAAG,CAAmB,GAAM,EAAE,IAAY,EAAW,EAAE,CAC7E,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/build/errors.d.ts CHANGED
@@ -56,4 +56,13 @@ export declare class EnvelopeExpired extends QueueError {
56
56
  static typeName: string;
57
57
  constructor(msg: string);
58
58
  }
59
+ /**
60
+ * A schedule the broker could not carry out as written: no id, both or neither of `every` and
61
+ * `pattern`, an option that only means something for the other one, or a job option the broker
62
+ * owns for a scheduled run. The message names the schedule id and the reason.
63
+ */
64
+ export declare class ScheduleMisdeclared extends QueueError {
65
+ static typeName: string;
66
+ constructor(msg: string);
67
+ }
59
68
  //# sourceMappingURL=errors.d.ts.map