better-cf 0.2.2 → 2.0.0

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.js CHANGED
@@ -62,7 +62,7 @@ async function consumeQueueDefinition(definition, batch, env, executionCtx) {
62
62
  await consumeMulti(definition.jobs, definition.shared, batch, env, executionCtx);
63
63
  }
64
64
  async function consumeSingle(config, batch, env, executionCtx) {
65
- if ("processBatch" in config && config.processBatch) {
65
+ if ("batchHandler" in config && config.batchHandler) {
66
66
  let ackOrRetryHandled = false;
67
67
  const batchCtx = {
68
68
  env,
@@ -81,14 +81,20 @@ async function consumeSingle(config, batch, env, executionCtx) {
81
81
  }
82
82
  }
83
83
  };
84
- const typedMessages = batch.messages.map((msg) => ({
85
- data: msg.body,
86
- id: msg.id,
87
- timestamp: msg.timestamp,
88
- attempts: msg.attempts
89
- }));
90
84
  try {
91
- await config.processBatch(batchCtx, typedMessages);
85
+ const typedMessages = batch.messages.map((msg) => {
86
+ const parsed = config.args.safeParse(msg.body);
87
+ if (!parsed.success) {
88
+ throw new Error(`Queue batch validation failed for ${msg.id}: ${parsed.error.message}`);
89
+ }
90
+ return {
91
+ data: parsed.data,
92
+ id: msg.id,
93
+ timestamp: msg.timestamp,
94
+ attempts: msg.attempts
95
+ };
96
+ });
97
+ await config.batchHandler(batchCtx, typedMessages);
92
98
  if (!ackOrRetryHandled) {
93
99
  batch.ackAll();
94
100
  }
@@ -113,11 +119,11 @@ async function consumeSingle(config, batch, env, executionCtx) {
113
119
  }
114
120
  };
115
121
  try {
116
- const parsed = config.message.safeParse(message.body);
122
+ const parsed = config.args.safeParse(message.body);
117
123
  if (!parsed.success) {
118
- throw new Error(`Queue message validation failed: ${parsed.error.message}`);
124
+ throw new Error(`Queue args validation failed: ${parsed.error.message}`);
119
125
  }
120
- await config.process(queueCtx, parsed.data);
126
+ await config.handler(queueCtx, parsed.data);
121
127
  message.ack();
122
128
  } catch (error) {
123
129
  await callFailureHandler(
@@ -152,11 +158,11 @@ async function consumeMulti(jobs, shared, batch, env, executionCtx) {
152
158
  }
153
159
  };
154
160
  try {
155
- const parsed = job.message.safeParse(envelope.data);
161
+ const parsed = job.args.safeParse(envelope.data);
156
162
  if (!parsed.success) {
157
163
  throw new Error(`Queue job validation failed for ${jobName}: ${parsed.error.message}`);
158
164
  }
159
- await job.process(queueCtx, parsed.data);
165
+ await job.handler(queueCtx, parsed.data);
160
166
  message.ack();
161
167
  } catch (error) {
162
168
  await callFailureHandler(
@@ -170,13 +176,13 @@ async function consumeMulti(jobs, shared, batch, env, executionCtx) {
170
176
  }
171
177
  }
172
178
  }
173
- async function callFailureHandler(handler, context, message, rawError) {
179
+ async function callFailureHandler(handler, context, args, rawError) {
174
180
  if (!handler) {
175
181
  return;
176
182
  }
177
183
  const error = rawError instanceof Error ? rawError : new Error(String(rawError));
178
184
  try {
179
- await handler(context, message, error);
185
+ await handler(context, args, error);
180
186
  } catch {
181
187
  }
182
188
  }
@@ -192,135 +198,150 @@ var RESERVED_MULTI_JOB_KEYS = /* @__PURE__ */ new Set([
192
198
  "deliveryDelay",
193
199
  "visibilityTimeout",
194
200
  "batch",
195
- "consumer"
201
+ "consumer",
202
+ "args",
203
+ "handler",
204
+ "batchHandler",
205
+ "onFailure",
206
+ "message",
207
+ "process",
208
+ "processBatch"
196
209
  ]);
197
210
 
198
211
  // src/queue/define-queue.ts
212
+ var LEGACY_SCHEMA_KEY = "message";
213
+ var LEGACY_SINGLE_HANDLER_KEYS = ["process", "processBatch"];
199
214
  function defineQueueFactory() {
200
- function defineQueue(config) {
201
- const definition = toQueueDefinition(config);
202
- let bindingName = null;
203
- const sendBase = async (ctx, body, options) => {
204
- if (!bindingName) {
205
- throw new Error(
206
- "Queue binding not initialized. Run through better-cf dev/generate/deploy generated entry."
207
- );
208
- }
209
- const binding = ctx.env[bindingName];
210
- if (!binding || typeof binding.send !== "function") {
211
- throw new Error(`Queue binding ${bindingName} not found in env.`);
212
- }
213
- await binding.send(body, toCloudflareSendOptions(options));
214
- };
215
- const sendBatchBase = async (ctx, messages) => {
216
- if (!bindingName) {
217
- throw new Error(
218
- "Queue binding not initialized. Run through better-cf dev/generate/deploy generated entry."
219
- );
220
- }
221
- const binding = ctx.env[bindingName];
222
- if (!binding || typeof binding.sendBatch !== "function") {
223
- throw new Error(`Queue binding ${bindingName} not found in env.`);
215
+ return function defineQueue(config) {
216
+ assertNoLegacySingleQueueKeys(config);
217
+ const definition = toSingleQueueDefinition(config);
218
+ return createQueueHandle(definition);
219
+ };
220
+ }
221
+ function defineQueuesFactory() {
222
+ return function defineQueues(config) {
223
+ assertNoLegacyMultiQueueKeys(config);
224
+ const definition = toMultiQueueDefinition(config);
225
+ return createQueueHandle(definition);
226
+ };
227
+ }
228
+ function createQueueHandle(definition) {
229
+ let bindingName = null;
230
+ const sendBase = async (ctx, body, options) => {
231
+ if (!bindingName) {
232
+ throw new Error("Queue binding not initialized. Run through better-cf dev/generate/deploy generated entry.");
233
+ }
234
+ const binding = ctx.env[bindingName];
235
+ if (!binding || typeof binding.send !== "function") {
236
+ throw new Error(`Queue binding ${bindingName} not found in env.`);
237
+ }
238
+ await binding.send(body, toCloudflareSendOptions(options));
239
+ };
240
+ const sendBatchBase = async (ctx, messages) => {
241
+ if (!bindingName) {
242
+ throw new Error("Queue binding not initialized. Run through better-cf dev/generate/deploy generated entry.");
243
+ }
244
+ const binding = ctx.env[bindingName];
245
+ if (!binding || typeof binding.sendBatch !== "function") {
246
+ throw new Error(`Queue binding ${bindingName} not found in env.`);
247
+ }
248
+ const payload = messages.map((item) => ({
249
+ body: item.body,
250
+ ...toCloudflareSendOptions(item.options)
251
+ }));
252
+ await binding.sendBatch(payload);
253
+ };
254
+ const baseHandle = {
255
+ async send(ctx, data, options) {
256
+ if (definition.kind === "single") {
257
+ await sendBase(ctx, data, options);
258
+ return;
224
259
  }
225
- const payload = messages.map((item) => ({
226
- body: item.body,
227
- ...toCloudflareSendOptions(item.options)
228
- }));
229
- await binding.sendBatch(payload);
230
- };
231
- const baseHandle = {
232
- async send(ctx, data, options) {
233
- if (definition.kind === "single") {
234
- await sendBase(ctx, data, options);
235
- return;
236
- }
237
- await sendBase(ctx, { _job: "__default", data }, options);
238
- },
239
- async sendBatch(ctx, messages, options) {
240
- if (definition.kind === "single") {
241
- await sendBatchBase(
242
- ctx,
243
- messages.map((message) => ({
244
- body: message.data,
245
- options: mergeSendOptions(message, options)
246
- }))
247
- );
248
- return;
249
- }
260
+ await sendBase(ctx, { _job: "__default", data }, options);
261
+ },
262
+ async sendBatch(ctx, messages, options) {
263
+ if (definition.kind === "single") {
250
264
  await sendBatchBase(
251
265
  ctx,
252
266
  messages.map((message) => ({
253
- body: { _job: "__default", data: message.data },
267
+ body: message.data,
254
268
  options: mergeSendOptions(message, options)
255
269
  }))
256
270
  );
271
+ return;
257
272
  }
258
- };
259
- if (definition.kind === "multi") {
260
- for (const jobName of Object.keys(definition.jobs)) {
261
- baseHandle[jobName] = {
262
- async send(ctx, data, options) {
263
- await sendBase(ctx, { _job: jobName, data }, options);
264
- },
265
- async sendBatch(ctx, messages, options) {
266
- await sendBatchBase(
267
- ctx,
268
- messages.map((message) => ({
269
- body: { _job: jobName, data: message.data },
270
- options: mergeSendOptions(message, options)
271
- }))
272
- );
273
- }
274
- };
275
- }
273
+ await sendBatchBase(
274
+ ctx,
275
+ messages.map((message) => ({
276
+ body: { _job: "__default", data: message.data },
277
+ options: mergeSendOptions(message, options)
278
+ }))
279
+ );
276
280
  }
277
- Object.defineProperty(baseHandle, kQueueInternals, {
278
- enumerable: false,
279
- configurable: false,
280
- writable: false,
281
- value: {
282
- setBinding(name) {
283
- bindingName = name;
284
- },
285
- getBinding() {
286
- return bindingName;
287
- },
288
- getDefinition() {
289
- return definition;
281
+ };
282
+ if (definition.kind === "multi") {
283
+ for (const jobName of Object.keys(definition.jobs)) {
284
+ baseHandle[jobName] = {
285
+ async send(ctx, data, options) {
286
+ await sendBase(ctx, { _job: jobName, data }, options);
290
287
  },
291
- consume(batch, env, executionCtx) {
292
- return consumeQueueDefinition(definition, batch, env, executionCtx);
288
+ async sendBatch(ctx, messages, options) {
289
+ await sendBatchBase(
290
+ ctx,
291
+ messages.map((message) => ({
292
+ body: { _job: jobName, data: message.data },
293
+ options: mergeSendOptions(message, options)
294
+ }))
295
+ );
293
296
  }
294
- }
295
- });
296
- return baseHandle;
297
- }
298
- return defineQueue;
299
- }
300
- function toQueueDefinition(config) {
301
- if (isSingleQueue(config)) {
302
- if (isPullQueue(config)) {
303
- if ("process" in config || "processBatch" in config) {
304
- throw new Error('Queue config with consumer.type="http_pull" cannot define process/processBatch.');
305
- }
306
- return {
307
- kind: "single",
308
- mode: "pull",
309
- config
310
297
  };
311
298
  }
312
- if (hasProcess(config) && hasProcessBatch(config)) {
313
- throw new Error("Queue config cannot define both process and processBatch.");
299
+ }
300
+ Object.defineProperty(baseHandle, kQueueInternals, {
301
+ enumerable: false,
302
+ configurable: false,
303
+ writable: false,
304
+ value: {
305
+ setBinding(name) {
306
+ bindingName = name;
307
+ },
308
+ getBinding() {
309
+ return bindingName;
310
+ },
311
+ getDefinition() {
312
+ return definition;
313
+ },
314
+ consume(batch, env, executionCtx) {
315
+ return consumeQueueDefinition(definition, batch, env, executionCtx);
316
+ }
314
317
  }
315
- if (!hasProcess(config) && !hasProcessBatch(config)) {
316
- throw new Error("Queue config must define one of process or processBatch in worker consumer mode.");
318
+ });
319
+ return baseHandle;
320
+ }
321
+ function toSingleQueueDefinition(config) {
322
+ if (isPullQueue(config)) {
323
+ if ("handler" in config || "batchHandler" in config) {
324
+ throw new Error('Queue config with consumer.type="http_pull" cannot define handler/batchHandler.');
317
325
  }
318
326
  return {
319
327
  kind: "single",
320
- mode: "push",
328
+ mode: "pull",
321
329
  config
322
330
  };
323
331
  }
332
+ if (hasHandler(config) && hasBatchHandler(config)) {
333
+ throw new Error("Queue config cannot define both handler and batchHandler.");
334
+ }
335
+ if (!hasHandler(config) && !hasBatchHandler(config)) {
336
+ throw new Error("Queue config must define one of handler or batchHandler in worker consumer mode.");
337
+ }
338
+ return {
339
+ kind: "single",
340
+ mode: "push",
341
+ config
342
+ };
343
+ }
344
+ function toMultiQueueDefinition(config) {
324
345
  const shared = {
325
346
  retry: config.retry,
326
347
  retryDelay: config.retryDelay,
@@ -334,6 +355,14 @@ function toQueueDefinition(config) {
334
355
  if (RESERVED_MULTI_JOB_KEYS.has(key)) {
335
356
  continue;
336
357
  }
358
+ if (hasLegacyKeys(value)) {
359
+ throw new Error(
360
+ `Multi-job queue "${key}" uses legacy keys. Rename message->args and process/processBatch->handler/batchHandler.`
361
+ );
362
+ }
363
+ if (isPlainObject(value) && "batchHandler" in value) {
364
+ throw new Error(`Multi-job queue "${key}" cannot define batchHandler. Use handler per job.`);
365
+ }
337
366
  if (isJobConfig(value)) {
338
367
  jobs[key] = value;
339
368
  }
@@ -347,23 +376,56 @@ function toQueueDefinition(config) {
347
376
  shared
348
377
  };
349
378
  }
350
- function isSingleQueue(config) {
351
- return "message" in config;
352
- }
353
379
  function isPullQueue(config) {
354
380
  return "consumer" in config && Boolean(config.consumer && config.consumer.type === "http_pull");
355
381
  }
356
- function hasProcess(config) {
357
- return "process" in config && typeof config.process === "function";
382
+ function hasHandler(config) {
383
+ return "handler" in config && typeof config.handler === "function";
358
384
  }
359
- function hasProcessBatch(config) {
360
- return "processBatch" in config && typeof config.processBatch === "function";
385
+ function hasBatchHandler(config) {
386
+ return "batchHandler" in config && typeof config.batchHandler === "function";
361
387
  }
362
388
  function isJobConfig(value) {
363
389
  if (!isPlainObject(value)) {
364
390
  return false;
365
391
  }
366
- return "message" in value && "process" in value;
392
+ return "args" in value && "handler" in value;
393
+ }
394
+ function assertNoLegacySingleQueueKeys(config) {
395
+ if (!isPlainObject(config)) {
396
+ return;
397
+ }
398
+ const legacyKeys = [LEGACY_SCHEMA_KEY, ...LEGACY_SINGLE_HANDLER_KEYS].filter((key) => key in config);
399
+ if (legacyKeys.length === 0) {
400
+ return;
401
+ }
402
+ throw new Error(
403
+ `Queue config uses legacy keys (${legacyKeys.join(", ")}). Rename message->args, process->handler, processBatch->batchHandler.`
404
+ );
405
+ }
406
+ function assertNoLegacyMultiQueueKeys(config) {
407
+ const topLevelLegacyKeys = [LEGACY_SCHEMA_KEY, ...LEGACY_SINGLE_HANDLER_KEYS].filter((key) => key in config);
408
+ if (topLevelLegacyKeys.length > 0) {
409
+ throw new Error(
410
+ `Multi-job queue config uses legacy keys (${topLevelLegacyKeys.join(", ")}). Use defineQueues({ job: { args, handler } }).`
411
+ );
412
+ }
413
+ for (const [jobName, value] of Object.entries(config)) {
414
+ if (RESERVED_MULTI_JOB_KEYS.has(jobName) || !isPlainObject(value)) {
415
+ continue;
416
+ }
417
+ if (hasLegacyKeys(value)) {
418
+ throw new Error(
419
+ `Multi-job queue "${jobName}" uses legacy keys. Rename message->args and process/processBatch->handler/batchHandler.`
420
+ );
421
+ }
422
+ }
423
+ }
424
+ function hasLegacyKeys(value) {
425
+ if (!isPlainObject(value)) {
426
+ return false;
427
+ }
428
+ return LEGACY_SCHEMA_KEY in value || LEGACY_SINGLE_HANDLER_KEYS.some((key) => key in value);
367
429
  }
368
430
 
369
431
  // src/queue/define-worker.ts
@@ -390,8 +452,11 @@ function defineWorkerFactory() {
390
452
 
391
453
  // src/queue/create-sdk.ts
392
454
  function createSDK() {
455
+ const defineQueue = defineQueueFactory();
456
+ const defineQueues = defineQueuesFactory();
393
457
  return {
394
- defineQueue: defineQueueFactory(),
458
+ defineQueue,
459
+ defineQueues,
395
460
  defineWorker: defineWorkerFactory()
396
461
  };
397
462
  }