@owlmeans/queue 0.1.18-rc.7 → 0.1.18-rc.8
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 +111 -3
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/queue/SKILL.md +125 -12
- package/build/bridge.d.ts +27 -0
- package/build/bridge.d.ts.map +1 -0
- package/build/bridge.js +93 -0
- package/build/bridge.js.map +1 -0
- package/build/config.d.ts +22 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +41 -0
- package/build/config.js.map +1 -0
- package/build/consts.d.ts +33 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +35 -0
- package/build/consts.js.map +1 -0
- package/build/errors.d.ts +59 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +90 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +7 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +7 -1
- package/build/index.js.map +1 -1
- package/build/transport.d.ts +15 -0
- package/build/transport.d.ts.map +1 -0
- package/build/transport.js +62 -0
- package/build/transport.js.map +1 -0
- package/build/types.d.ts +240 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/worker.d.ts +27 -0
- package/build/worker.d.ts.map +1 -0
- package/build/worker.js +58 -0
- package/build/worker.js.map +1 -0
- package/package.json +10 -1
- package/src/bridge.ts +109 -0
- package/src/config.ts +56 -0
- package/src/consts.ts +39 -0
- package/src/errors.ts +106 -0
- package/src/index.ts +7 -0
- package/src/transport.ts +82 -0
- package/src/types.ts +251 -0
- package/src/worker.ts +72 -0
- package/tests/bridge.spec.ts +64 -0
- package/tests/config.spec.ts +66 -0
- package/tsconfig.json +1 -0
package/README.md
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
# @owlmeans/queue
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Job queues as resources, and the route protocol that lets a queued call look like any other call.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
This package is
|
|
7
|
+
This package is contracts only — it carries no broker code. A driver implements them
|
|
8
|
+
(`@owlmeans/redis-queue` does, over BullMQ); depend on this package from a shared contract
|
|
9
|
+
package, and on the driver only where the application wires itself up.
|
|
10
|
+
|
|
11
|
+
The point of it: a route that names `RouteProtocols.QUEUE` is taken by the queue transport, so the
|
|
12
|
+
caller writes `ep.call(...)` exactly as it would for HTTP. Moving a service-to-service call onto
|
|
13
|
+
the broker is a change to one declaration rather than to every call site, and a call that used to
|
|
14
|
+
die with the process survives a restart.
|
|
8
15
|
|
|
9
16
|
## Installation
|
|
10
17
|
|
|
@@ -12,9 +19,110 @@ This package is a placeholder for future queue/task processing functionality wit
|
|
|
12
19
|
bun add @owlmeans/queue
|
|
13
20
|
```
|
|
14
21
|
|
|
22
|
+
## Declaring queues
|
|
23
|
+
|
|
24
|
+
Queues and the job names they accept belong in the shared backend package, so producer and
|
|
25
|
+
consumer read the same list:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { declareQueue, listenQueues } from '@owlmeans/queue'
|
|
29
|
+
|
|
30
|
+
declareQueue(cfg, AGENT_WORK, ['agent:story:develop', 'agent:story:code'], {
|
|
31
|
+
worker: { concurrency: 3, lockDuration: 60_000 }
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Which queues a given process consumes is a separate, process-local statement:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
listenQueues(cfg, AGENT_WORK, AGENT_OPS) // absent ⇒ this process only produces
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
A declaration is an address; `listen` is a deployment fact. Keeping them apart is what lets the
|
|
42
|
+
same binary run as a producer in one deployment and a worker in another.
|
|
43
|
+
|
|
44
|
+
## Declaring a queued entrypoint
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { job } from '@owlmeans/route'
|
|
48
|
+
|
|
49
|
+
entrypoint(
|
|
50
|
+
route(agent.story.develop, '/:id/develop',
|
|
51
|
+
job({ parent: agent.story.base, service: AGENT, queue: AGENT_WORK, timeout: 30_000 })),
|
|
52
|
+
filter(params(StoryParamsSchema))
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
It is served with `elevate(...)` and called with `call()` like any backend entrypoint. Add
|
|
57
|
+
`reply: false` to return as soon as the job is accepted — the call resolves `Accepted` with
|
|
58
|
+
`{ id, queue }`, which is what a long pipeline wants.
|
|
59
|
+
|
|
60
|
+
## Jobs are records
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const jobs = ctx.jobs<DevelopInput, DevelopResult>(AGENT_WORK)
|
|
64
|
+
|
|
65
|
+
const queued = await jobs.create({ queue: AGENT_WORK, name: 'agent:story:code', data: input })
|
|
66
|
+
const result = await jobs.wait(queued.id, { timeout: 120_000 })
|
|
67
|
+
|
|
68
|
+
const failed = await jobs.list({ state: JobState.Failed })
|
|
69
|
+
await jobs.take(queued.id) // cancel and return
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`QueueResource<D, R>` is a `Resource<JobRecord<D, R>>` composed with `PubSubResource<JobEvent<R>>`,
|
|
73
|
+
so criteria, sorting and paging behave as they do for every other backend. Only what the base
|
|
74
|
+
contract cannot express is added: `wait`, `flow` and `counts`.
|
|
75
|
+
|
|
76
|
+
Deriving `JobOptions.id` from what the job is about (`develop:<storyId>`) makes a duplicate
|
|
77
|
+
enqueue a no-op, which is what makes an admission step safe to retry.
|
|
78
|
+
|
|
79
|
+
## Processors
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
worker.process(AGENT_WORK, 'agent:story:code', async job => {
|
|
83
|
+
for (const screen of screens) {
|
|
84
|
+
await job.touch()
|
|
85
|
+
await develop(screen)
|
|
86
|
+
}
|
|
87
|
+
return { ok: true }
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`touch()` is not optional in a long loop. The broker judges liveness by the lock, so going quiet
|
|
92
|
+
for longer than `lockDuration` is indistinguishable from a dead worker and the job is re-run
|
|
93
|
+
elsewhere while this one is still going.
|
|
94
|
+
|
|
95
|
+
A child job reports a domain failure by RETURNING `{ ok: false, error }`. Throwing means
|
|
96
|
+
"infrastructure broke, retry me". Parents read `children()` and decide — children never fail
|
|
97
|
+
their parent directly.
|
|
98
|
+
|
|
99
|
+
Retries default to a single attempt: this work mostly talks to a model, and a blind retry
|
|
100
|
+
re-spends the tokens that just failed.
|
|
101
|
+
|
|
102
|
+
## Job graphs
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
await jobs.flow({
|
|
106
|
+
name: 'story:finalize', data: ref, children: [
|
|
107
|
+
{ name: 'story:gate', data: ref, children: [{ name: 'story:code', data: ref }] }
|
|
108
|
+
]
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Nesting is sequence, siblings are parallel, and a parent reading `children()` is the join.
|
|
113
|
+
|
|
114
|
+
## Errors
|
|
115
|
+
|
|
116
|
+
A refusal the caller must recognise travels in the reply and is rebuilt on the producing side as
|
|
117
|
+
its own class, so `instanceof` keeps working across the broker. `QueueTimeout` means the WAIT
|
|
118
|
+
ended, not the job — read the job back to learn what became of it.
|
|
119
|
+
|
|
15
120
|
## Related Packages
|
|
16
121
|
|
|
17
|
-
- [`@owlmeans/redis-
|
|
122
|
+
- [`@owlmeans/redis-queue`](../redis-queue) — the BullMQ driver, and where queue integration tests live
|
|
123
|
+
- [`@owlmeans/route`](../route) — `RouteProtocols.QUEUE` and the `job()` builder
|
|
124
|
+
- [`@owlmeans/entrypoint`](../entrypoint) — declarations and the transport seam
|
|
125
|
+
- [`@owlmeans/resource`](../resource) — the `Resource` contract jobs are read through
|
|
18
126
|
|
|
19
127
|
<!-- owlmeans:agent-guidance:start -->
|
|
20
128
|
## Agent guidance
|
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-09-
|
|
4
|
+
"version": "0.1.18-rc.8",
|
|
5
|
+
"generatedAt": "2026-09-01T17:39:40.768Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: queue
|
|
3
|
-
description: How to use @owlmeans/queue —
|
|
3
|
+
description: How to use @owlmeans/queue — job queues as resources, the QUEUE route protocol that makes a queued call indistinguishable from an HTTP one, declaring queues and job names, the producer/consumer split, job graphs, and the rules a processor must follow. Auto-invoked when importing queue types or declaring a job route.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,24 +8,137 @@ 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.8"` in `dependencies`
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Contracts only. It carries no broker code — a driver package (`@owlmeans/redis-queue`) implements
|
|
14
|
+
them. Depend on this one from a shared contract package; depend on the driver only where the
|
|
15
|
+
application wires itself up.
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
## The one idea
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
A queued call is an ordinary entrypoint call. The route names `RouteProtocols.QUEUE`, the
|
|
20
|
+
transport registered under that protocol takes it, and the caller writes `ep.call(...)` exactly as
|
|
21
|
+
for HTTP. Nothing at a call site says "queue".
|
|
22
|
+
|
|
23
|
+
That is what makes it worth having: moving a service-to-service call onto the broker is a change
|
|
24
|
+
to a declaration, not to the ~100 places that call it, and a call that used to die with the
|
|
25
|
+
process now survives a restart.
|
|
26
|
+
|
|
27
|
+
## Declaring
|
|
28
|
+
|
|
29
|
+
Queues and the jobs they accept live in the SHARED backend package, so producers and consumers
|
|
30
|
+
read one list:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
declareQueue(cfg, AGENT_WORK, ['agent:story:develop', 'agent:story:code'], {
|
|
34
|
+
worker: { concurrency: 3, lockDuration: 60_000 }
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Which queues a process CONSUMES is separate, and lives in that process's own config:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
listenQueues(cfg, AGENT_WORK, AGENT_OPS) // absent ⇒ producer only
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Keep them apart. A declaration is an address; `listen` is a deployment fact. If a worker bound
|
|
45
|
+
whatever it could serve, every deployment of the same binary would consume everything it imports.
|
|
46
|
+
|
|
47
|
+
A queued entrypoint is declared like any other, with `job()` in place of `backend()`:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
entrypoint(
|
|
51
|
+
route(agent.story.develop, '/:id/develop',
|
|
52
|
+
job({ parent: agent.story.base, service: AGENT, queue: AGENT_WORK, timeout: 30_000 })),
|
|
53
|
+
filter(params(StoryParamsSchema))
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`reply: false` makes it fire-and-forget: the call resolves `Accepted` with `{ id, queue }` as soon
|
|
58
|
+
as the job is taken, which is what a long pipeline wants. The default is to wait for the value.
|
|
20
59
|
|
|
21
|
-
##
|
|
60
|
+
## Jobs are records
|
|
61
|
+
|
|
62
|
+
`ctx.jobs<D, R>(queue)` returns a `QueueResource<D, R>` — a `Resource<JobRecord<D, R>>` plus
|
|
63
|
+
pub/sub. So inspecting a queue is the resource contract you already know: `create` enqueues,
|
|
64
|
+
`get`/`load`/`list`/`count` inspect, `delete`/`take` cancel, `purge` bulk-cancels. Only what the
|
|
65
|
+
base contract cannot express is added: `wait(id, { timeout })`, `flow(root)`, `counts()`.
|
|
66
|
+
|
|
67
|
+
Give `JobOptions.id` a value derived from what the job is ABOUT (`develop:<storyId>`) and a
|
|
68
|
+
duplicate enqueue becomes a no-op. That is what makes an admission step safe to retry.
|
|
69
|
+
|
|
70
|
+
## Writing a processor
|
|
22
71
|
|
|
23
72
|
```typescript
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
73
|
+
worker.process(AGENT_WORK, 'agent:story:code', async job => {
|
|
74
|
+
for (const screen of screens) {
|
|
75
|
+
await job.touch() // ← not optional
|
|
76
|
+
await develop(screen)
|
|
77
|
+
}
|
|
78
|
+
return { ok: true }
|
|
79
|
+
})
|
|
27
80
|
```
|
|
28
81
|
|
|
82
|
+
Three rules, each of which has a failure mode attached:
|
|
83
|
+
|
|
84
|
+
- **`touch()` in every long loop.** The broker judges liveness by the lock. Silence for longer
|
|
85
|
+
than `lockDuration` is indistinguishable from a dead worker, so the job is handed to someone
|
|
86
|
+
else while this one is still working — you get two runs, not one.
|
|
87
|
+
- **Children report, parents decide.** A child returns `{ ok: false, error }` for a DOMAIN
|
|
88
|
+
failure; it does not throw. Throwing means "infrastructure broke, retry me". Children never set
|
|
89
|
+
`failParentOnFailure` — the parent reads `children()` / `failedChildren()` and decides.
|
|
90
|
+
- **Retries default to one attempt.** These jobs mostly talk to a model, and a blind retry
|
|
91
|
+
re-spends the tokens that just failed. Raise `attempts` per queue only where jobs are cheap and
|
|
92
|
+
idempotent.
|
|
93
|
+
|
|
94
|
+
## Being restartable
|
|
95
|
+
|
|
96
|
+
A worker can die mid-job; the lock expires and the step re-runs. So a processor has to be safe to
|
|
97
|
+
run twice — skip work already recorded, delete the rows a previous attempt created, or accept
|
|
98
|
+
regenerating over a baseline. Decide this per job and say so in a comment; there is no way to make
|
|
99
|
+
it automatic.
|
|
100
|
+
|
|
101
|
+
## Errors cross the hop as classes
|
|
102
|
+
|
|
103
|
+
A refusal the caller must recognise — `ContentRefused`, `AgentLocked` — is marshalled into the
|
|
104
|
+
reply and rebuilt on the producer side as its own class, so `catch (e) { if (e instanceof ...) }`
|
|
105
|
+
keeps working across the broker. Only broker- and connection-level faults propagate as throws,
|
|
106
|
+
because those are the only ones a retry can fix.
|
|
107
|
+
|
|
108
|
+
## What runs where
|
|
109
|
+
|
|
110
|
+
`queueWorkerMiddleware()` starts the worker at the **Ready** stage — processors register while the
|
|
111
|
+
application wires up, and binding earlier would take jobs the process cannot yet run. A process
|
|
112
|
+
that listens to nothing registers no worker and pays nothing.
|
|
113
|
+
|
|
114
|
+
`canServe` in `@owlmeans/server-api` excludes QUEUE routes, the same way it excludes sockets: the
|
|
115
|
+
worker takes those jobs off the broker, and mounting them on the HTTP server too would answer
|
|
116
|
+
every call twice.
|
|
117
|
+
|
|
118
|
+
## Key exports
|
|
119
|
+
|
|
120
|
+
| Export | Description |
|
|
121
|
+
|--------|-------------|
|
|
122
|
+
| `declareQueue` / `listenQueues` / `queueOf` | Configuration — what exists, what this process consumes |
|
|
123
|
+
| `QueueResource<D, R>` | A queue addressed as a resource, plus `wait` / `flow` / `counts` |
|
|
124
|
+
| `JobRecord<D, R>` / `JobState` / `JobEvent` | The record shape and lifecycle |
|
|
125
|
+
| `QueueWorkerService` | `process(queue, name, fn)`, `start`, `stop`, `listening` |
|
|
126
|
+
| `JobContext<D>` | What a processor gets: `data`, `signal`, `touch`, `progress`, `children` |
|
|
127
|
+
| `appendQueueTransport` | Binds the QUEUE protocol so `call()` routes through the broker |
|
|
128
|
+
| `queueWorkerMiddleware` | Starts the worker at Ready stage |
|
|
129
|
+
| `handleJob` / `servedJobs` / `entrypointProcessor` | The bridge a driver dispatches through |
|
|
130
|
+
| `QueueHooks` | `onJobResult`, `onJobStalled`, `onJobDead` — the compensation seam |
|
|
131
|
+
|
|
29
132
|
## Depends On
|
|
30
133
|
|
|
31
|
-
- `@owlmeans/context` — service registration
|
|
134
|
+
- `@owlmeans/context` — service registration and the middleware stage
|
|
135
|
+
- `@owlmeans/entrypoint` — the transport seam and the served-entrypoint dispatch
|
|
136
|
+
- `@owlmeans/route` — `RouteProtocols.QUEUE` and the `job()` builder
|
|
137
|
+
- `@owlmeans/resource` — the `Resource` contract jobs are read through
|
|
138
|
+
- `@owlmeans/error` — marshalling a refusal across the hop
|
|
139
|
+
|
|
140
|
+
## Related
|
|
141
|
+
|
|
142
|
+
- `redis-queue` — the BullMQ driver; integration tests for queue behaviour live there
|
|
143
|
+
- `entrypoint` — declarations, elevation, and the transport lookup
|
|
144
|
+
- `resource` — the criteria and paging semantics `list` follows
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AbstractRequest } from '@owlmeans/entrypoint';
|
|
2
|
+
import type { Config, Context, JobContext, JobEnvelope, JobReply } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Rebuild the request the producer described. A queued call is still a call: the same guards, the
|
|
5
|
+
* same filter, the same handler run against it — only the wire is different.
|
|
6
|
+
*/
|
|
7
|
+
export declare const requestOf: (envelope: JobEnvelope, path: string) => AbstractRequest;
|
|
8
|
+
/**
|
|
9
|
+
* Freshness is judged from when the job was ENQUEUED, not from now.
|
|
10
|
+
*
|
|
11
|
+
* A signed envelope proves who produced it, and a replay window keeps a captured one from being
|
|
12
|
+
* useful forever. But a job can legitimately sit behind a long backlog, and judging it on pickup
|
|
13
|
+
* would reject exactly the work that a busy queue delayed — so the producer's timestamp is what
|
|
14
|
+
* the window applies to.
|
|
15
|
+
*
|
|
16
|
+
* @throws {EnvelopeExpired}
|
|
17
|
+
*/
|
|
18
|
+
export declare const assertFresh: (envelope: JobEnvelope, ttl?: number) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Run one queued entrypoint call and describe the outcome.
|
|
21
|
+
*
|
|
22
|
+
* It never throws for a DOMAIN failure — the error is marshalled into the returned reply so the
|
|
23
|
+
* producer can rebuild it as its own class, and so the broker does not count a legitimate refusal
|
|
24
|
+
* as a job to retry. Infrastructure failures are left to propagate: those are what retries are for.
|
|
25
|
+
*/
|
|
26
|
+
export declare const handleJob: <C extends Config, T extends Context<C>>(context: T, job: JobContext<JobEnvelope>) => Promise<JobReply>;
|
|
27
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAkC,MAAM,sBAAsB,CAAA;AAM3F,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAGpF;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,UAAU,WAAW,EAAE,MAAM,MAAM,KAAG,eAO9D,CAAA;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,WAAW,EAAE,MAAM,MAAM,KAAG,IAQjE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,GAAU,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,EACpE,SAAS,CAAC,EAAE,KAAK,UAAU,CAAC,WAAW,CAAC,KACvC,OAAO,CAAC,QAAQ,CAyDlB,CAAA"}
|
package/build/bridge.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { EntrypointOutcome, provideResponse } from '@owlmeans/entrypoint';
|
|
2
|
+
import { AuthorizationError } from '@owlmeans/auth';
|
|
3
|
+
import { attachEntity } from '@owlmeans/auth-common';
|
|
4
|
+
import { ResilientError } from '@owlmeans/error';
|
|
5
|
+
import { EnvelopeExpired, JobNotServed } from './errors.js';
|
|
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
|
+
export const requestOf = (envelope, path) => ({
|
|
11
|
+
alias: envelope.alias,
|
|
12
|
+
params: (envelope.params ?? {}),
|
|
13
|
+
body: envelope.body,
|
|
14
|
+
headers: (envelope.headers ?? {}),
|
|
15
|
+
query: (envelope.query ?? {}),
|
|
16
|
+
path,
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Freshness is judged from when the job was ENQUEUED, not from now.
|
|
20
|
+
*
|
|
21
|
+
* A signed envelope proves who produced it, and a replay window keeps a captured one from being
|
|
22
|
+
* useful forever. But a job can legitimately sit behind a long backlog, and judging it on pickup
|
|
23
|
+
* would reject exactly the work that a busy queue delayed — so the producer's timestamp is what
|
|
24
|
+
* the window applies to.
|
|
25
|
+
*
|
|
26
|
+
* @throws {EnvelopeExpired}
|
|
27
|
+
*/
|
|
28
|
+
export const assertFresh = (envelope, ttl) => {
|
|
29
|
+
if (ttl == null || envelope.enqueuedAt == null) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const age = (Date.now() - new Date(envelope.enqueuedAt).getTime()) / 1000;
|
|
33
|
+
if (age > ttl) {
|
|
34
|
+
throw new EnvelopeExpired(`${envelope.alias}: ${Math.round(age)}s > ${ttl}s`);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Run one queued entrypoint call and describe the outcome.
|
|
39
|
+
*
|
|
40
|
+
* It never throws for a DOMAIN failure — the error is marshalled into the returned reply so the
|
|
41
|
+
* producer can rebuild it as its own class, and so the broker does not count a legitimate refusal
|
|
42
|
+
* as a job to retry. Infrastructure failures are left to propagate: those are what retries are for.
|
|
43
|
+
*/
|
|
44
|
+
export const handleJob = async (context, job) => {
|
|
45
|
+
const envelope = job.data;
|
|
46
|
+
try {
|
|
47
|
+
assertFresh(envelope, context.cfg.queue?.envelopeTtl);
|
|
48
|
+
const entrypoint = context.entrypoint(envelope.alias);
|
|
49
|
+
if (entrypoint.handle == null) {
|
|
50
|
+
throw new JobNotServed(envelope.alias);
|
|
51
|
+
}
|
|
52
|
+
const request = requestOf(envelope, entrypoint.path());
|
|
53
|
+
const response = provideResponse();
|
|
54
|
+
const guards = entrypoint.getGuards();
|
|
55
|
+
if (guards.length > 0) {
|
|
56
|
+
let matched;
|
|
57
|
+
for (const alias of guards) {
|
|
58
|
+
const guard = context.service(alias);
|
|
59
|
+
if (await guard.match(request, response)) {
|
|
60
|
+
matched = guard;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (matched == null) {
|
|
65
|
+
throw new AuthorizationError(`queue:${envelope.alias}`);
|
|
66
|
+
}
|
|
67
|
+
const auth = provideResponse();
|
|
68
|
+
if (!await matched.handle(request, auth)) {
|
|
69
|
+
throw new AuthorizationError(`queue:${envelope.alias}:${matched.alias}`);
|
|
70
|
+
}
|
|
71
|
+
request.auth = auth.value;
|
|
72
|
+
const entity = await attachEntity(context, request);
|
|
73
|
+
if (entity != null) {
|
|
74
|
+
request.entity = entity;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
await entrypoint.handle(request, response);
|
|
78
|
+
if (response.error != null) {
|
|
79
|
+
return { error: ResilientError.marshal(response.error).message };
|
|
80
|
+
}
|
|
81
|
+
return { value: response.value, outcome: response.outcome ?? EntrypointOutcome.Ok };
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
// A refusal the caller must see as its own class — `ContentRefused`, `AgentLocked` — travels
|
|
85
|
+
// in the reply. Only a broker- or connection-level fault should ever reach the retry logic:
|
|
86
|
+
// retrying a refusal just spends the work again to be refused identically.
|
|
87
|
+
if (e instanceof ResilientError) {
|
|
88
|
+
return { error: ResilientError.marshal(e).message };
|
|
89
|
+
}
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Config, JobOptions, QueueDeclaration, QueueWorkerOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Add a queue to the shared configuration. Every process in the monorepo loads the same
|
|
4
|
+
* declarations — a producer needs them to know a queue exists and what it accepts, a worker needs
|
|
5
|
+
* them to bind. Declaring twice replaces, so a helper that runs per app is safe to call.
|
|
6
|
+
*/
|
|
7
|
+
export declare const declareQueue: <C extends Config>(cfg: C, name: string, jobs: string[], opts?: {
|
|
8
|
+
worker?: QueueWorkerOptions;
|
|
9
|
+
defaults?: JobOptions;
|
|
10
|
+
}) => C;
|
|
11
|
+
/**
|
|
12
|
+
* Name the queues this process consumes. It is what turns an otherwise identical binary into a
|
|
13
|
+
* worker, which is why it lives in the process's own config and never in a declaration.
|
|
14
|
+
*/
|
|
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
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +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,EAC3C,KAAK,CAAC,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,EAAE,OAAO;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,EAAE,KAAK,CAAC,EAAE,GAAG,OAAO,MAAM,EAAE,KAAG,CAM3E,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,MAAM,KAAG,gBAQhE,CAAA;AAED,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,MAAM,KAAG,gBAAgB,GAAG,SACpB,CAAA;AAElE,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,MAAM,KAAG,OACzB,CAAA"}
|
package/build/config.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { UnknownQueue } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Add a queue to the shared configuration. Every process in the monorepo loads the same
|
|
4
|
+
* declarations — a producer needs them to know a queue exists and what it accepts, a worker needs
|
|
5
|
+
* them to bind. Declaring twice replaces, so a helper that runs per app is safe to call.
|
|
6
|
+
*/
|
|
7
|
+
export const declareQueue = (cfg, name, jobs, opts) => {
|
|
8
|
+
const queue = { name, jobs, ...opts };
|
|
9
|
+
const queues = cfg.queue?.queues ?? [];
|
|
10
|
+
const existing = queues.findIndex(declared => declared.name === name);
|
|
11
|
+
if (existing < 0) {
|
|
12
|
+
queues.push(queue);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
queues[existing] = queue;
|
|
16
|
+
}
|
|
17
|
+
cfg.queue = { ...cfg.queue, queues };
|
|
18
|
+
return cfg;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Name the queues this process consumes. It is what turns an otherwise identical binary into a
|
|
22
|
+
* worker, which is why it lives in the process's own config and never in a declaration.
|
|
23
|
+
*/
|
|
24
|
+
export const listenQueues = (cfg, ...names) => {
|
|
25
|
+
const listen = new Set([...(cfg.queue?.listen ?? []), ...names]);
|
|
26
|
+
cfg.queue = { ...cfg.queue, listen: [...listen] };
|
|
27
|
+
return cfg;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* @throws {UnknownQueue}
|
|
31
|
+
*/
|
|
32
|
+
export const queueOf = (cfg, name) => {
|
|
33
|
+
const queue = cfg.queue?.queues?.find(declared => declared.name === name);
|
|
34
|
+
if (queue == null) {
|
|
35
|
+
throw new UnknownQueue(name);
|
|
36
|
+
}
|
|
37
|
+
return queue;
|
|
38
|
+
};
|
|
39
|
+
export const queueOfJob = (cfg, job) => cfg.queue?.queues?.find(declared => declared.jobs.includes(job));
|
|
40
|
+
export const isListening = (cfg, name) => cfg.queue?.listen?.includes(name) === true;
|
|
41
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export declare const DEFAULT_ALIAS = "queue";
|
|
2
|
+
/**
|
|
3
|
+
* The state a job is in, as the broker reports it. `Waiting` covers both a job queued normally
|
|
4
|
+
* and one held by an unfinished parent — the difference matters to the broker, never to a caller
|
|
5
|
+
* asking whether an answer has arrived.
|
|
6
|
+
*/
|
|
7
|
+
export declare enum JobState {
|
|
8
|
+
Waiting = "waiting",
|
|
9
|
+
Delayed = "delayed",
|
|
10
|
+
Active = "active",
|
|
11
|
+
Completed = "completed",
|
|
12
|
+
Failed = "failed",
|
|
13
|
+
Unknown = "unknown"
|
|
14
|
+
}
|
|
15
|
+
/** A job has reached a state it will not leave on its own. */
|
|
16
|
+
export declare const isSettled: (state?: JobState) => boolean;
|
|
17
|
+
export declare enum JobEventType {
|
|
18
|
+
Completed = "completed",
|
|
19
|
+
Failed = "failed",
|
|
20
|
+
Progress = "progress"
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* How long a caller waits for a reply when neither the route nor the request says. Long enough
|
|
24
|
+
* for work that talks to a model, short enough that a lost job surfaces as an error rather than a
|
|
25
|
+
* hung request.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DEFAULT_JOB_TIMEOUT = 60000;
|
|
28
|
+
/**
|
|
29
|
+
* Retries default to one attempt: the jobs here are mostly LLM work, and a blind retry re-spends
|
|
30
|
+
* the tokens that just failed. A queue whose jobs are cheap and idempotent raises it per queue.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_ATTEMPTS = 1;
|
|
33
|
+
//# sourceMappingURL=consts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,aAAa,UAAU,CAAA;AAEpC;;;;GAIG;AACH,oBAAY,QAAQ;IAClB,OAAO,YAAY;IACnB,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,SAAS,cAAc;IACvB,MAAM,WAAW;IACjB,OAAO,YAAY;CACpB;AAED,8DAA8D;AAC9D,eAAO,MAAM,SAAS,GAAI,QAAQ,QAAQ,KAAG,OACc,CAAA;AAE3D,oBAAY,YAAY;IACtB,SAAS,cAAc;IACvB,MAAM,WAAW;IACjB,QAAQ,aAAa;CACtB;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,QAAS,CAAA;AAEzC;;;GAGG;AACH,eAAO,MAAM,gBAAgB,IAAI,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const DEFAULT_ALIAS = 'queue';
|
|
2
|
+
/**
|
|
3
|
+
* The state a job is in, as the broker reports it. `Waiting` covers both a job queued normally
|
|
4
|
+
* and one held by an unfinished parent — the difference matters to the broker, never to a caller
|
|
5
|
+
* asking whether an answer has arrived.
|
|
6
|
+
*/
|
|
7
|
+
export var JobState;
|
|
8
|
+
(function (JobState) {
|
|
9
|
+
JobState["Waiting"] = "waiting";
|
|
10
|
+
JobState["Delayed"] = "delayed";
|
|
11
|
+
JobState["Active"] = "active";
|
|
12
|
+
JobState["Completed"] = "completed";
|
|
13
|
+
JobState["Failed"] = "failed";
|
|
14
|
+
JobState["Unknown"] = "unknown";
|
|
15
|
+
})(JobState || (JobState = {}));
|
|
16
|
+
/** A job has reached a state it will not leave on its own. */
|
|
17
|
+
export const isSettled = (state) => state === JobState.Completed || state === JobState.Failed;
|
|
18
|
+
export var JobEventType;
|
|
19
|
+
(function (JobEventType) {
|
|
20
|
+
JobEventType["Completed"] = "completed";
|
|
21
|
+
JobEventType["Failed"] = "failed";
|
|
22
|
+
JobEventType["Progress"] = "progress";
|
|
23
|
+
})(JobEventType || (JobEventType = {}));
|
|
24
|
+
/**
|
|
25
|
+
* How long a caller waits for a reply when neither the route nor the request says. Long enough
|
|
26
|
+
* for work that talks to a model, short enough that a lost job surfaces as an error rather than a
|
|
27
|
+
* hung request.
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_JOB_TIMEOUT = 60_000;
|
|
30
|
+
/**
|
|
31
|
+
* Retries default to one attempt: the jobs here are mostly LLM work, and a blind retry re-spends
|
|
32
|
+
* the tokens that just failed. A queue whose jobs are cheap and idempotent raises it per queue.
|
|
33
|
+
*/
|
|
34
|
+
export const DEFAULT_ATTEMPTS = 1;
|
|
35
|
+
//# sourceMappingURL=consts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAA;AAEpC;;;;GAIG;AACH,MAAM,CAAN,IAAY,QAOX;AAPD,WAAY,QAAQ;IAClB,+BAAmB,CAAA;IACnB,+BAAmB,CAAA;IACnB,6BAAiB,CAAA;IACjB,mCAAuB,CAAA;IACvB,6BAAiB,CAAA;IACjB,+BAAmB,CAAA;AACrB,CAAC,EAPW,QAAQ,KAAR,QAAQ,QAOnB;AAED,8DAA8D;AAC9D,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAgB,EAAW,EAAE,CACrD,KAAK,KAAK,QAAQ,CAAC,SAAS,IAAI,KAAK,KAAK,QAAQ,CAAC,MAAM,CAAA;AAE3D,MAAM,CAAN,IAAY,YAIX;AAJD,WAAY,YAAY;IACtB,uCAAuB,CAAA;IACvB,iCAAiB,CAAA;IACjB,qCAAqB,CAAA;AACvB,CAAC,EAJW,YAAY,KAAZ,YAAY,QAIvB;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAA;AAEzC;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ResilientError } from '@owlmeans/error';
|
|
2
|
+
export declare class QueueError extends ResilientError {
|
|
3
|
+
static typeName: string;
|
|
4
|
+
constructor(message?: string);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The caller stopped waiting. The job is untouched — it may still be queued, running, or already
|
|
8
|
+
* finished; only the wait ended. Read the job back to learn which.
|
|
9
|
+
*/
|
|
10
|
+
export declare class QueueTimeout extends QueueError {
|
|
11
|
+
static typeName: string;
|
|
12
|
+
constructor(msg: string);
|
|
13
|
+
}
|
|
14
|
+
/** No job with that id in this queue — it never existed, or it was removed after completing. */
|
|
15
|
+
export declare class UnknownJob extends QueueError {
|
|
16
|
+
static typeName: string;
|
|
17
|
+
constructor(msg: string);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A job name the queue does not declare. Declaring every job a queue accepts is what keeps an
|
|
21
|
+
* enqueue from succeeding into a queue where nothing can ever process it.
|
|
22
|
+
*/
|
|
23
|
+
export declare class UnknownJobName extends QueueError {
|
|
24
|
+
static typeName: string;
|
|
25
|
+
constructor(msg: string);
|
|
26
|
+
}
|
|
27
|
+
/** A queue asked for that no declaration names. */
|
|
28
|
+
export declare class UnknownQueue extends QueueError {
|
|
29
|
+
static typeName: string;
|
|
30
|
+
constructor(msg: string);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* This process was asked to consume a queue it does not listen to. Which queues a process
|
|
34
|
+
* consumes is configuration (`cfg.queue.listen`), so this is a deployment question, not a code
|
|
35
|
+
* one — the same binary is a producer or a worker depending on it.
|
|
36
|
+
*/
|
|
37
|
+
export declare class QueueNotListening extends QueueError {
|
|
38
|
+
static typeName: string;
|
|
39
|
+
constructor(msg: string);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The job named an entrypoint this process does not serve. Unlike a missing queue this is not a
|
|
43
|
+
* declaration problem: the alias exists, but nothing here elevated it, so the job was taken by a
|
|
44
|
+
* worker that cannot run it.
|
|
45
|
+
*/
|
|
46
|
+
export declare class JobNotServed extends QueueError {
|
|
47
|
+
static typeName: string;
|
|
48
|
+
constructor(msg: string);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The signed envelope is older than the configured lifetime. Freshness is judged from when the
|
|
52
|
+
* producer enqueued the job, not from when a worker picked it up, so a long backlog does not
|
|
53
|
+
* invalidate work that was legitimate when it was submitted.
|
|
54
|
+
*/
|
|
55
|
+
export declare class EnvelopeExpired extends QueueError {
|
|
56
|
+
static typeName: string;
|
|
57
|
+
constructor(msg: string);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +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;gBAElC,OAAO,GAAE,MAAgB;CAGtC;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAkC;gBAErD,GAAG,EAAE,MAAM;CAIxB;AAED,gGAAgG;AAChG,qBAAa,UAAW,SAAQ,UAAU;IACxC,OAAuB,QAAQ,SAAqC;gBAExD,GAAG,EAAE,MAAM;CAIxB;AAED;;;GAGG;AACH,qBAAa,cAAe,SAAQ,UAAU;IAC5C,OAAuB,QAAQ,SAAyC;gBAE5D,GAAG,EAAE,MAAM;CAIxB;AAED,mDAAmD;AACnD,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAuC;gBAE1D,GAAG,EAAE,MAAM;CAIxB;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,UAAU;IAC/C,OAAuB,QAAQ,SAAuC;gBAE1D,GAAG,EAAE,MAAM;CAIxB;AAED;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,UAAU;IAC1C,OAAuB,QAAQ,SAAuC;gBAE1D,GAAG,EAAE,MAAM;CAIxB;AAED;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,OAAuB,QAAQ,SAA0C;gBAE7D,GAAG,EAAE,MAAM;CAIxB"}
|