@owlmeans/queue 0.1.18-rc.2 → 0.1.18-rc.20
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 +237 -8
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/queue/SKILL.md +198 -12
- package/build/bridge.d.ts +30 -0
- package/build/bridge.d.ts.map +1 -0
- package/build/bridge.js +108 -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 +8 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +8 -1
- package/build/index.js.map +1 -1
- package/build/protocol.d.ts +11 -0
- package/build/protocol.d.ts.map +1 -0
- package/build/protocol.js +59 -0
- package/build/protocol.js.map +1 -0
- 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 +248 -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 +12 -2
- package/src/bridge.ts +126 -0
- package/src/config.ts +56 -0
- package/src/consts.ts +39 -0
- package/src/errors.ts +106 -0
- package/src/index.ts +8 -0
- package/src/protocol.ts +81 -0
- package/src/transport.ts +82 -0
- package/src/types.ts +260 -0
- package/src/worker.ts +72 -0
- package/tests/bridge.spec.ts +64 -0
- package/tests/config.spec.ts +66 -0
- package/tests/protocol.spec.ts +58 -0
- package/tsconfig.json +1 -0
- package/build/.gitkeep +0 -0
package/README.md
CHANGED
|
@@ -1,20 +1,249 @@
|
|
|
1
1
|
# @owlmeans/queue
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Job queues as resources, plus the route protocol that makes a queued call look like any other call.
|
|
4
|
+
Use it when work must outlive the request. Three cases qualify: the user would otherwise wait more
|
|
5
|
+
than about five seconds, accepted work must survive a process restart, or the work is retried
|
|
6
|
+
against a third party (payment capture, provider sync, bulk mail, report generation, model
|
|
7
|
+
pipelines). A request that finishes in under about two seconds and makes no external call should
|
|
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.
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
This package is contracts only and carries no broker code. A driver implements them
|
|
13
|
+
(`@owlmeans/redis-queue` does, over BullMQ). Depend on this package from a shared contract package,
|
|
14
|
+
and on the driver only where the application wires itself up.
|
|
8
15
|
|
|
9
16
|
## Installation
|
|
10
17
|
|
|
11
18
|
```bash
|
|
12
|
-
bun add @owlmeans/queue
|
|
19
|
+
bun add @owlmeans/queue@^0.1.18-rc.19
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Concepts
|
|
23
|
+
|
|
24
|
+
- **Queue declaration**: `declareQueue(cfg, name, jobs, opts?)` in the shared backend config. It
|
|
25
|
+
names a queue, the job names it accepts, and its worker and default job options. It is an
|
|
26
|
+
address, not a deployment.
|
|
27
|
+
- **Listen**: `listenQueues(cfg, ...names)` in one process's own config says which queues *that
|
|
28
|
+
process* consumes. A process that listens to nothing is a producer only.
|
|
29
|
+
- **Queued protocol**: an immutable entrypoint protocol whose route uses `job()` instead of
|
|
30
|
+
`backend()` (`RouteProtocols.QUEUE`). Callers use `ctx.entrypoint(protocol).call(...)` unchanged,
|
|
31
|
+
and the QUEUE transport carries the call through the broker.
|
|
32
|
+
- **Job record**: `ctx.jobs(queue)` returns a `QueueResource`, the ordinary `Resource<JobRecord>`
|
|
33
|
+
contract plus `wait`, `flow`, `counts` and `close`. `create` enqueues, `list` inspects and `take`
|
|
34
|
+
cancels.
|
|
35
|
+
- **Processor**: a function registered with `QueueWorkerService.process(queue, name, fn)` that gets
|
|
36
|
+
a `JobContext`. It must be safe to run twice and must `touch()` in long loops.
|
|
37
|
+
- **Single-flight id**: a `JobOptions.id` derived from what the job is about (`develop:<storyId>`).
|
|
38
|
+
A duplicate enqueue then returns the existing job instead of a second one.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Declare queues in the shared backend config
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { declareQueue } from '@owlmeans/queue'
|
|
46
|
+
|
|
47
|
+
// Split lanes by how long work takes, not by topic: a one-second read queued behind
|
|
48
|
+
// a thirty-minute job is a UI that looks broken.
|
|
49
|
+
declareQueue(cfg, APP_OPS, [appProtocols.files.get.alias, appProtocols.report.build.alias], {
|
|
50
|
+
worker: { concurrency: 16, lockDuration: 30_000 }
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
declareQueue(cfg, APP_BILLING, [appProtocols.invoice.capture.alias], {
|
|
54
|
+
worker: { concurrency: 4, lockDuration: 30_000 },
|
|
55
|
+
defaults: { attempts: 3, backoff: { type: 'exponential', delay: 250 } }
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The worker process names what it consumes, in its own config:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { listenQueues } from '@owlmeans/queue'
|
|
63
|
+
|
|
64
|
+
listenQueues(cfg, APP_OPS, APP_BILLING) // the api process omits this and only produces
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Both processes build their context through the same backend factory, which wires the driver once:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { appendRedisQueue } from '@owlmeans/redis-queue'
|
|
71
|
+
|
|
72
|
+
appendRedis<C, T>(context)
|
|
73
|
+
appendRedisQueue(context, { hooks: billingQueueHooks(context) })
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Declare a queued entrypoint
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { contract, protocol, typed } from '@owlmeans/entrypoint'
|
|
80
|
+
import { backend, job, route } from '@owlmeans/route'
|
|
81
|
+
|
|
82
|
+
const aliases = { base: 'app:report', build: 'app:report:build' } as const
|
|
83
|
+
const reportBase = protocol(route(aliases.base, '/reports', backend({ service: APP_WORKER })), contract())
|
|
84
|
+
|
|
85
|
+
export const appProtocols = {
|
|
86
|
+
report: {
|
|
87
|
+
base: reportBase,
|
|
88
|
+
build: protocol(
|
|
89
|
+
route(aliases.build, '/:id/build',
|
|
90
|
+
job({ parent: reportBase, service: APP_WORKER, queue: APP_OPS, timeout: 30_000 })),
|
|
91
|
+
contract.request({ params: typed<ReportParams>(ReportParamsSchema) }, typed<ReportResult>()),
|
|
92
|
+
),
|
|
93
|
+
},
|
|
94
|
+
} as const
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The worker process serves it like any backend entrypoint; the HTTP server skips QUEUE routes. A
|
|
98
|
+
producer calls it with no knowledge of the broker:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const result = await context.entrypoint(appProtocols.report.build).call({ params: { id } })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Add `reply: false` to the `job()` options to resolve `Accepted` with `{ id, queue }` as soon as the
|
|
105
|
+
broker has taken the job.
|
|
106
|
+
|
|
107
|
+
### Enqueue with broker options and wait
|
|
108
|
+
|
|
109
|
+
When a producer needs a job id, a delay or retries, pass the same protocol object to the typed
|
|
110
|
+
helpers. Never pass its alias.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { enqueueProtocol, waitForProtocol } from '@owlmeans/queue'
|
|
114
|
+
|
|
115
|
+
const queued = await enqueueProtocol(context, appProtocols.invoice.capture, {
|
|
116
|
+
body: { invoiceId },
|
|
117
|
+
}, {
|
|
118
|
+
id: `capture:${invoiceId}:${attemptSequence}`, // derived from what the job is about
|
|
119
|
+
delay: 250, attempts: 3, backoff: { type: 'exponential', delay: 250 },
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const receipt = await waitForProtocol(context, appProtocols.invoice.capture, queued, { timeout: 120_000 })
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`queueJobOf(req)` gives a protocol handler `{ id, name, queue, attempt, touch }` when it must compare
|
|
126
|
+
the broker job with a claim it persisted.
|
|
127
|
+
|
|
128
|
+
### Processors, hooks and job graphs
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { DEFAULT_ALIAS } from '@owlmeans/queue'
|
|
132
|
+
import type { QueueWorkerService } from '@owlmeans/queue'
|
|
133
|
+
|
|
134
|
+
const worker = context.service<QueueWorkerService>(DEFAULT_ALIAS)
|
|
135
|
+
|
|
136
|
+
worker.process(APP_OPS, 'app:import:rows', async job => {
|
|
137
|
+
for (const chunk of chunks(job.data.fileId)) {
|
|
138
|
+
await job.touch() // renew the lock on every iteration
|
|
139
|
+
await importChunk(chunk) // idempotent: skips rows already imported
|
|
140
|
+
}
|
|
141
|
+
return { ok: true }
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
worker.hooks({
|
|
145
|
+
onJobDead: async (record, reason) => { // retries exhausted: compensate
|
|
146
|
+
await imports.patch({ id: record.data.importId, status: 'failed', error: reason })
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// children complete before the parent; siblings run in parallel
|
|
151
|
+
await context.jobs(APP_OPS).flow({
|
|
152
|
+
name: 'app:import:finalize', data: ref, children: [
|
|
153
|
+
{ name: 'app:import:validate', data: ref, children: [{ name: 'app:import:rows', data: ref }] }
|
|
154
|
+
]
|
|
155
|
+
})
|
|
13
156
|
```
|
|
14
157
|
|
|
15
|
-
|
|
158
|
+
### Jobs are records
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { JobState } from '@owlmeans/queue'
|
|
162
|
+
|
|
163
|
+
const jobs = context.jobs<ImportInput, ImportResult>(APP_OPS)
|
|
164
|
+
|
|
165
|
+
const failed = await jobs.list({ state: JobState.Failed }, { size: 20 })
|
|
166
|
+
const counts = await jobs.counts()
|
|
167
|
+
await jobs.take(jobId) // cancel and return
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Processor rules
|
|
171
|
+
|
|
172
|
+
- **`touch()` in every long loop.** The broker judges liveness by the lock. Silence longer than
|
|
173
|
+
`lockDuration` looks like a dead worker, so the job is re-run elsewhere while this one is still
|
|
174
|
+
going.
|
|
175
|
+
- **Children report, parents decide.** A child returns `{ ok: false, error }` for a domain failure.
|
|
176
|
+
Throwing means "infrastructure broke, retry me". Parents read `children()` / `failedChildren()`.
|
|
177
|
+
- **Retries default to one attempt** (`DEFAULT_ATTEMPTS`), because a blind retry of model work
|
|
178
|
+
re-spends the tokens that just failed. Raise `attempts` per queue for cheap idempotent work.
|
|
179
|
+
- **`job.signal` is not an interrupt.** An orderly stop closes workers first and waits for jobs in
|
|
180
|
+
flight to return.
|
|
181
|
+
|
|
182
|
+
## Guards and errors
|
|
183
|
+
|
|
184
|
+
`handleJob` rebuilds the request from the envelope and runs the entrypoint's guards the way the
|
|
185
|
+
HTTP boundary does, attaching the entity. A guarded queued entrypoint therefore needs the
|
|
186
|
+
producer's credentials in the envelope `headers`. `cfg.queue.envelopeTtl` (seconds) bounds how long
|
|
187
|
+
after `enqueuedAt` an envelope is still accepted; past it, the job answers `EnvelopeExpired`.
|
|
188
|
+
|
|
189
|
+
A refusal the caller must recognise is carried in the reply and rebuilt on the producing side as its
|
|
190
|
+
own class, so `instanceof` keeps working across the broker. `QueueTimeout` means the WAIT ended,
|
|
191
|
+
not the job, so read the job back to learn what became of it.
|
|
192
|
+
|
|
193
|
+
## API
|
|
194
|
+
|
|
195
|
+
| Symbol | Kind | Purpose |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| `declareQueue(cfg, name, jobs, opts?)` | function | Declare a queue and the job names it accepts; re-declaring replaces |
|
|
198
|
+
| `listenQueues(cfg, ...names)` | function | Name the queues this process consumes |
|
|
199
|
+
| `queueOf(cfg, name)`, `queueOfJob(cfg, job)`, `isListening(cfg, name)` | function | Read declarations back; `queueOf` throws `UnknownQueue` |
|
|
200
|
+
| `enqueueProtocol(ctx, protocol, request, options?)` | function | Enqueue a QUEUE protocol with `JobOptions`, typed by the protocol |
|
|
201
|
+
| `waitForProtocol(ctx, protocol, job, { timeout }?)` | function | Wait for that job and unwrap the typed reply |
|
|
202
|
+
| `queueJobOf(request)` | function | `QueueJobMeta` of the broker job behind a handler's request, or `null` |
|
|
203
|
+
| `appendQueueTransport(ctx, alias?)`, `makeQueueTransport(alias?)` | function | Register the QUEUE transport so `call()` routes through the broker |
|
|
204
|
+
| `queueWorkerMiddleware(alias?)` | function | Start the worker at the Ready stage, only in a process that listens |
|
|
205
|
+
| `handleJob(ctx, job)`, `entrypointProcessor(ctx)`, `servedJobs(ctx)` | function | The bridge a driver dispatches entrypoint jobs through |
|
|
206
|
+
| `requestOf(envelope)`, `assertFresh(envelope, ttl?)` | function | Rebuild a request from an envelope; enforce `envelopeTtl` |
|
|
207
|
+
| `QueueConfig`, `QueueDeclaration`, `QueueWorkerOptions`, `JobOptions` | type | Configuration shapes |
|
|
208
|
+
| `QueueResource<D, R>` | type | A queue as a resource, plus `queue`, `wait`, `flow`, `counts`, `close` |
|
|
209
|
+
| `JobRecord<D, R>`, `JobEvent<R>`, `FlowSpec<D>` | type | A job as a record, a lifecycle event, a graph node |
|
|
210
|
+
| `QueueWorkerService` | type | `process`, `start`, `stop`, `listening`, `hooks` |
|
|
211
|
+
| `JobContext<D>`, `JobProcessor<D, R>` | type | What a processor receives; the processor signature |
|
|
212
|
+
| `QueueHooks` | type | `wrapHandler`, `onJobResult`, `onJobStalled`, `onJobDead` |
|
|
213
|
+
| `QueueJobMeta`, `JobEnvelope`, `JobReply<T>` | type | Broker identity for a handler, the call envelope, the reply shape |
|
|
214
|
+
| `QueueAppend`, `QueueDriver` | type | The `ctx.jobs(queue?)` mixin a driver installs; what a driver supplies |
|
|
215
|
+
| `Config`, `Context`, `QueueTransportRequest`, `QueueTransportResponse` | type | Context, config and transport types |
|
|
216
|
+
| `JobState`, `JobEventType` | enum | Job lifecycle states and event types |
|
|
217
|
+
| `isSettled(state?)` | function | Whether a state is final |
|
|
218
|
+
| `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 |
|
|
220
|
+
|
|
221
|
+
## Common pitfalls
|
|
222
|
+
|
|
223
|
+
- **Queueing work the request could finish.** It adds a second process to deploy and a place to fail.
|
|
224
|
+
- **Passing alias strings to `enqueueProtocol`/`waitForProtocol`.** They take the protocol object and
|
|
225
|
+
reject strings and non-QUEUE declarations.
|
|
226
|
+
- **Enqueueing a job name the queue did not declare.** It throws `UnknownJobName` rather than
|
|
227
|
+
parking a job nothing can process.
|
|
228
|
+
- **Registering a processor in a process that does not `listen`.** It throws `QueueNotListening`.
|
|
229
|
+
- **Putting `listen` in the shared declaration.** Every deployment of that binary would then
|
|
230
|
+
consume everything.
|
|
231
|
+
- **Random job ids on an admission step.** Derive the id from the subject, or a retried request
|
|
232
|
+
enqueues twice.
|
|
233
|
+
- **Long loops without `touch()`**, or processors that are not safe to run twice.
|
|
234
|
+
- **Throwing from a child for a domain failure.** Return `{ ok: false, error }` instead.
|
|
235
|
+
- **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.
|
|
237
|
+
- **Calling `ctx.jobs()` with no name once a second queue exists.** It throws `UnknownQueue`.
|
|
238
|
+
|
|
239
|
+
## Related packages
|
|
16
240
|
|
|
17
|
-
- [`@owlmeans/redis-
|
|
241
|
+
- [`@owlmeans/redis-queue`](../redis-queue): the BullMQ driver, and where queue integration tests live
|
|
242
|
+
- [`@owlmeans/route`](../route): `RouteProtocols.QUEUE` and the `job()` builder
|
|
243
|
+
- [`@owlmeans/entrypoint`](../entrypoint): protocol declarations and the transport seam
|
|
244
|
+
- [`@owlmeans/resource`](../resource): the `Resource` contract jobs are read through
|
|
245
|
+
- [`@owlmeans/server-job`](../server-job) / [`@owlmeans/client-job`](../client-job): listing,
|
|
246
|
+
cancelling and following jobs from an application's UI
|
|
18
247
|
|
|
19
248
|
<!-- owlmeans:agent-guidance:start -->
|
|
20
249
|
## Agent guidance
|
|
@@ -24,7 +253,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
24
253
|
your project's skill store (`.agents/skills/`):
|
|
25
254
|
|
|
26
255
|
```sh
|
|
27
|
-
npx @owlmeans/agent-skills
|
|
256
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.23
|
|
28
257
|
```
|
|
29
258
|
|
|
30
259
|
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-
|
|
4
|
+
"version": "0.1.18-rc.20",
|
|
5
|
+
"generatedAt": "2026-09-15T12:50:32.652Z",
|
|
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 — 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.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,24 +8,210 @@ 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.20"` 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 immutable entrypoint protocol whose route names `RouteProtocols.QUEUE`.
|
|
20
|
+
Ordinary calls use `ctx.entrypoint(protocol).call(request)`. Code that needs broker options or a
|
|
21
|
+
job handle uses `enqueueProtocol(ctx, protocol, request, options)` and
|
|
22
|
+
`waitForProtocol(ctx, protocol, job)`. Both derive queue and job name from the protocol object and
|
|
23
|
+
preserve its exact request/reply pair; raw strings and non-QUEUE declarations are rejected.
|
|
24
|
+
|
|
25
|
+
That is what makes it worth having: moving a service-to-service call onto the broker is a change
|
|
26
|
+
to a declaration, not to the places that call it, and the broker holds the work across a restart of
|
|
27
|
+
either side — which an in-process call cannot.
|
|
28
|
+
|
|
29
|
+
## Declaring
|
|
30
|
+
|
|
31
|
+
Queues and the jobs they accept live in the SHARED backend package, so producers and consumers
|
|
32
|
+
read one list:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
declareQueue(cfg, AGENT_WORK, [story.develop.alias], {
|
|
36
|
+
worker: { concurrency: 3, lockDuration: 60_000 }
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The job list is enforced, not documentation: enqueueing a name the queue does not declare throws
|
|
41
|
+
`UnknownJobName` instead of parking a job nothing can ever process. Re-declaring a name replaces,
|
|
42
|
+
so a helper that runs once per composed app is safe to call twice.
|
|
43
|
+
|
|
44
|
+
Which queues a process CONSUMES is separate, and lives in that process's own config:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
listenQueues(cfg, AGENT_WORK, AGENT_OPS) // absent ⇒ producer only
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Keep them apart. A declaration is an address; `listen` is a deployment fact. If a worker bound
|
|
51
|
+
whatever it could serve, every deployment of the same binary would consume everything it imports.
|
|
52
|
+
|
|
53
|
+
A queued entrypoint is an immutable protocol with `job()` in place of `backend()`:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
protocol(
|
|
57
|
+
route(agent.story.develop, '/:id/develop',
|
|
58
|
+
job({ parent: agent.story.base, service: AGENT, queue: AGENT_WORK, timeout: 30_000 })),
|
|
59
|
+
contract.request({ params: typed<StoryParams>(StoryParamsSchema) }, typed()),
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The alias appears in `declareQueue(...jobs)` only because the broker is a string-keyed adapter.
|
|
64
|
+
Application producers and handlers keep the protocol object.
|
|
65
|
+
|
|
66
|
+
When an admission record owns single flight, select the job id atomically first, then enqueue it:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const queued = await enqueueProtocol(context, tokenBalance.reconcile, {
|
|
70
|
+
body: { projectionId },
|
|
71
|
+
}, { id: claimId, delay: 250, attempts: 3, backoff: { type: 'exponential', delay: 250 } })
|
|
72
|
+
const result = await waitForProtocol(context, tokenBalance.reconcile, queued)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Per-call `id`, delay, attempts, backoff and retention belong here; putting them into an alias-based
|
|
76
|
+
helper forfeits protocol inference. `queueJobOf(request)` exposes `{ id, name, queue, attempt }` to
|
|
77
|
+
the rare protocol handler that must compare a broker job with its persisted claim.
|
|
78
|
+
|
|
79
|
+
`reply: false` makes it fire-and-forget: the call resolves `Accepted` with `{ id, queue }` as soon
|
|
80
|
+
as the BROKER has accepted the enqueue — before any worker has picked the job up, so the resolution
|
|
81
|
+
says the job exists and nothing about it having started. That is what a long pipeline wants; watch
|
|
82
|
+
the job by its id for anything further. The default is to wait for the value, for
|
|
83
|
+
`req.timeout ?? route.timeout ?? DEFAULT_JOB_TIMEOUT` (60 s).
|
|
20
84
|
|
|
21
|
-
##
|
|
85
|
+
## Jobs are records
|
|
86
|
+
|
|
87
|
+
`ctx.jobs<D, R>(queue)` returns a `QueueResource<D, R>` — a `Resource<JobRecord<D, R>>` plus
|
|
88
|
+
pub/sub. So inspecting a queue is the resource contract you already know: `create` enqueues,
|
|
89
|
+
`get`/`load`/`list`/`count` inspect, `delete`/`take` cancel, `purge` bulk-cancels. Only what the
|
|
90
|
+
base contract cannot express is added: `wait(id, { timeout })`, `flow(root)`, `counts()`, and
|
|
91
|
+
`close()` for the connections a producer that waited holds.
|
|
92
|
+
|
|
93
|
+
Called with no queue name it answers the SOLE declared queue and throws `UnknownQueue` once there
|
|
94
|
+
are two — an application that grows a second queue is told to name one rather than quietly served
|
|
95
|
+
the wrong backlog. A queue nothing declared throws the same error at the call site.
|
|
96
|
+
|
|
97
|
+
Give `JobOptions.id` a value derived from what the job is ABOUT (`develop:<storyId>`) and a
|
|
98
|
+
duplicate enqueue becomes a no-op returning the job already there — no `RecordExists`. That is
|
|
99
|
+
what makes an admission step safe to retry.
|
|
100
|
+
|
|
101
|
+
## Writing a processor
|
|
22
102
|
|
|
23
103
|
```typescript
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
104
|
+
const worker = context.service<QueueWorkerService>(DEFAULT_ALIAS)
|
|
105
|
+
|
|
106
|
+
worker.process(AGENT_WORK, 'agent:story:code', async job => {
|
|
107
|
+
for (const screen of screens) {
|
|
108
|
+
await job.touch() // ← not optional
|
|
109
|
+
await develop(screen)
|
|
110
|
+
}
|
|
111
|
+
return { ok: true }
|
|
112
|
+
})
|
|
27
113
|
```
|
|
28
114
|
|
|
115
|
+
Registering against a queue this process does not `listen` to throws `QueueNotListening` — the
|
|
116
|
+
question is a deployment one, so it is answered at wiring time rather than when a job arrives.
|
|
117
|
+
|
|
118
|
+
Three rules, each of which has a failure mode attached:
|
|
119
|
+
|
|
120
|
+
- **`touch()` in every long loop.** The broker judges liveness by the lock. Silence for longer
|
|
121
|
+
than `lockDuration` is indistinguishable from a dead worker, so the job is handed to someone
|
|
122
|
+
else while this one is still working — you get two runs, not one. `progress(value)` reports
|
|
123
|
+
without renewing, and the value must be JSON-carryable.
|
|
124
|
+
- **Children report, parents decide.** A child returns `{ ok: false, error }` for a DOMAIN
|
|
125
|
+
failure; it does not throw. Throwing means "infrastructure broke, retry me". A child that
|
|
126
|
+
exhausts its attempts does not fail its parent: the parent runs and reads `children()` /
|
|
127
|
+
`failedChildren()` — both keyed by bare job id — and decides. Give the children of a flow that
|
|
128
|
+
spans queues distinct ids, because that re-keying collapses two ids that are equal across queues.
|
|
129
|
+
- **Retries default to one attempt** (`DEFAULT_ATTEMPTS`). These jobs mostly talk to a model, and
|
|
130
|
+
a blind retry re-spends the tokens that just failed. Raise `attempts` per queue only where jobs
|
|
131
|
+
are cheap and idempotent.
|
|
132
|
+
|
|
133
|
+
`job.signal` is the queue's abort signal, and it is NOT an interrupt. The driver stops by closing
|
|
134
|
+
every worker first — which waits for the jobs in flight to return — and aborts only afterwards, so
|
|
135
|
+
a processor cannot bail out on it and does not need to. An orderly stop drains; an outright kill is
|
|
136
|
+
covered by the rule below.
|
|
137
|
+
|
|
138
|
+
## Being restartable
|
|
139
|
+
|
|
140
|
+
A worker can die mid-job; the lock expires and the step re-runs. So a processor has to be safe to
|
|
141
|
+
run twice — skip work already recorded, delete the rows a previous attempt created, or accept
|
|
142
|
+
regenerating over a baseline. Decide this per job and say so in a comment; there is no way to make
|
|
143
|
+
it automatic.
|
|
144
|
+
|
|
145
|
+
## A queued call is still a guarded call
|
|
146
|
+
|
|
147
|
+
`handleJob` rebuilds the request from the envelope and runs the entrypoint as the HTTP boundary
|
|
148
|
+
would: the declared guards are tried in order, the first that matches authorizes, the entity is
|
|
149
|
+
attached, and only then does the handler run. A queued entrypoint with guards therefore needs the
|
|
150
|
+
producer's credentials on the envelope's `headers`, and an unauthorized job answers
|
|
151
|
+
`AuthorizationError`. An alias this process does not serve answers `JobNotServed` — the job was
|
|
152
|
+
taken by a worker that has no handler for it.
|
|
153
|
+
|
|
154
|
+
Set `cfg.queue.envelopeTtl` (seconds) to bound how long a job envelope stays acceptable. The age is
|
|
155
|
+
measured from the envelope's `enqueuedAt` — when the PRODUCER enqueued it, not when a worker picked
|
|
156
|
+
it up — so a captured envelope stops being replayable, and so does a job that sat behind a backlog
|
|
157
|
+
longer than the window: it answers `EnvelopeExpired` at pickup rather than running late. Size the
|
|
158
|
+
window against the longest backlog you are willing to let run, and leave `envelopeTtl` unset — the
|
|
159
|
+
default — for a queue whose jobs may legitimately wait.
|
|
160
|
+
|
|
161
|
+
## Errors cross the hop as classes
|
|
162
|
+
|
|
163
|
+
A refusal the caller must recognise — `ContentRefused`, `AgentLocked` — is marshalled into the
|
|
164
|
+
reply and rebuilt on the producer side as its own class, so `catch (e) { if (e instanceof ...) }`
|
|
165
|
+
keeps working across the broker. Only broker- and connection-level faults propagate as throws,
|
|
166
|
+
because those are the only ones a retry can fix.
|
|
167
|
+
|
|
168
|
+
## What runs where
|
|
169
|
+
|
|
170
|
+
`queueWorkerMiddleware()` starts the worker at the **Ready** stage — processors register while the
|
|
171
|
+
application wires up, and binding earlier would take jobs the process cannot yet run. A process
|
|
172
|
+
that listens to nothing registers no worker and pays nothing.
|
|
173
|
+
|
|
174
|
+
`canServeModule` in `@owlmeans/server-api/utils` excludes QUEUE routes, the same way it excludes
|
|
175
|
+
sockets: the worker takes those jobs off the broker, and mounting them on the HTTP server too would
|
|
176
|
+
answer every call twice. `servedJobs` is the mirror image — the entrypoints this process both serves and
|
|
177
|
+
listens to, grouped by queue, which is what a driver binds.
|
|
178
|
+
|
|
179
|
+
## Key exports
|
|
180
|
+
|
|
181
|
+
| Export | Description |
|
|
182
|
+
|--------|-------------|
|
|
183
|
+
| `enqueueProtocol` / `waitForProtocol` | Typed protocol-object enqueue and result wait; validates protocol kind and queue configuration. |
|
|
184
|
+
| `queueJobOf(request)` / `QueueJobMeta` | Explicit broker identity boundary for a protocol handler, including `touch()` for long work. |
|
|
185
|
+
| `declareQueue` / `listenQueues` | Configuration — what exists, and what this process consumes |
|
|
186
|
+
| `queueOf` / `queueOfJob` / `isListening` | Reading it back; `queueOf` throws `UnknownQueue` |
|
|
187
|
+
| `QueueConfig` / `QueueDeclaration` / `QueueWorkerOptions` / `JobOptions` | The configuration shapes |
|
|
188
|
+
| `QueueResource<D, R>` | A queue addressed as a resource, plus `wait` / `flow` / `counts` / `close` |
|
|
189
|
+
| `JobRecord<D, R>` / `JobState` / `isSettled` / `JobEvent` / `JobEventType` | The record shape and lifecycle |
|
|
190
|
+
| `FlowSpec<D>` | A graph node — `name`, optional `queue`, `data`, `opts`, `children` |
|
|
191
|
+
| `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` |
|
|
193
|
+
| `QueueHooks` | `wrapHandler`, `onJobResult`, `onJobStalled`, `onJobDead` — the compensation seam |
|
|
194
|
+
| `QueueAppend` | The `ctx.jobs(queue?)` mixin a driver installs; `QueueDriver` is what a driver supplies |
|
|
195
|
+
| `appendQueueTransport` / `makeQueueTransport` | Binds the QUEUE protocol so `call()` routes through the broker |
|
|
196
|
+
| `queueWorkerMiddleware` | Starts the worker at Ready stage |
|
|
197
|
+
| `handleJob` / `servedJobs` / `entrypointProcessor` | The bridge a driver dispatches through |
|
|
198
|
+
| `requestOf` / `assertFresh` / `JobEnvelope` / `JobReply` | The envelope, and rebuilding a request from it |
|
|
199
|
+
| Errors | `QueueError`, `QueueTimeout`, `UnknownJob`, `UnknownJobName`, `UnknownQueue`, `QueueNotListening`, `JobNotServed`, `EnvelopeExpired` |
|
|
200
|
+
| Constants | `DEFAULT_ALIAS` (`queue`), `DEFAULT_JOB_TIMEOUT` (60 000 ms), `DEFAULT_ATTEMPTS` (1) |
|
|
201
|
+
|
|
29
202
|
## Depends On
|
|
30
203
|
|
|
31
|
-
- `@owlmeans/context` — service registration
|
|
204
|
+
- `@owlmeans/context` — service registration and the middleware stage
|
|
205
|
+
- `@owlmeans/entrypoint` — the transport seam and the served-entrypoint dispatch
|
|
206
|
+
- `@owlmeans/route` — `RouteProtocols.QUEUE` and the `job()` builder
|
|
207
|
+
- `@owlmeans/resource` — the `Resource` contract jobs are read through
|
|
208
|
+
- `@owlmeans/auth` / `@owlmeans/auth-common` — guarding a queued call and attaching its entity
|
|
209
|
+
- `@owlmeans/error` — marshalling a refusal across the hop
|
|
210
|
+
|
|
211
|
+
## Related
|
|
212
|
+
|
|
213
|
+
- `redis-queue` — the BullMQ driver; integration tests for queue behaviour live there
|
|
214
|
+
- `server-job` / `client-job` — exposing a queue's jobs to an application's UI (list, cancel,
|
|
215
|
+
progress over a socket), without touching the contracts here
|
|
216
|
+
- `entrypoint` — protocol declarations, bindings, and the transport lookup
|
|
217
|
+
- `resource` — the criteria and paging semantics `list` follows
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AbstractRequest } from '@owlmeans/entrypoint';
|
|
2
|
+
import type { BasicContext } from '@owlmeans/context';
|
|
3
|
+
import type { Config, Context, JobContext, JobEnvelope, JobReply, QueueJobMeta } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rebuild the request the producer described. A queued call is still a call: the same guards, the
|
|
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. */
|
|
10
|
+
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
|
+
//# 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;AAC3F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAMrD,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAGlG;;;GAGG;AACH,eAAO,MAAM,SAAS,aACV,WAAW,QAAQ,MAAM,YAAY,YAAY,CAAC,GAAG,CAAC,QAAQ,YAAY,KACnF,eAWD,CAAA;AAEF,oGAAoG;AACpG,eAAO,MAAM,UAAU,YAAa,OAAO,KAAG,YAAY,GAAG,IAK5D,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,aAAc,WAAW,QAAQ,MAAM,KAAG,IAQjE,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,GAAU,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,WAC3D,CAAC,OAAO,UAAU,CAAC,WAAW,CAAC,KACvC,OAAO,CAAC,QAAQ,CA2DlB,CAAA"}
|
package/build/bridge.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
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, context, job) => ({
|
|
11
|
+
alias: envelope.alias,
|
|
12
|
+
params: (envelope.params ?? {}),
|
|
13
|
+
body: envelope.body,
|
|
14
|
+
headers: (envelope.headers ?? {}),
|
|
15
|
+
query: (envelope.query ?? {}),
|
|
16
|
+
path,
|
|
17
|
+
// Handlers reach their context through `original._ctx`, which the HTTP boundary sets on the raw
|
|
18
|
+
// Fastify request. There is no raw request here, so the shape is supplied deliberately — a
|
|
19
|
+
// handler must not have to know which transport delivered it.
|
|
20
|
+
original: context != null || job != null ? { _ctx: context, _job: job } : undefined,
|
|
21
|
+
});
|
|
22
|
+
/** Read broker identity only where an entrypoint truly needs to bind work to an admission claim. */
|
|
23
|
+
export const queueJobOf = (request) => {
|
|
24
|
+
if (request == null || typeof request !== 'object' || !('original' in request))
|
|
25
|
+
return null;
|
|
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 };
|
|
95
|
+
}
|
|
96
|
+
return { value: response.value, outcome: response.outcome ?? EntrypointOutcome.Ok };
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
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 };
|
|
104
|
+
}
|
|
105
|
+
throw e;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|