@lunora/queue 1.0.0-alpha.3 → 1.0.0-alpha.5
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/LICENSE.md +6 -0
- package/dist/index.d.mts +97 -30
- package/dist/index.d.ts +97 -30
- package/dist/index.mjs +2 -1
- package/dist/packem_shared/createQueueCaptureSink-CRacRMqV.mjs +54 -0
- package/dist/packem_shared/dispatchQueueBatch-BjWnrUuU.mjs +131 -0
- package/package.json +2 -2
- package/dist/packem_shared/dispatchQueueBatch-DR15pYS7.mjs +0 -19
package/LICENSE.md
CHANGED
|
@@ -103,3 +103,9 @@ Unless required by applicable law or agreed to in writing, software distributed
|
|
|
103
103
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
104
|
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
105
|
specific language governing permissions and limitations under the License.
|
|
106
|
+
|
|
107
|
+
<!-- DEPENDENCIES -->
|
|
108
|
+
<!-- /DEPENDENCIES -->
|
|
109
|
+
|
|
110
|
+
<!-- TYPE_DEPENDENCIES -->
|
|
111
|
+
<!-- /TYPE_DEPENDENCIES -->
|
package/dist/index.d.mts
CHANGED
|
@@ -169,6 +169,102 @@ interface QueueBindingSpec {
|
|
|
169
169
|
/** The stable wrangler queue name, e.g. `email-queue`. */
|
|
170
170
|
name: string;
|
|
171
171
|
}
|
|
172
|
+
/** One declared queue, keyed for batch routing by its stable wrangler name. */
|
|
173
|
+
interface QueueRegistryEntry {
|
|
174
|
+
/**
|
|
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<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
|
+
*/
|
|
182
|
+
definition: QueueDefinition<any>;
|
|
183
|
+
/** The `lunora/queues.ts` export name, for log correlation. */
|
|
184
|
+
exportName: string;
|
|
185
|
+
}
|
|
186
|
+
/** Map of stable wrangler queue name → registry entry, built by codegen. */
|
|
187
|
+
type QueueRegistry = Record<string, QueueRegistryEntry>;
|
|
188
|
+
/** The disposition a consumer left one message in for a single delivery attempt. */
|
|
189
|
+
type QueueMessageOutcome = "ack" | "error" | "retry";
|
|
190
|
+
/**
|
|
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
|
+
*/
|
|
196
|
+
interface CapturedQueueMessage {
|
|
197
|
+
/** Delivery attempt number for this message (`message.attempts`). */
|
|
198
|
+
attempts: number;
|
|
199
|
+
/** The message body (JSON-encoded + capped by the catcher). */
|
|
200
|
+
body: unknown;
|
|
201
|
+
/** `true` when this failed delivery was the message's last (its retries are exhausted — the broker dead-letters it). */
|
|
202
|
+
deadLettered: boolean;
|
|
203
|
+
/** Handler error message when `outcome` is `error`; absent otherwise. */
|
|
204
|
+
error?: string;
|
|
205
|
+
/** The `lunora/queues.ts` export name that consumed it. */
|
|
206
|
+
exportName: string;
|
|
207
|
+
/** The delivered message id (`message.id`). */
|
|
208
|
+
messageId: string;
|
|
209
|
+
/** How the handler disposed of the message this attempt. */
|
|
210
|
+
outcome: QueueMessageOutcome;
|
|
211
|
+
/** The stable wrangler queue name the batch was delivered from (`batch.queue`). */
|
|
212
|
+
queue: string;
|
|
213
|
+
/** Original message timestamp in epoch-ms (`message.timestamp`). */
|
|
214
|
+
timestamp: number;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
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
|
+
*/
|
|
222
|
+
type QueueCaptureSink = (messages: CapturedQueueMessage[]) => Promise<void> | void;
|
|
223
|
+
interface DispatchOptions {
|
|
224
|
+
/**
|
|
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
|
+
*/
|
|
230
|
+
capture?: QueueCaptureSink;
|
|
231
|
+
/** Worker `env`, forwarded to the queue run context. */
|
|
232
|
+
env: Record<string, unknown>;
|
|
233
|
+
/** Injectable fetch for the `ctx.run` dispatcher (tests). */
|
|
234
|
+
fetchImpl?: typeof fetch;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
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
|
+
*/
|
|
242
|
+
declare const dispatchQueueBatch: (batch: MessageBatchLike, registry: QueueRegistry, options: DispatchOptions) => Promise<void>;
|
|
243
|
+
/** A Worker `env` projected as a plain record (vars, secrets, and bindings are `unknown`-valued). */
|
|
244
|
+
type QueueEnv = Record<string, unknown>;
|
|
245
|
+
/** Options for {@link createQueueCaptureSink}. */
|
|
246
|
+
interface QueueCaptureOptions {
|
|
247
|
+
/** Pin the consumed-message log to a Cloudflare data-residency jurisdiction (match the worker's `jurisdiction`). */
|
|
248
|
+
jurisdiction?: string;
|
|
249
|
+
/** Shard the consumed-message log lives on; override if the worker sets a custom `defaultShardKey`. */
|
|
250
|
+
rootShard?: string;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
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
|
+
*/
|
|
259
|
+
declare const shouldCaptureQueue: (env: QueueEnv) => boolean;
|
|
260
|
+
/**
|
|
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
|
+
*/
|
|
267
|
+
declare const createQueueCaptureSink: (env: QueueEnv, options?: QueueCaptureOptions) => QueueCaptureSink;
|
|
172
268
|
/**
|
|
173
269
|
* Build the `ctx.queues` map for a request: resolve every spec's `env[binding]`
|
|
174
270
|
* into the `exportName → Queue binding` map and wrap it in {@link createQueues}.
|
|
@@ -232,35 +328,6 @@ declare const queueDefaultName: (exportName: string) => string;
|
|
|
232
328
|
declare const defineQueue: <Body = unknown>(config: QueueConfig<Body>) => QueueDefinition<Body>;
|
|
233
329
|
/** True when a value is a `defineQueue` result (the runtime brand check). */
|
|
234
330
|
declare const isQueueDefinition: (value: unknown) => value is QueueDefinition;
|
|
235
|
-
/** One declared queue, keyed for batch routing by its stable wrangler name. */
|
|
236
|
-
interface QueueRegistryEntry {
|
|
237
|
-
/**
|
|
238
|
-
* The `defineQueue` result (carries the push handler). The body type is
|
|
239
|
-
* erased to `any` here because the registry is heterogeneous — different
|
|
240
|
-
* queues carry different message bodies, and the handler param is
|
|
241
|
-
* contravariant, so a precise `QueueDefinition<Body>` would not be assignable
|
|
242
|
-
* to a shared `unknown`-bodied slot. Runtime dispatch passes the delivered
|
|
243
|
-
* batch straight through, so the erasure is type-only.
|
|
244
|
-
*/
|
|
245
|
-
definition: QueueDefinition<any>;
|
|
246
|
-
/** The `lunora/queues.ts` export name, for log correlation. */
|
|
247
|
-
exportName: string;
|
|
248
|
-
}
|
|
249
|
-
/** Map of stable wrangler queue name → registry entry, built by codegen. */
|
|
250
|
-
type QueueRegistry = Record<string, QueueRegistryEntry>;
|
|
251
|
-
interface DispatchOptions {
|
|
252
|
-
/** Worker `env`, forwarded to the queue run context. */
|
|
253
|
-
env: Record<string, unknown>;
|
|
254
|
-
/** Injectable fetch for the `ctx.run` dispatcher (tests). */
|
|
255
|
-
fetchImpl?: typeof fetch;
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Look up the handler for `batch.queue` and invoke it with a fresh
|
|
259
|
-
* `QueueRunContext`. Throws a directed error when no push handler is registered
|
|
260
|
-
* for the delivered queue (a misconfiguration — the consumer was declared
|
|
261
|
-
* `pull`, or the queue name drifted from the `defineQueue` export).
|
|
262
|
-
*/
|
|
263
|
-
declare const dispatchQueueBatch: (batch: MessageBatchLike, registry: QueueRegistry, options: DispatchOptions) => Promise<void>;
|
|
264
331
|
interface RunContextOptions {
|
|
265
332
|
env: Record<string, unknown>;
|
|
266
333
|
exportName: string;
|
|
@@ -268,4 +335,4 @@ interface RunContextOptions {
|
|
|
268
335
|
}
|
|
269
336
|
/** Assemble the {@link QueueRunContext} passed to a `defineQueue` handler. */
|
|
270
337
|
declare const createQueueRunContext: (options: RunContextOptions) => QueueRunContext;
|
|
271
|
-
export { type ArgsOf, type FunctionReference, type LunoraQueuesOptions, type MessageBatchLike, type MessageLike, type MessageSendRequestLike, type QueueBindingLike, type QueueBindingSpec, type QueueConfig, type QueueConsumerMode, type QueueConsumerTuning, type QueueContentType, type QueueDefinition, 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, createQueueContext, createQueueRunContext, createQueues, defineQueue, dispatchQueueBatch, isQueueDefinition, queueBindingName, queueDefaultName };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -169,6 +169,102 @@ interface QueueBindingSpec {
|
|
|
169
169
|
/** The stable wrangler queue name, e.g. `email-queue`. */
|
|
170
170
|
name: string;
|
|
171
171
|
}
|
|
172
|
+
/** One declared queue, keyed for batch routing by its stable wrangler name. */
|
|
173
|
+
interface QueueRegistryEntry {
|
|
174
|
+
/**
|
|
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<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
|
+
*/
|
|
182
|
+
definition: QueueDefinition<any>;
|
|
183
|
+
/** The `lunora/queues.ts` export name, for log correlation. */
|
|
184
|
+
exportName: string;
|
|
185
|
+
}
|
|
186
|
+
/** Map of stable wrangler queue name → registry entry, built by codegen. */
|
|
187
|
+
type QueueRegistry = Record<string, QueueRegistryEntry>;
|
|
188
|
+
/** The disposition a consumer left one message in for a single delivery attempt. */
|
|
189
|
+
type QueueMessageOutcome = "ack" | "error" | "retry";
|
|
190
|
+
/**
|
|
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
|
+
*/
|
|
196
|
+
interface CapturedQueueMessage {
|
|
197
|
+
/** Delivery attempt number for this message (`message.attempts`). */
|
|
198
|
+
attempts: number;
|
|
199
|
+
/** The message body (JSON-encoded + capped by the catcher). */
|
|
200
|
+
body: unknown;
|
|
201
|
+
/** `true` when this failed delivery was the message's last (its retries are exhausted — the broker dead-letters it). */
|
|
202
|
+
deadLettered: boolean;
|
|
203
|
+
/** Handler error message when `outcome` is `error`; absent otherwise. */
|
|
204
|
+
error?: string;
|
|
205
|
+
/** The `lunora/queues.ts` export name that consumed it. */
|
|
206
|
+
exportName: string;
|
|
207
|
+
/** The delivered message id (`message.id`). */
|
|
208
|
+
messageId: string;
|
|
209
|
+
/** How the handler disposed of the message this attempt. */
|
|
210
|
+
outcome: QueueMessageOutcome;
|
|
211
|
+
/** The stable wrangler queue name the batch was delivered from (`batch.queue`). */
|
|
212
|
+
queue: string;
|
|
213
|
+
/** Original message timestamp in epoch-ms (`message.timestamp`). */
|
|
214
|
+
timestamp: number;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
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
|
+
*/
|
|
222
|
+
type QueueCaptureSink = (messages: CapturedQueueMessage[]) => Promise<void> | void;
|
|
223
|
+
interface DispatchOptions {
|
|
224
|
+
/**
|
|
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
|
+
*/
|
|
230
|
+
capture?: QueueCaptureSink;
|
|
231
|
+
/** Worker `env`, forwarded to the queue run context. */
|
|
232
|
+
env: Record<string, unknown>;
|
|
233
|
+
/** Injectable fetch for the `ctx.run` dispatcher (tests). */
|
|
234
|
+
fetchImpl?: typeof fetch;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
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
|
+
*/
|
|
242
|
+
declare const dispatchQueueBatch: (batch: MessageBatchLike, registry: QueueRegistry, options: DispatchOptions) => Promise<void>;
|
|
243
|
+
/** A Worker `env` projected as a plain record (vars, secrets, and bindings are `unknown`-valued). */
|
|
244
|
+
type QueueEnv = Record<string, unknown>;
|
|
245
|
+
/** Options for {@link createQueueCaptureSink}. */
|
|
246
|
+
interface QueueCaptureOptions {
|
|
247
|
+
/** Pin the consumed-message log to a Cloudflare data-residency jurisdiction (match the worker's `jurisdiction`). */
|
|
248
|
+
jurisdiction?: string;
|
|
249
|
+
/** Shard the consumed-message log lives on; override if the worker sets a custom `defaultShardKey`. */
|
|
250
|
+
rootShard?: string;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
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
|
+
*/
|
|
259
|
+
declare const shouldCaptureQueue: (env: QueueEnv) => boolean;
|
|
260
|
+
/**
|
|
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
|
+
*/
|
|
267
|
+
declare const createQueueCaptureSink: (env: QueueEnv, options?: QueueCaptureOptions) => QueueCaptureSink;
|
|
172
268
|
/**
|
|
173
269
|
* Build the `ctx.queues` map for a request: resolve every spec's `env[binding]`
|
|
174
270
|
* into the `exportName → Queue binding` map and wrap it in {@link createQueues}.
|
|
@@ -232,35 +328,6 @@ declare const queueDefaultName: (exportName: string) => string;
|
|
|
232
328
|
declare const defineQueue: <Body = unknown>(config: QueueConfig<Body>) => QueueDefinition<Body>;
|
|
233
329
|
/** True when a value is a `defineQueue` result (the runtime brand check). */
|
|
234
330
|
declare const isQueueDefinition: (value: unknown) => value is QueueDefinition;
|
|
235
|
-
/** One declared queue, keyed for batch routing by its stable wrangler name. */
|
|
236
|
-
interface QueueRegistryEntry {
|
|
237
|
-
/**
|
|
238
|
-
* The `defineQueue` result (carries the push handler). The body type is
|
|
239
|
-
* erased to `any` here because the registry is heterogeneous — different
|
|
240
|
-
* queues carry different message bodies, and the handler param is
|
|
241
|
-
* contravariant, so a precise `QueueDefinition<Body>` would not be assignable
|
|
242
|
-
* to a shared `unknown`-bodied slot. Runtime dispatch passes the delivered
|
|
243
|
-
* batch straight through, so the erasure is type-only.
|
|
244
|
-
*/
|
|
245
|
-
definition: QueueDefinition<any>;
|
|
246
|
-
/** The `lunora/queues.ts` export name, for log correlation. */
|
|
247
|
-
exportName: string;
|
|
248
|
-
}
|
|
249
|
-
/** Map of stable wrangler queue name → registry entry, built by codegen. */
|
|
250
|
-
type QueueRegistry = Record<string, QueueRegistryEntry>;
|
|
251
|
-
interface DispatchOptions {
|
|
252
|
-
/** Worker `env`, forwarded to the queue run context. */
|
|
253
|
-
env: Record<string, unknown>;
|
|
254
|
-
/** Injectable fetch for the `ctx.run` dispatcher (tests). */
|
|
255
|
-
fetchImpl?: typeof fetch;
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* Look up the handler for `batch.queue` and invoke it with a fresh
|
|
259
|
-
* `QueueRunContext`. Throws a directed error when no push handler is registered
|
|
260
|
-
* for the delivered queue (a misconfiguration — the consumer was declared
|
|
261
|
-
* `pull`, or the queue name drifted from the `defineQueue` export).
|
|
262
|
-
*/
|
|
263
|
-
declare const dispatchQueueBatch: (batch: MessageBatchLike, registry: QueueRegistry, options: DispatchOptions) => Promise<void>;
|
|
264
331
|
interface RunContextOptions {
|
|
265
332
|
env: Record<string, unknown>;
|
|
266
333
|
exportName: string;
|
|
@@ -268,4 +335,4 @@ interface RunContextOptions {
|
|
|
268
335
|
}
|
|
269
336
|
/** Assemble the {@link QueueRunContext} passed to a `defineQueue` handler. */
|
|
270
337
|
declare const createQueueRunContext: (options: RunContextOptions) => QueueRunContext;
|
|
271
|
-
export { type ArgsOf, type FunctionReference, type LunoraQueuesOptions, type MessageBatchLike, type MessageLike, type MessageSendRequestLike, type QueueBindingLike, type QueueBindingSpec, type QueueConfig, type QueueConsumerMode, type QueueConsumerTuning, type QueueContentType, type QueueDefinition, 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, createQueueContext, createQueueRunContext, createQueues, defineQueue, dispatchQueueBatch, isQueueDefinition, queueBindingName, queueDefaultName };
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
export { createQueueCaptureSink, shouldCaptureQueue } from './packem_shared/createQueueCaptureSink-CRacRMqV.mjs';
|
|
1
2
|
export { createQueueContext } from './packem_shared/createQueueContext-D0XCdCsd.mjs';
|
|
2
3
|
export { default as createQueues } from './packem_shared/createQueues-14-vSICK.mjs';
|
|
3
4
|
export { defineQueue, isQueueDefinition, queueBindingName, queueDefaultName } from './packem_shared/defineQueue-D40gREfg.mjs';
|
|
4
|
-
export { dispatchQueueBatch } from './packem_shared/dispatchQueueBatch-
|
|
5
|
+
export { dispatchQueueBatch } from './packem_shared/dispatchQueueBatch-BjWnrUuU.mjs';
|
|
5
6
|
export { createQueueRunContext } from './packem_shared/createQueueRunContext-_2hD-TK7.mjs';
|
|
@@ -0,0 +1,54 @@
|
|
|
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 };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
2
|
+
import { createQueueRunContext } from './createQueueRunContext-_2hD-TK7.mjs';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
5
|
+
const timestampToMs = (value) => {
|
|
6
|
+
if (value instanceof Date) {
|
|
7
|
+
return value.getTime();
|
|
8
|
+
}
|
|
9
|
+
const asNumber = typeof value === "number" ? value : Number(value);
|
|
10
|
+
return Number.isFinite(asNumber) ? asNumber : 0;
|
|
11
|
+
};
|
|
12
|
+
const instrumentBatch = (batch) => {
|
|
13
|
+
const dispositions = /* @__PURE__ */ new Map();
|
|
14
|
+
const originals = batch.messages;
|
|
15
|
+
const wrappedMessages = originals.map((message) => {
|
|
16
|
+
return {
|
|
17
|
+
ack: () => {
|
|
18
|
+
dispositions.set(message, "ack");
|
|
19
|
+
message.ack();
|
|
20
|
+
},
|
|
21
|
+
get attempts() {
|
|
22
|
+
return message.attempts;
|
|
23
|
+
},
|
|
24
|
+
get body() {
|
|
25
|
+
return message.body;
|
|
26
|
+
},
|
|
27
|
+
get id() {
|
|
28
|
+
return message.id;
|
|
29
|
+
},
|
|
30
|
+
retry: (options) => {
|
|
31
|
+
dispositions.set(message, "retry");
|
|
32
|
+
message.retry(options);
|
|
33
|
+
},
|
|
34
|
+
get timestamp() {
|
|
35
|
+
return message.timestamp;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
const fillUndecided = (outcome) => {
|
|
40
|
+
for (const message of originals) {
|
|
41
|
+
if (!dispositions.has(message)) {
|
|
42
|
+
dispositions.set(message, outcome);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const wrappedBatch = {
|
|
47
|
+
ackAll: () => {
|
|
48
|
+
fillUndecided("ack");
|
|
49
|
+
batch.ackAll();
|
|
50
|
+
},
|
|
51
|
+
messages: wrappedMessages,
|
|
52
|
+
queue: batch.queue,
|
|
53
|
+
retryAll: (options) => {
|
|
54
|
+
fillUndecided("retry");
|
|
55
|
+
batch.retryAll(options);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return { dispositions, originals, wrappedBatch };
|
|
59
|
+
};
|
|
60
|
+
const describeThrownError = (handlerError) => {
|
|
61
|
+
if (handlerError instanceof Error) {
|
|
62
|
+
return handlerError.message;
|
|
63
|
+
}
|
|
64
|
+
if (typeof handlerError === "string") {
|
|
65
|
+
return handlerError;
|
|
66
|
+
}
|
|
67
|
+
if (handlerError !== null && typeof handlerError === "object") {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.stringify(handlerError);
|
|
70
|
+
} catch {
|
|
71
|
+
return "[unserializable thrown value]";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return String(handlerError);
|
|
75
|
+
};
|
|
76
|
+
const buildCaptureRecords = (harness, entry, queue, threw, handlerError) => {
|
|
77
|
+
const errorMessage = threw ? describeThrownError(handlerError) : void 0;
|
|
78
|
+
const maxRetries = typeof entry.definition.maxRetries === "number" ? entry.definition.maxRetries : DEFAULT_MAX_RETRIES;
|
|
79
|
+
return harness.originals.map((message) => {
|
|
80
|
+
const decided = harness.dispositions.get(message);
|
|
81
|
+
const outcome = decided ?? (threw ? "error" : "ack");
|
|
82
|
+
const attempts = typeof message.attempts === "number" ? message.attempts : 1;
|
|
83
|
+
return {
|
|
84
|
+
attempts,
|
|
85
|
+
body: message.body,
|
|
86
|
+
deadLettered: outcome !== "ack" && attempts > maxRetries,
|
|
87
|
+
error: outcome === "error" ? errorMessage : void 0,
|
|
88
|
+
exportName: entry.exportName,
|
|
89
|
+
messageId: message.id,
|
|
90
|
+
outcome,
|
|
91
|
+
queue,
|
|
92
|
+
timestamp: timestampToMs(message.timestamp)
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
const dispatchQueueBatch = async (batch, registry, options) => {
|
|
97
|
+
const entry = registry[batch.queue];
|
|
98
|
+
if (entry === void 0) {
|
|
99
|
+
const known = Object.keys(registry);
|
|
100
|
+
const suffix = known.length === 0 ? "no push queues are declared" : `known push queues: ${known.join(", ")}`;
|
|
101
|
+
throw new LunoraError("INTERNAL", `@lunora/queue: received a batch for queue "${batch.queue}" but no push handler is registered (${suffix})`);
|
|
102
|
+
}
|
|
103
|
+
const { handler } = entry.definition;
|
|
104
|
+
if (typeof handler !== "function") {
|
|
105
|
+
throw new TypeError(`@lunora/queue: queue "${batch.queue}" (${entry.exportName}) has no push handler — it is declared as a pull consumer`);
|
|
106
|
+
}
|
|
107
|
+
const context = createQueueRunContext({ env: options.env, exportName: entry.exportName, fetchImpl: options.fetchImpl });
|
|
108
|
+
if (options.capture === void 0) {
|
|
109
|
+
await handler(context, batch);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const harness = instrumentBatch(batch);
|
|
113
|
+
let threw = false;
|
|
114
|
+
let handlerError;
|
|
115
|
+
try {
|
|
116
|
+
await handler(context, harness.wrappedBatch);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
threw = true;
|
|
119
|
+
handlerError = error;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const records = buildCaptureRecords(harness, entry, batch.queue, threw, handlerError);
|
|
123
|
+
await options.capture(records);
|
|
124
|
+
} catch {
|
|
125
|
+
}
|
|
126
|
+
if (threw) {
|
|
127
|
+
throw handlerError;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export { dispatchQueueBatch };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/queue",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.5",
|
|
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,7 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
47
|
+
"@lunora/errors": "1.0.0-alpha.2"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": "^22.15.0 || >=24.11.0"
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { createQueueRunContext } from './createQueueRunContext-_2hD-TK7.mjs';
|
|
3
|
-
|
|
4
|
-
const dispatchQueueBatch = async (batch, registry, options) => {
|
|
5
|
-
const entry = registry[batch.queue];
|
|
6
|
-
if (entry === void 0) {
|
|
7
|
-
const known = Object.keys(registry);
|
|
8
|
-
const suffix = known.length === 0 ? "no push queues are declared" : `known push queues: ${known.join(", ")}`;
|
|
9
|
-
throw new LunoraError("INTERNAL", `@lunora/queue: received a batch for queue "${batch.queue}" but no push handler is registered (${suffix})`);
|
|
10
|
-
}
|
|
11
|
-
const { handler } = entry.definition;
|
|
12
|
-
if (typeof handler !== "function") {
|
|
13
|
-
throw new TypeError(`@lunora/queue: queue "${batch.queue}" (${entry.exportName}) has no push handler — it is declared as a pull consumer`);
|
|
14
|
-
}
|
|
15
|
-
const context = createQueueRunContext({ env: options.env, exportName: entry.exportName, fetchImpl: options.fetchImpl });
|
|
16
|
-
await handler(context, batch);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export { dispatchQueueBatch };
|