@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.
Files changed (110) hide show
  1. package/README.md +286 -8
  2. package/agent-meta/manifest.json +9 -2
  3. package/agent-meta/skills/queue/SKILL.md +243 -12
  4. package/agent-meta/skills/scheduled-jobs/SKILL.md +151 -0
  5. package/build/bridge/types.d.ts +25 -0
  6. package/build/bridge/types.d.ts.map +1 -0
  7. package/build/bridge/types.js +2 -0
  8. package/build/bridge/types.js.map +1 -0
  9. package/build/bridge.d.ts +7 -0
  10. package/build/bridge.d.ts.map +1 -0
  11. package/build/bridge.js +37 -0
  12. package/build/bridge.js.map +1 -0
  13. package/build/config.d.ts +34 -0
  14. package/build/config.d.ts.map +1 -0
  15. package/build/config.js +59 -0
  16. package/build/config.js.map +1 -0
  17. package/build/consts.d.ts +33 -0
  18. package/build/consts.d.ts.map +1 -0
  19. package/build/consts.js +35 -0
  20. package/build/consts.js.map +1 -0
  21. package/build/entrypoint-jobs/types.d.ts +29 -0
  22. package/build/entrypoint-jobs/types.d.ts.map +1 -0
  23. package/build/entrypoint-jobs/types.js +2 -0
  24. package/build/entrypoint-jobs/types.js.map +1 -0
  25. package/build/entrypoint-jobs.d.ts +5 -0
  26. package/build/entrypoint-jobs.d.ts.map +1 -0
  27. package/build/entrypoint-jobs.js +84 -0
  28. package/build/entrypoint-jobs.js.map +1 -0
  29. package/build/errors.d.ts +68 -0
  30. package/build/errors.d.ts.map +1 -0
  31. package/build/errors.js +103 -0
  32. package/build/errors.js.map +1 -0
  33. package/build/index.d.ts +18 -0
  34. package/build/index.d.ts.map +1 -1
  35. package/build/index.js +13 -1
  36. package/build/index.js.map +1 -1
  37. package/build/protocol/types.d.ts +12 -0
  38. package/build/protocol/types.d.ts.map +1 -0
  39. package/build/protocol/types.js +2 -0
  40. package/build/protocol/types.js.map +1 -0
  41. package/build/protocol.d.ts +13 -0
  42. package/build/protocol.d.ts.map +1 -0
  43. package/build/protocol.js +63 -0
  44. package/build/protocol.js.map +1 -0
  45. package/build/queue-config/types.d.ts +29 -0
  46. package/build/queue-config/types.d.ts.map +1 -0
  47. package/build/queue-config/types.js +2 -0
  48. package/build/queue-config/types.js.map +1 -0
  49. package/build/queue-config.d.ts +9 -0
  50. package/build/queue-config.d.ts.map +1 -0
  51. package/build/queue-config.js +83 -0
  52. package/build/queue-config.js.map +1 -0
  53. package/build/queue-route/types.d.ts +14 -0
  54. package/build/queue-route/types.d.ts.map +1 -0
  55. package/build/queue-route/types.js +2 -0
  56. package/build/queue-route/types.js.map +1 -0
  57. package/build/queue-route.d.ts +4 -0
  58. package/build/queue-route.d.ts.map +1 -0
  59. package/build/queue-route.js +24 -0
  60. package/build/queue-route.js.map +1 -0
  61. package/build/route.d.ts +9 -0
  62. package/build/route.d.ts.map +1 -0
  63. package/build/route.js +16 -0
  64. package/build/route.js.map +1 -0
  65. package/build/transport.d.ts +15 -0
  66. package/build/transport.d.ts.map +1 -0
  67. package/build/transport.js +59 -0
  68. package/build/transport.js.map +1 -0
  69. package/build/types.d.ts +312 -0
  70. package/build/types.d.ts.map +1 -0
  71. package/build/types.js +2 -0
  72. package/build/types.js.map +1 -0
  73. package/build/types.local.d.ts +4 -0
  74. package/build/types.local.d.ts.map +1 -0
  75. package/build/types.local.js +2 -0
  76. package/build/types.local.js.map +1 -0
  77. package/build/utils.d.ts +4 -0
  78. package/build/utils.d.ts.map +1 -0
  79. package/build/utils.js +4 -0
  80. package/build/utils.js.map +1 -0
  81. package/build/worker.d.ts +10 -0
  82. package/build/worker.d.ts.map +1 -0
  83. package/build/worker.js +24 -0
  84. package/build/worker.js.map +1 -0
  85. package/package.json +13 -3
  86. package/src/bridge/types.ts +25 -0
  87. package/src/bridge.ts +46 -0
  88. package/src/config.ts +80 -0
  89. package/src/consts.ts +38 -0
  90. package/src/entrypoint-jobs/types.ts +29 -0
  91. package/src/entrypoint-jobs.ts +105 -0
  92. package/src/errors.ts +121 -0
  93. package/src/index.ts +18 -0
  94. package/src/protocol/types.ts +16 -0
  95. package/src/protocol.ts +95 -0
  96. package/src/queue-config/types.ts +29 -0
  97. package/src/queue-config.ts +102 -0
  98. package/src/queue-route/types.ts +14 -0
  99. package/src/queue-route.ts +33 -0
  100. package/src/route.ts +23 -0
  101. package/src/transport.ts +77 -0
  102. package/src/types.ts +326 -0
  103. package/src/utils.ts +5 -0
  104. package/src/worker.ts +27 -0
  105. package/tests/bridge.spec.ts +64 -0
  106. package/tests/config.spec.ts +67 -0
  107. package/tests/protocol.spec.ts +58 -0
  108. package/tests/schedule.spec.ts +99 -0
  109. package/tsconfig.json +1 -0
  110. package/build/.gitkeep +0 -0
@@ -0,0 +1,59 @@
1
+ import { assertContext, createService } from '@owlmeans/context';
2
+ import { EntrypointOutcome, transportAlias } from '@owlmeans/entrypoint';
3
+ import { ResilientError } from '@owlmeans/error';
4
+ import { DEFAULT_JOB_TIMEOUT, QUEUE_PROTOCOL } from './consts.js';
5
+ import { queueRouteHelper } from './queue-route.js';
6
+ /**
7
+ * The producing half of the bridge: it turns a call on a QUEUE entrypoint into a job, and the
8
+ * job's outcome back into a reply.
9
+ *
10
+ * Registering it is the whole opt-in. `apiHandler` looks up a transport by the route's protocol,
11
+ * so a consumer writes `ep.call(...)` against a declaration it shares with every other kind of
12
+ * entrypoint and never learns which one carried it. That is also why the envelope is built from
13
+ * the request rather than from the entrypoint: the far side rebuilds a request, and anything only
14
+ * HTTP understands would not survive the trip anyway.
15
+ */
16
+ export const makeQueueTransport = (alias = transportAlias(QUEUE_PROTOCOL)) => {
17
+ const location = `queue-transport:${alias}`;
18
+ const service = createService(alias, {
19
+ protocol: QUEUE_PROTOCOL,
20
+ handle: (async (req, res) => {
21
+ const context = assertContext(service.ctx, location);
22
+ const entrypoint = context.entrypoint(req.alias);
23
+ const route = entrypoint.route.route;
24
+ const { queue, reply: waitsForReply } = queueRouteHelper.queueRouteOptions(route);
25
+ const jobs = context.jobs(queue);
26
+ const envelope = {
27
+ alias: req.alias,
28
+ params: req.params,
29
+ body: req.body,
30
+ query: req.query,
31
+ headers: req.headers,
32
+ enqueuedAt: new Date().toISOString(),
33
+ };
34
+ const job = await jobs.create({ queue, name: req.alias, data: envelope });
35
+ // `reply: false` says the caller is not waiting for the work, only for the promise that it
36
+ // was taken. `Accepted` plus the job's identity is the whole answer — enough to watch it.
37
+ if (waitsForReply === false) {
38
+ res.resolve({ id: job.id, queue }, EntrypointOutcome.Accepted);
39
+ return;
40
+ }
41
+ const reply = await jobs.wait(job.id, {
42
+ timeout: req.timeout ?? route.timeout ?? DEFAULT_JOB_TIMEOUT
43
+ });
44
+ // A processor reports a domain failure by returning it, so it crosses the hop as the class
45
+ // it was thrown as rather than as a string a caller would have to parse.
46
+ if (reply?.error != null) {
47
+ throw ResilientError.ensure(reply.error);
48
+ }
49
+ res.resolve(reply?.value, reply?.outcome ?? EntrypointOutcome.Ok);
50
+ })
51
+ });
52
+ return service;
53
+ };
54
+ export const appendQueueTransport = (context, alias) => {
55
+ const service = makeQueueTransport(alias);
56
+ context.registerService(service);
57
+ return context;
58
+ };
59
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,312 @@
1
+ import type { BasicConfig, BasicContext, InitializedService } from '@owlmeans/context';
2
+ import type { PubSubResource, Resource, ResourceRecord } from '@owlmeans/resource';
3
+ import type { AbstractRequest, AbstractResponse } from '@owlmeans/entrypoint';
4
+ import type { JobEventType, JobState } from './consts.js';
5
+ import type { RouteOptions } from '@owlmeans/route';
6
+ /**
7
+ * Everything the monorepo knows about its queues, declared in the shared backend package so that
8
+ * producer and consumer read the same list. A process becomes a worker by naming queues in
9
+ * `listen` — nothing in a declaration decides where it runs.
10
+ */
11
+ export interface QueueConfig {
12
+ queues?: QueueDeclaration[];
13
+ /** The queues THIS process consumes. Absent or empty means producer only. */
14
+ listen?: string[];
15
+ /** Which db service carries the queues. Defaults to the queue service's own alias. */
16
+ db?: string;
17
+ defaults?: JobOptions;
18
+ /**
19
+ * How long a signed job envelope stays acceptable, in seconds. It bounds both the freshness
20
+ * check and the nonce cache, so a job delayed behind a long backlog is still admitted.
21
+ */
22
+ envelopeTtl?: number;
23
+ /**
24
+ * Recurring jobs. Declared beside the queues, in the shared package, because every process that
25
+ * listens to a schedule's queue reconciles the broker against this list when its worker starts —
26
+ * two listeners loading different lists would remove each other's schedules.
27
+ */
28
+ schedules?: ScheduleDeclaration[];
29
+ }
30
+ /**
31
+ * A job the broker produces on its own, on an interval or a cron pattern.
32
+ *
33
+ * A scheduled run is an ordinary job of a name the queue declares, dispatched to the processor
34
+ * registered for that name — never to a guarded entrypoint, because nobody's credentials travel
35
+ * with it. Only a process that listens to the queue creates the scheduler, and the broker produces
36
+ * each next run when the previous one STARTS processing — a schedule needs a running consumer.
37
+ */
38
+ export interface ScheduleDeclaration {
39
+ /**
40
+ * The schedule's stable identity: the broker keeps one scheduler per id, so changing any other
41
+ * field updates it in place, while renaming the id removes one schedule and creates another.
42
+ */
43
+ id: string;
44
+ queue: string;
45
+ /** A job name the queue declares. */
46
+ name: string;
47
+ /**
48
+ * Milliseconds between runs. Exactly one of `every` and `pattern`. Without `startDate` the first
49
+ * run is produced as soon as the scheduler is created.
50
+ */
51
+ every?: number;
52
+ /** A cron pattern (an optional leading seconds field is accepted). Exactly one of the two. */
53
+ pattern?: string;
54
+ /** The IANA time zone `pattern` is read in. Pattern only. */
55
+ tz?: string;
56
+ startDate?: Date | number | string;
57
+ /** Once passed, the schedule is treated as absent and a scheduler left from it is removed. */
58
+ endDate?: Date | number | string;
59
+ /** Stop after this many runs. */
60
+ limit?: number;
61
+ /**
62
+ * Run once as soon as the scheduler is created or its declaration changes, then follow the
63
+ * pattern. Pattern only, and never together with `startDate`. A restart that finds the
64
+ * declaration unchanged does not run it again.
65
+ */
66
+ immediately?: boolean;
67
+ data?: unknown;
68
+ /**
69
+ * Options every run is enqueued with, over the queue's defaults. `id` and `delay` belong to the
70
+ * broker for a scheduled run and are refused.
71
+ */
72
+ opts?: Omit<JobOptions, 'id' | 'delay'>;
73
+ }
74
+ export interface QueueDeclaration {
75
+ name: string;
76
+ /**
77
+ * The job names this queue accepts. A job the queue does not declare is refused at enqueue
78
+ * time rather than becoming a job nothing can process.
79
+ */
80
+ jobs: string[];
81
+ worker?: QueueWorkerOptions;
82
+ defaults?: JobOptions;
83
+ }
84
+ export interface QueueWorkerOptions {
85
+ concurrency?: number;
86
+ /**
87
+ * How long a job may hold its lock without reporting progress. A processor that runs longer
88
+ * than this between `touch()` calls is treated as stalled and its job is handed to someone else.
89
+ */
90
+ lockDuration?: number;
91
+ stalledInterval?: number;
92
+ maxStalledCount?: number;
93
+ }
94
+ export interface JobOptions {
95
+ attempts?: number;
96
+ backoff?: {
97
+ type: 'fixed' | 'exponential';
98
+ delay: number;
99
+ };
100
+ delay?: number;
101
+ priority?: number;
102
+ /**
103
+ * A caller-chosen identity for the job. Enqueueing the same id twice while the first is still
104
+ * around is a no-op, which is what makes an admission step safe to retry.
105
+ */
106
+ id?: string;
107
+ removeOnComplete?: boolean | number;
108
+ removeOnFail?: boolean | number;
109
+ }
110
+ /**
111
+ * A job as a RECORD — the same shape the resource contract reads and writes, so inspecting a
112
+ * queue is `list`/`get`/`count` rather than a broker-specific API.
113
+ */
114
+ export interface JobRecord<D = unknown, R = unknown> extends ResourceRecord {
115
+ queue: string;
116
+ name: string;
117
+ data: D;
118
+ state?: JobState;
119
+ result?: R;
120
+ error?: string;
121
+ attempts?: number;
122
+ progress?: unknown;
123
+ parentId?: string;
124
+ createdAt?: string;
125
+ startedAt?: string;
126
+ finishedAt?: string;
127
+ opts?: JobOptions;
128
+ }
129
+ export interface JobEvent<R = unknown> {
130
+ type: JobEventType;
131
+ id: string;
132
+ queue: string;
133
+ /**
134
+ * Empty when the job is already gone. A broker's completion event carries only an id, so the
135
+ * name is read back from the job — and a queue that removes jobs on completion has nothing left
136
+ * to read. Subscribe for the id and the outcome; do not branch on the name.
137
+ */
138
+ name: string;
139
+ result?: R;
140
+ error?: string;
141
+ progress?: unknown;
142
+ }
143
+ /** A node of a job graph: children run first, siblings run together. */
144
+ export interface FlowSpec<D = unknown> {
145
+ name: string;
146
+ queue?: string;
147
+ data: D;
148
+ opts?: JobOptions;
149
+ children?: FlowSpec[];
150
+ }
151
+ /**
152
+ * One queue, addressed as a resource.
153
+ *
154
+ * `create` enqueues, `get`/`load`/`list`/`count` inspect, `delete`/`take` cancel. What the base
155
+ * contract cannot express — waiting for an answer, submitting a graph, releasing the connection —
156
+ * is added here.
157
+ *
158
+ * Three places where the base `Resource` contract reads differently against a broker, because a
159
+ * job is not a row:
160
+ * - `create` does NOT throw `RecordExists` for a repeated `opts.id`. Re-enqueueing an id that is
161
+ * still around returns the existing job, which is the idempotency that makes an admission step
162
+ * safe to retry.
163
+ * - `WriteOptions.ttl` is refused. A job's lifetime is `removeOnComplete`/`removeOnFail` —
164
+ * explicit deletion once it has finished, not expiry while it waits.
165
+ * - `update`/`save` reach only the job's `data`. Everything else about a job belongs to the
166
+ * broker, and an implementation refuses rather than pretending to write it.
167
+ */
168
+ export interface QueueResource<D = unknown, R = unknown> extends Resource<JobRecord<D, R>>, PubSubResource<JobEvent<R>> {
169
+ queue: string;
170
+ /**
171
+ * Release the connections this queue holds.
172
+ *
173
+ * Watching for completions means a blocking connection, and a process that never releases it
174
+ * does not exit. Nothing else in the resource contract can express that, so it lives here and a
175
+ * producer calls it on shutdown.
176
+ */
177
+ close: () => Promise<void>;
178
+ /**
179
+ * Wait for a job's return value.
180
+ * @throws {QueueTimeout} when the wait elapses — the job itself is left alone.
181
+ * @throws the processor's own error, rebuilt as its original class.
182
+ */
183
+ wait: (id: string, opts?: {
184
+ timeout?: number;
185
+ }) => Promise<R>;
186
+ /** Submit a graph and get its root back. Children complete before their parent starts. */
187
+ flow: (root: FlowSpec<D>) => Promise<JobRecord<D, R>>;
188
+ counts: () => Promise<Record<JobState, number>>;
189
+ }
190
+ /**
191
+ * What a processor is handed. `data` is the job's payload; everything else is the means to stay
192
+ * alive and to read what the children reported.
193
+ */
194
+ export interface JobContext<D = unknown> {
195
+ id: string;
196
+ name: string;
197
+ queue: string;
198
+ attempt: number;
199
+ data: D;
200
+ signal: AbortSignal;
201
+ /**
202
+ * The id of the schedule that produced this run (`ScheduleDeclaration.id`). Absent for a job
203
+ * enqueued any other way.
204
+ */
205
+ scheduled?: string;
206
+ /**
207
+ * Renew the lock. A processor that runs a long loop MUST call it as it goes: the broker judges
208
+ * liveness by the lock, so silence for longer than `lockDuration` is indistinguishable from a
209
+ * dead worker, and the job is re-run somewhere else while this one is still working on it.
210
+ */
211
+ touch: () => Promise<void>;
212
+ progress: (value: unknown) => Promise<void>;
213
+ /**
214
+ * What the children returned, keyed by child job id.
215
+ *
216
+ * A broker keys them by its own fully qualified job key; an implementation re-keys to the bare
217
+ * id, so two children of one parent that live in DIFFERENT queues and happen to share an id
218
+ * collide here. Give the children of a cross-queue flow distinct ids.
219
+ */
220
+ children: <T = unknown>() => Promise<Record<string, T>>;
221
+ failedChildren: () => Promise<Record<string, string>>;
222
+ }
223
+ export interface JobProcessor<D = unknown, R = unknown> {
224
+ (job: JobContext<D>): Promise<R>;
225
+ }
226
+ /**
227
+ * The consuming half. It is registered only in processes that consume: `start()` binds the
228
+ * queues named in `cfg.listen` and dispatches by job name, to a processor registered here or to
229
+ * an entrypoint served in this process.
230
+ */
231
+ export interface QueueWorkerService extends InitializedService {
232
+ process: <D, R>(queue: string, name: string, processor: JobProcessor<D, R>) => void;
233
+ start: () => Promise<void>;
234
+ stop: () => Promise<void>;
235
+ /** The queues this process actually consumes. Empty in a producer. */
236
+ listening: () => string[];
237
+ /**
238
+ * Bind the application's reactions to job lifecycle events. Registering more than once merges,
239
+ * so an application composed of several parts can each contribute what it cares about.
240
+ *
241
+ * It belongs on the contract rather than on a driver because `onJobDead` is where an
242
+ * application COMPENSATES — releases the lock the admission step took, marks the record failed —
243
+ * and that behaviour must not have to be rewritten when the broker changes.
244
+ */
245
+ hooks: (hooks: QueueHooks) => void;
246
+ }
247
+ /**
248
+ * The envelope a queued entrypoint call travels in. It is the request, minus everything that only
249
+ * means something to HTTP — the transport rebuilds a request from it on the far side.
250
+ */
251
+ export interface JobEnvelope {
252
+ alias: string;
253
+ params?: Record<string, unknown>;
254
+ body?: unknown;
255
+ query?: Record<string, unknown>;
256
+ headers?: Record<string, string | undefined>;
257
+ /** When the producer enqueued it — freshness is judged here, not at processing time. */
258
+ enqueuedAt?: string;
259
+ }
260
+ export interface QueueJobMeta {
261
+ id: string;
262
+ name: string;
263
+ queue: string;
264
+ attempt: number;
265
+ /** Renew the broker lock for protocol handlers whose work can outlive one lock duration. */
266
+ touch: () => Promise<void>;
267
+ }
268
+ /** What a processor returns for an entrypoint job, so the caller can rebuild the reply. */
269
+ export interface JobReply<T = unknown> {
270
+ value?: T;
271
+ outcome?: string;
272
+ error?: unknown;
273
+ }
274
+ export interface QueueHooks {
275
+ wrapHandler?: (job: JobContext, next: () => Promise<unknown>) => Promise<unknown>;
276
+ onJobResult?: (event: JobEvent) => void | Promise<void>;
277
+ onJobStalled?: (meta: {
278
+ id: string;
279
+ queue: string;
280
+ name: string;
281
+ }, phase: 'stalled' | 'failed') => void | Promise<void>;
282
+ /**
283
+ * The job is finished for good — retries exhausted, or it was dropped. This is where an
284
+ * application compensates: release the lock the admission step took, mark the record failed.
285
+ */
286
+ onJobDead?: (job: JobRecord, reason: string) => void | Promise<void>;
287
+ }
288
+ export interface QueueDriver {
289
+ resource: <D, R>(queue: string) => QueueResource<D, R>;
290
+ worker: () => QueueWorkerService;
291
+ }
292
+ export interface Config extends BasicConfig {
293
+ queue?: QueueConfig;
294
+ }
295
+ export interface Context<C extends Config = Config> extends BasicContext<C> {
296
+ }
297
+ export interface QueueAppend {
298
+ jobs: <D = unknown, R = unknown>(queue?: string) => QueueResource<D, R>;
299
+ }
300
+ export interface QueueTransportRequest extends AbstractRequest {
301
+ }
302
+ export interface QueueTransportResponse extends AbstractResponse<unknown> {
303
+ }
304
+ /** Queue-owned options stored in a generic route declaration. */
305
+ export interface QueueRouteOptions extends Omit<RouteOptions, 'protocol' | 'protocolOptions'> {
306
+ queue: string;
307
+ reply?: boolean;
308
+ }
309
+ /** The context the queue protocol helper is bound to: an app context with the queue appended. */
310
+ export interface QueueContext extends Context<Config>, QueueAppend {
311
+ }
312
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAClF,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEnD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC3B,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,sFAAsF;IACtF,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAA;CAClC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6DAA6D;IAC7D,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;IAClC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;IAChC,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;OAGG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,OAAO,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B,QAAQ,CAAC,EAAE,UAAU,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,aAAa,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACnC,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,CAAE,SAAQ,cAAc;IACzE,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,CAAC,CAAA;IACP,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,MAAM,CAAC,EAAE,CAAC,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACnC,IAAI,EAAE,YAAY,CAAA;IAClB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,CAAC,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,wEAAwE;AACxE,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACnC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,CAAC,CAAA;IACP,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAA;CACtB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,CACrD,SAAQ,QAAQ,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9D,KAAK,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B;;;;OAIG;IACH,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IAC7D,0FAA0F;IAC1F,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACrD,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAA;CAChD;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,CAAC,CAAA;IACP,MAAM,EAAE,WAAW,CAAA;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3C;;;;;;OAMG;IACH,QAAQ,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAA;IACvD,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;CACtD;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACpD,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC5D,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,CAAA;IACnF,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,sEAAsE;IACtE,SAAS,EAAE,MAAM,MAAM,EAAE,CAAA;IACzB;;;;;;;OAOG;IACH,KAAK,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;CACnC;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAChC,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC5C,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,2FAA2F;AAC3F,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACnC,KAAK,CAAC,EAAE,CAAC,CAAA;IACT,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACjF,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,EAAE,SAAS,GAAG,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACvH;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrE;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACtD,MAAM,EAAE,MAAM,kBAAkB,CAAA;CACjC;AAED,MAAM,WAAW,MAAO,SAAQ,WAAW;IACzC,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAED,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,YAAY,CAAC,CAAC,CAAC;CAC1E;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;CACxE;AAED,MAAM,WAAW,qBAAsB,SAAQ,eAAe;CAC7D;AAED,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB,CAAC,OAAO,CAAC;CACxE;AAED,iEAAiE;AACjE,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,iBAAiB,CAAC;IAC3F,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,iGAAiG;AACjG,MAAM,WAAW,YAAa,SAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW;CAAG"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { Config, Context, QueueAppend } from './types.js';
2
+ export interface QueueContext extends Context<Config>, QueueAppend {
3
+ }
4
+ //# sourceMappingURL=types.local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.local.d.ts","sourceRoot":"","sources":["../src/types.local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE9D,MAAM,WAAW,YAAa,SAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW;CAAG"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.local.js","sourceRoot":"","sources":["../src/types.local.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import { JobState } from './consts.js';
2
+ /** A job has reached a state it will not leave on its own. */
3
+ export declare const isSettled: (state?: JobState) => boolean;
4
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,8DAA8D;AAC9D,eAAO,MAAM,SAAS,WAAY,QAAQ,KAAG,OACc,CAAA"}
package/build/utils.js ADDED
@@ -0,0 +1,4 @@
1
+ import { JobState } from './consts.js';
2
+ /** A job has reached a state it will not leave on its own. */
3
+ export const isSettled = (state) => state === JobState.Completed || state === JobState.Failed;
4
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,8DAA8D;AAC9D,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAgB,EAAW,EAAE,CACrD,KAAK,KAAK,QAAQ,CAAC,SAAS,IAAI,KAAK,KAAK,QAAQ,CAAC,MAAM,CAAA"}
@@ -0,0 +1,10 @@
1
+ import type { Middleware } from '@owlmeans/context';
2
+ /**
3
+ * Start the worker once the context is ready.
4
+ *
5
+ * Ready stage, not Loading: processors are registered while the application wires itself up, and
6
+ * binding the queues before that finished would take jobs this process cannot yet run. A process
7
+ * that listens to nothing registers no worker at all, so a producer pays nothing for this.
8
+ */
9
+ export declare const queueWorkerMiddleware: (alias?: string) => Middleware;
10
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAKnD;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,WAAW,MAAM,KAAmB,UAcpE,CAAA"}
@@ -0,0 +1,24 @@
1
+ import { MiddlewareStage, MiddlewareType } from '@owlmeans/context';
2
+ import { DEFAULT_ALIAS } from './consts.js';
3
+ /**
4
+ * Start the worker once the context is ready.
5
+ *
6
+ * Ready stage, not Loading: processors are registered while the application wires itself up, and
7
+ * binding the queues before that finished would take jobs this process cannot yet run. A process
8
+ * that listens to nothing registers no worker at all, so a producer pays nothing for this.
9
+ */
10
+ export const queueWorkerMiddleware = (alias = DEFAULT_ALIAS) => ({
11
+ type: MiddlewareType.Context,
12
+ stage: MiddlewareStage.Ready,
13
+ apply: async (context) => {
14
+ const ctx = context;
15
+ if ((ctx.cfg.queue?.listen ?? []).length === 0) {
16
+ return;
17
+ }
18
+ if (!ctx.hasService(alias)) {
19
+ return;
20
+ }
21
+ await ctx.service(alias).start();
22
+ }
23
+ });
24
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEnE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAK,GAAW,aAAa,EAAc,EAAE,CAAC,CAAC;IACnF,IAAI,EAAE,cAAc,CAAC,OAAO;IAC5B,KAAK,EAAE,eAAe,CAAC,KAAK;IAC5B,KAAK,EAAE,KAAK,EAAC,OAAO,EAAC,EAAE;QACrB,MAAM,GAAG,GAAG,OAAqC,CAAA;QACjD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/C,OAAM;QACR,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAM;QACR,CAAC;QAED,MAAM,GAAG,CAAC,OAAO,CAAqB,KAAK,CAAC,CAAC,KAAK,EAAE,CAAA;IACtD,CAAC;CACF,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@owlmeans/queue",
3
- "version": "0.1.18-rc.4",
3
+ "version": "0.1.18-rc.41",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsc -b",
8
8
  "dev": "sleep 234 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
9
- "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ "watch": "tsc -b -w --preserveWatchOutput --pretty",
10
+ "test": "bun test ./tests"
10
11
  },
11
12
  "main": "build/index.js",
12
13
  "module": "build/index.js",
@@ -22,11 +23,20 @@
22
23
  },
23
24
  "devDependencies": {
24
25
  "@owlmeans/dep-config": "workspace:*",
25
- "@types/node": "^26.1.0",
26
+ "@types/node": "^26.6.4",
26
27
  "nodemon": "^3.1.14",
27
28
  "typescript": "^7.0.2"
28
29
  },
29
30
  "publishConfig": {
30
31
  "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@owlmeans/auth": "^0.1.18-rc.40",
35
+ "@owlmeans/auth-common": "^0.1.18-rc.47",
36
+ "@owlmeans/context": "^0.1.18-rc.34",
37
+ "@owlmeans/entrypoint": "^0.1.18-rc.41",
38
+ "@owlmeans/error": "^0.1.18-rc.38",
39
+ "@owlmeans/resource": "^0.1.18-rc.40",
40
+ "@owlmeans/route": "^0.1.18-rc.35"
31
41
  }
32
42
  }
@@ -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
+
5
+ /** The envelope ↔ request bridge a queued entrypoint call crosses. */
6
+ export interface QueueBridgeHelper {
7
+ /**
8
+ * Rebuild the request the producer described. A queued call is still a call: the same guards, the
9
+ * same filter, the same handler run against it — only the wire is different.
10
+ */
11
+ requestOf: (envelope: JobEnvelope, path: string, context?: BasicContext<any>, job?: QueueJobMeta) => AbstractRequest
12
+ /** Read broker identity only where an entrypoint truly needs to bind work to an admission claim. */
13
+ queueJobOf: (request: unknown) => QueueJobMeta | null
14
+ /**
15
+ * Freshness is judged from when the job was ENQUEUED, not from now.
16
+ *
17
+ * A signed envelope proves who produced it, and a replay window keeps a captured one from being
18
+ * useful forever. But a job can legitimately sit behind a long backlog, and judging it on pickup
19
+ * would reject exactly the work that a busy queue delayed — so the producer's timestamp is what
20
+ * the window applies to.
21
+ *
22
+ * @throws {EnvelopeExpired}
23
+ */
24
+ assertFresh: (envelope: JobEnvelope, ttl?: number) => void
25
+ }
package/src/bridge.ts ADDED
@@ -0,0 +1,46 @@
1
+ import type { AbstractRequest } from '@owlmeans/entrypoint'
2
+ import type { BasicContext } from '@owlmeans/context'
3
+ import type { JobEnvelope, QueueJobMeta } from './types.js'
4
+ import type { QueueBridgeHelper } from './bridge/types.js'
5
+ import { EnvelopeExpired } from './errors.js'
6
+
7
+ export const createQueueBridgeHelper = (): QueueBridgeHelper => {
8
+ const requestOf = (
9
+ envelope: JobEnvelope, path: string, context?: BasicContext<any>, job?: QueueJobMeta,
10
+ ): AbstractRequest => ({
11
+ alias: envelope.alias,
12
+ params: (envelope.params ?? {}) as AbstractRequest['params'],
13
+ body: envelope.body as AbstractRequest['body'],
14
+ headers: (envelope.headers ?? {}) as AbstractRequest['headers'],
15
+ query: (envelope.query ?? {}) as AbstractRequest['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
+
23
+ const queueJobOf = (request: unknown): QueueJobMeta | null => {
24
+ if (request == null || typeof request !== 'object' || !('original' in request)) return null
25
+ const original = (request as { original?: unknown }).original
26
+ if (original == null || typeof original !== 'object' || !('_job' in original)) return null
27
+ return (original as { _job?: QueueJobMeta })._job ?? null
28
+ }
29
+
30
+ const assertFresh = (envelope: JobEnvelope, ttl?: number): void => {
31
+ if (ttl == null || envelope.enqueuedAt == null) {
32
+ return
33
+ }
34
+ const age = (Date.now() - new Date(envelope.enqueuedAt).getTime()) / 1000
35
+ if (age > ttl) {
36
+ throw new EnvelopeExpired(`${envelope.alias}: ${Math.round(age)}s > ${ttl}s`)
37
+ }
38
+ }
39
+
40
+ return { requestOf, queueJobOf, assertFresh }
41
+ }
42
+
43
+ export const queueBridgeHelper = createQueueBridgeHelper()
44
+
45
+ /** @deprecated compat:factory-refactor — use `queueBridgeHelper.queueJobOf(…)` */
46
+ export const queueJobOf = (request: unknown): QueueJobMeta | null => queueBridgeHelper.queueJobOf(request)