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

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.23
20
20
  ```
21
21
 
22
22
  ## Concepts
@@ -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
 
@@ -155,6 +158,41 @@ await context.jobs(APP_OPS).flow({
155
158
  })
156
159
  ```
157
160
 
161
+ ### Recurring jobs
162
+
163
+ ```ts
164
+ import { declareQueue, declareSchedule } from '@owlmeans/queue'
165
+
166
+ // Shared config: the queue first, then its schedules.
167
+ declareQueue(cfg, APP_MAINTENANCE, ['app:maintenance:reconcile', 'app:maintenance:purge'], {
168
+ worker: { concurrency: 1, lockDuration: 60_000 }
169
+ })
170
+ declareSchedule(cfg, {
171
+ id: 'app-nightly-reconcile', queue: APP_MAINTENANCE, name: 'app:maintenance:reconcile',
172
+ pattern: '17 3 * * *', tz: 'UTC', data: {},
173
+ })
174
+ declareSchedule(cfg, {
175
+ id: 'app-purge', queue: APP_MAINTENANCE, name: 'app:maintenance:purge', every: 15 * 60_000,
176
+ })
177
+ ```
178
+
179
+ The process that `listenQueues(cfg, APP_MAINTENANCE)` registers the processors; nothing else is
180
+ wired. A scheduled run carries no caller and no credentials, so it goes to a processor, never to a
181
+ guarded entrypoint:
182
+
183
+ ```ts
184
+ worker.process(APP_MAINTENANCE, 'app:maintenance:reconcile', async job => {
185
+ for await (const page of organizations()) {
186
+ await job.touch()
187
+ await reconcile(page) // safe to run twice: a restart re-runs the whole sweep
188
+ }
189
+ return { scheduled: job.scheduled } // 'app-nightly-reconcile'
190
+ })
191
+ ```
192
+
193
+ Renaming a schedule id removes the old scheduler and creates a new one; deleting a declaration
194
+ removes its scheduler at the next worker start.
195
+
158
196
  ### Jobs are records
159
197
 
160
198
  ```ts
@@ -197,6 +235,9 @@ not the job, so read the job back to learn what became of it.
197
235
  | `declareQueue(cfg, name, jobs, opts?)` | function | Declare a queue and the job names it accepts; re-declaring replaces |
198
236
  | `listenQueues(cfg, ...names)` | function | Name the queues this process consumes |
199
237
  | `queueOf(cfg, name)`, `queueOfJob(cfg, job)`, `isListening(cfg, name)` | function | Read declarations back; `queueOf` throws `UnknownQueue` |
238
+ | `declareSchedule(cfg, schedule)` | function | Declare a recurring job after checking it; re-declaring an id replaces |
239
+ | `schedulesOf(cfg, queue?)` | function | The declared schedules, of one queue when named |
240
+ | `assertSchedule(cfg, schedule)`, `assertSchedules(cfg)` | function | Check one schedule, or every declared one plus duplicate ids |
200
241
  | `enqueueProtocol(ctx, protocol, request, options?)` | function | Enqueue a QUEUE protocol with `JobOptions`, typed by the protocol |
201
242
  | `waitForProtocol(ctx, protocol, job, { timeout }?)` | function | Wait for that job and unwrap the typed reply |
202
243
  | `queueJobOf(request)` | function | `QueueJobMeta` of the broker job behind a handler's request, or `null` |
@@ -204,11 +245,11 @@ not the job, so read the job back to learn what became of it.
204
245
  | `queueWorkerMiddleware(alias?)` | function | Start the worker at the Ready stage, only in a process that listens |
205
246
  | `handleJob(ctx, job)`, `entrypointProcessor(ctx)`, `servedJobs(ctx)` | function | The bridge a driver dispatches entrypoint jobs through |
206
247
  | `requestOf(envelope)`, `assertFresh(envelope, ttl?)` | function | Rebuild a request from an envelope; enforce `envelopeTtl` |
207
- | `QueueConfig`, `QueueDeclaration`, `QueueWorkerOptions`, `JobOptions` | type | Configuration shapes |
248
+ | `QueueConfig`, `QueueDeclaration`, `QueueWorkerOptions`, `JobOptions`, `ScheduleDeclaration` | type | Configuration shapes |
208
249
  | `QueueResource<D, R>` | type | A queue as a resource, plus `queue`, `wait`, `flow`, `counts`, `close` |
209
250
  | `JobRecord<D, R>`, `JobEvent<R>`, `FlowSpec<D>` | type | A job as a record, a lifecycle event, a graph node |
210
251
  | `QueueWorkerService` | type | `process`, `start`, `stop`, `listening`, `hooks` |
211
- | `JobContext<D>`, `JobProcessor<D, R>` | type | What a processor receives; the processor signature |
252
+ | `JobContext<D>`, `JobProcessor<D, R>` | type | What a processor receives (`scheduled` names the schedule of a scheduled run); the processor signature |
212
253
  | `QueueHooks` | type | `wrapHandler`, `onJobResult`, `onJobStalled`, `onJobDead` |
213
254
  | `QueueJobMeta`, `JobEnvelope`, `JobReply<T>` | type | Broker identity for a handler, the call envelope, the reply shape |
214
255
  | `QueueAppend`, `QueueDriver` | type | The `ctx.jobs(queue?)` mixin a driver installs; what a driver supplies |
@@ -216,7 +257,7 @@ not the job, so read the job back to learn what became of it.
216
257
  | `JobState`, `JobEventType` | enum | Job lifecycle states and event types |
217
258
  | `isSettled(state?)` | function | Whether a state is final |
218
259
  | `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 |
260
+ | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired`, `ScheduleMisdeclared` | class | Error family |
220
261
 
221
262
  ## Common pitfalls
222
263
 
@@ -233,7 +274,13 @@ not the job, so read the job back to learn what became of it.
233
274
  - **Long loops without `touch()`**, or processors that are not safe to run twice.
234
275
  - **Throwing from a child for a domain failure.** Return `{ ok: false, error }` instead.
235
276
  - **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.
277
+ - **A self-re-enqueueing job as a cron.** Declare a schedule. `delay` defers a single job once.
278
+ - **A schedule on a queue nothing consumes.** Only a listening worker creates the scheduler, and the
279
+ broker produces each next run when the previous one starts.
280
+ - **Loading different schedule lists in two processes that listen to one queue.** Each start removes
281
+ the schedules its own list does not name.
282
+ - **Declaring a schedule before its queue.** `declareSchedule` checks the queue and the job name and
283
+ throws `UnknownQueue` / `UnknownJobName`.
237
284
  - **Calling `ctx.jobs()` with no name once a second queue exists.** It throws `UnknownQueue`.
238
285
 
239
286
  ## Related packages
@@ -253,7 +300,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
253
300
  your project's skill store (`.agents/skills/`):
254
301
 
255
302
  ```sh
256
- npx @owlmeans/agent-skills@^0.1.18-rc.27
303
+ npx @owlmeans/agent-skills@^0.1.18-rc.28
257
304
  ```
258
305
 
259
306
  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.23",
5
+ "generatedAt": "2026-09-17T08:16:11.196Z",
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,7 +8,7 @@ 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.23"` in `dependencies`
12
12
 
13
13
  Contracts only. It carries no broker code — a driver package (`@owlmeans/redis-queue`) implements
14
14
  them. Depend on this one from a shared contract package; depend on the driver only where the
@@ -142,6 +142,43 @@ run twice — skip work already recorded, delete the rows a previous attempt cre
142
142
  regenerating over a baseline. Decide this per job and say so in a comment; there is no way to make
143
143
  it automatic.
144
144
 
145
+ ## Recurring jobs
146
+
147
+ A recurring sweep is a SCHEDULE on a queue, run by a registered processor:
148
+
149
+ ```typescript
150
+ declareQueue(cfg, APP_MAINTENANCE, ['app:maintenance:reconcile'], {
151
+ worker: { concurrency: 1, lockDuration: 60_000 }
152
+ })
153
+ declareSchedule(cfg, {
154
+ id: 'app-nightly-reconcile', queue: APP_MAINTENANCE, name: 'app:maintenance:reconcile',
155
+ pattern: '17 3 * * *', tz: 'UTC', data: {},
156
+ })
157
+ ```
158
+
159
+ - **Declare it in the shared package, after its queue.** `declareSchedule` runs `assertSchedule`
160
+ first: the queue must be declared (`UnknownQueue`) and must accept the job name
161
+ (`UnknownJobName`), exactly one of `every` (a positive integer of milliseconds) and `pattern`
162
+ (cron), `tz` and `immediately` only with a pattern, never `immediately` with `startDate`, and
163
+ `opts.id` / `opts.delay` refused — the broker names and places every run
164
+ (`ScheduleMisdeclared`, message `<id>:<reason>`). Re-declaring an id replaces it.
165
+ - **The id is the identity.** Changing any other field updates the schedule in place; renaming the
166
+ id is one schedule removed and another created.
167
+ - **Every process that listens to the queue reconciles it at worker start** — the driver creates
168
+ and updates what is declared and removes the schedules it owns that are not. Two listeners of one
169
+ queue must therefore load the same schedule list, which is why it lives in the shared package.
170
+ A producer creates nothing.
171
+ - **A scheduled run is a processor job, never a guarded entrypoint.** Nobody's credentials travel
172
+ with it. `job.scheduled` carries the schedule id; it is absent on a job enqueued any other way.
173
+ - **It needs a consumer.** The broker produces the next run when the previous one starts
174
+ processing, so a schedule on a queue nothing consumes stops producing.
175
+ - **The processor obeys every rule above** — `touch()` through the sweep, and safe to run twice,
176
+ because a restart mid-sweep re-runs it from the start.
177
+
178
+ `schedulesOf(cfg, queue?)` reads the declarations back; `assertSchedules(cfg)` checks all of them,
179
+ including one written into `cfg.queue.schedules` directly, and refuses a duplicate id.
180
+ Which recurring work belongs on a schedule at all is `scheduled-jobs`.
181
+
145
182
  ## A queued call is still a guarded call
146
183
 
147
184
  `handleJob` rebuilds the request from the envelope and runs the entrypoint as the HTTP boundary
@@ -183,20 +220,21 @@ listens to, grouped by queue, which is what a driver binds.
183
220
  | `enqueueProtocol` / `waitForProtocol` | Typed protocol-object enqueue and result wait; validates protocol kind and queue configuration. |
184
221
  | `queueJobOf(request)` / `QueueJobMeta` | Explicit broker identity boundary for a protocol handler, including `touch()` for long work. |
185
222
  | `declareQueue` / `listenQueues` | Configuration — what exists, and what this process consumes |
223
+ | `declareSchedule` / `schedulesOf` / `assertSchedule` / `assertSchedules` | Recurring jobs — declare, read back, check |
186
224
  | `queueOf` / `queueOfJob` / `isListening` | Reading it back; `queueOf` throws `UnknownQueue` |
187
- | `QueueConfig` / `QueueDeclaration` / `QueueWorkerOptions` / `JobOptions` | The configuration shapes |
225
+ | `QueueConfig` / `QueueDeclaration` / `QueueWorkerOptions` / `JobOptions` / `ScheduleDeclaration` | The configuration shapes |
188
226
  | `QueueResource<D, R>` | A queue addressed as a resource, plus `wait` / `flow` / `counts` / `close` |
189
227
  | `JobRecord<D, R>` / `JobState` / `isSettled` / `JobEvent` / `JobEventType` | The record shape and lifecycle |
190
228
  | `FlowSpec<D>` | A graph node — `name`, optional `queue`, `data`, `opts`, `children` |
191
229
  | `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` |
230
+ | `JobContext<D>` | What a processor gets: `id`, `name`, `queue`, `attempt`, `data`, `signal`, `scheduled?`, `touch`, `progress`, `children`, `failedChildren` |
193
231
  | `QueueHooks` | `wrapHandler`, `onJobResult`, `onJobStalled`, `onJobDead` — the compensation seam |
194
232
  | `QueueAppend` | The `ctx.jobs(queue?)` mixin a driver installs; `QueueDriver` is what a driver supplies |
195
233
  | `appendQueueTransport` / `makeQueueTransport` | Binds the QUEUE protocol so `call()` routes through the broker |
196
234
  | `queueWorkerMiddleware` | Starts the worker at Ready stage |
197
235
  | `handleJob` / `servedJobs` / `entrypointProcessor` | The bridge a driver dispatches through |
198
236
  | `requestOf` / `assertFresh` / `JobEnvelope` / `JobReply` | The envelope, and rebuilding a request from it |
199
- | Errors | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired` |
237
+ | Errors | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired`, `ScheduleMisdeclared` |
200
238
  | Constants | `DEFAULT_ALIAS` (`queue`), `DEFAULT_JOB_TIMEOUT` (60 000 ms), `DEFAULT_ATTEMPTS` (1) |
201
239
 
202
240
  ## Depends On
@@ -210,7 +248,8 @@ listens to, grouped by queue, which is what a driver binds.
210
248
 
211
249
  ## Related
212
250
 
213
- - `redis-queue` — the BullMQ driver; integration tests for queue behaviour live there
251
+ - `redis-queue` — the BullMQ driver; how schedules are reconciled; integration tests live there
252
+ - `scheduled-jobs` — when recurring work is a schedule, and how to write the sweep it runs
214
253
  - `server-job` / `client-job` — exposing a queue's jobs to an application's UI (list, cancel,
215
254
  progress over a socket), without touching the contracts here
216
255
  - `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
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,UAAW,SAAQ,cAAc;IAC5C,OAAuB,QAAQ,SAAe;IAE9C,YAAY,OAAO,GAAE,MAAgB,EAEpC;CACF;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAkC;IAEjE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,gGAAgG;AAChG,qBAAa,UAAW,SAAQ,UAAU;IACxC,OAAuB,QAAQ,SAAqC;IAEpE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,UAAU;IAC5C,OAAuB,QAAQ,SAAyC;IAExE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,mDAAmD;AACnD,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;IAC/C,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,OAAuB,QAAQ,SAA0C;IAEzE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,qBAAa,UAAW,SAAQ,cAAc;IAC5C,OAAuB,QAAQ,SAAe;IAE9C,YAAY,OAAO,GAAE,MAAgB,EAEpC;CACF;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAkC;IAEjE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,gGAAgG;AAChG,qBAAa,UAAW,SAAQ,UAAU;IACxC,OAAuB,QAAQ,SAAqC;IAEpE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,UAAU;IAC5C,OAAuB,QAAQ,SAAyC;IAExE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,mDAAmD;AACnD,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;IAC/C,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAuC;IAEtE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,OAAuB,QAAQ,SAA0C;IAEzE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,UAAU;IACjD,OAAuB,QAAQ,SAA8C;IAE7E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF"}
package/build/errors.js CHANGED
@@ -79,6 +79,18 @@ export class EnvelopeExpired extends QueueError {
79
79
  this.type = EnvelopeExpired.typeName;
80
80
  }
81
81
  }
82
+ /**
83
+ * A schedule the broker could not carry out as written: no id, both or neither of `every` and
84
+ * `pattern`, an option that only means something for the other one, or a job option the broker
85
+ * owns for a scheduled run. The message names the schedule id and the reason.
86
+ */
87
+ export class ScheduleMisdeclared extends QueueError {
88
+ static typeName = `${QueueError.typeName}ScheduleMisdeclared`;
89
+ constructor(msg) {
90
+ super(`schedule-misdeclared:${msg}`);
91
+ this.type = ScheduleMisdeclared.typeName;
92
+ }
93
+ }
82
94
  ResilientError.registerErrorClass(QueueError);
83
95
  ResilientError.registerErrorClass(QueueTimeout);
84
96
  ResilientError.registerErrorClass(UnknownJob);
@@ -87,4 +99,5 @@ ResilientError.registerErrorClass(UnknownQueue);
87
99
  ResilientError.registerErrorClass(QueueNotListening);
88
100
  ResilientError.registerErrorClass(JobNotServed);
89
101
  ResilientError.registerErrorClass(EnvelopeExpired);
102
+ ResilientError.registerErrorClass(ScheduleMisdeclared);
90
103
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/build/types.d.ts CHANGED
@@ -19,6 +19,56 @@ export interface QueueConfig {
19
19
  * check and the nonce cache, so a job delayed behind a long backlog is still admitted.
20
20
  */
21
21
  envelopeTtl?: number;
22
+ /**
23
+ * Recurring jobs. Declared beside the queues, in the shared package, because every process that
24
+ * listens to a schedule's queue reconciles the broker against this list when its worker starts —
25
+ * two listeners loading different lists would remove each other's schedules.
26
+ */
27
+ schedules?: ScheduleDeclaration[];
28
+ }
29
+ /**
30
+ * A job the broker produces on its own, on an interval or a cron pattern.
31
+ *
32
+ * A scheduled run is an ordinary job of a name the queue declares, dispatched to the processor
33
+ * registered for that name — never to a guarded entrypoint, because nobody's credentials travel
34
+ * with it. Only a process that listens to the queue creates the scheduler, and the broker produces
35
+ * each next run when the previous one STARTS processing — a schedule needs a running consumer.
36
+ */
37
+ export interface ScheduleDeclaration {
38
+ /**
39
+ * The schedule's stable identity: the broker keeps one scheduler per id, so changing any other
40
+ * field updates it in place, while renaming the id removes one schedule and creates another.
41
+ */
42
+ id: string;
43
+ queue: string;
44
+ /** A job name the queue declares. */
45
+ name: string;
46
+ /**
47
+ * Milliseconds between runs. Exactly one of `every` and `pattern`. Without `startDate` the first
48
+ * run is produced as soon as the scheduler is created.
49
+ */
50
+ every?: number;
51
+ /** A cron pattern (an optional leading seconds field is accepted). Exactly one of the two. */
52
+ pattern?: string;
53
+ /** The IANA time zone `pattern` is read in. Pattern only. */
54
+ tz?: string;
55
+ startDate?: Date | number | string;
56
+ /** Once passed, the schedule is treated as absent and a scheduler left from it is removed. */
57
+ endDate?: Date | number | string;
58
+ /** Stop after this many runs. */
59
+ limit?: number;
60
+ /**
61
+ * Run once as soon as the scheduler is created or its declaration changes, then follow the
62
+ * pattern. Pattern only, and never together with `startDate`. A restart that finds the
63
+ * declaration unchanged does not run it again.
64
+ */
65
+ immediately?: boolean;
66
+ data?: unknown;
67
+ /**
68
+ * Options every run is enqueued with, over the queue's defaults. `id` and `delay` belong to the
69
+ * broker for a scheduled run and are refused.
70
+ */
71
+ opts?: Omit<JobOptions, 'id' | 'delay'>;
22
72
  }
23
73
  export interface QueueDeclaration {
24
74
  name: string;
@@ -147,6 +197,11 @@ export interface JobContext<D = unknown> {
147
197
  attempt: number;
148
198
  data: D;
149
199
  signal: AbortSignal;
200
+ /**
201
+ * The id of the schedule that produced this run (`ScheduleDeclaration.id`). Absent for a job
202
+ * enqueued any other way.
203
+ */
204
+ scheduled?: string;
150
205
  /**
151
206
  * Renew the lock. A processor that runs a long loop MUST call it as it goes: the broker judges
152
207
  * liveness by the lock, so silence for longer than `lockDuration` is indistinguishable from a
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/queue",
3
- "version": "0.1.18-rc.22",
3
+ "version": "0.1.18-rc.23",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -31,12 +31,12 @@
31
31
  "access": "public"
32
32
  },
33
33
  "dependencies": {
34
- "@owlmeans/auth": "^0.1.18-rc.26",
35
- "@owlmeans/auth-common": "^0.1.18-rc.28",
36
- "@owlmeans/context": "^0.1.18-rc.24",
37
- "@owlmeans/entrypoint": "^0.1.18-rc.27",
38
- "@owlmeans/error": "^0.1.18-rc.26",
39
- "@owlmeans/resource": "^0.1.18-rc.26",
40
- "@owlmeans/route": "^0.1.18-rc.25"
34
+ "@owlmeans/auth": "^0.1.18-rc.27",
35
+ "@owlmeans/auth-common": "^0.1.18-rc.29",
36
+ "@owlmeans/context": "^0.1.18-rc.25",
37
+ "@owlmeans/entrypoint": "^0.1.18-rc.28",
38
+ "@owlmeans/error": "^0.1.18-rc.27",
39
+ "@owlmeans/resource": "^0.1.18-rc.27",
40
+ "@owlmeans/route": "^0.1.18-rc.26"
41
41
  }
42
42
  }
package/src/config.ts CHANGED
@@ -1,5 +1,7 @@
1
- import type { Config, JobOptions, QueueDeclaration, QueueWorkerOptions } from './types.js'
2
- import { UnknownQueue } from './errors.js'
1
+ import type {
2
+ Config, JobOptions, QueueDeclaration, QueueWorkerOptions, ScheduleDeclaration
3
+ } from './types.js'
4
+ import { ScheduleMisdeclared, UnknownJobName, UnknownQueue } from './errors.js'
3
5
 
4
6
  /**
5
7
  * Add a queue to the shared configuration. Every process in the monorepo loads the same
@@ -54,3 +56,116 @@ export const queueOfJob = <C extends Config>(cfg: C, job: string): QueueDeclarat
54
56
 
55
57
  export const isListening = <C extends Config>(cfg: C, name: string): boolean =>
56
58
  cfg.queue?.listen?.includes(name) === true
59
+
60
+ /** A date option the broker can read — a `Date`, epoch milliseconds or a parsable string. */
61
+ const isInstant = (value: Date | number | string): boolean =>
62
+ Number.isFinite((value instanceof Date ? value : new Date(value)).getTime())
63
+
64
+ /**
65
+ * Whether the broker can carry a schedule out as written. Nothing is recorded or changed.
66
+ *
67
+ * @throws {ScheduleMisdeclared} naming the schedule id and the reason.
68
+ * @throws {UnknownQueue} when the schedule's queue is not declared.
69
+ * @throws {UnknownJobName} when that queue does not accept the schedule's job name — a scheduled
70
+ * run of a name nothing declares would fail on every tick instead of once, at declaration.
71
+ */
72
+ export const assertSchedule = <C extends Config>(cfg: C, schedule: ScheduleDeclaration): void => {
73
+ const { id } = schedule
74
+ if (typeof id !== 'string' || id.trim() === '') {
75
+ throw new ScheduleMisdeclared('(empty):id')
76
+ }
77
+
78
+ const queue = queueOf(cfg, schedule.queue)
79
+ if (!queue.jobs.includes(schedule.name)) {
80
+ throw new UnknownJobName(`${schedule.queue}:${schedule.name}`)
81
+ }
82
+
83
+ const every = schedule.every != null
84
+ const pattern = schedule.pattern != null
85
+ if (every === pattern) {
86
+ throw new ScheduleMisdeclared(`${id}:every-or-pattern`)
87
+ }
88
+ if (every && !(Number.isSafeInteger(schedule.every) && (schedule.every as number) > 0)) {
89
+ throw new ScheduleMisdeclared(`${id}:every`)
90
+ }
91
+ if (pattern && (typeof schedule.pattern !== 'string' || schedule.pattern.trim() === '')) {
92
+ throw new ScheduleMisdeclared(`${id}:pattern`)
93
+ }
94
+ // Both only mean something to a cron pattern: an interval has no wall clock to read in a time
95
+ // zone, and its first run is immediate already.
96
+ if (!pattern && schedule.tz != null) {
97
+ throw new ScheduleMisdeclared(`${id}:tz`)
98
+ }
99
+ if (!pattern && schedule.immediately === true) {
100
+ throw new ScheduleMisdeclared(`${id}:immediately`)
101
+ }
102
+ if (schedule.immediately === true && schedule.startDate != null) {
103
+ throw new ScheduleMisdeclared(`${id}:immediately-start-date`)
104
+ }
105
+ if (schedule.limit != null && !(Number.isSafeInteger(schedule.limit) && schedule.limit > 0)) {
106
+ throw new ScheduleMisdeclared(`${id}:limit`)
107
+ }
108
+ if (schedule.startDate != null && !isInstant(schedule.startDate)) {
109
+ throw new ScheduleMisdeclared(`${id}:start-date`)
110
+ }
111
+ if (schedule.endDate != null && !isInstant(schedule.endDate)) {
112
+ throw new ScheduleMisdeclared(`${id}:end-date`)
113
+ }
114
+
115
+ // The type already omits both; a declaration assembled at runtime still reaches this check. The
116
+ // broker names every run itself and places it on the schedule, so either would be overridden.
117
+ const opts = schedule.opts as JobOptions | undefined
118
+ if (opts?.id != null) {
119
+ throw new ScheduleMisdeclared(`${id}:opts-id`)
120
+ }
121
+ if (opts?.delay != null) {
122
+ throw new ScheduleMisdeclared(`${id}:opts-delay`)
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Add a recurring job to the shared configuration. Declare the queue first — a schedule is checked
128
+ * against it here. Declaring an id twice replaces, like `declareQueue`.
129
+ *
130
+ * Declaring is all this does: the driver creates, updates and removes the broker's schedulers when
131
+ * a worker that listens to the queue starts.
132
+ *
133
+ * @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} — see `assertSchedule`.
134
+ */
135
+ export const declareSchedule = <C extends Config>(cfg: C, schedule: ScheduleDeclaration): C => {
136
+ assertSchedule(cfg, schedule)
137
+
138
+ const schedules = cfg.queue?.schedules ?? []
139
+ const existing = schedules.findIndex(declared => declared.id === schedule.id)
140
+
141
+ if (existing < 0) {
142
+ schedules.push(schedule)
143
+ } else {
144
+ schedules[existing] = schedule
145
+ }
146
+
147
+ cfg.queue = { ...cfg.queue, schedules }
148
+
149
+ return cfg
150
+ }
151
+
152
+ /** The declared schedules, of one queue when it is named. */
153
+ export const schedulesOf = <C extends Config>(cfg: C, queue?: string): ScheduleDeclaration[] =>
154
+ (cfg.queue?.schedules ?? []).filter(schedule => queue == null || schedule.queue === queue)
155
+
156
+ /**
157
+ * Check every declared schedule, including any written into the configuration without
158
+ * `declareSchedule`, and refuse two under one id.
159
+ *
160
+ * @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} for the first one that fails.
161
+ */
162
+ export const assertSchedules = <C extends Config>(cfg: C): void => {
163
+ const seen = new Set<string>()
164
+ for (const schedule of schedulesOf(cfg)) {
165
+ assertSchedule(cfg, schedule)
166
+ if (seen.has(schedule.id)) {
167
+ throw new ScheduleMisdeclared(`${schedule.id}:duplicate`)
168
+ }
169
+ seen.add(schedule.id)
170
+ }
171
+ }
package/src/errors.ts CHANGED
@@ -96,6 +96,20 @@ export class EnvelopeExpired extends QueueError {
96
96
  }
97
97
  }
98
98
 
99
+ /**
100
+ * A schedule the broker could not carry out as written: no id, both or neither of `every` and
101
+ * `pattern`, an option that only means something for the other one, or a job option the broker
102
+ * owns for a scheduled run. The message names the schedule id and the reason.
103
+ */
104
+ export class ScheduleMisdeclared extends QueueError {
105
+ public static override typeName = `${QueueError.typeName}ScheduleMisdeclared`
106
+
107
+ constructor(msg: string) {
108
+ super(`schedule-misdeclared:${msg}`)
109
+ this.type = ScheduleMisdeclared.typeName
110
+ }
111
+ }
112
+
99
113
  ResilientError.registerErrorClass(QueueError)
100
114
  ResilientError.registerErrorClass(QueueTimeout)
101
115
  ResilientError.registerErrorClass(UnknownJob)
@@ -104,3 +118,4 @@ ResilientError.registerErrorClass(UnknownQueue)
104
118
  ResilientError.registerErrorClass(QueueNotListening)
105
119
  ResilientError.registerErrorClass(JobNotServed)
106
120
  ResilientError.registerErrorClass(EnvelopeExpired)
121
+ ResilientError.registerErrorClass(ScheduleMisdeclared)
package/src/types.ts CHANGED
@@ -20,6 +20,57 @@ export interface QueueConfig {
20
20
  * check and the nonce cache, so a job delayed behind a long backlog is still admitted.
21
21
  */
22
22
  envelopeTtl?: number
23
+ /**
24
+ * Recurring jobs. Declared beside the queues, in the shared package, because every process that
25
+ * listens to a schedule's queue reconciles the broker against this list when its worker starts —
26
+ * two listeners loading different lists would remove each other's schedules.
27
+ */
28
+ schedules?: ScheduleDeclaration[]
29
+ }
30
+
31
+ /**
32
+ * A job the broker produces on its own, on an interval or a cron pattern.
33
+ *
34
+ * A scheduled run is an ordinary job of a name the queue declares, dispatched to the processor
35
+ * registered for that name — never to a guarded entrypoint, because nobody's credentials travel
36
+ * with it. Only a process that listens to the queue creates the scheduler, and the broker produces
37
+ * each next run when the previous one STARTS processing — a schedule needs a running consumer.
38
+ */
39
+ export interface ScheduleDeclaration {
40
+ /**
41
+ * The schedule's stable identity: the broker keeps one scheduler per id, so changing any other
42
+ * field updates it in place, while renaming the id removes one schedule and creates another.
43
+ */
44
+ id: string
45
+ queue: string
46
+ /** A job name the queue declares. */
47
+ name: string
48
+ /**
49
+ * Milliseconds between runs. Exactly one of `every` and `pattern`. Without `startDate` the first
50
+ * run is produced as soon as the scheduler is created.
51
+ */
52
+ every?: number
53
+ /** A cron pattern (an optional leading seconds field is accepted). Exactly one of the two. */
54
+ pattern?: string
55
+ /** The IANA time zone `pattern` is read in. Pattern only. */
56
+ tz?: string
57
+ startDate?: Date | number | string
58
+ /** Once passed, the schedule is treated as absent and a scheduler left from it is removed. */
59
+ endDate?: Date | number | string
60
+ /** Stop after this many runs. */
61
+ limit?: number
62
+ /**
63
+ * Run once as soon as the scheduler is created or its declaration changes, then follow the
64
+ * pattern. Pattern only, and never together with `startDate`. A restart that finds the
65
+ * declaration unchanged does not run it again.
66
+ */
67
+ immediately?: boolean
68
+ data?: unknown
69
+ /**
70
+ * Options every run is enqueued with, over the queue's defaults. `id` and `delay` belong to the
71
+ * broker for a scheduled run and are refused.
72
+ */
73
+ opts?: Omit<JobOptions, 'id' | 'delay'>
23
74
  }
24
75
 
25
76
  export interface QueueDeclaration {
@@ -152,6 +203,11 @@ export interface JobContext<D = unknown> {
152
203
  attempt: number
153
204
  data: D
154
205
  signal: AbortSignal
206
+ /**
207
+ * The id of the schedule that produced this run (`ScheduleDeclaration.id`). Absent for a job
208
+ * enqueued any other way.
209
+ */
210
+ scheduled?: string
155
211
  /**
156
212
  * Renew the lock. A processor that runs a long loop MUST call it as it goes: the broker judges
157
213
  * liveness by the lock, so silence for longer than `lockDuration` is indistinguishable from a
@@ -0,0 +1,100 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+ import {
3
+ assertSchedule, assertSchedules, declareQueue, declareSchedule, queueOf, schedulesOf
4
+ } from '../src/config.js'
5
+ import { ScheduleMisdeclared, UnknownJobName, UnknownQueue } from '../src/errors.js'
6
+ import type { Config, ScheduleDeclaration } from '../src/types.js'
7
+
8
+ const cfg = (): Config => declareQueue(
9
+ declareQueue({ ready: false, service: 'test', type: 'backend' } as unknown as Config,
10
+ 'maintenance', ['reconcile', 'sweep']),
11
+ 'work', ['story:code']
12
+ )
13
+
14
+ const nightly: ScheduleDeclaration = {
15
+ id: 'nightly-reconcile', queue: 'maintenance', name: 'reconcile', pattern: '17 3 * * *', tz: 'UTC',
16
+ }
17
+
18
+ const misdeclared = (schedule: Partial<ScheduleDeclaration>): () => void =>
19
+ () => assertSchedule(cfg(), { ...nightly, ...schedule } as ScheduleDeclaration)
20
+
21
+ describe('declaring schedules', () => {
22
+ test('a declaration is recorded and read back by its queue', () => {
23
+ const config = declareSchedule(cfg(), nightly)
24
+
25
+ expect(schedulesOf(config, 'maintenance')).toEqual([nightly])
26
+ expect(schedulesOf(config, 'work')).toEqual([])
27
+ expect(schedulesOf(config)).toHaveLength(1)
28
+ })
29
+
30
+ test('re-declaring an id replaces rather than appends', () => {
31
+ let config = declareSchedule(cfg(), nightly)
32
+ config = declareSchedule(config, { ...nightly, pattern: '0 4 * * *' })
33
+
34
+ expect(schedulesOf(config)).toHaveLength(1)
35
+ expect(schedulesOf(config)[0].pattern).toBe('0 4 * * *')
36
+ })
37
+
38
+ /**
39
+ * A scheduled run of a name the queue does not accept would fail on every tick. Refusing at
40
+ * declaration turns that into one error at boot, and the refused schedule is not recorded.
41
+ */
42
+ test('a schedule for an undeclared queue or job name is refused and not recorded', () => {
43
+ const config = cfg()
44
+
45
+ expect(() => declareSchedule(config, { ...nightly, queue: 'missing' })).toThrow(UnknownQueue)
46
+ expect(() => declareSchedule(config, { ...nightly, name: 'story:code' })).toThrow(UnknownJobName)
47
+ expect(schedulesOf(config)).toHaveLength(0)
48
+ })
49
+
50
+ test('re-declaring the queue keeps its schedules', () => {
51
+ const config = declareQueue(declareSchedule(cfg(), nightly), 'maintenance', ['reconcile'])
52
+
53
+ expect(queueOf(config, 'maintenance').jobs).toEqual(['reconcile'])
54
+ expect(schedulesOf(config)).toEqual([nightly])
55
+ })
56
+ })
57
+
58
+ describe('asserting a schedule', () => {
59
+ test('exactly one of every and pattern', () => {
60
+ expect(misdeclared({ every: 1_000 })).toThrow(/every-or-pattern/)
61
+ expect(misdeclared({ pattern: undefined, tz: undefined })).toThrow(/every-or-pattern/)
62
+ expect(misdeclared({ pattern: undefined, tz: undefined, every: 0 })).toThrow(/:every$/)
63
+ expect(misdeclared({ pattern: undefined, tz: undefined, every: 1.5 })).toThrow(ScheduleMisdeclared)
64
+ })
65
+
66
+ test('tz and immediately belong to a pattern only', () => {
67
+ expect(misdeclared({ pattern: undefined, every: 1_000 })).toThrow(/:tz$/)
68
+ expect(misdeclared({ pattern: undefined, tz: undefined, every: 1_000, immediately: true }))
69
+ .toThrow(/:immediately$/)
70
+ expect(misdeclared({ immediately: true, startDate: new Date() })).toThrow(/immediately-start-date/)
71
+ expect(misdeclared({ immediately: true })).not.toThrow()
72
+ })
73
+
74
+ test('the broker owns a scheduled run\'s id and delay', () => {
75
+ expect(misdeclared({ opts: { id: 'fixed' } as ScheduleDeclaration['opts'] })).toThrow(/opts-id/)
76
+ expect(misdeclared({ opts: { delay: 10 } as ScheduleDeclaration['opts'] })).toThrow(/opts-delay/)
77
+ expect(misdeclared({ opts: { attempts: 3 } })).not.toThrow()
78
+ })
79
+
80
+ test('an empty id is refused', () => {
81
+ expect(misdeclared({ id: ' ' })).toThrow(ScheduleMisdeclared)
82
+ })
83
+ })
84
+
85
+ describe('asserting every schedule', () => {
86
+ test('two schedules under one id are refused', () => {
87
+ const config = declareSchedule(cfg(), nightly)
88
+ config.queue?.schedules?.push({ ...nightly, pattern: '0 5 * * *' })
89
+
90
+ expect(() => assertSchedules(config)).toThrow(/duplicate/)
91
+ })
92
+
93
+ /** The configuration is plain data; a schedule written into it directly is checked too. */
94
+ test('a schedule written past declareSchedule is still checked', () => {
95
+ const config = declareSchedule(cfg(), nightly)
96
+ config.queue?.schedules?.push({ id: 'orphan', queue: 'maintenance', name: 'gone', every: 1_000 })
97
+
98
+ expect(() => assertSchedules(config)).toThrow(UnknownJobName)
99
+ })
100
+ })