@owlmeans/queue 0.1.18-rc.40 → 0.1.18-rc.42
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 +17 -16
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/queue/SKILL.md +26 -24
- package/agent-meta/skills/scheduled-jobs/SKILL.md +1 -1
- package/build/bridge/types.d.ts +25 -0
- package/build/bridge/types.d.ts.map +1 -0
- package/build/bridge/types.js +2 -0
- package/build/bridge/types.js.map +1 -0
- package/build/bridge.d.ts +5 -28
- package/build/bridge.d.ts.map +1 -1
- package/build/bridge.js +33 -104
- package/build/bridge.js.map +1 -1
- package/build/config.d.ts +6 -22
- package/build/config.d.ts.map +1 -1
- package/build/config.js +10 -92
- package/build/config.js.map +1 -1
- package/build/entrypoint-jobs/types.d.ts +29 -0
- package/build/entrypoint-jobs/types.d.ts.map +1 -0
- package/build/entrypoint-jobs/types.js +2 -0
- package/build/entrypoint-jobs/types.js.map +1 -0
- package/build/entrypoint-jobs.d.ts +5 -0
- package/build/entrypoint-jobs.d.ts.map +1 -0
- package/build/entrypoint-jobs.js +84 -0
- package/build/entrypoint-jobs.js.map +1 -0
- package/build/index.d.ts +8 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/build/protocol/types.d.ts +12 -0
- package/build/protocol/types.d.ts.map +1 -0
- package/build/protocol/types.js +2 -0
- package/build/protocol/types.js.map +1 -0
- package/build/protocol.d.ts +6 -3
- package/build/protocol.d.ts.map +1 -1
- package/build/protocol.js +57 -50
- package/build/protocol.js.map +1 -1
- package/build/queue-config/types.d.ts +29 -0
- package/build/queue-config/types.d.ts.map +1 -0
- package/build/queue-config/types.js +2 -0
- package/build/queue-config/types.js.map +1 -0
- package/build/queue-config.d.ts +9 -0
- package/build/queue-config.d.ts.map +1 -0
- package/build/queue-config.js +83 -0
- package/build/queue-config.js.map +1 -0
- package/build/queue-route/types.d.ts +14 -0
- package/build/queue-route/types.d.ts.map +1 -0
- package/build/queue-route/types.js +2 -0
- package/build/queue-route/types.js.map +1 -0
- package/build/queue-route.d.ts +4 -0
- package/build/queue-route.d.ts.map +1 -0
- package/build/queue-route.js +24 -0
- package/build/queue-route.js.map +1 -0
- package/build/route.d.ts +2 -2
- package/build/route.d.ts.map +1 -1
- package/build/route.js +5 -20
- package/build/route.js.map +1 -1
- package/build/transport.d.ts.map +1 -1
- package/build/transport.js +2 -2
- package/build/transport.js.map +1 -1
- package/build/types.d.ts +3 -0
- package/build/types.d.ts.map +1 -1
- package/build/worker.d.ts +0 -17
- package/build/worker.d.ts.map +1 -1
- package/build/worker.js +1 -36
- package/build/worker.js.map +1 -1
- package/package.json +8 -8
- package/src/bridge/types.ts +25 -0
- package/src/bridge.ts +38 -118
- package/src/config.ts +14 -105
- package/src/entrypoint-jobs/types.ts +29 -0
- package/src/entrypoint-jobs.ts +105 -0
- package/src/index.ts +8 -0
- package/src/protocol/types.ts +16 -0
- package/src/protocol.ts +74 -53
- package/src/queue-config/types.ts +29 -0
- package/src/queue-config.ts +102 -0
- package/src/queue-route/types.ts +14 -0
- package/src/queue-route.ts +33 -0
- package/src/route.ts +6 -23
- package/src/transport.ts +3 -3
- package/src/types.ts +3 -0
- package/src/worker.ts +2 -48
- package/tests/bridge.spec.ts +8 -8
- package/tests/config.spec.ts +11 -10
- package/tests/protocol.spec.ts +6 -6
- package/tests/schedule.spec.ts +13 -14
- package/build/types.local.d.ts +0 -3
- package/build/types.local.d.ts.map +0 -1
- package/build/types.local.js +0 -2
- package/build/types.local.js.map +0 -1
- package/src/types.local.ts +0 -3
package/README.md
CHANGED
|
@@ -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.
|
|
19
|
+
bun add @owlmeans/queue@^0.1.18-rc.42
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## Concepts
|
|
@@ -114,19 +114,20 @@ When a producer needs a job id, a delay or retries, pass the same protocol objec
|
|
|
114
114
|
helpers. Never pass its alias.
|
|
115
115
|
|
|
116
116
|
```ts
|
|
117
|
-
import {
|
|
117
|
+
import { queueProtocolOf } from '@owlmeans/queue'
|
|
118
118
|
|
|
119
|
-
const
|
|
119
|
+
const queue = queueProtocolOf(context)
|
|
120
|
+
const queued = await queue.enqueue(appProtocols.invoice.capture, {
|
|
120
121
|
body: { invoiceId },
|
|
121
122
|
}, {
|
|
122
123
|
id: `capture:${invoiceId}:${attemptSequence}`, // derived from what the job is about
|
|
123
124
|
delay: 250, attempts: 3, backoff: { type: 'exponential', delay: 250 },
|
|
124
125
|
})
|
|
125
126
|
|
|
126
|
-
const receipt = await
|
|
127
|
+
const receipt = await queue.waitFor(appProtocols.invoice.capture, queued, { timeout: 120_000 })
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
`queueJobOf(req)` gives a protocol handler `{ id, name, queue, attempt, touch }` when it must compare
|
|
130
|
+
`queueBridgeHelper.queueJobOf(req)` gives a protocol handler `{ id, name, queue, attempt, touch }` when it must compare
|
|
130
131
|
the broker job with a claim it persisted.
|
|
131
132
|
|
|
132
133
|
### Processors, hooks and job graphs
|
|
@@ -220,7 +221,7 @@ await jobs.take(jobId) // cancel and return
|
|
|
220
221
|
|
|
221
222
|
## Guards and errors
|
|
222
223
|
|
|
223
|
-
`
|
|
224
|
+
`entrypointJobsOf(ctx).handle` rebuilds the request from the envelope and runs the entrypoint's guards the way the
|
|
224
225
|
HTTP boundary does, attaching the entity. A guarded queued entrypoint therefore needs the
|
|
225
226
|
producer's credentials in the envelope `headers`. `cfg.queue.envelopeTtl` (seconds) bounds how long
|
|
226
227
|
after `enqueuedAt` an envelope is still accepted; past it, the job answers `EnvelopeExpired`.
|
|
@@ -235,17 +236,17 @@ not the job, so read the job back to learn what became of it.
|
|
|
235
236
|
|---|---|---|
|
|
236
237
|
| `declareQueue(cfg, name, jobs, opts?)` | function | Declare a queue and the job names it accepts; re-declaring replaces |
|
|
237
238
|
| `listenQueues(cfg, ...names)` | function | Name the queues this process consumes |
|
|
238
|
-
| `
|
|
239
|
+
| `queueConfigOf(cfg)` — `.queueOf(name)`, `.queueOfJob(job)`, `.isListening(name)` | helper | Read declarations back; `queueOf` throws `UnknownQueue` |
|
|
239
240
|
| `declareSchedule(cfg, schedule)` | function | Declare a recurring job after checking it; re-declaring an id replaces |
|
|
240
|
-
| `schedulesOf(
|
|
241
|
-
| `
|
|
242
|
-
| `
|
|
243
|
-
| `
|
|
244
|
-
| `queueJobOf(request)` |
|
|
241
|
+
| `queueConfigOf(cfg).schedulesOf(queue?)` | helper | The declared schedules, of one queue when named |
|
|
242
|
+
| `queueConfigOf(cfg)` — `.assertSchedule(schedule)`, `.assertSchedules()` | helper | Check one schedule, or every declared one plus duplicate ids |
|
|
243
|
+
| `queueProtocolOf(ctx).enqueue(protocol, request, options?)` | helper | Enqueue a QUEUE protocol with `JobOptions`, typed by the protocol |
|
|
244
|
+
| `queueProtocolOf(ctx).waitFor(protocol, job, { timeout }?)` | helper | Wait for that job and unwrap the typed reply |
|
|
245
|
+
| `queueBridgeHelper.queueJobOf(request)` | helper | `QueueJobMeta` of the broker job behind a handler's request, or `null` |
|
|
245
246
|
| `appendQueueTransport(ctx, alias?)`, `makeQueueTransport(alias?)` | function | Register the QUEUE transport so `call()` routes through the broker |
|
|
246
247
|
| `queueWorkerMiddleware(alias?)` | function | Start the worker at the Ready stage, only in a process that listens |
|
|
247
|
-
| `
|
|
248
|
-
| `requestOf(envelope)`,
|
|
248
|
+
| `entrypointJobsOf(ctx)` — `.handle(job)`, `.processor()`, `.served()` | helper | The bridge a driver dispatches entrypoint jobs through |
|
|
249
|
+
| `queueBridgeHelper` — `.requestOf(envelope, path)`, `.assertFresh(envelope, ttl?)` | helper | Rebuild a request from an envelope; enforce `envelopeTtl` |
|
|
249
250
|
| `QueueConfig`, `QueueDeclaration`, `QueueWorkerOptions`, `JobOptions`, `ScheduleDeclaration` | type | Configuration shapes |
|
|
250
251
|
| `QueueResource<D, R>` | type | A queue as a resource, plus `queue`, `wait`, `flow`, `counts`, `close` |
|
|
251
252
|
| `JobRecord<D, R>`, `JobEvent<R>`, `FlowSpec<D>` | type | A job as a record, a lifecycle event, a graph node |
|
|
@@ -263,7 +264,7 @@ not the job, so read the job back to learn what became of it.
|
|
|
263
264
|
## Common pitfalls
|
|
264
265
|
|
|
265
266
|
- **Queueing work the request could finish.** It adds a second process to deploy and a place to fail.
|
|
266
|
-
- **Passing alias strings to `
|
|
267
|
+
- **Passing alias strings to `queueProtocolOf(ctx).enqueue`/`.waitFor`.** They take the protocol object and
|
|
267
268
|
reject strings and non-QUEUE declarations.
|
|
268
269
|
- **Enqueueing a job name the queue did not declare.** It throws `UnknownJobName` rather than
|
|
269
270
|
parking a job nothing can process.
|
|
@@ -301,7 +302,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
301
302
|
your project's skill store (`.agents/skills/`):
|
|
302
303
|
|
|
303
304
|
```sh
|
|
304
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
305
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.51
|
|
305
306
|
```
|
|
306
307
|
|
|
307
308
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/queue",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-10-
|
|
4
|
+
"version": "0.1.18-rc.42",
|
|
5
|
+
"generatedAt": "2026-10-06T11:45:57.655Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -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.
|
|
11
|
+
**Install:** `"@owlmeans/queue": "^0.1.18-rc.42"` in `dependencies`
|
|
12
12
|
|
|
13
13
|
Backend transport contracts only. It carries no broker code — a driver package
|
|
14
14
|
(`@owlmeans/redis-queue`) implements them. Depend on this one from a shared backend package;
|
|
@@ -19,8 +19,8 @@ application wires itself up.
|
|
|
19
19
|
|
|
20
20
|
A queued call is an immutable entrypoint protocol whose route names `QUEUE_PROTOCOL`.
|
|
21
21
|
Ordinary calls use `ctx.entrypoint(protocol).call(request)`. Code that needs broker options or a
|
|
22
|
-
job handle uses `
|
|
23
|
-
`
|
|
22
|
+
job handle uses `queueProtocolOf(ctx).enqueue(protocol, request, options)` and
|
|
23
|
+
`queueProtocolOf(ctx).waitFor(protocol, job)`. Both derive queue and job name from the protocol object and
|
|
24
24
|
preserve its exact request/reply pair; raw strings and non-queue declarations are rejected.
|
|
25
25
|
|
|
26
26
|
That is what makes it worth having: moving a service-to-service call onto the broker is a change
|
|
@@ -61,7 +61,8 @@ protocol(
|
|
|
61
61
|
)
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
Import `job`, `QUEUE_PROTOCOL
|
|
64
|
+
Import `job`, `QUEUE_PROTOCOL` and `queueRouteHelper` (`isQueueRoute`, `queueRouteOptions`) from
|
|
65
|
+
`@owlmeans/queue`.
|
|
65
66
|
The generic route package deliberately knows none of the queue option shape.
|
|
66
67
|
|
|
67
68
|
The alias appears in `declareQueue(...jobs)` only because the broker is a string-keyed adapter.
|
|
@@ -70,15 +71,16 @@ Application producers and handlers keep the protocol object.
|
|
|
70
71
|
When an admission record owns single flight, select the job id atomically first, then enqueue it:
|
|
71
72
|
|
|
72
73
|
```typescript
|
|
73
|
-
const
|
|
74
|
+
const queue = queueProtocolOf(context)
|
|
75
|
+
const queued = await queue.enqueue(tokenBalance.reconcile, {
|
|
74
76
|
body: { projectionId },
|
|
75
77
|
}, { id: claimId, delay: 250, attempts: 3, backoff: { type: 'exponential', delay: 250 } })
|
|
76
|
-
const result = await
|
|
78
|
+
const result = await queue.waitFor(tokenBalance.reconcile, queued)
|
|
77
79
|
```
|
|
78
80
|
|
|
79
81
|
Per-call `id`, delay, attempts, backoff and retention belong here; putting them into an alias-based
|
|
80
|
-
helper forfeits protocol inference. `queueJobOf(request)` exposes `{ id, name,
|
|
81
|
-
the rare protocol handler that must compare a broker job with its persisted claim.
|
|
82
|
+
helper forfeits protocol inference. `queueBridgeHelper.queueJobOf(request)` exposes `{ id, name,
|
|
83
|
+
queue, attempt }` to the rare protocol handler that must compare a broker job with its persisted claim.
|
|
82
84
|
|
|
83
85
|
`reply: false` makes it fire-and-forget: the call resolves `Accepted` with `{ id, queue }` as soon
|
|
84
86
|
as the BROKER has accepted the enqueue — before any worker has picked the job up, so the resolution
|
|
@@ -160,11 +162,11 @@ declareSchedule(cfg, {
|
|
|
160
162
|
})
|
|
161
163
|
```
|
|
162
164
|
|
|
163
|
-
- **Declare it in the shared package, after its queue.** `declareSchedule` runs
|
|
164
|
-
first: the queue must be declared (`UnknownQueue`) and must
|
|
165
|
-
(`UnknownJobName`), exactly one of `every` (a positive integer of
|
|
166
|
-
(cron), `tz` and `immediately` only with a pattern, never
|
|
167
|
-
`opts.id` / `opts.delay` refused — the broker names and places every run
|
|
165
|
+
- **Declare it in the shared package, after its queue.** `declareSchedule` runs
|
|
166
|
+
`queueConfigOf(cfg).assertSchedule` first: the queue must be declared (`UnknownQueue`) and must
|
|
167
|
+
accept the job name (`UnknownJobName`), exactly one of `every` (a positive integer of
|
|
168
|
+
milliseconds) and `pattern` (cron), `tz` and `immediately` only with a pattern, never
|
|
169
|
+
`immediately` with `startDate`, and `opts.id` / `opts.delay` refused — the broker names and places every run
|
|
168
170
|
(`ScheduleMisdeclared`, message `<id>:<reason>`). Re-declaring an id replaces it.
|
|
169
171
|
- **The id is the identity.** Changing any other field updates the schedule in place; renaming the
|
|
170
172
|
id is one schedule removed and another created.
|
|
@@ -179,13 +181,13 @@ declareSchedule(cfg, {
|
|
|
179
181
|
- **The processor obeys every rule above** — `touch()` through the sweep, and safe to run twice,
|
|
180
182
|
because a restart mid-sweep re-runs it from the start.
|
|
181
183
|
|
|
182
|
-
`schedulesOf(
|
|
183
|
-
including one written into `cfg.queue.schedules` directly, and refuses a duplicate id.
|
|
184
|
+
`queueConfigOf(cfg).schedulesOf(queue?)` reads the declarations back; `.assertSchedules()` checks
|
|
185
|
+
all of them, including one written into `cfg.queue.schedules` directly, and refuses a duplicate id.
|
|
184
186
|
Which recurring work belongs on a schedule at all is `scheduled-jobs`.
|
|
185
187
|
|
|
186
188
|
## A queued call is still a guarded call
|
|
187
189
|
|
|
188
|
-
`
|
|
190
|
+
`entrypointJobsOf(ctx).handle(job)` rebuilds the request from the envelope and runs the entrypoint as the HTTP boundary
|
|
189
191
|
would: the declared guards are tried in order, the first that matches authorizes, the entity is
|
|
190
192
|
attached, and only then does the handler run. A queued entrypoint with guards therefore needs the
|
|
191
193
|
producer's credentials on the envelope's `headers`, and an unauthorized job answers
|
|
@@ -214,18 +216,18 @@ that listens to nothing registers no worker and pays nothing.
|
|
|
214
216
|
|
|
215
217
|
`canServeModule` in `@owlmeans/server-api/utils` excludes QUEUE routes, the same way it excludes
|
|
216
218
|
sockets: the worker takes those jobs off the broker, and mounting them on the HTTP server too would
|
|
217
|
-
answer every call twice. `
|
|
218
|
-
listens to, grouped by queue, which is what a driver binds.
|
|
219
|
+
answer every call twice. `entrypointJobsOf(ctx).served()` is the mirror image — the entrypoints
|
|
220
|
+
this process both serves and listens to, grouped by queue, which is what a driver binds.
|
|
219
221
|
|
|
220
222
|
## Key exports
|
|
221
223
|
|
|
222
224
|
| Export | Description |
|
|
223
225
|
|--------|-------------|
|
|
224
|
-
| `
|
|
225
|
-
| `queueJobOf(request)` / `QueueJobMeta` | Explicit broker identity boundary for a protocol handler, including `touch()` for long work. |
|
|
226
|
+
| `queueProtocolOf(ctx)` — `enqueue` / `waitFor` | Typed protocol-object enqueue and result wait; validates protocol kind and queue configuration. |
|
|
227
|
+
| `queueBridgeHelper.queueJobOf(request)` / `QueueJobMeta` | Explicit broker identity boundary for a protocol handler, including `touch()` for long work. |
|
|
226
228
|
| `declareQueue` / `listenQueues` | Configuration — what exists, and what this process consumes |
|
|
227
|
-
| `declareSchedule`
|
|
228
|
-
| `queueOf` / `queueOfJob` / `isListening` | Reading it back; `queueOf` throws `UnknownQueue` |
|
|
229
|
+
| `declareSchedule`; `queueConfigOf(cfg)` — `schedulesOf` / `assertSchedule` / `assertSchedules` | Recurring jobs — declare, read back, check |
|
|
230
|
+
| `queueConfigOf(cfg)` — `queueOf` / `queueOfJob` / `isListening` | Reading it back; `queueOf` throws `UnknownQueue` |
|
|
229
231
|
| `QueueConfig` / `QueueDeclaration` / `QueueWorkerOptions` / `JobOptions` / `ScheduleDeclaration` | The configuration shapes |
|
|
230
232
|
| `QueueResource<D, R>` | A queue addressed as a resource, plus `wait` / `flow` / `counts` / `close` |
|
|
231
233
|
| `JobRecord<D, R>` / `JobState` / `isSettled` / `JobEvent` / `JobEventType` | The record shape and lifecycle |
|
|
@@ -236,8 +238,8 @@ listens to, grouped by queue, which is what a driver binds.
|
|
|
236
238
|
| `QueueAppend` | The `ctx.jobs(queue?)` mixin a driver installs; `QueueDriver` is what a driver supplies |
|
|
237
239
|
| `appendQueueTransport` / `makeQueueTransport` | Binds the QUEUE protocol so `call()` routes through the broker |
|
|
238
240
|
| `queueWorkerMiddleware` | Starts the worker at Ready stage |
|
|
239
|
-
| `
|
|
240
|
-
| `
|
|
241
|
+
| `entrypointJobsOf(ctx)` — `handle` / `served` / `processor` | The bridge a driver dispatches through |
|
|
242
|
+
| `queueBridgeHelper` — `requestOf` / `assertFresh`; `JobEnvelope` / `JobReply` | The envelope, and rebuilding a request from it |
|
|
241
243
|
| Errors | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired`, `ScheduleMisdeclared` |
|
|
242
244
|
| Constants | `DEFAULT_ALIAS` (`queue`), `DEFAULT_JOB_TIMEOUT` (60 000 ms), `DEFAULT_ATTEMPTS` (1) |
|
|
243
245
|
|
|
@@ -129,7 +129,7 @@ worker.process<Record<string, never>, SweepReport>(APP_MAINTENANCE, 'app:mainten
|
|
|
129
129
|
## Testing
|
|
130
130
|
|
|
131
131
|
- **The declaration** is category A in the application's own shared package: build the config,
|
|
132
|
-
`
|
|
132
|
+
`queueConfigOf(cfg).assertSchedules()`, and pin ids and job names against the queue declaration.
|
|
133
133
|
- **The processor** is tested as a function over a fake `JobContext` or a real context — it is
|
|
134
134
|
ordinary code with a `touch`.
|
|
135
135
|
- **Against a broker** (Redis-gated): the worker's Ready-stage start is not awaited by
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BasicContext } from '@owlmeans/context';
|
|
2
|
+
import type { AbstractRequest } from '@owlmeans/entrypoint';
|
|
3
|
+
import type { JobEnvelope, QueueJobMeta } from '../types.js';
|
|
4
|
+
/** The envelope ↔ request bridge a queued entrypoint call crosses. */
|
|
5
|
+
export interface QueueBridgeHelper {
|
|
6
|
+
/**
|
|
7
|
+
* Rebuild the request the producer described. A queued call is still a call: the same guards, the
|
|
8
|
+
* same filter, the same handler run against it — only the wire is different.
|
|
9
|
+
*/
|
|
10
|
+
requestOf: (envelope: JobEnvelope, path: string, context?: BasicContext<any>, job?: QueueJobMeta) => AbstractRequest;
|
|
11
|
+
/** Read broker identity only where an entrypoint truly needs to bind work to an admission claim. */
|
|
12
|
+
queueJobOf: (request: unknown) => QueueJobMeta | null;
|
|
13
|
+
/**
|
|
14
|
+
* Freshness is judged from when the job was ENQUEUED, not from now.
|
|
15
|
+
*
|
|
16
|
+
* A signed envelope proves who produced it, and a replay window keeps a captured one from being
|
|
17
|
+
* useful forever. But a job can legitimately sit behind a long backlog, and judging it on pickup
|
|
18
|
+
* would reject exactly the work that a busy queue delayed — so the producer's timestamp is what
|
|
19
|
+
* the window applies to.
|
|
20
|
+
*
|
|
21
|
+
* @throws {EnvelopeExpired}
|
|
22
|
+
*/
|
|
23
|
+
assertFresh: (envelope: JobEnvelope, ttl?: number) => void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bridge/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE5D,sEAAsE;AACtE,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,SAAS,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,YAAY,KAAK,eAAe,CAAA;IACpH,oGAAoG;IACpG,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,YAAY,GAAG,IAAI,CAAA;IACrD;;;;;;;;;OASG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC3D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/bridge/types.ts"],"names":[],"mappings":""}
|
package/build/bridge.d.ts
CHANGED
|
@@ -1,30 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* same filter, the same handler run against it — only the wire is different.
|
|
7
|
-
*/
|
|
8
|
-
export declare const requestOf: (envelope: JobEnvelope, path: string, context?: BasicContext<any>, job?: QueueJobMeta) => AbstractRequest;
|
|
9
|
-
/** Read broker identity only where an entrypoint truly needs to bind work to an admission claim. */
|
|
1
|
+
import type { QueueJobMeta } from './types.js';
|
|
2
|
+
import type { QueueBridgeHelper } from './bridge/types.js';
|
|
3
|
+
export declare const createQueueBridgeHelper: () => QueueBridgeHelper;
|
|
4
|
+
export declare const queueBridgeHelper: QueueBridgeHelper;
|
|
5
|
+
/** @deprecated compat:factory-refactor — use `queueBridgeHelper.queueJobOf(…)` */
|
|
10
6
|
export declare const queueJobOf: (request: unknown) => QueueJobMeta | null;
|
|
11
|
-
/**
|
|
12
|
-
* Freshness is judged from when the job was ENQUEUED, not from now.
|
|
13
|
-
*
|
|
14
|
-
* A signed envelope proves who produced it, and a replay window keeps a captured one from being
|
|
15
|
-
* useful forever. But a job can legitimately sit behind a long backlog, and judging it on pickup
|
|
16
|
-
* would reject exactly the work that a busy queue delayed — so the producer's timestamp is what
|
|
17
|
-
* the window applies to.
|
|
18
|
-
*
|
|
19
|
-
* @throws {EnvelopeExpired}
|
|
20
|
-
*/
|
|
21
|
-
export declare const assertFresh: (envelope: JobEnvelope, ttl?: number) => void;
|
|
22
|
-
/**
|
|
23
|
-
* Run one queued entrypoint call and describe the outcome.
|
|
24
|
-
*
|
|
25
|
-
* It never throws for a DOMAIN failure — the error is marshalled into the returned reply so the
|
|
26
|
-
* producer can rebuild it as its own class, and so the broker does not count a legitimate refusal
|
|
27
|
-
* as a job to retry. Infrastructure failures are left to propagate: those are what retries are for.
|
|
28
|
-
*/
|
|
29
|
-
export declare const handleJob: <C extends Config, T extends Context<C>>(context: T, job: JobContext<JobEnvelope>) => Promise<JobReply>;
|
|
30
7
|
//# sourceMappingURL=bridge.d.ts.map
|
package/build/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,YAAY,EAAE,MAAM,YAAY,CAAA;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAG1D,eAAO,MAAM,uBAAuB,QAAO,iBAkC1C,CAAA;AAED,eAAO,MAAM,iBAAiB,mBAA4B,CAAA;AAE1D,kFAAkF;AAClF,eAAO,MAAM,UAAU,YAAa,OAAO,KAAG,YAAY,GAAG,IAA6C,CAAA"}
|
package/build/bridge.js
CHANGED
|
@@ -1,108 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const original = request.original;
|
|
27
|
-
if (original == null || typeof original !== 'object' || !('_job' in original))
|
|
28
|
-
return null;
|
|
29
|
-
return original._job ?? null;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Freshness is judged from when the job was ENQUEUED, not from now.
|
|
33
|
-
*
|
|
34
|
-
* A signed envelope proves who produced it, and a replay window keeps a captured one from being
|
|
35
|
-
* useful forever. But a job can legitimately sit behind a long backlog, and judging it on pickup
|
|
36
|
-
* would reject exactly the work that a busy queue delayed — so the producer's timestamp is what
|
|
37
|
-
* the window applies to.
|
|
38
|
-
*
|
|
39
|
-
* @throws {EnvelopeExpired}
|
|
40
|
-
*/
|
|
41
|
-
export const assertFresh = (envelope, ttl) => {
|
|
42
|
-
if (ttl == null || envelope.enqueuedAt == null) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
const age = (Date.now() - new Date(envelope.enqueuedAt).getTime()) / 1000;
|
|
46
|
-
if (age > ttl) {
|
|
47
|
-
throw new EnvelopeExpired(`${envelope.alias}: ${Math.round(age)}s > ${ttl}s`);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Run one queued entrypoint call and describe the outcome.
|
|
52
|
-
*
|
|
53
|
-
* It never throws for a DOMAIN failure — the error is marshalled into the returned reply so the
|
|
54
|
-
* producer can rebuild it as its own class, and so the broker does not count a legitimate refusal
|
|
55
|
-
* as a job to retry. Infrastructure failures are left to propagate: those are what retries are for.
|
|
56
|
-
*/
|
|
57
|
-
export const handleJob = async (context, job) => {
|
|
58
|
-
const envelope = job.data;
|
|
59
|
-
try {
|
|
60
|
-
assertFresh(envelope, context.cfg.queue?.envelopeTtl);
|
|
61
|
-
const entrypoint = context.entrypoint(envelope.alias);
|
|
62
|
-
if (entrypoint.handle == null) {
|
|
63
|
-
throw new JobNotServed(envelope.alias);
|
|
64
|
-
}
|
|
65
|
-
const request = requestOf(envelope, entrypoint.path(), context, {
|
|
66
|
-
id: job.id, name: job.name, queue: job.queue, attempt: job.attempt, touch: job.touch,
|
|
67
|
-
});
|
|
68
|
-
const response = provideResponse();
|
|
69
|
-
const guards = entrypoint.getGuards();
|
|
70
|
-
if (guards.length > 0) {
|
|
71
|
-
let matched;
|
|
72
|
-
for (const alias of guards) {
|
|
73
|
-
const guard = context.service(alias);
|
|
74
|
-
if (await guard.match(request, response)) {
|
|
75
|
-
matched = guard;
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (matched == null) {
|
|
80
|
-
throw new AuthorizationError(`queue:${envelope.alias}`);
|
|
81
|
-
}
|
|
82
|
-
const auth = provideResponse();
|
|
83
|
-
if (!await matched.handle(request, auth)) {
|
|
84
|
-
throw new AuthorizationError(`queue:${envelope.alias}:${matched.alias}`);
|
|
85
|
-
}
|
|
86
|
-
request.auth = auth.value;
|
|
87
|
-
const entity = await attachEntity(context, request);
|
|
88
|
-
if (entity != null) {
|
|
89
|
-
request.entity = entity;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
await entrypoint.handle(request, response);
|
|
93
|
-
if (response.error != null) {
|
|
94
|
-
return { error: ResilientError.marshal(response.error).message };
|
|
1
|
+
import { EnvelopeExpired } from './errors.js';
|
|
2
|
+
export const createQueueBridgeHelper = () => {
|
|
3
|
+
const requestOf = (envelope, path, context, job) => ({
|
|
4
|
+
alias: envelope.alias,
|
|
5
|
+
params: (envelope.params ?? {}),
|
|
6
|
+
body: envelope.body,
|
|
7
|
+
headers: (envelope.headers ?? {}),
|
|
8
|
+
query: (envelope.query ?? {}),
|
|
9
|
+
path,
|
|
10
|
+
// Handlers reach their context through `original._ctx`, which the HTTP boundary sets on the raw
|
|
11
|
+
// Fastify request. There is no raw request here, so the shape is supplied deliberately — a
|
|
12
|
+
// handler must not have to know which transport delivered it.
|
|
13
|
+
original: context != null || job != null ? { _ctx: context, _job: job } : undefined,
|
|
14
|
+
});
|
|
15
|
+
const queueJobOf = (request) => {
|
|
16
|
+
if (request == null || typeof request !== 'object' || !('original' in request))
|
|
17
|
+
return null;
|
|
18
|
+
const original = request.original;
|
|
19
|
+
if (original == null || typeof original !== 'object' || !('_job' in original))
|
|
20
|
+
return null;
|
|
21
|
+
return original._job ?? null;
|
|
22
|
+
};
|
|
23
|
+
const assertFresh = (envelope, ttl) => {
|
|
24
|
+
if (ttl == null || envelope.enqueuedAt == null) {
|
|
25
|
+
return;
|
|
95
26
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// A refusal the caller must see as its own class — `ContentRefused`, `AgentLocked` — travels
|
|
100
|
-
// in the reply. Only a broker- or connection-level fault should ever reach the retry logic:
|
|
101
|
-
// retrying a refusal just spends the work again to be refused identically.
|
|
102
|
-
if (e instanceof ResilientError) {
|
|
103
|
-
return { error: ResilientError.marshal(e).message };
|
|
27
|
+
const age = (Date.now() - new Date(envelope.enqueuedAt).getTime()) / 1000;
|
|
28
|
+
if (age > ttl) {
|
|
29
|
+
throw new EnvelopeExpired(`${envelope.alias}: ${Math.round(age)}s > ${ttl}s`);
|
|
104
30
|
}
|
|
105
|
-
|
|
106
|
-
}
|
|
31
|
+
};
|
|
32
|
+
return { requestOf, queueJobOf, assertFresh };
|
|
107
33
|
};
|
|
34
|
+
export const queueBridgeHelper = createQueueBridgeHelper();
|
|
35
|
+
/** @deprecated compat:factory-refactor — use `queueBridgeHelper.queueJobOf(…)` */
|
|
36
|
+
export const queueJobOf = (request) => queueBridgeHelper.queueJobOf(request);
|
|
108
37
|
//# sourceMappingURL=bridge.js.map
|
package/build/bridge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAsB,EAAE;IAC7D,MAAM,SAAS,GAAG,CAChB,QAAqB,EAAE,IAAY,EAAE,OAA2B,EAAE,GAAkB,EACnE,EAAE,CAAC,CAAC;QACrB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAA8B;QAC5D,IAAI,EAAE,QAAQ,CAAC,IAA+B;QAC9C,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAA+B;QAC/D,KAAK,EAAE,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAA6B;QACzD,IAAI;QACJ,gGAAgG;QAChG,2FAA2F;QAC3F,8DAA8D;QAC9D,QAAQ,EAAE,OAAO,IAAI,IAAI,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;KACpF,CAAC,CAAA;IAEF,MAAM,UAAU,GAAG,CAAC,OAAgB,EAAuB,EAAE;QAC3D,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,UAAU,IAAI,OAAO,CAAC;YAAE,OAAO,IAAI,CAAA;QAC3F,MAAM,QAAQ,GAAI,OAAkC,CAAC,QAAQ,CAAA;QAC7D,IAAI,QAAQ,IAAI,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAA;QAC1F,OAAQ,QAAoC,CAAC,IAAI,IAAI,IAAI,CAAA;IAC3D,CAAC,CAAA;IAED,MAAM,WAAW,GAAG,CAAC,QAAqB,EAAE,GAAY,EAAQ,EAAE;QAChE,IAAI,GAAG,IAAI,IAAI,IAAI,QAAQ,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;YAC/C,OAAM;QACR,CAAC;QACD,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAA;QACzE,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;YACd,MAAM,IAAI,eAAe,CAAC,GAAG,QAAQ,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,CAAA;QAC/E,CAAC;IACH,CAAC,CAAA;IAED,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,CAAA;AAC/C,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,uBAAuB,EAAE,CAAA;AAE1D,kFAAkF;AAClF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAgB,EAAuB,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA"}
|
package/build/config.d.ts
CHANGED
|
@@ -13,21 +13,6 @@ export declare const declareQueue: <C extends Config>(cfg: C, name: string, jobs
|
|
|
13
13
|
* worker, which is why it lives in the process's own config and never in a declaration.
|
|
14
14
|
*/
|
|
15
15
|
export declare const listenQueues: <C extends Config>(cfg: C, ...names: string[]) => C;
|
|
16
|
-
/**
|
|
17
|
-
* @throws {UnknownQueue}
|
|
18
|
-
*/
|
|
19
|
-
export declare const queueOf: <C extends Config>(cfg: C, name: string) => QueueDeclaration;
|
|
20
|
-
export declare const queueOfJob: <C extends Config>(cfg: C, job: string) => QueueDeclaration | undefined;
|
|
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
16
|
/**
|
|
32
17
|
* Add a recurring job to the shared configuration. Declare the queue first — a schedule is checked
|
|
33
18
|
* against it here. Declaring an id twice replaces, like `declareQueue`.
|
|
@@ -38,13 +23,12 @@ export declare const assertSchedule: <C extends Config>(cfg: C, schedule: Schedu
|
|
|
38
23
|
* @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} — see `assertSchedule`.
|
|
39
24
|
*/
|
|
40
25
|
export declare const declareSchedule: <C extends Config>(cfg: C, schedule: ScheduleDeclaration) => C;
|
|
41
|
-
/**
|
|
26
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).queueOfJob(…)` */
|
|
27
|
+
export declare const queueOfJob: <C extends Config>(cfg: C, job: string) => QueueDeclaration | undefined;
|
|
28
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).isListening(…)` */
|
|
29
|
+
export declare const isListening: <C extends Config>(cfg: C, name: string) => boolean;
|
|
30
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).schedulesOf(…)` */
|
|
42
31
|
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
|
-
*/
|
|
32
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).assertSchedules()` */
|
|
49
33
|
export declare const assertSchedules: <C extends Config>(cfg: C) => void;
|
|
50
34
|
//# sourceMappingURL=config.d.ts.map
|
package/build/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
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;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,YAAY,mBAAmB,KAAG,CAezF,CAAA;AAED,mFAAmF;AACnF,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,OAAO,MAAM,KAAG,gBAAgB,GAAG,SAClD,CAAA;AAEpC,oFAAoF;AACpF,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,QAAQ,MAAM,KAAG,OAC/B,CAAA;AAEtC,oFAAoF;AACpF,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,UAAU,MAAM,KAAG,mBAAmB,EACnD,CAAA;AAEvC,uFAAuF;AACvF,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,OAAO,CAAC,KAAG,IAA4C,CAAA"}
|