@lunora/queue 1.0.0-alpha.4 → 1.0.0-alpha.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/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
+ import { QueueBindingLike, MessageBatchLike, MessageLike, QueueSendOptions, MessageSendRequestLike, QueueSendBatchOptions } from '@lunora/platform';
2
+ export type { MessageBatchLike, MessageLike, MessageSendRequestLike, QueueBindingLike, QueueContentType, QueueRetryOptions, QueueSendBatchOptions, QueueSendOptions } from '@lunora/platform';
1
3
  /**
2
- * Shared types for dispatching a Lunora function back into the worker from a
3
- * server-initiated context (a workflow body, a queue handler, a scheduled job).
4
- * Node-safe — no Cloudflare runtime imports — so the consumers stay unit-testable
5
- * with plain-object doubles.
6
- */
4
+ * Shared types for dispatching a Lunora function back into the worker from a
5
+ * server-initiated context (a workflow body, a queue handler, a scheduled job).
6
+ * Node-safe — no Cloudflare runtime imports — so the consumers stay unit-testable
7
+ * with plain-object doubles.
8
+ */
7
9
  /** Opaque generated function reference (`api.foo.bar`), carrying its dispatch id. */
8
10
  interface FunctionReference {
9
11
  __lunoraRef: string;
@@ -12,8 +14,45 @@ interface FunctionReference {
12
14
  type ArgsOf<F> = F extends FunctionReference ? Record<string, unknown> : never;
13
15
  /** Options for a function call made via a dispatch runner. */
14
16
  interface RunFunctionOptions {
17
+ /**
18
+ * The at-least-once dedup key for THIS ONE CALL, sent to the dispatch
19
+ * endpoint as the body's `id` and forwarded to the shard as the
20
+ * replay-dedup `mutationId` — so a redelivery that re-runs the same call
21
+ * applies it exactly once instead of twice.
22
+ *
23
+ * Must be unique per call and stable across redeliveries. It is NOT
24
+ * {@link RunFunctionOptions.messageId}: the shard's dedup table is keyed
25
+ * `(identity, mutationId)` with no function path in it, and every
26
+ * server-initiated dispatch shares the `"system:"` identity, so reusing
27
+ * one id across two calls makes the second return the FIRST call's cached
28
+ * result without ever executing. A per-message id is 1:N with the calls a
29
+ * handler makes; this is 1:1.
30
+ *
31
+ * `@lunora/queue`'s `message.run` derives one automatically as
32
+ * `<messageId>#<n>`, `n` counting that message's calls in order. That is
33
+ * stable across redeliveries because the handler replays from the start —
34
+ * it relies on the handler issuing its `run` calls in a DETERMINISTIC
35
+ * order, which at-least-once replay already assumes. A handler whose call
36
+ * order varies per attempt (branching on `Date.now()`, `Math.random()`,
37
+ * or unordered concurrent settles) must pass its own stable ids instead.
38
+ *
39
+ * Optional; when omitted the dispatch is at-least-once.
40
+ */
41
+ dedupId?: string;
42
+ /**
43
+ * Correlate this call with a caller-defined message/item id (e.g. a queue
44
+ * message's `id`), for failure attribution only — never sent to the
45
+ * dispatch endpoint. Carried onto the `LunoraError` a deterministic
46
+ * dispatch failure throws, so a batching consumer (`@lunora/queue`'s push
47
+ * handler) can read it back and attribute the failure to the one item that
48
+ * caused it instead of the whole batch. Deliberately NOT the dedup key —
49
+ * see {@link RunFunctionOptions.dedupId}. Optional and inert when omitted.
50
+ */
51
+ messageId?: string;
15
52
  /** Route the call to a specific shard (defaults to the worker's root shard). */
16
53
  shardKey?: string;
54
+ /** Abort the dispatch after this many ms; the abort is retryable. Overrides the runner's default. */
55
+ timeoutMs?: number;
17
56
  }
18
57
  /** Invoke a Lunora function (query/mutation/action) by reference. The shape of `ctx.run`. */
19
58
  type DispatchRunFunction = <F extends FunctionReference>(function_: F, args?: ArgsOf<F>, options?: RunFunctionOptions) => Promise<unknown>;
@@ -24,66 +63,11 @@ interface DispatchLogger {
24
63
  info: (message: unknown, ...rest: unknown[]) => void;
25
64
  warn: (message: unknown, ...rest: unknown[]) => void;
26
65
  }
27
- /** Build a {@link DispatchLogger} that prefixes every line with `prefix` (e.g. `[queue:email]`). */
28
- /** How a queue message body is serialized on the wire (Cloudflare default `"json"`). */
29
- type QueueContentType = "bytes" | "json" | "text" | "v8";
30
- /** Options for a single `producer.send(body, options?)`. */
31
- interface QueueSendOptions {
32
- /** Wire serialization for this message (defaults to the queue's content type). */
33
- contentType?: QueueContentType;
34
- /** Per-message delivery delay in seconds (0–43200, i.e. up to 12 hours). */
35
- delaySeconds?: number;
36
- }
37
- /** Options for a `producer.sendBatch(messages, options?)`. */
38
- interface QueueSendBatchOptions {
39
- /** Delivery delay applied to the whole batch, in seconds. */
40
- delaySeconds?: number;
41
- }
42
- /** One entry in a `sendBatch` call — a body plus optional per-message overrides. */
43
- interface MessageSendRequestLike<Body = unknown> {
44
- body: Body;
45
- contentType?: QueueContentType;
46
- delaySeconds?: number;
47
- }
48
66
  /**
49
- * Minimal structural projection of workers-types' `Queue&lt;Body>` (the producer
50
- * binding). The real binding's `send`/`sendBatch` resolve to a metadata object;
51
- * we widen the return to `Promise&lt;unknown>` so a plain-object fake satisfies it.
52
- */
53
- interface QueueBindingLike<Body = unknown> {
54
- send: (message: Body, options?: QueueSendOptions) => Promise<unknown>;
55
- sendBatch: (messages: Iterable<MessageSendRequestLike<Body>>, options?: QueueSendBatchOptions) => Promise<unknown>;
56
- }
57
- /** Options for retrying a message / batch (`message.retry({ delaySeconds })`). */
58
- interface QueueRetryOptions {
59
- delaySeconds?: number;
60
- }
61
- /** Structural mirror of workers-types' `Message&lt;Body>` (one delivered message). */
62
- interface MessageLike<Body = unknown> {
63
- /** Acknowledge this message so it is not redelivered. */
64
- ack: () => void;
65
- readonly attempts: number;
66
- readonly body: Body;
67
- readonly id: string;
68
- /** Explicitly retry this message (optionally after a delay). */
69
- retry: (options?: QueueRetryOptions) => void;
70
- readonly timestamp: Date;
71
- }
72
- /** Structural mirror of workers-types' `MessageBatch&lt;Body>` handed to a consumer. */
73
- interface MessageBatchLike<Body = unknown> {
74
- /** Acknowledge every message in the batch. */
75
- ackAll: () => void;
76
- readonly messages: ReadonlyArray<MessageLike<Body>>;
77
- /** The queue name this batch was delivered from (`batch.queue`), used to route. */
78
- readonly queue: string;
79
- /** Retry every message in the batch (optionally after a delay). */
80
- retryAll: (options?: QueueRetryOptions) => void;
81
- }
82
- /**
83
- * The typed producer bound to `ctx.queues.&lt;name>`. Sending is a side effect, so
84
- * the generated context exposes this only on `MutationCtx` / `ActionCtx` (never
85
- * the deterministic `QueryCtx`), mirroring `ctx.scheduler` / `ctx.workflows`.
86
- */
67
+ * The typed producer bound to `ctx.queues.<name>`. Sending is a side effect, so
68
+ * the generated context exposes this only on `MutationCtx` / `ActionCtx` (never
69
+ * the deterministic `QueryCtx`), mirroring `ctx.scheduler` / `ctx.workflows`.
70
+ */
87
71
  interface QueueProducer<Body = unknown> {
88
72
  /** Enqueue one message. */
89
73
  send: (body: Body, options?: QueueSendOptions) => Promise<void>;
@@ -91,10 +75,10 @@ interface QueueProducer<Body = unknown> {
91
75
  sendBatch: (messages: Iterable<MessageSendRequestLike<Body>>, options?: QueueSendBatchOptions) => Promise<void>;
92
76
  }
93
77
  /**
94
- * `ctx.queues` — the map of declared queue export names → typed producers.
95
- * Codegen narrows this to the exact export names; the package keeps it open so
96
- * `createQueues` stays schema-agnostic.
97
- */
78
+ * `ctx.queues` — the map of declared queue export names → typed producers.
79
+ * Codegen narrows this to the exact export names; the package keeps it open so
80
+ * `createQueues` stays schema-agnostic.
81
+ */
98
82
  interface Queues {
99
83
  [exportName: string]: QueueProducer;
100
84
  }
@@ -104,23 +88,49 @@ interface LunoraQueuesOptions {
104
88
  bindings: Record<string, QueueBindingLike>;
105
89
  }
106
90
  /**
107
- * The context handed to a `defineQueue` handler. Decoupled from `@lunora/server`
108
- * (like the workflow run context): to touch data, call a Lunora mutation/action
109
- * via `ctx.run(api.x.y, args)` — the dispatch goes through the same
110
- * `/_lunora/scheduler/dispatch` path the SchedulerDO and workflows use.
111
- */
91
+ * The context handed to a `defineQueue` handler. Decoupled from `@lunora/server`
92
+ * (like the workflow run context): to touch data, call a Lunora mutation/action
93
+ * via `ctx.run(api.x.y, args)` — the dispatch goes through the same
94
+ * `/_lunora/scheduler/dispatch` path the SchedulerDO and workflows use.
95
+ */
112
96
  interface QueueRunContext {
113
97
  /** The worker `env` (bindings + vars). */
114
98
  readonly env: Record<string, unknown>;
115
99
  /** Queue-name-prefixed logger. */
116
100
  readonly log: DispatchLogger;
117
- /** Invoke a Lunora function (query/mutation/action) by reference. */
101
+ /**
102
+ * Invoke a Lunora function (query/mutation/action) by reference.
103
+ *
104
+ * Batch-unaware: a failure it throws is attributed to nothing, so a
105
+ * deterministic failure retries the whole batch. Inside the `batch.messages`
106
+ * loop, prefer {@link QueueMessage.run}, which pins the call to its message.
107
+ */
118
108
  readonly run: DispatchRunFunction;
119
109
  }
110
+ /**
111
+ * One delivered message as the push handler sees it: the Cloudflare `Message`
112
+ * plus `run` — a {@link QueueRunContext.run} pinned to THIS message.
113
+ *
114
+ * Prefer `message.run(api.x.y, args)` over `ctx.run(...)` inside the batch
115
+ * loop. The pin is what lets the dispatcher attribute a deterministic dispatch
116
+ * failure (400/403/404/422) to the one message that caused it: that message is
117
+ * acked and every other one is retried, instead of the whole batch being
118
+ * re-delivered because of a single poison message. A plain `ctx.run` call
119
+ * carries no message id, so its failure stays unattributed and the whole batch
120
+ * retries.
121
+ */
122
+ interface QueueMessage<Body = unknown> extends MessageLike<Body> {
123
+ /** {@link QueueRunContext.run}, pinned to this message for failure attribution. */
124
+ readonly run: DispatchRunFunction;
125
+ }
126
+ /** The delivered batch as the push handler sees it — {@link QueueMessage}s rather than bare `Message`s. */
127
+ interface QueueMessageBatch<Body = unknown> extends Omit<MessageBatchLike<Body>, "messages"> {
128
+ readonly messages: ReadonlyArray<QueueMessage<Body>>;
129
+ }
120
130
  /** Whether a declared queue is consumed by this worker (push) or polled externally (pull). */
121
131
  type QueueConsumerMode = "pull" | "push";
122
132
  /** The handler body run for each delivered batch (push consumers only). */
123
- type QueueHandler<Body = unknown> = (context: QueueRunContext, batch: MessageBatchLike<Body>) => Promise<void> | void;
133
+ type QueueHandler<Body = unknown> = (context: QueueRunContext, batch: QueueMessageBatch<Body>) => Promise<void> | void;
124
134
  /** Push-consumer batch/retry tuning, mirrored onto the wrangler `queues.consumers[]` entry. */
125
135
  interface QueueConsumerTuning {
126
136
  /** Name of the dead-letter queue messages land in after `maxRetries`. */
@@ -137,25 +147,25 @@ interface QueueConsumerTuning {
137
147
  /** The config object passed to `defineQueue`. */
138
148
  interface QueueConfig<Body = unknown> extends QueueConsumerTuning {
139
149
  /**
140
- * The push-consumer body. Required for `mode: "push"` (the default); omit it
141
- * for `mode: "pull"`, where an external worker polls the queue over HTTP.
142
- */
150
+ * The push-consumer body. Required for `mode: "push"` (the default); omit it
151
+ * for `mode: "pull"`, where an external worker polls the queue over HTTP.
152
+ */
143
153
  handler?: QueueHandler<Body>;
144
154
  /** How this queue is consumed. Defaults to `"push"`. */
145
155
  mode?: QueueConsumerMode;
146
156
  /**
147
- * Stable wrangler queue name (`queues.producers[].queue`). Defaults to the
148
- * kebab-cased export name (`emailQueue` → `email-queue`).
149
- */
157
+ * Stable wrangler queue name (`queues.producers[].queue`). Defaults to the
158
+ * kebab-cased export name (`emailQueue` → `email-queue`).
159
+ */
150
160
  name?: string;
151
161
  }
152
162
  /** The branded result of `defineQueue`, discovered by codegen + config. */
153
163
  interface QueueDefinition<Body = unknown> extends QueueConfig<Body> {
154
164
  /**
155
- * Phantom carrier for the message body type, so codegen can type the
156
- * generated `ctx.queues.&lt;name>` producer as `QueueProducer&lt;Body>` from
157
- * `typeof &lt;export>`. Never assigned at runtime (type-only).
158
- */
165
+ * Phantom carrier for the message body type, so codegen can type the
166
+ * generated `ctx.queues.<name>` producer as `QueueProducer<Body>` from
167
+ * `typeof <export>`. Never assigned at runtime (type-only).
168
+ */
159
169
  readonly __lunoraBody?: Body;
160
170
  /** Runtime brand identifying a `defineQueue` result. */
161
171
  isLunoraQueue: true;
@@ -172,13 +182,13 @@ interface QueueBindingSpec {
172
182
  /** One declared queue, keyed for batch routing by its stable wrangler name. */
173
183
  interface QueueRegistryEntry {
174
184
  /**
175
- * The `defineQueue` result (carries the push handler). The body type is
176
- * erased to `any` here because the registry is heterogeneous — different
177
- * queues carry different message bodies, and the handler param is
178
- * contravariant, so a precise `QueueDefinition&lt;Body>` would not be assignable
179
- * to a shared `unknown`-bodied slot. Runtime dispatch passes the delivered
180
- * batch straight through, so the erasure is type-only.
181
- */
185
+ * The `defineQueue` result (carries the push handler). The body type is
186
+ * erased to `any` here because the registry is heterogeneous — different
187
+ * queues carry different message bodies, and the handler param is
188
+ * contravariant, so a precise `QueueDefinition<Body>` would not be assignable
189
+ * to a shared `unknown`-bodied slot. Runtime dispatch passes the delivered
190
+ * batch straight through, so the erasure is type-only.
191
+ */
182
192
  definition: QueueDefinition<any>;
183
193
  /** The `lunora/queues.ts` export name, for log correlation. */
184
194
  exportName: string;
@@ -188,17 +198,17 @@ type QueueRegistry = Record<string, QueueRegistryEntry>;
188
198
  /** The disposition a consumer left one message in for a single delivery attempt. */
189
199
  type QueueMessageOutcome = "ack" | "error" | "retry";
190
200
  /**
191
- * One consumed message as captured by {@link dispatchQueueBatch} and handed to an
192
- * {@link QueueCaptureSink}. Structurally matches `@lunora/do`'s
193
- * `RecordQueueMessageInput` (the reserved `recordQueueMessage` admin RPC payload);
194
- * the two packages share only this contract, so keep them in sync by hand.
195
- */
201
+ * One consumed message as captured by {@link dispatchQueueBatch} and handed to an
202
+ * {@link QueueCaptureSink}. Structurally matches `@lunora/do`'s
203
+ * `RecordQueueMessageInput` (the reserved `recordQueueMessage` admin RPC payload);
204
+ * the two packages share only this contract, so keep them in sync by hand.
205
+ */
196
206
  interface CapturedQueueMessage {
197
207
  /** Delivery attempt number for this message (`message.attempts`). */
198
208
  attempts: number;
199
209
  /** The message body (JSON-encoded + capped by the catcher). */
200
210
  body: unknown;
201
- /** `true` when this non-ack disposition exhausted the queue's `maxRetries` (dead-letters next). */
211
+ /** `true` when this failed delivery was the message's last (its retries are exhausted — the broker dead-letters it). */
202
212
  deadLettered: boolean;
203
213
  /** Handler error message when `outcome` is `error`; absent otherwise. */
204
214
  error?: string;
@@ -214,19 +224,20 @@ interface CapturedQueueMessage {
214
224
  timestamp: number;
215
225
  }
216
226
  /**
217
- * Persists a batch of consumed messages. The codegen worker wires this to POST the
218
- * batch to the root shard's `recordQueueMessage` admin RPC (the dev queue catcher).
219
- * Best-effort by contract: {@link dispatchQueueBatch} swallows a rejection so a
220
- * capture failure never changes delivery semantics.
221
- */
227
+ * Persists a batch of consumed messages. The codegen worker wires this to POST the
228
+ * batch to the root shard's `recordQueueMessage` admin RPC (the dev queue catcher).
229
+ * Best-effort by contract: {@link dispatchQueueBatch} swallows a rejection so a
230
+ * capture failure never changes delivery semantics.
231
+ */
222
232
  type QueueCaptureSink = (messages: CapturedQueueMessage[]) => Promise<void> | void;
223
233
  interface DispatchOptions {
224
234
  /**
225
- * Optional capture sink. When set, the batch is instrumented and every
226
- * message's final disposition is recorded and handed to this sink after the
227
- * handler runs. Omitted in production unless queue capture is enabled, so a
228
- * consumer pays no instrumentation cost by default.
229
- */
235
+ * Optional capture sink. When set, every message's final disposition is
236
+ * turned into a record and handed to this sink after the handler runs.
237
+ * Omitted in production unless queue capture is enabled, so a consumer pays
238
+ * no record-building or sink cost by default. Delivery semantics — including
239
+ * poison-message isolation — do not depend on it.
240
+ */
230
241
  capture?: QueueCaptureSink;
231
242
  /** Worker `env`, forwarded to the queue run context. */
232
243
  env: Record<string, unknown>;
@@ -234,11 +245,11 @@ interface DispatchOptions {
234
245
  fetchImpl?: typeof fetch;
235
246
  }
236
247
  /**
237
- * Look up the handler for `batch.queue` and invoke it with a fresh
238
- * `QueueRunContext`. Throws a directed error when no push handler is registered
239
- * for the delivered queue (a misconfiguration — the consumer was declared
240
- * `pull`, or the queue name drifted from the `defineQueue` export).
241
- */
248
+ * Look up the handler for `batch.queue` and invoke it with a fresh
249
+ * `QueueRunContext`. Throws a directed error when no push handler is registered
250
+ * for the delivered queue (a misconfiguration — the consumer was declared
251
+ * `pull`, or the queue name drifted from the `defineQueue` export).
252
+ */
242
253
  declare const dispatchQueueBatch: (batch: MessageBatchLike, registry: QueueRegistry, options: DispatchOptions) => Promise<void>;
243
254
  /** A Worker `env` projected as a plain record (vars, secrets, and bindings are `unknown`-valued). */
244
255
  type QueueEnv = Record<string, unknown>;
@@ -250,81 +261,87 @@ interface QueueCaptureOptions {
250
261
  rootShard?: string;
251
262
  }
252
263
  /**
253
- * Whether consumed queue messages should be captured into the studio's log.
254
- * Explicit `LUNORA_QUEUE_CAPTURE` (`"1"`/`"true"` vs `"0"`/`"false"`) always wins;
255
- * unset, capture is on only in a development environment. Mirrors
256
- * `@lunora/mail`'s `shouldCaptureMail` so mail and queue dev capture toggle the
257
- * same way.
258
- */
264
+ * Whether consumed queue messages should be captured into the studio's log.
265
+ * Explicit `LUNORA_QUEUE_CAPTURE` (`"1"`/`"true"` vs `"0"`/`"false"`) always wins;
266
+ * unset, capture is on only in a development environment. Mirrors
267
+ * `@lunora/mail`'s `shouldCaptureMail` so mail and queue dev capture toggle the
268
+ * same way.
269
+ */
259
270
  declare const shouldCaptureQueue: (env: QueueEnv) => boolean;
260
271
  /**
261
- * Build the {@link QueueCaptureSink} that records a processed batch into the
262
- * studio's root-shard consumed-message log via the reserved `recordQueueMessage`
263
- * admin RPC. Best-effort by contract: without the `SHARD` binding or
264
- * `LUNORA_ADMIN_TOKEN` it no-ops, and `dispatchQueueBatch` swallows a
265
- * rejection, so capture never changes delivery semantics.
266
- */
272
+ * Build the {@link QueueCaptureSink} that records a processed batch into the
273
+ * studio's root-shard consumed-message log via the reserved `recordQueueMessage`
274
+ * admin RPC. Best-effort by contract: without the `SHARD` binding or
275
+ * `LUNORA_ADMIN_TOKEN` it no-ops, and `dispatchQueueBatch` swallows a
276
+ * rejection, so capture never changes delivery semantics.
277
+ */
267
278
  declare const createQueueCaptureSink: (env: QueueEnv, options?: QueueCaptureOptions) => QueueCaptureSink;
268
279
  /**
269
- * Build the `ctx.queues` map for a request: resolve every spec's `env[binding]`
270
- * into the `exportName → Queue binding` map and wrap it in {@link createQueues}.
271
- * A spec whose binding is absent from `env` is skipped here — the helpful "no
272
- * queue named …" error is raised lazily by `ctx.queues.&lt;name>.send(...)` when
273
- * the missing queue is actually used.
274
- */
280
+ * Build the `ctx.queues` map for a request: resolve every spec's `env[binding]`
281
+ * into the `exportName → Queue binding` map and wrap it in {@link createQueues}.
282
+ * A spec whose binding is absent from `env` is skipped here — the helpful "no
283
+ * queue named …" error is raised lazily by `ctx.queues.<name>.send(...)` when
284
+ * the missing queue is actually used.
285
+ */
275
286
  declare const createQueueContext: (env: Record<string, unknown>, specs: ReadonlyArray<QueueBindingSpec>) => Queues;
276
287
  /**
277
- * Build the `ctx.queues` map from `lunora/queues.ts` export name → Cloudflare
278
- * `Queue` binding. Each property is a typed {@link QueueProducer}; accessing an
279
- * export whose binding is absent throws a directed error naming the declared
280
- * queues (raised lazily on first use).
281
- */
288
+ * Build the `ctx.queues` map from `lunora/queues.ts` export name → Cloudflare
289
+ * `Queue` binding. Each property is a typed {@link QueueProducer}; accessing an
290
+ * export whose binding is absent throws a directed error naming the declared
291
+ * queues (raised lazily on first use).
292
+ */
282
293
  declare const createQueues: (options: LunoraQueuesOptions) => Queues;
283
294
  /**
284
- * The wrangler producer binding name for a queue export: `emailQueue` →
285
- * `QUEUE_EMAIL_QUEUE`, `email` → `QUEUE_EMAIL`. The `QUEUE_` prefix namespaces
286
- * these away from `SHARD`/`SESSION`/`SCHEDULER`/`WORKFLOW_*`/`CONTAINER_*` so a
287
- * queue export can never collide with the built-in bindings.
288
- */
295
+ * The wrangler producer binding name for a queue export: `emailQueue` →
296
+ * `QUEUE_EMAIL_QUEUE`, `email` → `QUEUE_EMAIL`. The `QUEUE_` prefix namespaces
297
+ * these away from `SHARD`/`SESSION`/`SCHEDULER`/`WORKFLOW_*`/`CONTAINER_*` so a
298
+ * queue export can never collide with the built-in bindings.
299
+ */
289
300
  declare const queueBindingName: (exportName: string) => string;
290
301
  /**
291
- * The stable queue name wrangler registers (`queues.producers[].queue` and
292
- * `queues.consumers[].queue`): `emailQueue` → `email-queue`. Used as the
293
- * deployed queue's identifier when no explicit `name` override is given.
294
- */
302
+ * The stable queue name wrangler registers (`queues.producers[].queue` and
303
+ * `queues.consumers[].queue`): `emailQueue` → `email-queue`. Used as the
304
+ * deployed queue's identifier when no explicit `name` override is given.
305
+ */
295
306
  declare const queueDefaultName: (exportName: string) => string;
296
307
  /**
297
- * Declare a Cloudflare Queue deployed alongside the app. Pure validation +
298
- * branding: codegen discovers the export, emits the typed `ctx.queues.&lt;name>`
299
- * producer and (for push consumers) the worker `queue()` dispatch; the config
300
- * layer reconciles the wrangler `queues.producers[]` / `queues.consumers[]`
301
- * entries from the same definition.
302
- *
303
- * ```ts
304
- * // lunora/queues.ts
305
- * import { defineQueue } from "@lunora/queue";
306
- * import { api } from "./_generated/api";
307
- *
308
- * export const emailQueue = defineQueue&lt;{ to: string }>({
309
- * handler: async (ctx, batch) => {
310
- * for (const message of batch.messages) {
311
- * await ctx.run(api.email.send, { to: message.body.to });
312
- * message.ack();
313
- * }
314
- * },
315
- * });
316
- * ```
317
- *
318
- * Enqueue from a mutation or action: `await ctx.queues.emailQueue.send({ to })`.
319
- *
320
- * ⚠️ **Privileged dispatch.** A push handler's `ctx.run(...)` calls back into
321
- * Lunora functions over the admin-authenticated dispatch endpoint (the same
322
- * trusted path the scheduler and workflows use), so those calls run with the
323
- * system identity **end-user RLS is not applied**. Treat a queue handler as
324
- * trusted server code: validate `message.body` (it may be attacker-influenced if
325
- * anything user-facing can enqueue) before acting on it, and don't forward an
326
- * unchecked body straight into a privileged mutation.
327
- */
308
+ * Declare a Cloudflare Queue deployed alongside the app. Pure validation +
309
+ * branding: codegen discovers the export, emits the typed `ctx.queues.<name>`
310
+ * producer and (for push consumers) the worker `queue()` dispatch; the config
311
+ * layer reconciles the wrangler `queues.producers[]` / `queues.consumers[]`
312
+ * entries from the same definition.
313
+ *
314
+ * ```ts
315
+ * // lunora/queues.ts
316
+ * import { defineQueue } from "@lunora/queue";
317
+ * import { api } from "./_generated/api";
318
+ *
319
+ * export const emailQueue = defineQueue<{ to: string }>({
320
+ * handler: async (ctx, batch) => {
321
+ * for (const message of batch.messages) {
322
+ * await message.run(api.email.send, { to: message.body.to });
323
+ * message.ack();
324
+ * }
325
+ * },
326
+ * });
327
+ * ```
328
+ *
329
+ * Enqueue from a mutation or action: `await ctx.queues.emailQueue.send({ to })`.
330
+ *
331
+ * `message.run(...)` is `ctx.run(...)` pinned to that message — prefer it inside
332
+ * the batch loop. A deterministic failure (400/403/404/422) from a pinned call
333
+ * is attributed to its message: that one is acked and the rest are retried,
334
+ * instead of one poison message re-delivering (and eventually dead-lettering)
335
+ * the whole batch.
336
+ *
337
+ * ⚠️ **Privileged dispatch.** A push handler's `ctx.run(...)` calls back into
338
+ * Lunora functions over the admin-authenticated dispatch endpoint (the same
339
+ * trusted path the scheduler and workflows use), so those calls run with the
340
+ * system identity — **end-user RLS is not applied**. Treat a queue handler as
341
+ * trusted server code: validate `message.body` (it may be attacker-influenced if
342
+ * anything user-facing can enqueue) before acting on it, and don't forward an
343
+ * unchecked body straight into a privileged mutation.
344
+ */
328
345
  declare const defineQueue: <Body = unknown>(config: QueueConfig<Body>) => QueueDefinition<Body>;
329
346
  /** True when a value is a `defineQueue` result (the runtime brand check). */
330
347
  declare const isQueueDefinition: (value: unknown) => value is QueueDefinition;
@@ -335,4 +352,4 @@ interface RunContextOptions {
335
352
  }
336
353
  /** Assemble the {@link QueueRunContext} passed to a `defineQueue` handler. */
337
354
  declare const createQueueRunContext: (options: RunContextOptions) => QueueRunContext;
338
- export { type ArgsOf, type CapturedQueueMessage, type FunctionReference, type LunoraQueuesOptions, type MessageBatchLike, type MessageLike, type MessageSendRequestLike, type QueueBindingLike, type QueueBindingSpec, type QueueCaptureOptions, type QueueCaptureSink, type QueueConfig, type QueueConsumerMode, type QueueConsumerTuning, type QueueContentType, type QueueDefinition, type QueueEnv, type QueueHandler, type DispatchLogger as QueueLogger, type QueueProducer, type QueueRegistry, type QueueRegistryEntry, type QueueRetryOptions, type QueueRunContext, type DispatchRunFunction as QueueRunFunction, type QueueSendBatchOptions, type QueueSendOptions, type Queues, type RunFunctionOptions, createQueueCaptureSink, createQueueContext, createQueueRunContext, createQueues, defineQueue, dispatchQueueBatch, isQueueDefinition, queueBindingName, queueDefaultName, shouldCaptureQueue };
355
+ export { type ArgsOf, type CapturedQueueMessage, type FunctionReference, type LunoraQueuesOptions, type QueueBindingSpec, type QueueCaptureOptions, type QueueCaptureSink, type QueueConfig, type QueueConsumerMode, type QueueConsumerTuning, type QueueDefinition, type QueueEnv, type QueueHandler, type DispatchLogger as QueueLogger, type QueueMessage, type QueueMessageBatch, type QueueProducer, type QueueRegistry, type QueueRegistryEntry, type QueueRunContext, type DispatchRunFunction as QueueRunFunction, type Queues, type RunFunctionOptions, createQueueCaptureSink, createQueueContext, createQueueRunContext, createQueues, defineQueue, dispatchQueueBatch, isQueueDefinition, queueBindingName, queueDefaultName, shouldCaptureQueue };
package/dist/index.mjs CHANGED
@@ -1,6 +1 @@
1
- export { createQueueCaptureSink, shouldCaptureQueue } from './packem_shared/createQueueCaptureSink-CRacRMqV.mjs';
2
- export { createQueueContext } from './packem_shared/createQueueContext-D0XCdCsd.mjs';
3
- export { default as createQueues } from './packem_shared/createQueues-14-vSICK.mjs';
4
- export { defineQueue, isQueueDefinition, queueBindingName, queueDefaultName } from './packem_shared/defineQueue-D40gREfg.mjs';
5
- export { dispatchQueueBatch } from './packem_shared/dispatchQueueBatch-D4zU7C-C.mjs';
6
- export { createQueueRunContext } from './packem_shared/createQueueRunContext-_2hD-TK7.mjs';
1
+ import{createQueueCaptureSink as t,shouldCaptureQueue as r}from"./packem_shared/createQueueCaptureSink-CSOPftvj.mjs";import{createQueueContext as a}from"./packem_shared/createQueueContext-B9PgIzBM.mjs";import{default as i}from"./packem_shared/createQueues-FpKDoLFX.mjs";import{defineQueue as p,isQueueDefinition as m,queueBindingName as x,queueDefaultName as Q}from"./packem_shared/defineQueue-TY4-i3nG.mjs";import{dispatchQueueBatch as s}from"./packem_shared/dispatchQueueBatch-x_PWzYXq.mjs";import{c as C}from"./packem_shared/run-context-Bz18TJFU.mjs";export{t as createQueueCaptureSink,a as createQueueContext,C as createQueueRunContext,i as createQueues,p as defineQueue,s as dispatchQueueBatch,m as isQueueDefinition,x as queueBindingName,Q as queueDefaultName,r as shouldCaptureQueue};
@@ -0,0 +1 @@
1
+ import{LunoraError as E}from"@lunora/errors";const _="__lunora_admin__:recordQueueMessage",N="__root__",f=/^(?:dev(?:elopment)?|local(?:host)?|test)$/iu,p=["CF_ENV","ENVIRONMENT","NODE_ENV","WORKER_ENV"],T=5e3,O=o=>{const t=o.LUNORA_QUEUE_CAPTURE;if(typeof t=="string"){const e=t.toLowerCase();if(e==="1"||e==="true")return!0;if(e==="0"||e==="false")return!1;console.warn(`@lunora/queue: unrecognized LUNORA_QUEUE_CAPTURE value "${t}" — expected "1"/"true" or "0"/"false"; falling back to environment detection.`)}return p.some(e=>{const r=o[e];return typeof r=="string"&&f.test(r)})},h=(o,t={})=>{const e=t.rootShard??N;return async r=>{if(r.length===0)return;const i=o.SHARD,s=typeof o.LUNORA_ADMIN_TOKEN=="string"?o.LUNORA_ADMIN_TOKEN:void 0;if(i===void 0||s===void 0)return;let a=i;if(t.jurisdiction!==void 0){if(typeof i.jurisdiction!="function")throw new TypeError(`@lunora/queue: Durable Object namespace does not support jurisdiction("${t.jurisdiction}") — update @cloudflare/workers-types or remove the jurisdiction option`);a=i.jurisdiction(t.jurisdiction)}const l=a.get(a.idFromName(e)),u=new AbortController,d=setTimeout(()=>{u.abort()},T);try{const n=await l.fetch("https://shard.internal/rpc",{body:JSON.stringify({args:{messages:r},functionPath:_}),headers:{authorization:`Bearer ${s}`,"content-type":"application/json"},method:"POST",signal:u.signal});if(!n.ok){const c=await n.text().catch(()=>"");throw new E("INTERNAL",`@lunora/queue: capture write to the root shard failed (${String(n.status)} ${n.statusText})${c===""?"":`: ${c}`}`)}await n.body?.cancel()}finally{clearTimeout(d)}}};export{h as createQueueCaptureSink,O as shouldCaptureQueue};
@@ -0,0 +1 @@
1
+ import i from"./createQueues-FpKDoLFX.mjs";const s=(o,c)=>{const n={};for(const t of c){const e=o[t.binding];e&&typeof e.send=="function"&&typeof e.sendBatch=="function"&&(n[t.exportName]=e)}return i({bindings:n})};export{s as createQueueContext};
@@ -0,0 +1 @@
1
+ import{c as o}from"./run-context-Bz18TJFU.mjs";export{o as createQueueRunContext};
@@ -0,0 +1 @@
1
+ import{LunoraError as i}from"@lunora/errors";const u=100,d=o=>({send:async(r,n)=>{await o.send(r,n)},sendBatch:async(r,n)=>{const t=[...r];if(t.length>u)throw new i("VALIDATION_ERROR",`@lunora/queue: sendBatch exceeds ${String(u)} (got ${String(t.length)}) — split across calls`);await o.sendBatch(t,n)}}),g=o=>{const r=o.bindings??{},n=Object.create(null);for(const[s,e]of Object.entries(r))n[s]=d(e);const t=Object.keys(n),a=s=>{const e=t.length===0?"no queues are declared":`known queues: ${t.join(", ")}`,c=()=>Promise.reject(new Error(`@lunora/queue: no queue named "${s}" (${e})`));return{send:c,sendBatch:c}};return new Proxy(n,{get(s,e){if(typeof e=="string")return Object.hasOwn(s,e)?s[e]:a(e)}})};export{g as default};
@@ -0,0 +1 @@
1
+ const n=e=>`QUEUE_${e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"_").toUpperCase()}`,t=e=>e.replaceAll(/(?<=[a-z0-9])(?=[A-Z])/g,"-").toLowerCase(),o=e=>{const u=e.mode??"push";if(u!=="push"&&u!=="pull")throw new TypeError(`defineQueue: \`mode\` must be "push" or "pull" (got ${JSON.stringify(e.mode)})`);if(u==="push"&&typeof e.handler!="function")throw new TypeError('defineQueue: `handler` must be a function for a push consumer (omit it only when `mode: "pull"`)');if(e.name!==void 0&&(typeof e.name!="string"||e.name.length===0))throw new TypeError("defineQueue: `name` must be a non-empty string when provided");return{...e,isLunoraQueue:!0,mode:u}},r=e=>typeof e=="object"&&e!==null&&e.isLunoraQueue===!0;export{o as defineQueue,r as isQueueDefinition,n as queueBindingName,t as queueDefaultName};
@@ -0,0 +1 @@
1
+ import{c as m,B as w,M as h}from"./run-context-Bz18TJFU.mjs";import{LunoraError as y,toErrorBody as v}from"@lunora/errors";const x=3,q=e=>{if(e instanceof Date)return e.getTime();const r=typeof e=="number"?e:Number(e);return Number.isFinite(r)?r:0},b=(e,r)=>{let t=0;return(n,o,i)=>(t+=1,e(n,o,{...i,dedupId:`${r}#${String(t)}`,messageId:r}))},k=(e,r,t)=>{const n=b(t,e.id);return new Proxy(e,{get:(o,i)=>i==="ack"?()=>{r.set(o,"ack"),o.ack()}:i==="retry"?c=>{r.set(o,"retry"),o.retry(c)}:i==="run"?n:Reflect.get(o,i,o)})},g=(e,r,t)=>new Proxy(e,{get:(n,o)=>o==="ackAll"?()=>{t("ack"),n.ackAll()}:o==="retryAll"?i=>{t("retry"),n.retryAll(i)}:o==="messages"?r:Reflect.get(n,o,n)}),N=(e,r)=>{const t=new Map,n=e.messages,o=n.map(a=>k(a,t,r)),c=g(e,o,a=>{for(const d of n)t.has(d)||t.set(d,a)});return{dispositions:t,originals:n,wrappedBatch:c}},$=e=>{if(e instanceof Error)return e.message;if(typeof e=="string")return e;if(e!==null&&typeof e=="object")try{return JSON.stringify(e)}catch{return"[unserializable thrown value]"}return String(e)},R=(e,r,t,n,o,i)=>{const c=n?$(o):void 0,a=typeof r.definition.maxRetries=="number"?r.definition.maxRetries:x,d=n?"error":"ack";return e.originals.map(s=>{const l=s===i,u=e.dispositions.get(s),f=l?"error":u??d,p=typeof s.attempts=="number"?s.attempts:1;return{attempts:p,body:s.body,deadLettered:!l&&f!=="ack"&&p>a,error:f==="error"?c:void 0,exportName:r.exportName,messageId:s.id,outcome:f,queue:t,timestamp:q(s.timestamp)}})},A=(e,r,t)=>{if(!r||!w(t))return;const n=h(t);if(n===void 0)return;const o=e.originals.find(i=>i.id===n);if(!(o===void 0||e.dispositions.has(o)))return o},M=(e,r)=>{r.ack();for(const t of e.originals)t!==r&&!e.dispositions.has(t)&&(e.dispositions.set(t,"retry"),t.retry())},I=async(e,r,t)=>{const n=Object.hasOwn(r,e.queue)?r[e.queue]:void 0;if(n===void 0){const u=Object.keys(r),f=u.length===0?"no push queues are declared":`known push queues: ${u.join(", ")}`;throw new y("INTERNAL",`@lunora/queue: received a batch for queue "${e.queue}" but no push handler is registered (${f})`)}const{handler:o}=n.definition;if(typeof o!="function")throw new TypeError(`@lunora/queue: queue "${e.queue}" (${n.exportName}) has no push handler — it is declared as a pull consumer`);const i=m({env:t.env,exportName:n.exportName,fetchImpl:t.fetchImpl}),c=N(e,i.run);let a=!1,d;try{await o(i,c.wrappedBatch)}catch(u){a=!0,d=u}const s=A(c,a,d);if(s!==void 0){M(c,s);const{body:u,status:f}=v(d,{redactedMessage:"internal error"});console.error(`@lunora/queue: dropped message ${s.id} on queue "${e.queue}" (${n.exportName}) — a dispatch it made failed with a deterministic ${String(f)} (${u.code}: ${u.message}), so it was acked, not retried. Its retries are NOT exhausted and it is not dead-lettered — it will never be redelivered.`)}const l=a&&s===void 0;if(t.capture!==void 0)try{await t.capture(R(c,n,e.queue,a,d,s))}catch(u){console.warn("@lunora/queue: capture sink failed (delivery unaffected):",u)}if(l)throw d};export{I as dispatchQueueBatch};
@@ -0,0 +1 @@
1
+ import{LunoraError as d,isLunoraError as A}from"@lunora/errors";const R=t=>({debug:(e,...n)=>{console.debug(t,e,...n)},error:(e,...n)=>{console.error(t,e,...n)},info:(e,...n)=>{console.info(t,e,...n)},warn:(e,...n)=>{console.warn(t,e,...n)}}),E=(t,e,n)=>{if(e===void 0)return{dispose:()=>{},signal:t};const s=new AbortController,o=setTimeout(()=>{s.abort(n())},e);return{dispose:()=>{clearTimeout(o)},signal:s.signal}},a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",b=t=>{let e="",n=0;const s=t.length-2;for(;n<s;n+=3){const r=t[n]<<16|t[n+1]<<8|t[n+2];e+=a.charAt(r>>18&63)+a.charAt(r>>12&63)+a.charAt(r>>6&63)+a.charAt(r&63)}const o=t.length-n;if(o===1){const r=t[n]<<16;e+=a.charAt(r>>18&63)+a.charAt(r>>12&63)}else if(o===2){const r=t[n]<<16|t[n+1]<<8;e+=a.charAt(r>>18&63)+a.charAt(r>>12&63)+a.charAt(r>>6&63)}return e};new TextDecoder;const I=new TextEncoder,N="=",L=t=>{for(let e=0;e<t.length;e+=1)if(t.charCodeAt(e)>255)return!1;return!0},_=t=>b(I.encode(JSON.stringify(t))),S=t=>!t.startsWith(N)&&L(t)?t:`${N}${b(I.encode(t))}`,x="/_lunora/scheduler/dispatch",D=3e4,U=t=>{let e=t.length;for(;e>0&&t[e-1]==="/";)e-=1;return t.slice(0,e)},v=Symbol("lunoraDispatchFailure"),$=Symbol("lunoraDispatchMessageId"),w=(t,e)=>(Object.defineProperty(t,v,{value:!0}),e!==void 0&&Object.defineProperty(t,$,{value:e}),t),C=t=>A(t)?t[$]:void 0,M=(t,e,n,s)=>{try{const o=JSON.parse(n)?.error;if(typeof o=="object"&&o!==null&&typeof o.code=="string"){const{code:r,data:u,message:l}=o;return w(new d(r,typeof l=="string"?l:void 0,{data:u,status:e}),s)}}catch{}return w(new d("INTERNAL",`${t}: function dispatch failed (${String(e)}): ${n}`,{status:e}),s)},J=new Set([400,403,404,422]),K=t=>A(t)&&t[v]===!0&&J.has(t.status),P=(t,e,n)=>new d("INTERNAL",`${t}: function dispatch to "${e}" timed out after ${String(n)}ms`,{status:503}),j=t=>{const{label:e}=t,n=globalThis.fetch,s=t.fetchImpl??(typeof n=="function"?n.bind(globalThis):void 0);return async(o,r,u={})=>{if(typeof s!="function")throw new TypeError(`${e}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);const l=t.env.LUNORA_ORIGIN_URL;if(typeof l!="string"||l.length===0)throw new d("INTERNAL",`${e}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);const f=t.env.LUNORA_ADMIN_TOKEN;if(typeof f!="string"||f.length===0)throw new d("INTERNAL",`${e}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);const O=`${U(l)}${x}`,p={authorization:`Bearer ${f}`,"content-type":"application/json"};t.identity?.userId!==void 0&&(p["x-lunora-userid"]=S(t.identity.userId)),t.identity?.claims!==void 0&&(p["x-lunora-identity"]=_(t.identity.claims));const g=u.timeoutMs??D,m=E(void 0,g,()=>new DOMException(`dispatch timed out after ${String(g)}ms`,"TimeoutError")),y=c=>{throw c instanceof Error&&c.name==="TimeoutError"?P(e,o.__lunoraRef,g):c};let i;try{try{i=await s(O,{body:JSON.stringify({args:r??{},functionPath:o.__lunoraRef,id:u.dedupId,shardKey:u.shardKey}),headers:p,method:"POST",signal:m.signal})}catch(h){return y(h)}if(!i.ok){let h;try{h=await i.text()}catch(T){return y(T)}throw M(e,i.status,h,u.messageId)}let c;try{c=await i.text()}catch(h){return y(h)}if(c.length===0)return;try{return JSON.parse(c)}catch{throw new d("INTERNAL",`${e}: function dispatch returned a non-JSON body (${String(i.status)}): ${c}`,{status:i.status})}}finally{m.dispose()}}},W=t=>({env:t.env,log:R(`[queue:${t.exportName}]`),run:j({env:t.env,fetchImpl:t.fetchImpl,label:"@lunora/queue"})});export{K as B,C as M,W as c};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/queue",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.41",
4
4
  "description": "Cloudflare Queues for Lunora: defineQueue producers + consumers, the ctx.queues surface, and the generated queue() worker handler",
5
5
  "keywords": [
6
6
  "background-jobs",
@@ -44,7 +44,8 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@lunora/errors": "1.0.0-alpha.1"
47
+ "@lunora/errors": "1.0.0-alpha.28",
48
+ "@lunora/platform": "1.0.0-alpha.23"
48
49
  },
49
50
  "engines": {
50
51
  "node": "^22.15.0 || >=24.11.0"
@@ -1,54 +0,0 @@
1
- const RECORD_QUEUE_MESSAGE_OP = "__lunora_admin__:recordQueueMessage";
2
- const DEFAULT_ROOT_SHARD = "__root__";
3
- const DEV_ENVIRONMENT_PATTERN = /^(?:dev(?:elopment)?|local(?:host)?|test)$/iu;
4
- const ENVIRONMENT_VARS = ["CF_ENV", "ENVIRONMENT", "NODE_ENV", "WORKER_ENV"];
5
- const CAPTURE_FETCH_TIMEOUT_MS = 5e3;
6
- const shouldCaptureQueue = (env) => {
7
- const flag = env["LUNORA_QUEUE_CAPTURE"];
8
- if (typeof flag === "string") {
9
- return flag === "1" || flag.toLowerCase() === "true";
10
- }
11
- return ENVIRONMENT_VARS.some((key) => {
12
- const value = env[key];
13
- return typeof value === "string" && DEV_ENVIRONMENT_PATTERN.test(value);
14
- });
15
- };
16
- const createQueueCaptureSink = (env, options = {}) => {
17
- const rootShard = options.rootShard ?? DEFAULT_ROOT_SHARD;
18
- return async (messages) => {
19
- if (messages.length === 0) {
20
- return;
21
- }
22
- const binding = env["SHARD"];
23
- const adminToken = typeof env["LUNORA_ADMIN_TOKEN"] === "string" ? env["LUNORA_ADMIN_TOKEN"] : void 0;
24
- if (binding === void 0 || adminToken === void 0) {
25
- return;
26
- }
27
- let namespace = binding;
28
- if (options.jurisdiction !== void 0) {
29
- if (typeof binding.jurisdiction !== "function") {
30
- throw new TypeError(
31
- `@lunora/queue: Durable Object namespace does not support jurisdiction("${options.jurisdiction}") — update @cloudflare/workers-types or remove the jurisdiction option`
32
- );
33
- }
34
- namespace = binding.jurisdiction(options.jurisdiction);
35
- }
36
- const stub = namespace.get(namespace.idFromName(rootShard));
37
- const controller = new AbortController();
38
- const timeout = setTimeout(() => {
39
- controller.abort();
40
- }, CAPTURE_FETCH_TIMEOUT_MS);
41
- try {
42
- await stub.fetch("https://shard.internal/rpc", {
43
- body: JSON.stringify({ args: { messages }, functionPath: RECORD_QUEUE_MESSAGE_OP }),
44
- headers: { authorization: `Bearer ${adminToken}`, "content-type": "application/json" },
45
- method: "POST",
46
- signal: controller.signal
47
- });
48
- } finally {
49
- clearTimeout(timeout);
50
- }
51
- };
52
- };
53
-
54
- export { createQueueCaptureSink, shouldCaptureQueue };
@@ -1,14 +0,0 @@
1
- import createQueues from './createQueues-14-vSICK.mjs';
2
-
3
- const createQueueContext = (env, specs) => {
4
- const bindings = {};
5
- for (const spec of specs) {
6
- const binding = env[spec.binding];
7
- if (binding && typeof binding.send === "function" && typeof binding.sendBatch === "function") {
8
- bindings[spec.exportName] = binding;
9
- }
10
- }
11
- return createQueues({ bindings });
12
- };
13
-
14
- export { createQueueContext };