@vercel/queue 0.0.1 → 0.1.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.mjs CHANGED
@@ -964,7 +964,7 @@ function parseQueueHeaders(headers) {
964
964
  receiptHandle
965
965
  };
966
966
  }
967
- var DEFAULT_BASE_URL_RESOLVER = (region) => `https://${region}.vercel-queue.com`;
967
+ var DEFAULT_BASE_URL_RESOLVER = (region) => new URL(`https://${region}.vercel-queue.com`);
968
968
  function resolveBaseUrl(region, resolver) {
969
969
  return (resolver ?? DEFAULT_BASE_URL_RESOLVER)(region);
970
970
  }
@@ -1022,7 +1022,7 @@ var ApiClient = class _ApiClient {
1022
1022
  return;
1023
1023
  }
1024
1024
  throw new Error(
1025
- 'No deployment ID available. VERCEL_DEPLOYMENT_ID is not set.\n\nThis usually means the code is running outside a Vercel deployment (e.g. during build or in a non-Vercel environment).\n\nTo fix this, create a new QueueClient with an explicit deploymentId:\n new QueueClient({ region: "iad1", deploymentId: "dpl_xxx" })\nOr explicitly opt out of deployment pinning:\n new QueueClient({ region: "iad1", deploymentId: null })'
1025
+ 'No deployment ID available. VERCEL_DEPLOYMENT_ID is not set.\n\nThis usually means the code is running outside a Vercel deployment (e.g. during build or in a non-Vercel environment).\n\nTo fix this, create a client with an explicit deploymentId:\n new QueueClient({ deploymentId: "dpl_xxx" })\nOr explicitly opt out of deployment pinning:\n new QueueClient({ deploymentId: null })'
1026
1026
  );
1027
1027
  }
1028
1028
  getSendDeploymentId() {
@@ -1055,7 +1055,8 @@ var ApiClient = class _ApiClient {
1055
1055
  const encodedQueue = encodeURIComponent(queueName);
1056
1056
  const segments = pathSegments.map((s) => encodeURIComponent(s));
1057
1057
  const path2 = segments.length > 0 ? "/" + segments.join("/") : "";
1058
- return `${this.baseUrl}${BASE_PATH}/${encodedQueue}${path2}`;
1058
+ const basePath = this.baseUrl.pathname.replace(/\/+$/, "");
1059
+ return `${this.baseUrl.origin}${basePath}${BASE_PATH}/${encodedQueue}${path2}`;
1059
1060
  }
1060
1061
  async fetch(url, init) {
1061
1062
  const method = init.method || "GET";
@@ -1079,7 +1080,7 @@ var ApiClient = class _ApiClient {
1079
1080
  }
1080
1081
  console.debug("[VQS Debug] Request:", JSON.stringify(logData, null, 2));
1081
1082
  }
1082
- init.headers.set("User-Agent", `@vercel/queue/${"0.0.1"}`);
1083
+ init.headers.set("User-Agent", `@vercel/queue/${"0.1.0"}`);
1083
1084
  init.headers.set("Vqs-Client-Ts", (/* @__PURE__ */ new Date()).toISOString());
1084
1085
  const response = await fetch(url, init);
1085
1086
  if (isDebugEnabled()) {
@@ -1415,23 +1416,37 @@ var ApiClient = class _ApiClient {
1415
1416
 
1416
1417
  // src/client.ts
1417
1418
  var apiClients = /* @__PURE__ */ new WeakMap();
1418
- function getApiClient(client) {
1419
+ function getApi(client) {
1419
1420
  const api = apiClients.get(client);
1420
1421
  if (!api) {
1421
- throw new Error("QueueClient not initialized");
1422
+ throw new Error("Client not initialized");
1422
1423
  }
1423
1424
  return api;
1424
1425
  }
1426
+ function getApiClient(client) {
1427
+ return getApi(client);
1428
+ }
1429
+ var DEFAULT_REGION = "iad1";
1430
+ function resolveRegion(region) {
1431
+ if (region) return region;
1432
+ const fromEnv = process.env.VERCEL_REGION;
1433
+ if (fromEnv) return fromEnv;
1434
+ console.warn(
1435
+ `[QueueClient] Region not detected \u2014 defaulting to "${DEFAULT_REGION}". On Vercel this is set automatically via VERCEL_REGION. To silence this warning, pass region explicitly: new QueueClient({ region: "iad1" })`
1436
+ );
1437
+ return DEFAULT_REGION;
1438
+ }
1425
1439
  var QueueClient = class {
1426
- constructor(options) {
1427
- apiClients.set(this, new ApiClient(options));
1440
+ constructor(options = {}) {
1441
+ const region = resolveRegion(options.region);
1442
+ apiClients.set(this, new ApiClient({ ...options, region }));
1428
1443
  }
1429
1444
  /**
1430
1445
  * Send a message to a topic.
1431
1446
  *
1432
1447
  * This is an arrow function property so it can be destructured:
1433
1448
  * ```typescript
1434
- * const { send } = new QueueClient({ region: process.env.QUEUE_REGION! });
1449
+ * const { send } = new QueueClient();
1435
1450
  * await send("my-topic", payload);
1436
1451
  * ```
1437
1452
  *
@@ -1442,7 +1457,7 @@ var QueueClient = class {
1442
1457
  * the message for deferred processing (no ID available yet)
1443
1458
  */
1444
1459
  send = async (topicName, payload, options) => {
1445
- const api = getApiClient(this);
1460
+ const api = getApi(this);
1446
1461
  const result = await api.sendMessage({
1447
1462
  queueName: topicName,
1448
1463
  payload,
@@ -1461,75 +1476,6 @@ var QueueClient = class {
1461
1476
  }
1462
1477
  return { messageId: result.messageId };
1463
1478
  };
1464
- /**
1465
- * Receive and process messages from a topic.
1466
- *
1467
- * Each message is automatically locked, kept alive via periodic visibility
1468
- * extensions during processing, and acknowledged upon successful handler completion.
1469
- * The handler is not called when the queue is empty — check `result.ok` instead.
1470
- *
1471
- * This is an arrow function property so it can be destructured:
1472
- * ```typescript
1473
- * const { receive } = new QueueClient({ region: process.env.QUEUE_REGION! });
1474
- * const result = await receive("my-topic", "my-group", handler);
1475
- * if (!result.ok) console.log(result.reason);
1476
- * ```
1477
- *
1478
- * @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
1479
- * @param consumerGroup - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
1480
- * @param handler - Function to process each message payload and metadata.
1481
- * Not called when the queue is empty.
1482
- * @param options - Optional receive options (visibilityTimeoutSeconds, limit, or messageId)
1483
- * @returns Discriminated result: `{ ok: true }` on success, `{ ok: false, reason }` otherwise
1484
- */
1485
- receive = async (topicName, consumerGroup, handler, options) => {
1486
- const api = getApiClient(this);
1487
- const topic = new Topic(api, topicName);
1488
- const visibilityTimeoutSeconds = options && "visibilityTimeoutSeconds" in options ? options.visibilityTimeoutSeconds : void 0;
1489
- const consumer = topic.consumerGroup(
1490
- consumerGroup,
1491
- visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : {}
1492
- );
1493
- try {
1494
- let count;
1495
- const retry = options?.retry;
1496
- if (options && "messageId" in options) {
1497
- count = await consumer.consume(handler, {
1498
- messageId: options.messageId,
1499
- retry
1500
- });
1501
- } else {
1502
- const limit = options && "limit" in options ? options.limit : void 0;
1503
- count = await consumer.consume(handler, {
1504
- ...limit !== void 0 ? { limit } : {},
1505
- retry
1506
- });
1507
- }
1508
- if (count === 0) {
1509
- return { ok: false, reason: "empty" };
1510
- }
1511
- return { ok: true };
1512
- } catch (error) {
1513
- if (options && "messageId" in options && error instanceof MessageNotFoundError) {
1514
- return { ok: false, reason: "not_found", messageId: options.messageId };
1515
- }
1516
- if (options && "messageId" in options && error instanceof MessageNotAvailableError) {
1517
- return {
1518
- ok: false,
1519
- reason: "not_available",
1520
- messageId: options.messageId
1521
- };
1522
- }
1523
- if (options && "messageId" in options && error instanceof MessageAlreadyProcessedError) {
1524
- return {
1525
- ok: false,
1526
- reason: "already_processed",
1527
- messageId: options.messageId
1528
- };
1529
- }
1530
- throw error;
1531
- }
1532
- };
1533
1479
  /**
1534
1480
  * Create a Web API route handler for processing queue callback messages.
1535
1481
  *
@@ -1538,7 +1484,7 @@ var QueueClient = class {
1538
1484
  *
1539
1485
  * This is an arrow function property so it can be destructured:
1540
1486
  * ```typescript
1541
- * const { handleCallback } = new QueueClient({ region: process.env.QUEUE_REGION! });
1487
+ * const { handleCallback } = new QueueClient();
1542
1488
  * export const POST = handleCallback(handler);
1543
1489
  * ```
1544
1490
  *
@@ -1580,7 +1526,7 @@ var QueueClient = class {
1580
1526
  *
1581
1527
  * This is an arrow function property so it can be destructured:
1582
1528
  * ```typescript
1583
- * const { handleNodeCallback } = new QueueClient({ region: process.env.QUEUE_REGION! });
1529
+ * const { handleNodeCallback } = new QueueClient();
1584
1530
  * app.post("/api/queue", handleNodeCallback(handler));
1585
1531
  * ```
1586
1532
  *
@@ -1616,6 +1562,107 @@ var QueueClient = class {
1616
1562
  };
1617
1563
  };
1618
1564
  };
1565
+ var PollingQueueClient = class {
1566
+ constructor(options) {
1567
+ apiClients.set(this, new ApiClient(options));
1568
+ }
1569
+ /**
1570
+ * Send a message to a topic.
1571
+ *
1572
+ * This is an arrow function property so it can be destructured:
1573
+ * ```typescript
1574
+ * const { send } = new PollingQueueClient({ region: "iad1" });
1575
+ * await send("my-topic", payload);
1576
+ * ```
1577
+ *
1578
+ * @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
1579
+ * @param payload - The data to send (serialized via the configured transport)
1580
+ * @param options - Optional send options (idempotencyKey, retentionSeconds, delaySeconds, headers)
1581
+ * @returns `{ messageId }` — `messageId` is `null` when the server accepted
1582
+ * the message for deferred processing (no ID available yet)
1583
+ */
1584
+ send = async (topicName, payload, options) => {
1585
+ const api = getApi(this);
1586
+ const result = await api.sendMessage({
1587
+ queueName: topicName,
1588
+ payload,
1589
+ idempotencyKey: options?.idempotencyKey,
1590
+ retentionSeconds: options?.retentionSeconds,
1591
+ delaySeconds: options?.delaySeconds,
1592
+ headers: options?.headers
1593
+ });
1594
+ return { messageId: result.messageId };
1595
+ };
1596
+ /**
1597
+ * Receive and process messages from a topic.
1598
+ *
1599
+ * Each message is automatically locked, kept alive via periodic visibility
1600
+ * extensions during processing, and acknowledged upon successful handler completion.
1601
+ * The handler is not called when the queue is empty — check `result.ok` instead.
1602
+ *
1603
+ * This is an arrow function property so it can be destructured:
1604
+ * ```typescript
1605
+ * const { receive } = new PollingQueueClient({ region: "iad1" });
1606
+ * const result = await receive("my-topic", "my-group", handler);
1607
+ * if (!result.ok) console.log(result.reason);
1608
+ * ```
1609
+ *
1610
+ * @param topicName - Name of the topic (pattern: `[A-Za-z0-9_-]+`)
1611
+ * @param consumerGroup - Name of the consumer group (pattern: `[A-Za-z0-9_-]+`)
1612
+ * @param handler - Function to process each message payload and metadata.
1613
+ * Not called when the queue is empty.
1614
+ * @param options - Optional receive options (visibilityTimeoutSeconds, limit, or messageId)
1615
+ * @returns Discriminated result: `{ ok: true }` on success, `{ ok: false, reason }` otherwise
1616
+ */
1617
+ receive = async (topicName, consumerGroup, handler, options) => {
1618
+ const api = getApi(this);
1619
+ const topic = new Topic(api, topicName);
1620
+ const visibilityTimeoutSeconds = options && "visibilityTimeoutSeconds" in options ? options.visibilityTimeoutSeconds : void 0;
1621
+ const consumer = topic.consumerGroup(
1622
+ consumerGroup,
1623
+ visibilityTimeoutSeconds !== void 0 ? { visibilityTimeoutSeconds } : {}
1624
+ );
1625
+ try {
1626
+ let count;
1627
+ const retry = options?.retry;
1628
+ if (options && "messageId" in options) {
1629
+ count = await consumer.consume(handler, {
1630
+ messageId: options.messageId,
1631
+ retry
1632
+ });
1633
+ } else {
1634
+ const limit = options && "limit" in options ? options.limit : void 0;
1635
+ count = await consumer.consume(handler, {
1636
+ ...limit !== void 0 ? { limit } : {},
1637
+ retry
1638
+ });
1639
+ }
1640
+ if (count === 0) {
1641
+ return { ok: false, reason: "empty" };
1642
+ }
1643
+ return { ok: true };
1644
+ } catch (error) {
1645
+ if (options && "messageId" in options && error instanceof MessageNotFoundError) {
1646
+ return { ok: false, reason: "not_found", messageId: options.messageId };
1647
+ }
1648
+ if (options && "messageId" in options && error instanceof MessageNotAvailableError) {
1649
+ return {
1650
+ ok: false,
1651
+ reason: "not_available",
1652
+ messageId: options.messageId
1653
+ };
1654
+ }
1655
+ if (options && "messageId" in options && error instanceof MessageAlreadyProcessedError) {
1656
+ return {
1657
+ ok: false,
1658
+ reason: "already_processed",
1659
+ messageId: options.messageId
1660
+ };
1661
+ }
1662
+ throw error;
1663
+ }
1664
+ };
1665
+ };
1619
1666
  export {
1620
1667
  BadRequestError,
1621
1668
  BufferTransport,
@@ -1633,6 +1680,7 @@ export {
1633
1680
  MessageLockedError,
1634
1681
  MessageNotAvailableError,
1635
1682
  MessageNotFoundError,
1683
+ PollingQueueClient,
1636
1684
  QueueClient,
1637
1685
  QueueEmptyError,
1638
1686
  StreamTransport,