@owlmeans/queue 0.1.18-rc.4 → 0.1.18-rc.41
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 +286 -8
- package/agent-meta/manifest.json +9 -2
- package/agent-meta/skills/queue/SKILL.md +243 -12
- package/agent-meta/skills/scheduled-jobs/SKILL.md +151 -0
- 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 +7 -0
- package/build/bridge.d.ts.map +1 -0
- package/build/bridge.js +37 -0
- package/build/bridge.js.map +1 -0
- package/build/config.d.ts +34 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +59 -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/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/errors.d.ts +68 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +103 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +18 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +13 -1
- 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 +13 -0
- package/build/protocol.d.ts.map +1 -0
- package/build/protocol.js +63 -0
- package/build/protocol.js.map +1 -0
- 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 +9 -0
- package/build/route.d.ts.map +1 -0
- package/build/route.js +16 -0
- package/build/route.js.map +1 -0
- package/build/transport.d.ts +15 -0
- package/build/transport.d.ts.map +1 -0
- package/build/transport.js +59 -0
- package/build/transport.js.map +1 -0
- package/build/types.d.ts +312 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/types.local.d.ts +4 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/build/utils.d.ts +4 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +4 -0
- package/build/utils.js.map +1 -0
- package/build/worker.d.ts +10 -0
- package/build/worker.d.ts.map +1 -0
- package/build/worker.js +24 -0
- package/build/worker.js.map +1 -0
- package/package.json +13 -3
- package/src/bridge/types.ts +25 -0
- package/src/bridge.ts +46 -0
- package/src/config.ts +80 -0
- package/src/consts.ts +38 -0
- package/src/entrypoint-jobs/types.ts +29 -0
- package/src/entrypoint-jobs.ts +105 -0
- package/src/errors.ts +121 -0
- package/src/index.ts +18 -0
- package/src/protocol/types.ts +16 -0
- package/src/protocol.ts +95 -0
- 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 +23 -0
- package/src/transport.ts +77 -0
- package/src/types.ts +326 -0
- package/src/utils.ts +5 -0
- package/src/worker.ts +27 -0
- package/tests/bridge.spec.ts +64 -0
- package/tests/config.spec.ts +67 -0
- package/tests/protocol.spec.ts +58 -0
- package/tests/schedule.spec.ts +99 -0
- package/tsconfig.json +1 -0
- package/build/.gitkeep +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scheduled-jobs
|
|
3
|
+
description: Recurring background work on OwlMeans queues — declareSchedule in @owlmeans/queue, reconciled into BullMQ job schedulers by @owlmeans/redis-queue when a listening worker starts. Covers interval vs cron schedules, the processor a sweep runs, overlap and restart semantics, renaming and removing a schedule, multi-replica and rolling-deploy behaviour, and testing. Use when adding a nightly or periodic job, replacing a platform CronJob or a setInterval with a schedule, or diagnosing a schedule that does not run.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
7
|
+
|
|
8
|
+
# Scheduled jobs
|
|
9
|
+
|
|
10
|
+
**Layer:** Infra · **Packages:** `@owlmeans/queue` (lead — the declaration), `@owlmeans/redis-queue`
|
|
11
|
+
(the reconciliation)
|
|
12
|
+
|
|
13
|
+
A schedule is a declaration that makes the broker produce an ordinary job on an interval or a cron
|
|
14
|
+
pattern. A worker that consumes the queue runs it with a processor. There is no second scheduler
|
|
15
|
+
process, no cron table and no timer in application code.
|
|
16
|
+
|
|
17
|
+
## When a schedule is the answer
|
|
18
|
+
|
|
19
|
+
| Situation | Answer |
|
|
20
|
+
|---|---|
|
|
21
|
+
| Periodic sweep, reconciliation, digest, retention delete, rollup | A schedule on a queue a worker consumes |
|
|
22
|
+
| "Remind this user in 20 minutes" | `JobOptions.delay` on one job — a schedule is not per record |
|
|
23
|
+
| A far-future or cancellable per-record deadline | A row with a due date, swept by one schedule |
|
|
24
|
+
| A deployment that runs no worker | A platform `CronJob` calling a guarded entrypoint |
|
|
25
|
+
| A `setInterval` inside a request handler or a long-lived service | A schedule — the interval dies with the process and runs once per replica |
|
|
26
|
+
|
|
27
|
+
Whether the work belongs on a queue at all is `resource-choice`.
|
|
28
|
+
|
|
29
|
+
## Declaring
|
|
30
|
+
|
|
31
|
+
In the SHARED package every process loads, after the queue:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
declareQueue(cfg, APP_MAINTENANCE, ['app:maintenance:reconcile', 'app:maintenance:purge'], {
|
|
35
|
+
worker: { concurrency: 1, lockDuration: 60_000 }
|
|
36
|
+
})
|
|
37
|
+
declareSchedule(cfg, {
|
|
38
|
+
id: 'app-nightly-reconcile', queue: APP_MAINTENANCE, name: 'app:maintenance:reconcile',
|
|
39
|
+
pattern: '17 3 * * *', tz: 'UTC', data: {},
|
|
40
|
+
})
|
|
41
|
+
declareSchedule(cfg, {
|
|
42
|
+
id: 'app-purge-expired', queue: APP_MAINTENANCE, name: 'app:maintenance:purge', every: 15 * 60_000,
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
And only in the process that runs them: `listenQueues(cfg, APP_MAINTENANCE)` plus a processor per
|
|
47
|
+
job name, registered while the context is wired.
|
|
48
|
+
|
|
49
|
+
- **`pattern` for the calendar, `every` for a frequency.** A nightly job is a pattern with an
|
|
50
|
+
explicit `tz`; "every quarter hour, whenever" is `every`. Pick an odd minute (`17 3 * * *`) so
|
|
51
|
+
many applications' sweeps do not stampede one Redis at the top of the hour.
|
|
52
|
+
- **`every` runs at once** when the scheduler is created (unless `startDate` says otherwise); a
|
|
53
|
+
pattern waits for its next match unless `immediately: true`, which fires only when the scheduler
|
|
54
|
+
is created or its declaration changes — never on an unchanged restart.
|
|
55
|
+
- **The id is permanent.** It is the scheduler's identity in the broker: any other field changes in
|
|
56
|
+
place, a renamed id is a removed schedule plus a new one. Prefix it with the application
|
|
57
|
+
(`app-…`) — ids share one namespace per queue.
|
|
58
|
+
- **Declaration errors surface at boot.** `declareSchedule` throws `UnknownQueue`,
|
|
59
|
+
`UnknownJobName` or `ScheduleMisdeclared` (`<id>:<reason>`) rather than letting the broker fail
|
|
60
|
+
on every tick.
|
|
61
|
+
- **`opts` sets retries and retention for every run; `id` and `delay` are refused** — the broker
|
|
62
|
+
names each run and places it on the schedule.
|
|
63
|
+
|
|
64
|
+
## Writing the processor
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const log = logger('maintenance') // from '@owlmeans/log'
|
|
68
|
+
|
|
69
|
+
worker.process<Record<string, never>, SweepReport>(APP_MAINTENANCE, 'app:maintenance:reconcile',
|
|
70
|
+
async job => {
|
|
71
|
+
const report = { scanned: 0, repaired: 0, failed: 0 }
|
|
72
|
+
for await (const page of organizations({ size: 200 })) {
|
|
73
|
+
await job.touch()
|
|
74
|
+
for (const organization of page) {
|
|
75
|
+
try {
|
|
76
|
+
report.repaired += await reconcile(organization.entityId) ? 1 : 0
|
|
77
|
+
} catch (error) {
|
|
78
|
+
report.failed++
|
|
79
|
+
log.error('Reconcile failed', { entityId: organization.entityId, error })
|
|
80
|
+
}
|
|
81
|
+
report.scanned++
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
log.info('Maintenance run finished', { scheduled: job.scheduled, ...report }, { event: 'job.stop' })
|
|
85
|
+
return report
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- **A scheduled run has no caller.** No credentials, no entity, no request: it is a processor job,
|
|
90
|
+
never a guarded entrypoint. A step that must go through another service's guard calls that
|
|
91
|
+
service with service credentials.
|
|
92
|
+
- **Idempotent per item, not per run.** A worker rolled out mid-sweep, a stalled lock or a manual
|
|
93
|
+
re-run repeats the whole sweep; each item's repair must be a no-op the second time.
|
|
94
|
+
- **One item's failure is logged and counted, never thrown.** A thrown sweep is retried from the
|
|
95
|
+
start, or dropped after its attempts — either way the other items pay for one bad record.
|
|
96
|
+
- **`touch()` per page.** A sweep over every organization outlives `lockDuration` easily; silence
|
|
97
|
+
hands the job to another worker while this one is still going.
|
|
98
|
+
- **Return a small report.** It lands on the completed job, where an operator reads it.
|
|
99
|
+
|
|
100
|
+
## Overlap, replicas and deploys
|
|
101
|
+
|
|
102
|
+
- **One job per tick, whatever the replica count.** The broker produces one job; one worker takes
|
|
103
|
+
it. Several replicas listening to the queue do not multiply runs.
|
|
104
|
+
- **Runs can overlap on a concurrent queue.** The next run is produced when the previous one
|
|
105
|
+
starts, so a run that outlasts the interval meets its successor. Put sweeps on their own queue
|
|
106
|
+
with `concurrency: 1`, apart from interactive lanes.
|
|
107
|
+
- **A schedule needs a consumer.** Only a listening worker creates the scheduler, and the next run
|
|
108
|
+
is produced only when the previous one starts processing. A queue nothing consumes stops.
|
|
109
|
+
- **Every listener reconciles at start, from its own configuration.** It creates or updates what it
|
|
110
|
+
declares and removes the schedules it owns (`owlmeans:` scheduler ids) that it does not. Every
|
|
111
|
+
process listening to one queue must load the same schedule list; during a rolling deploy the
|
|
112
|
+
newest process to start wins, and the old pods leave the broker alone after their own start.
|
|
113
|
+
- **Removing a schedule is deleting its declaration.** The next worker start removes the scheduler.
|
|
114
|
+
So is a declaration whose job name the queue no longer accepts, or whose `endDate` has passed.
|
|
115
|
+
- **Scheduling never costs consumption.** A refused declaration or a broker failure while
|
|
116
|
+
reconciling is logged and skipped; the worker keeps consuming. Look for
|
|
117
|
+
`redis-queue-schedules:<queue>` in the worker's log when a schedule does not appear.
|
|
118
|
+
|
|
119
|
+
## Diagnosing "it never ran"
|
|
120
|
+
|
|
121
|
+
1. Is some process listening to the queue (`listenQueues`) and is its worker up?
|
|
122
|
+
2. Did that process's start log a `redis-queue-schedules:<queue>` refusal?
|
|
123
|
+
3. Does `new Queue(queue, { connection, prefix }).getJobSchedulers()` list `owlmeans:<id>` with the
|
|
124
|
+
expected `pattern`/`every`, `tz` and `next`?
|
|
125
|
+
4. Is a processor registered for the job name? A run without one fails once as `UnknownJobName`
|
|
126
|
+
and shows in the failed state.
|
|
127
|
+
5. Is a previous run still active on a `concurrency: 1` queue?
|
|
128
|
+
|
|
129
|
+
## Testing
|
|
130
|
+
|
|
131
|
+
- **The declaration** is category A in the application's own shared package: build the config,
|
|
132
|
+
`queueConfigOf(cfg).assertSchedules()`, and pin ids and job names against the queue declaration.
|
|
133
|
+
- **The processor** is tested as a function over a fake `JobContext` or a real context — it is
|
|
134
|
+
ordinary code with a `touch`.
|
|
135
|
+
- **Against a broker** (Redis-gated): the worker's Ready-stage start is not awaited by
|
|
136
|
+
`context.init()`, so poll the broker (`getJobSchedulers`) or wait for the first run instead of
|
|
137
|
+
asserting straight after boot. A queue's schedulers go with `obliterate()`.
|
|
138
|
+
|
|
139
|
+
## External docs
|
|
140
|
+
|
|
141
|
+
- https://docs.bullmq.io/guide/job-schedulers — `upsertJobScheduler(id, { every | pattern, tz,
|
|
142
|
+
startDate, endDate, limit, immediately }, { name, data, opts })` creates or updates one scheduler
|
|
143
|
+
per id and replaces its pending run; `getJobSchedulers()` / `removeJobScheduler(id)`. The next job
|
|
144
|
+
is added when the previous one starts processing, so a worker must be consuming. Produced jobs
|
|
145
|
+
carry `repeatJobKey` = the scheduler id.
|
|
146
|
+
|
|
147
|
+
## Related
|
|
148
|
+
|
|
149
|
+
- `queue` — `declareSchedule`, `JobContext.scheduled`, processor rules
|
|
150
|
+
- `redis-queue` — `syncSchedules`, the `owlmeans:` ownership prefix, the reconciliation rules
|
|
151
|
+
- `resource-choice` — whether recurring work belongs on a queue at all
|
|
@@ -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":""}
|
|
@@ -0,0 +1,7 @@
|
|
|
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(…)` */
|
|
6
|
+
export declare const queueJobOf: (request: unknown) => QueueJobMeta | null;
|
|
7
|
+
//# sourceMappingURL=bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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;
|
|
26
|
+
}
|
|
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`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return { requestOf, queueJobOf, assertFresh };
|
|
33
|
+
};
|
|
34
|
+
export const queueBridgeHelper = createQueueBridgeHelper();
|
|
35
|
+
/** @deprecated compat:factory-refactor — use `queueBridgeHelper.queueJobOf(…)` */
|
|
36
|
+
export const queueJobOf = (request) => queueBridgeHelper.queueJobOf(request);
|
|
37
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Config, JobOptions, QueueDeclaration, QueueWorkerOptions, ScheduleDeclaration } 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
|
+
* Add a recurring job to the shared configuration. Declare the queue first — a schedule is checked
|
|
18
|
+
* against it here. Declaring an id twice replaces, like `declareQueue`.
|
|
19
|
+
*
|
|
20
|
+
* Declaring is all this does: the driver creates, updates and removes the broker's schedulers when
|
|
21
|
+
* a worker that listens to the queue starts.
|
|
22
|
+
*
|
|
23
|
+
* @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} — see `assertSchedule`.
|
|
24
|
+
*/
|
|
25
|
+
export declare const declareSchedule: <C extends Config>(cfg: C, schedule: ScheduleDeclaration) => C;
|
|
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(…)` */
|
|
31
|
+
export declare const schedulesOf: <C extends Config>(cfg: C, queue?: string) => ScheduleDeclaration[];
|
|
32
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).assertSchedules()` */
|
|
33
|
+
export declare const assertSchedules: <C extends Config>(cfg: C) => void;
|
|
34
|
+
//# 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,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"}
|
package/build/config.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { queueConfigOf } from './queue-config.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
|
+
* Add a recurring job to the shared configuration. Declare the queue first — a schedule is checked
|
|
31
|
+
* against it here. Declaring an id twice replaces, like `declareQueue`.
|
|
32
|
+
*
|
|
33
|
+
* Declaring is all this does: the driver creates, updates and removes the broker's schedulers when
|
|
34
|
+
* a worker that listens to the queue starts.
|
|
35
|
+
*
|
|
36
|
+
* @throws {ScheduleMisdeclared} / {UnknownQueue} / {UnknownJobName} — see `assertSchedule`.
|
|
37
|
+
*/
|
|
38
|
+
export const declareSchedule = (cfg, schedule) => {
|
|
39
|
+
queueConfigOf(cfg).assertSchedule(schedule);
|
|
40
|
+
const schedules = cfg.queue?.schedules ?? [];
|
|
41
|
+
const existing = schedules.findIndex(declared => declared.id === schedule.id);
|
|
42
|
+
if (existing < 0) {
|
|
43
|
+
schedules.push(schedule);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
schedules[existing] = schedule;
|
|
47
|
+
}
|
|
48
|
+
cfg.queue = { ...cfg.queue, schedules };
|
|
49
|
+
return cfg;
|
|
50
|
+
};
|
|
51
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).queueOfJob(…)` */
|
|
52
|
+
export const queueOfJob = (cfg, job) => queueConfigOf(cfg).queueOfJob(job);
|
|
53
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).isListening(…)` */
|
|
54
|
+
export const isListening = (cfg, name) => queueConfigOf(cfg).isListening(name);
|
|
55
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).schedulesOf(…)` */
|
|
56
|
+
export const schedulesOf = (cfg, queue) => queueConfigOf(cfg).schedulesOf(queue);
|
|
57
|
+
/** @deprecated compat:factory-refactor — use `queueConfigOf(cfg).assertSchedules()` */
|
|
58
|
+
export const assertSchedules = (cfg) => queueConfigOf(cfg).assertSchedules();
|
|
59
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
+
export declare enum JobEventType {
|
|
16
|
+
Completed = "completed",
|
|
17
|
+
Failed = "failed",
|
|
18
|
+
Progress = "progress"
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* How long a caller waits for a reply when neither the route nor the request says. Long enough
|
|
22
|
+
* for work that talks to a model, short enough that a lost job surfaces as an error rather than a
|
|
23
|
+
* hung request.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_JOB_TIMEOUT = 60000;
|
|
26
|
+
/**
|
|
27
|
+
* Retries default to one attempt: the jobs here are mostly LLM work, and a blind retry re-spends
|
|
28
|
+
* the tokens that just failed. A queue whose jobs are cheap and idempotent raises it per queue.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_ATTEMPTS = 1;
|
|
31
|
+
/** The transport identifier owned by the queue package. */
|
|
32
|
+
export declare const QUEUE_PROTOCOL: 'queue';
|
|
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,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;AAEjC,2DAA2D;AAC3D,eAAO,MAAM,cAAc,EAAG,OAAgB,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
|
+
export var JobEventType;
|
|
17
|
+
(function (JobEventType) {
|
|
18
|
+
JobEventType["Completed"] = "completed";
|
|
19
|
+
JobEventType["Failed"] = "failed";
|
|
20
|
+
JobEventType["Progress"] = "progress";
|
|
21
|
+
})(JobEventType || (JobEventType = {}));
|
|
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 const DEFAULT_JOB_TIMEOUT = 60_000;
|
|
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 const DEFAULT_ATTEMPTS = 1;
|
|
33
|
+
/** The transport identifier owned by the queue package. */
|
|
34
|
+
export const QUEUE_PROTOCOL = 'queue';
|
|
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,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;AAEjC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,cAAc,GAAG,OAAgB,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CommonEntrypoint } from '@owlmeans/entrypoint';
|
|
2
|
+
import type { JobContext, JobEnvelope, JobProcessor, JobReply } from '../types.js';
|
|
3
|
+
/** The queued entrypoints one context serves as a worker, and how a job of theirs is run. */
|
|
4
|
+
export interface EntrypointJobsHelper {
|
|
5
|
+
/**
|
|
6
|
+
* Every queued entrypoint this process both SERVES and LISTENS to, grouped by queue.
|
|
7
|
+
*
|
|
8
|
+
* Both halves are required and they answer different questions. Serving is about code — the alias
|
|
9
|
+
* was bound here, so a handler exists. Listening is about deployment — this process was
|
|
10
|
+
* configured to consume that queue. A worker that bound queues by what it can serve would make
|
|
11
|
+
* every deployment of the same binary a worker for everything it happens to import.
|
|
12
|
+
*/
|
|
13
|
+
served: () => Map<string, CommonEntrypoint[]>;
|
|
14
|
+
/**
|
|
15
|
+
* A processor that runs a queued entrypoint call. The driver dispatches by job name, and an
|
|
16
|
+
* entrypoint job's name IS its alias — which is what lets one worker carry both entrypoint jobs
|
|
17
|
+
* and the internal steps an application registers with `process()`.
|
|
18
|
+
*/
|
|
19
|
+
processor: () => JobProcessor<JobEnvelope, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Run one queued entrypoint call and describe the outcome.
|
|
22
|
+
*
|
|
23
|
+
* It never throws for a DOMAIN failure — the error is marshalled into the returned reply so the
|
|
24
|
+
* producer can rebuild it as its own class, and so the broker does not count a legitimate refusal
|
|
25
|
+
* as a job to retry. Infrastructure failures are left to propagate: those are what retries are for.
|
|
26
|
+
*/
|
|
27
|
+
handle: (job: JobContext<JobEnvelope>) => Promise<JobReply>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/entrypoint-jobs/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAElF,6FAA6F;AAC7F,MAAM,WAAW,oBAAoB;IACnC;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,CAAA;IAC7C;;;;OAIG;IACH,SAAS,EAAE,MAAM,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;IACnD;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC5D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/entrypoint-jobs/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Config, Context } from './types.js';
|
|
2
|
+
import type { EntrypointJobsHelper } from './entrypoint-jobs/types.js';
|
|
3
|
+
export declare const makeEntrypointJobsHelper: (context: Context<Config>) => EntrypointJobsHelper;
|
|
4
|
+
export declare const entrypointJobsOf: (target: Context<Config>) => EntrypointJobsHelper;
|
|
5
|
+
//# sourceMappingURL=entrypoint-jobs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entrypoint-jobs.d.ts","sourceRoot":"","sources":["../src/entrypoint-jobs.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAmD,MAAM,YAAY,CAAA;AAClG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAMtE,eAAO,MAAM,wBAAwB,YAAa,OAAO,CAAC,MAAM,CAAC,KAAG,oBAyFnE,CAAA;AAED,eAAO,MAAM,gBAAgB,mDAA+C,CAAA"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { AppType, memoHelper } from '@owlmeans/context';
|
|
2
|
+
import { EntrypointOutcome, provideResponse } from '@owlmeans/entrypoint';
|
|
3
|
+
import { AuthorizationError } from '@owlmeans/auth';
|
|
4
|
+
import { ResilientError } from '@owlmeans/error';
|
|
5
|
+
import { JobNotServed } from './errors.js';
|
|
6
|
+
import { queueBridgeHelper } from './bridge.js';
|
|
7
|
+
import { queueRouteHelper } from './queue-route.js';
|
|
8
|
+
import { makeEntityScope } from '@owlmeans/auth-common';
|
|
9
|
+
export const makeEntrypointJobsHelper = (context) => {
|
|
10
|
+
const served = () => {
|
|
11
|
+
const listen = context.cfg.queue?.listen ?? [];
|
|
12
|
+
const served = new Map();
|
|
13
|
+
context.entrypoints().forEach(entrypoint => {
|
|
14
|
+
const route = entrypoint.route.route;
|
|
15
|
+
if (route.type !== AppType.Backend || !queueRouteHelper.isQueueRoute(route)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (route.service != null && route.service !== context.cfg.service) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const { queue } = queueRouteHelper.queueRouteOptions(route);
|
|
22
|
+
if (entrypoint.handle == null || !listen.includes(queue)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
served.set(queue, [...(served.get(queue) ?? []), entrypoint]);
|
|
26
|
+
});
|
|
27
|
+
return served;
|
|
28
|
+
};
|
|
29
|
+
const handle = async (job) => {
|
|
30
|
+
const envelope = job.data;
|
|
31
|
+
try {
|
|
32
|
+
queueBridgeHelper.assertFresh(envelope, context.cfg.queue?.envelopeTtl);
|
|
33
|
+
const entrypoint = context.entrypoint(envelope.alias);
|
|
34
|
+
if (entrypoint.handle == null) {
|
|
35
|
+
throw new JobNotServed(envelope.alias);
|
|
36
|
+
}
|
|
37
|
+
const request = queueBridgeHelper.requestOf(envelope, entrypoint.path(), context, {
|
|
38
|
+
id: job.id, name: job.name, queue: job.queue, attempt: job.attempt, touch: job.touch,
|
|
39
|
+
});
|
|
40
|
+
const response = provideResponse();
|
|
41
|
+
const guards = entrypoint.getGuards();
|
|
42
|
+
if (guards.length > 0) {
|
|
43
|
+
let matched;
|
|
44
|
+
for (const alias of guards) {
|
|
45
|
+
const guard = context.service(alias);
|
|
46
|
+
if (await guard.match(request, response)) {
|
|
47
|
+
matched = guard;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (matched == null) {
|
|
52
|
+
throw new AuthorizationError(`queue:${envelope.alias}`);
|
|
53
|
+
}
|
|
54
|
+
const auth = provideResponse();
|
|
55
|
+
if (!await matched.handle(request, auth)) {
|
|
56
|
+
throw new AuthorizationError(`queue:${envelope.alias}:${matched.alias}`);
|
|
57
|
+
}
|
|
58
|
+
request.auth = auth.value;
|
|
59
|
+
const entity = await makeEntityScope(request).attachEntity(context);
|
|
60
|
+
if (entity != null) {
|
|
61
|
+
request.entity = entity;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await entrypoint.handle(request, response);
|
|
65
|
+
if (response.error != null) {
|
|
66
|
+
return { error: ResilientError.marshal(response.error).message };
|
|
67
|
+
}
|
|
68
|
+
return { value: response.value, outcome: response.outcome ?? EntrypointOutcome.Ok };
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
// A refusal the caller must see as its own class — `ContentRefused`, `AgentLocked` — travels
|
|
72
|
+
// in the reply. Only a broker- or connection-level fault should ever reach the retry logic:
|
|
73
|
+
// retrying a refusal just spends the work again to be refused identically.
|
|
74
|
+
if (e instanceof ResilientError) {
|
|
75
|
+
return { error: ResilientError.marshal(e).message };
|
|
76
|
+
}
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const processor = () => async (job) => await handle(job);
|
|
81
|
+
return { served, processor, handle };
|
|
82
|
+
};
|
|
83
|
+
export const entrypointJobsOf = memoHelper.oncePer(makeEntrypointJobsHelper);
|
|
84
|
+
//# sourceMappingURL=entrypoint-jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entrypoint-jobs.js","sourceRoot":"","sources":["../src/entrypoint-jobs.ts"],"names":[],"mappings":"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"}
|