experimental-a2 0.18.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/{actor-Bjce9_5U.d.ts → actor-C7b-1arn.d.ts} +3 -3
  3. package/dist/{actor-Bjce9_5U.d.ts.map → actor-C7b-1arn.d.ts.map} +1 -1
  4. package/dist/actor-client.d.ts +1 -1
  5. package/dist/actor-react.d.ts +1 -1
  6. package/dist/actor-react.js +1 -1
  7. package/dist/actor.d.ts +1 -1
  8. package/dist/actor.js +2 -2
  9. package/dist/{ai-DrUaMyRt.d.ts → ai-BMLlI2Ff.d.ts} +2 -2
  10. package/dist/{ai-DrUaMyRt.d.ts.map → ai-BMLlI2Ff.d.ts.map} +1 -1
  11. package/dist/{ai-BpOGm-zB.js → ai-D6mGlO8j.js} +2 -2
  12. package/dist/{ai-BpOGm-zB.js.map → ai-D6mGlO8j.js.map} +1 -1
  13. package/dist/ai-client.d.ts +1 -1
  14. package/dist/ai-client.js +2 -2
  15. package/dist/ai-fx.d.ts +1 -1
  16. package/dist/ai-fx.d.ts.map +1 -1
  17. package/dist/ai-fx.js +92 -34
  18. package/dist/ai-fx.js.map +1 -1
  19. package/dist/{ai-harness-DvsPeJeB.d.ts → ai-harness-B2suP2Qh.d.ts} +2 -2
  20. package/dist/{ai-harness-DvsPeJeB.d.ts.map → ai-harness-B2suP2Qh.d.ts.map} +1 -1
  21. package/dist/ai-pi.d.ts +1 -1
  22. package/dist/ai-pi.d.ts.map +1 -1
  23. package/dist/ai-pi.js +59 -13
  24. package/dist/ai-pi.js.map +1 -1
  25. package/dist/ai-react.d.ts +1 -1
  26. package/dist/ai-react.js +2 -2
  27. package/dist/ai-server.d.ts +3 -3
  28. package/dist/ai-server.d.ts.map +1 -1
  29. package/dist/ai-server.js +10 -6
  30. package/dist/ai-server.js.map +1 -1
  31. package/dist/ai-tool-input-DmSqMUQi.js +40 -0
  32. package/dist/ai-tool-input-DmSqMUQi.js.map +1 -0
  33. package/dist/ai.d.ts +1 -1
  34. package/dist/ai.js +1 -1
  35. package/dist/{client-BReKqwZC.js → client-BhHLoNKF.js} +3 -3
  36. package/dist/{client-BReKqwZC.js.map → client-BhHLoNKF.js.map} +1 -1
  37. package/dist/client.js +1 -1
  38. package/dist/index.d.ts +2 -2
  39. package/dist/{internal-B2T3U4Rq.js → internal-CscSY7K_.js} +4 -2
  40. package/dist/{internal-B2T3U4Rq.js.map → internal-CscSY7K_.js.map} +1 -1
  41. package/dist/react.js +1 -1
  42. package/dist/scheduler-qstash.d.ts +2 -2
  43. package/dist/scheduler-qstash.js +3 -3
  44. package/dist/scheduler-qstash.js.map +1 -1
  45. package/dist/scheduler-vercel.d.ts +7 -7
  46. package/dist/scheduler-vercel.d.ts.map +1 -1
  47. package/dist/scheduler-vercel.js +9 -10
  48. package/dist/scheduler-vercel.js.map +1 -1
  49. package/dist/{server-Dw_k_7kO.js → server-D-Y4pqwu.js} +37 -11
  50. package/dist/server-D-Y4pqwu.js.map +1 -0
  51. package/dist/{server-En7cbi3v.d.ts → server-DYAATFuT.d.ts} +11 -5
  52. package/dist/server-DYAATFuT.d.ts.map +1 -0
  53. package/dist/server.d.ts +2 -2
  54. package/dist/server.js +1 -1
  55. package/dist/{session-clear-Bo-XTFEy.d.ts → session-clear-C-QwTXV1.d.ts} +2 -2
  56. package/dist/session-clear-C-QwTXV1.d.ts.map +1 -0
  57. package/dist/{store-Djdv0bnZ.d.ts → store-CyLUy86v.d.ts} +23 -4
  58. package/dist/store-CyLUy86v.d.ts.map +1 -0
  59. package/dist/store-N8PXxDAS.js.map +1 -1
  60. package/dist/store-memory.d.ts +1 -1
  61. package/dist/store-memory.js +25 -6
  62. package/dist/store-memory.js.map +1 -1
  63. package/dist/{store-polling-B5FcwJfo.js → store-polling-7WJV0YWP.js} +2 -2
  64. package/dist/{store-polling-B5FcwJfo.js.map → store-polling-7WJV0YWP.js.map} +1 -1
  65. package/dist/store-postgres.d.ts +1 -1
  66. package/dist/store-postgres.d.ts.map +1 -1
  67. package/dist/store-postgres.js +55 -25
  68. package/dist/store-postgres.js.map +1 -1
  69. package/dist/{store-presence-polling-BAwS8RWn.js → store-presence-polling-B1ixeqIS.js} +3 -3
  70. package/dist/{store-presence-polling-BAwS8RWn.js.map → store-presence-polling-B1ixeqIS.js.map} +1 -1
  71. package/dist/store-redis-http.d.ts +1 -1
  72. package/dist/store-redis-http.js +3 -3
  73. package/dist/{store-redis-notify-BRITti_o.js → store-redis-notify-CM0AfLY0.js} +88 -37
  74. package/dist/store-redis-notify-CM0AfLY0.js.map +1 -0
  75. package/dist/store-redis.d.ts +1 -1
  76. package/dist/store-redis.js +1 -1
  77. package/dist/store-sqlite.d.ts +1 -1
  78. package/dist/store-sqlite.js +35 -20
  79. package/dist/store-sqlite.js.map +1 -1
  80. package/dist/{wire-rSUglYo8.js → wire-BKxH1vbP.js} +2 -2
  81. package/dist/{wire-rSUglYo8.js.map → wire-BKxH1vbP.js.map} +1 -1
  82. package/docs/agents/03-conversations.mdx +2 -2
  83. package/docs/index.mdx +3 -3
  84. package/docs/reference/02-server.mdx +12 -7
  85. package/docs/reference/04-ai.mdx +16 -7
  86. package/docs/reference/05-adapters.mdx +4 -4
  87. package/docs/server/01-handlers.mdx +1 -1
  88. package/docs/server/03-cancellation.mdx +3 -2
  89. package/docs/server/05-production.mdx +2 -1
  90. package/docs/server/06-guarantees.mdx +20 -11
  91. package/examples/playground/DEPLOY.md +2 -4
  92. package/examples/playground/app/a0/[appId]/page.tsx +2 -8
  93. package/examples/playground/app/a0/devtools.tsx +2 -8
  94. package/examples/playground/app/a0/server.ts +8 -19
  95. package/examples/playground/package.json +1 -1
  96. package/package.json +1 -1
  97. package/src/ai-agent.ts +10 -3
  98. package/src/ai-fx.ts +126 -47
  99. package/src/ai-pi.ts +74 -15
  100. package/src/ai-tool-input.ts +45 -0
  101. package/src/internal.ts +6 -0
  102. package/src/scheduler-qstash.ts +5 -5
  103. package/src/scheduler-vercel.ts +14 -15
  104. package/src/server.ts +58 -12
  105. package/src/store-memory.ts +50 -9
  106. package/src/store-postgres.ts +79 -28
  107. package/src/store-redis-core.ts +112 -31
  108. package/src/store-sqlite.ts +61 -28
  109. package/src/store.ts +22 -3
  110. package/dist/server-Dw_k_7kO.js.map +0 -1
  111. package/dist/server-En7cbi3v.d.ts.map +0 -1
  112. package/dist/session-clear-Bo-XTFEy.d.ts.map +0 -1
  113. package/dist/store-Djdv0bnZ.d.ts.map +0 -1
  114. package/dist/store-redis-notify-BRITti_o.js.map +0 -1
package/dist/client.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as A2ClientError, t as createClient } from "./client-BReKqwZC.js";
1
+ import { a as A2ClientError, t as createClient } from "./client-BhHLoNKF.js";
2
2
  export { A2ClientError, createClient };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { _ as contract, a as reducer, c as ContractEvent, d as PresenceDefs, f as PresenceMap, h as ReducerOptions, i as StateSnapshot, l as EventBatchFactory, m as PresenceSnapshot, n as ReducerBuilder, o as AppendInput, p as PresencePatch, s as Contract, t as Reducer, u as EventDefs, v as StandardSchemaV1 } from "./reducer-CqA59Q6U.js";
2
2
  import { a as NonRetriableError, i as A2ServerErrorCode, n as A2Error, r as A2ErrorCode, t as InitialSnapshot } from "./initial-snapshot-Bx-c0pDP.js";
3
- import { i as Event } from "./store-Djdv0bnZ.js";
4
- import { t as SessionCleared } from "./session-clear-Bo-XTFEy.js";
3
+ import { i as Event } from "./store-CyLUy86v.js";
4
+ import { t as SessionCleared } from "./session-clear-C-QwTXV1.js";
5
5
  import { i as A2Telemetry, n as A2SpanHandle, r as A2SpanName, t as A2AttributeValue } from "./telemetry-Cyfqo6Qt.js";
6
6
  //#region src/idempotent-id.d.ts
7
7
  declare function idempotentId(...parts: string[]): Promise<string>;
@@ -130,7 +130,9 @@ const DRAIN_TIMINGS = {
130
130
  claimHeartbeatMs: 2e3,
131
131
  renewRetryMs: 400,
132
132
  watchdogGraceMs: 1e3,
133
- schedulerArmTimeoutMs: 2e3
133
+ schedulerArmTimeoutMs: 2e3,
134
+ failureRetryBaseMs: 1e3,
135
+ failureRetryMaxMs: 3e4
134
136
  };
135
137
  /**
136
138
  * Stream liveness knobs — the server-side SSE heartbeat and the
@@ -212,4 +214,4 @@ const PRESENCE_TIMINGS = { sendIntervalMs: 66 };
212
214
  //#endregion
213
215
  export { nullProtoRecord as _, PRESENCE_TIMINGS as a, STATE_CACHE as c, assertSchedulerTargets as d, classifySchedulerSendFailure as f, markSchedulerSendFailure as g, installAmbientToolScopeStorage as h, POLL_TIMINGS as i, STREAM_TIMINGS as l, failedPreviousAttempt as m, MAX_DATE_MS as n, RESERVED_PARTICIPANT_IDS as o, consumeSchedulerSendFailure as p, NOTIFY_TIMINGS as r, SOCKET_TIMINGS as s, DRAIN_TIMINGS as t, ambientToolScopeStorage as u, serverInternals as v, stateTailReaders as y };
214
216
 
215
- //# sourceMappingURL=internal-B2T3U4Rq.js.map
217
+ //# sourceMappingURL=internal-CscSY7K_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"internal-B2T3U4Rq.js","names":[],"sources":["../src/internal.ts"],"sourcesContent":["/**\n * Private seams shared by a2 entry points and a2's own tests. Not exported\n * from the package. Nothing here is public API.\n */\n\nimport type { ScheduledEvent } from './scheduler-task.ts'\nimport type { DrainableServer } from './server.ts'\nimport type { Event } from './store.ts'\n\nexport type DrainOutcome = 'busy' | 'stalled' | 'settled'\n\n/**\n * A null-prototype record for maps keyed by caller-controlled strings\n * (participant ids, presence field names). On a normal object,\n * assigning to '__proto__' rewrites the prototype instead of creating\n * an own property — the prototype-pollution class. Null-prototype\n * objects still satisfy Record types and JSON.stringify cleanly.\n */\nexport function nullProtoRecord<T extends object>(): T {\n return Object.create(null) as T\n}\n\n/**\n * ECMA-262's maximum Date value (±8.64e15 ms). A presence stamp past\n * it becomes an Invalid Date whose toISOString throws — one hostile\n * stamp would poison every subscriber's snapshot frame — so both\n * seams bound `at` to the representable range.\n */\nexport const MAX_DATE_MS = 8_640_000_000_000_000\n\n/**\n * Participant ids that collide with Object.prototype plumbing.\n * Null-prototype construction already defuses them, but they are\n * rejected at both seams (`parsePushBody`, `setPresence`) so a hostile\n * id never propagates into userland maps either.\n */\nexport const RESERVED_PARTICIPANT_IDS: ReadonlySet<string> = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n])\n\nexport type DrainResult = {\n settled: boolean\n outcome: DrainOutcome\n /** Existing successor arm a scheduler callback may join before acking. */\n schedulerArm?: Promise<void>\n}\n\nexport type ServerInternals = {\n /**\n * Await every in-flight background drain this machine has scheduled,\n * including ones scheduled while waiting. Lets tests flush the\n * fire-and-forget inline path deterministically — no sleeps.\n */\n settle(): Promise<void>\n /** The public `server.drain()` result deliberately remains `{ settled }`. */\n schedulerDrain(\n sessionId: string,\n opts?: { watchdogDueAt?: number },\n ): Promise<DrainResult>\n /** Deliver a scheduler-owned delayed append through the normal root path. */\n schedulerAppend(\n sessionId: string,\n events: readonly ScheduledEvent[],\n originIndex?: number,\n ): Promise<void>\n /** Deliver a scheduler-owned conditional clear, deletion included. */\n schedulerClear(sessionId: string, ifLastIndex: number): Promise<void>\n /** `session.history` with clear markers kept; reads start at the latest one. */\n historyWithMarkers(sessionId: string, gte: number): Promise<Event[]>\n}\n\nexport const serverInternals: WeakMap<object, ServerInternals> = new WeakMap()\n\n/**\n * Handler contexts whose previous attempt ended in a failure the handler\n * threw, as opposed to a claim that lapsed (a crash, a timeout, a frozen\n * process). Only the attempt's own outcome is known, not how far it got.\n */\nexport const failedPreviousAttempt: WeakSet<object> = new WeakSet()\n\nconst objectLike = (value: unknown): value is object =>\n (typeof value === 'object' && value !== null) || typeof value === 'function'\n\n/** The ambient value delivered to `handlerContext` during a tool execution. */\nexport type AmbientToolScope = {\n /** Executing server identity; a test scope from `runWithHandlerContext` has none. */\n contract?: { readonly name: string }\n context: unknown\n}\n\n/**\n * Structural AsyncLocalStorage surface for the ambient tool scope. The\n * instance is created by the server-only AI entry point (the only place\n * `node:async_hooks` may be imported) and shared through a global\n * symbol slot, so `handlerContext` in the isomorphic AI entry point and\n * independently bundled A2 copies all read the same scope.\n */\nexport type AmbientToolScopeStorage = {\n getStore(): AmbientToolScope | undefined\n run<R>(store: AmbientToolScope, callback: () => R): R\n}\n\nconst TOOL_SCOPE_STORAGE = Symbol.for('experimental-a2.ai.tool-scope')\n\nexport function ambientToolScopeStorage(): AmbientToolScopeStorage | undefined {\n const value = Reflect.get(globalThis, TOOL_SCOPE_STORAGE)\n if (\n !objectLike(value) ||\n typeof Reflect.get(value, 'getStore') !== 'function' ||\n typeof Reflect.get(value, 'run') !== 'function'\n ) {\n return undefined\n }\n return value as AmbientToolScopeStorage\n}\n\nexport function installAmbientToolScopeStorage(\n create: () => AmbientToolScopeStorage,\n): AmbientToolScopeStorage {\n const existing = ambientToolScopeStorage()\n if (existing !== undefined) return existing\n const storage = create()\n Reflect.set(globalThis, TOOL_SCOPE_STORAGE, storage)\n return storage\n}\n\nexport type SchedulerSendFailureDisposition = 'retryable' | 'terminal'\n\ntype SchedulerSendFailureMarker = {\n readonly version: 1\n readonly pending: SchedulerSendFailureDisposition[]\n readonly active: SchedulerSendFailureDisposition[]\n}\n\nconst SCHEDULER_SEND_FAILURE = Symbol.for(\n 'experimental-a2.scheduler.send-failure',\n)\n\nconst schedulerSendFailureMarker = (\n value: unknown,\n): SchedulerSendFailureMarker | undefined => {\n if (!objectLike(value)) return undefined\n const marker = Reflect.get(value, SCHEDULER_SEND_FAILURE)\n if (\n !objectLike(marker) ||\n Reflect.get(marker, 'version') !== 1 ||\n !Array.isArray(Reflect.get(marker, 'pending')) ||\n !Array.isArray(Reflect.get(marker, 'active'))\n ) {\n return undefined\n }\n return marker as SchedulerSendFailureMarker\n}\n\nconst schedulerFailureObject = (error: unknown): object => {\n if (\n objectLike(error) &&\n (schedulerSendFailureMarker(error) !== undefined ||\n Object.isExtensible(error))\n ) {\n return error\n }\n return new Error(String(error), { cause: error })\n}\n\nconst schedulerSendFailureState = (\n error: object,\n): SchedulerSendFailureMarker => {\n const existing = schedulerSendFailureMarker(error)\n if (existing !== undefined) return existing\n const marker: SchedulerSendFailureMarker = {\n version: 1,\n pending: [],\n active: [],\n }\n Object.defineProperty(error, SCHEDULER_SEND_FAILURE, {\n value: marker,\n enumerable: false,\n configurable: true,\n writable: false,\n })\n return marker\n}\n\nconst clearSchedulerSendFailureState = (\n error: object,\n marker: SchedulerSendFailureMarker,\n): void => {\n if (marker.pending.length === 0 && marker.active.length === 0) {\n Reflect.deleteProperty(error, SCHEDULER_SEND_FAILURE)\n }\n}\n\n/**\n * Classify an adapter error before throwing it. Unclassified adapter failures\n * are retryable because the caller cannot know whether a remote send landed.\n */\nexport function classifySchedulerSendFailure(\n error: unknown,\n disposition: SchedulerSendFailureDisposition,\n): unknown {\n const classified = schedulerFailureObject(error)\n schedulerSendFailureState(classified).pending.push(disposition)\n return classified\n}\n\n/** Consume a pending adapter classification and count this rejected send. */\nexport function markSchedulerSendFailure(error: unknown): unknown {\n const marked = schedulerFailureObject(error)\n const marker = schedulerSendFailureState(marked)\n marker.active.push(marker.pending.pop() ?? 'retryable')\n return marked\n}\n\nexport function consumeSchedulerSendFailure(\n error: unknown,\n): SchedulerSendFailureDisposition | undefined {\n if (!objectLike(error)) return undefined\n const marker = schedulerSendFailureMarker(error)\n const disposition = marker?.active.pop()\n if (marker !== undefined) clearSchedulerSendFailureState(error, marker)\n return disposition\n}\n\nconst isDrainableServer = (value: unknown): value is DrainableServer => {\n if (value === null || typeof value !== 'object') return false\n const contract = Reflect.get(value, 'contract')\n return (\n contract !== null &&\n typeof contract === 'object' &&\n typeof Reflect.get(contract, 'name') === 'string' &&\n typeof Reflect.get(value, 'drain') === 'function'\n )\n}\n\n/**\n * Validate the server list captured by a scheduler route at construction time.\n * The seam stays structural so custom scheduler integrations can supply their\n * own drainable targets.\n */\nexport function assertSchedulerTargets(\n servers: readonly unknown[],\n): asserts servers is readonly DrainableServer[] {\n const contracts = new Set<string>()\n for (const target of servers) {\n if (!isDrainableServer(target)) {\n throw new TypeError('a2 scheduler: expected a server from createServer()')\n }\n const name = target.contract.name\n if (contracts.has(name)) {\n throw new TypeError(\n `a2 scheduler: duplicate server contract '${name}' in one handler`,\n )\n }\n contracts.add(name)\n }\n}\n\n/**\n * Drain timing knobs. Mutable only as a white-box test seam. Claim\n * heartbeats run on real timers, so tests shrink these to keep suites\n * fast. Production always uses the defaults.\n */\nexport const DRAIN_TIMINGS: {\n /** How long a claim lives without renewal. */\n claimTtlMs: number\n /** Heartbeat cadence while a handler is running. */\n claimHeartbeatMs: number\n /** Base delay before retrying a failed renewal while the lease lives. */\n renewRetryMs: number\n /** Delay between a claim's expiry and its scheduler watchdog. */\n watchdogGraceMs: number\n /** Maximum time a top-level append waits for its initial scheduler arm. */\n schedulerArmTimeoutMs: number\n} = {\n claimTtlMs: 5_000,\n claimHeartbeatMs: 2_000,\n renewRetryMs: 400,\n watchdogGraceMs: 1_000,\n schedulerArmTimeoutMs: 2_000,\n}\n\n/**\n * Stream liveness knobs — the server-side SSE heartbeat and the\n * client-side stall watchdog. Paired by design: the client declares a\n * connection dead after roughly two missed heartbeats. Mutable only as\n * a white-box test seam; production always uses the defaults.\n */\n/**\n * Poll cadence for the poll-based store streams (sqlite, postgres) —\n * owned, not configurable: adaptive polling removed the operator's\n * reason to tune it (the floor keeps active streams smooth; the\n * ceiling bounds idle cost). Mutable only as a white-box test seam so\n * suites don't wait out real idle gaps.\n */\nexport const POLL_TIMINGS: {\n /** Gap between polls while a session is producing events. */\n activeFloorMs: number\n /** The idle ceiling — the longest gap between polls. */\n idleCeilingMs: number\n} = {\n activeFloorMs: 25,\n idleCeilingMs: 2_000,\n}\n\n/**\n * Safety-read cadence for the notify-driven redis live stream. A\n * parked feed re-reads the store at this interval even without a\n * pub/sub wake-up, so a lost notification (subscriber reconnect gap,\n * dropped PUBLISH) costs latency, never an event. Mutable only as a\n * white-box test seam.\n */\nexport const NOTIFY_TIMINGS: {\n safetyReadMs: number\n} = {\n safetyReadMs: 10_000,\n}\n\n/**\n * Bounds for each server instance's in-memory reducer-state cache. Sizes are\n * encoded JSON lengths, re-measured every `measureEvery` folded events, so\n * the byte budget is approximate. Mutable only as a white-box test seam.\n */\nexport const STATE_CACHE: {\n maxEntries: number\n maxBytes: number\n /** Events a cached state may advance before it writes a snapshot. */\n snapshotEvery: number\n /** Events a warm read may fold; longer tails read the stored snapshot. */\n maxTail: number\n /** Events folded between re-measuring a cached state's encoded size. */\n measureEvery: number\n} = {\n maxEntries: 1_000,\n maxBytes: 32 * 1024 * 1024,\n snapshotEvery: 128,\n maxTail: 255,\n measureEvery: 16,\n}\n\n/**\n * Warm state-read tails for stores that can serve one in a single request:\n * events from `fromIndex` (inclusive) through `throughIndex` or the newest\n * event, or null when that needs more than one request. Like every store\n * read, a tail starts no lower than the latest clear marker. Keyed by the exact\n * store object, so a wrapper built by spreading or proxying a store reads\n * through its own `readHistory` instead.\n */\nexport const stateTailReaders: WeakMap<\n object,\n (\n sessionId: string,\n fromIndex: number,\n throughIndex?: number,\n ) => Promise<Event[] | null>\n> = new WeakMap()\n\nexport const STREAM_TIMINGS: {\n /** Cadence of `: ping` comment frames from `sseResponse`. */\n sseHeartbeatMs: number\n /** How far ahead of an invocation deadline an SSE response closes. */\n sseDeadlineGraceMs: number\n /** Client: silence longer than this means the connection is dead. */\n stallTimeoutMs: number\n /** Client reconnect backoff: first retry delay, doubling per attempt. */\n reconnectBaseMs: number\n /** Client reconnect backoff ceiling. */\n reconnectMaxMs: number\n} = {\n sseHeartbeatMs: 15_000,\n sseDeadlineGraceMs: 1_000,\n stallTimeoutMs: 35_000,\n reconnectBaseMs: 500,\n reconnectMaxMs: 5_000,\n}\n\n/**\n * WebSocket down-pump backpressure — `sessionsSocket` parks its pull\n * loop while the socket reports more buffered bytes than the\n * high-water mark and re-checks on the poll cadence, so a slow reader\n * applies backpressure to the store subscription instead of buffering\n * without bound in the socket. Acks and heartbeats stay exempt: they\n * are small and correctness-relevant. Mutable only as a white-box test\n * seam.\n */\nexport const SOCKET_TIMINGS: {\n highWaterMarkBytes: number\n resumePollMs: number\n /** Up-lane bound: concurrent push/presence handlers per socket. POST\n * gets per-request platform limits for free; the socket must bring\n * its own. Over the cap, pushes are answered with a retryable error\n * ack and presence frames are shed (that plane repaints). */\n maxInFlightMessages: number\n /** Hard ceiling on `bufferedAmount`: past it the peer is a\n * non-reader (acks and pings are park-exempt, so only a peer that\n * never reads can reach it) and the socket closes 1008. */\n disconnectWaterMarkBytes: number\n} = {\n highWaterMarkBytes: 1_048_576,\n resumePollMs: 25,\n maxInFlightMessages: 32,\n disconnectWaterMarkBytes: 8_388_608,\n}\n\n/**\n * Client presence send cadence — a leading send immediately, then at\n * most one merged send per interval (~15Hz). Owned, not configurable\n * (decision #36's rule): coupling the cadence to the POST ack tied\n * remote cursor smoothness to RTT, and the ack serves no correctness\n * purpose on a fire-and-forget LWW plane. Mutable only as a white-box\n * test seam.\n */\nexport const PRESENCE_TIMINGS: {\n sendIntervalMs: number\n} = {\n sendIntervalMs: 66,\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"internal-CscSY7K_.js","names":[],"sources":["../src/internal.ts"],"sourcesContent":["/**\n * Private seams shared by a2 entry points and a2's own tests. Not exported\n * from the package. Nothing here is public API.\n */\n\nimport type { ScheduledEvent } from './scheduler-task.ts'\nimport type { DrainableServer } from './server.ts'\nimport type { Event } from './store.ts'\n\nexport type DrainOutcome = 'busy' | 'stalled' | 'settled'\n\n/**\n * A null-prototype record for maps keyed by caller-controlled strings\n * (participant ids, presence field names). On a normal object,\n * assigning to '__proto__' rewrites the prototype instead of creating\n * an own property — the prototype-pollution class. Null-prototype\n * objects still satisfy Record types and JSON.stringify cleanly.\n */\nexport function nullProtoRecord<T extends object>(): T {\n return Object.create(null) as T\n}\n\n/**\n * ECMA-262's maximum Date value (±8.64e15 ms). A presence stamp past\n * it becomes an Invalid Date whose toISOString throws — one hostile\n * stamp would poison every subscriber's snapshot frame — so both\n * seams bound `at` to the representable range.\n */\nexport const MAX_DATE_MS = 8_640_000_000_000_000\n\n/**\n * Participant ids that collide with Object.prototype plumbing.\n * Null-prototype construction already defuses them, but they are\n * rejected at both seams (`parsePushBody`, `setPresence`) so a hostile\n * id never propagates into userland maps either.\n */\nexport const RESERVED_PARTICIPANT_IDS: ReadonlySet<string> = new Set([\n '__proto__',\n 'constructor',\n 'prototype',\n])\n\nexport type DrainResult = {\n settled: boolean\n outcome: DrainOutcome\n /** Existing successor arm a scheduler callback may join before acking. */\n schedulerArm?: Promise<void>\n}\n\nexport type ServerInternals = {\n /**\n * Await every in-flight background drain this machine has scheduled,\n * including ones scheduled while waiting. Lets tests flush the\n * fire-and-forget inline path deterministically — no sleeps.\n */\n settle(): Promise<void>\n /** The public `server.drain()` result deliberately remains `{ settled }`. */\n schedulerDrain(\n sessionId: string,\n opts?: { watchdogDueAt?: number },\n ): Promise<DrainResult>\n /** Deliver a scheduler-owned delayed append through the normal root path. */\n schedulerAppend(\n sessionId: string,\n events: readonly ScheduledEvent[],\n originIndex?: number,\n ): Promise<void>\n /** Deliver a scheduler-owned conditional clear, deletion included. */\n schedulerClear(sessionId: string, ifLastIndex: number): Promise<void>\n /** `session.history` with clear markers kept; reads start at the latest one. */\n historyWithMarkers(sessionId: string, gte: number): Promise<Event[]>\n}\n\nexport const serverInternals: WeakMap<object, ServerInternals> = new WeakMap()\n\n/**\n * Handler contexts whose previous attempt ended in a failure the handler\n * threw, as opposed to a claim that lapsed (a crash, a timeout, a frozen\n * process). Only the attempt's own outcome is known, not how far it got.\n */\nexport const failedPreviousAttempt: WeakSet<object> = new WeakSet()\n\nconst objectLike = (value: unknown): value is object =>\n (typeof value === 'object' && value !== null) || typeof value === 'function'\n\n/** The ambient value delivered to `handlerContext` during a tool execution. */\nexport type AmbientToolScope = {\n /** Executing server identity; a test scope from `runWithHandlerContext` has none. */\n contract?: { readonly name: string }\n context: unknown\n}\n\n/**\n * Structural AsyncLocalStorage surface for the ambient tool scope. The\n * instance is created by the server-only AI entry point (the only place\n * `node:async_hooks` may be imported) and shared through a global\n * symbol slot, so `handlerContext` in the isomorphic AI entry point and\n * independently bundled A2 copies all read the same scope.\n */\nexport type AmbientToolScopeStorage = {\n getStore(): AmbientToolScope | undefined\n run<R>(store: AmbientToolScope, callback: () => R): R\n}\n\nconst TOOL_SCOPE_STORAGE = Symbol.for('experimental-a2.ai.tool-scope')\n\nexport function ambientToolScopeStorage(): AmbientToolScopeStorage | undefined {\n const value = Reflect.get(globalThis, TOOL_SCOPE_STORAGE)\n if (\n !objectLike(value) ||\n typeof Reflect.get(value, 'getStore') !== 'function' ||\n typeof Reflect.get(value, 'run') !== 'function'\n ) {\n return undefined\n }\n return value as AmbientToolScopeStorage\n}\n\nexport function installAmbientToolScopeStorage(\n create: () => AmbientToolScopeStorage,\n): AmbientToolScopeStorage {\n const existing = ambientToolScopeStorage()\n if (existing !== undefined) return existing\n const storage = create()\n Reflect.set(globalThis, TOOL_SCOPE_STORAGE, storage)\n return storage\n}\n\nexport type SchedulerSendFailureDisposition = 'retryable' | 'terminal'\n\ntype SchedulerSendFailureMarker = {\n readonly version: 1\n readonly pending: SchedulerSendFailureDisposition[]\n readonly active: SchedulerSendFailureDisposition[]\n}\n\nconst SCHEDULER_SEND_FAILURE = Symbol.for(\n 'experimental-a2.scheduler.send-failure',\n)\n\nconst schedulerSendFailureMarker = (\n value: unknown,\n): SchedulerSendFailureMarker | undefined => {\n if (!objectLike(value)) return undefined\n const marker = Reflect.get(value, SCHEDULER_SEND_FAILURE)\n if (\n !objectLike(marker) ||\n Reflect.get(marker, 'version') !== 1 ||\n !Array.isArray(Reflect.get(marker, 'pending')) ||\n !Array.isArray(Reflect.get(marker, 'active'))\n ) {\n return undefined\n }\n return marker as SchedulerSendFailureMarker\n}\n\nconst schedulerFailureObject = (error: unknown): object => {\n if (\n objectLike(error) &&\n (schedulerSendFailureMarker(error) !== undefined ||\n Object.isExtensible(error))\n ) {\n return error\n }\n return new Error(String(error), { cause: error })\n}\n\nconst schedulerSendFailureState = (\n error: object,\n): SchedulerSendFailureMarker => {\n const existing = schedulerSendFailureMarker(error)\n if (existing !== undefined) return existing\n const marker: SchedulerSendFailureMarker = {\n version: 1,\n pending: [],\n active: [],\n }\n Object.defineProperty(error, SCHEDULER_SEND_FAILURE, {\n value: marker,\n enumerable: false,\n configurable: true,\n writable: false,\n })\n return marker\n}\n\nconst clearSchedulerSendFailureState = (\n error: object,\n marker: SchedulerSendFailureMarker,\n): void => {\n if (marker.pending.length === 0 && marker.active.length === 0) {\n Reflect.deleteProperty(error, SCHEDULER_SEND_FAILURE)\n }\n}\n\n/**\n * Classify an adapter error before throwing it. Unclassified adapter failures\n * are retryable because the caller cannot know whether a remote send landed.\n */\nexport function classifySchedulerSendFailure(\n error: unknown,\n disposition: SchedulerSendFailureDisposition,\n): unknown {\n const classified = schedulerFailureObject(error)\n schedulerSendFailureState(classified).pending.push(disposition)\n return classified\n}\n\n/** Consume a pending adapter classification and count this rejected send. */\nexport function markSchedulerSendFailure(error: unknown): unknown {\n const marked = schedulerFailureObject(error)\n const marker = schedulerSendFailureState(marked)\n marker.active.push(marker.pending.pop() ?? 'retryable')\n return marked\n}\n\nexport function consumeSchedulerSendFailure(\n error: unknown,\n): SchedulerSendFailureDisposition | undefined {\n if (!objectLike(error)) return undefined\n const marker = schedulerSendFailureMarker(error)\n const disposition = marker?.active.pop()\n if (marker !== undefined) clearSchedulerSendFailureState(error, marker)\n return disposition\n}\n\nconst isDrainableServer = (value: unknown): value is DrainableServer => {\n if (value === null || typeof value !== 'object') return false\n const contract = Reflect.get(value, 'contract')\n return (\n contract !== null &&\n typeof contract === 'object' &&\n typeof Reflect.get(contract, 'name') === 'string' &&\n typeof Reflect.get(value, 'drain') === 'function'\n )\n}\n\n/**\n * Validate the server list captured by a scheduler route at construction time.\n * The seam stays structural so custom scheduler integrations can supply their\n * own drainable targets.\n */\nexport function assertSchedulerTargets(\n servers: readonly unknown[],\n): asserts servers is readonly DrainableServer[] {\n const contracts = new Set<string>()\n for (const target of servers) {\n if (!isDrainableServer(target)) {\n throw new TypeError('a2 scheduler: expected a server from createServer()')\n }\n const name = target.contract.name\n if (contracts.has(name)) {\n throw new TypeError(\n `a2 scheduler: duplicate server contract '${name}' in one handler`,\n )\n }\n contracts.add(name)\n }\n}\n\n/**\n * Drain timing knobs. Mutable only as a white-box test seam. Claim\n * heartbeats run on real timers, so tests shrink these to keep suites\n * fast. Production always uses the defaults.\n */\nexport const DRAIN_TIMINGS: {\n /** How long a claim lives without renewal. */\n claimTtlMs: number\n /** Heartbeat cadence while a handler is running. */\n claimHeartbeatMs: number\n /** Base delay before retrying a failed renewal while the lease lives. */\n renewRetryMs: number\n /** Delay between a claim's expiry and its scheduler watchdog. */\n watchdogGraceMs: number\n /** Maximum time a top-level append waits for its initial scheduler arm. */\n schedulerArmTimeoutMs: number\n /** First retry delay after a caught failure, doubling per failure. */\n failureRetryBaseMs: number\n /** Ceiling of the caught-failure retry delay. */\n failureRetryMaxMs: number\n} = {\n claimTtlMs: 5_000,\n claimHeartbeatMs: 2_000,\n renewRetryMs: 400,\n watchdogGraceMs: 1_000,\n schedulerArmTimeoutMs: 2_000,\n failureRetryBaseMs: 1_000,\n failureRetryMaxMs: 30_000,\n}\n\n/**\n * Stream liveness knobs — the server-side SSE heartbeat and the\n * client-side stall watchdog. Paired by design: the client declares a\n * connection dead after roughly two missed heartbeats. Mutable only as\n * a white-box test seam; production always uses the defaults.\n */\n/**\n * Poll cadence for the poll-based store streams (sqlite, postgres) —\n * owned, not configurable: adaptive polling removed the operator's\n * reason to tune it (the floor keeps active streams smooth; the\n * ceiling bounds idle cost). Mutable only as a white-box test seam so\n * suites don't wait out real idle gaps.\n */\nexport const POLL_TIMINGS: {\n /** Gap between polls while a session is producing events. */\n activeFloorMs: number\n /** The idle ceiling — the longest gap between polls. */\n idleCeilingMs: number\n} = {\n activeFloorMs: 25,\n idleCeilingMs: 2_000,\n}\n\n/**\n * Safety-read cadence for the notify-driven redis live stream. A\n * parked feed re-reads the store at this interval even without a\n * pub/sub wake-up, so a lost notification (subscriber reconnect gap,\n * dropped PUBLISH) costs latency, never an event. Mutable only as a\n * white-box test seam.\n */\nexport const NOTIFY_TIMINGS: {\n safetyReadMs: number\n} = {\n safetyReadMs: 10_000,\n}\n\n/**\n * Bounds for each server instance's in-memory reducer-state cache. Sizes are\n * encoded JSON lengths, re-measured every `measureEvery` folded events, so\n * the byte budget is approximate. Mutable only as a white-box test seam.\n */\nexport const STATE_CACHE: {\n maxEntries: number\n maxBytes: number\n /** Events a cached state may advance before it writes a snapshot. */\n snapshotEvery: number\n /** Events a warm read may fold; longer tails read the stored snapshot. */\n maxTail: number\n /** Events folded between re-measuring a cached state's encoded size. */\n measureEvery: number\n} = {\n maxEntries: 1_000,\n maxBytes: 32 * 1024 * 1024,\n snapshotEvery: 128,\n maxTail: 255,\n measureEvery: 16,\n}\n\n/**\n * Warm state-read tails for stores that can serve one in a single request:\n * events from `fromIndex` (inclusive) through `throughIndex` or the newest\n * event, or null when that needs more than one request. Like every store\n * read, a tail starts no lower than the latest clear marker. Keyed by the exact\n * store object, so a wrapper built by spreading or proxying a store reads\n * through its own `readHistory` instead.\n */\nexport const stateTailReaders: WeakMap<\n object,\n (\n sessionId: string,\n fromIndex: number,\n throughIndex?: number,\n ) => Promise<Event[] | null>\n> = new WeakMap()\n\nexport const STREAM_TIMINGS: {\n /** Cadence of `: ping` comment frames from `sseResponse`. */\n sseHeartbeatMs: number\n /** How far ahead of an invocation deadline an SSE response closes. */\n sseDeadlineGraceMs: number\n /** Client: silence longer than this means the connection is dead. */\n stallTimeoutMs: number\n /** Client reconnect backoff: first retry delay, doubling per attempt. */\n reconnectBaseMs: number\n /** Client reconnect backoff ceiling. */\n reconnectMaxMs: number\n} = {\n sseHeartbeatMs: 15_000,\n sseDeadlineGraceMs: 1_000,\n stallTimeoutMs: 35_000,\n reconnectBaseMs: 500,\n reconnectMaxMs: 5_000,\n}\n\n/**\n * WebSocket down-pump backpressure — `sessionsSocket` parks its pull\n * loop while the socket reports more buffered bytes than the\n * high-water mark and re-checks on the poll cadence, so a slow reader\n * applies backpressure to the store subscription instead of buffering\n * without bound in the socket. Acks and heartbeats stay exempt: they\n * are small and correctness-relevant. Mutable only as a white-box test\n * seam.\n */\nexport const SOCKET_TIMINGS: {\n highWaterMarkBytes: number\n resumePollMs: number\n /** Up-lane bound: concurrent push/presence handlers per socket. POST\n * gets per-request platform limits for free; the socket must bring\n * its own. Over the cap, pushes are answered with a retryable error\n * ack and presence frames are shed (that plane repaints). */\n maxInFlightMessages: number\n /** Hard ceiling on `bufferedAmount`: past it the peer is a\n * non-reader (acks and pings are park-exempt, so only a peer that\n * never reads can reach it) and the socket closes 1008. */\n disconnectWaterMarkBytes: number\n} = {\n highWaterMarkBytes: 1_048_576,\n resumePollMs: 25,\n maxInFlightMessages: 32,\n disconnectWaterMarkBytes: 8_388_608,\n}\n\n/**\n * Client presence send cadence — a leading send immediately, then at\n * most one merged send per interval (~15Hz). Owned, not configurable\n * (decision #36's rule): coupling the cadence to the POST ack tied\n * remote cursor smoothness to RTT, and the ack serves no correctness\n * purpose on a fire-and-forget LWW plane. Mutable only as a white-box\n * test seam.\n */\nexport const PRESENCE_TIMINGS: {\n sendIntervalMs: number\n} = {\n sendIntervalMs: 66,\n}\n"],"mappings":"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"}
package/dist/react.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { i as isInitialPromise, n as createRenderSession, r as resolveInitialForReact, t as createClient } from "./client-BReKqwZC.js";
2
+ import { i as isInitialPromise, n as createRenderSession, r as resolveInitialForReact, t as createClient } from "./client-BhHLoNKF.js";
3
3
  import { createContext, createElement, use, useContext, useEffect, useMemo, useSyncExternalStore } from "react";
4
4
  //#region src/react.ts
5
5
  function createReact(options) {
@@ -1,5 +1,5 @@
1
- import { r as Clock } from "./store-Djdv0bnZ.js";
2
- import { P as SchedulerTask, t as A2Scheduler } from "./server-En7cbi3v.js";
1
+ import { r as Clock } from "./store-CyLUy86v.js";
2
+ import { P as SchedulerTask, t as A2Scheduler } from "./server-DYAATFuT.js";
3
3
  //#region src/scheduler-qstash.d.ts
4
4
  type QStashStoreOutage = {
5
5
  version: 1;
@@ -1,7 +1,7 @@
1
1
  import { i as A2Error } from "./session-clear-DGI4jTQ5.js";
2
- import { d as assertSchedulerTargets, f as classifySchedulerSendFailure, t as DRAIN_TIMINGS, v as serverInternals } from "./internal-B2T3U4Rq.js";
2
+ import { d as assertSchedulerTargets, f as classifySchedulerSendFailure, t as DRAIN_TIMINGS, v as serverInternals } from "./internal-CscSY7K_.js";
3
3
  import { n as platformVercelOidcToken } from "./platform-B4TnJtWu.js";
4
- import { n as deliverSchedulerAppend, r as deliverSchedulerClear } from "./server-Dw_k_7kO.js";
4
+ import { n as deliverSchedulerAppend, r as deliverSchedulerClear } from "./server-D-Y4pqwu.js";
5
5
  import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
6
6
  import { n as SYSTEM_CLOCK } from "./store-N8PXxDAS.js";
7
7
  import { t as normalizeSchedulerTask } from "./scheduler-task-X1SToDjP.js";
@@ -495,7 +495,7 @@ function qstash(options = {}) {
495
495
  return response.ok();
496
496
  }
497
497
  if (result.settled) return response.ok();
498
- if (result.outcome === "busy") {
498
+ if (result.outcome === "busy" || result.outcome === "stalled") {
499
499
  if (result.schedulerArm) await result.schedulerArm;
500
500
  else await sendTask({
501
501
  version: 1,
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler-qstash.js","names":[],"sources":["../src/scheduler-qstash.ts"],"sourcesContent":["/**\n * experimental-a2/scheduler-qstash — HTTP scheduler over Upstash QStash.\n *\n * QStash publishes one signed HTTP delivery per scheduler task. Drain tasks\n * use an absolute, whole-second `notBefore` and a fixed-size deduplication id;\n * append and clear tasks hash their stable task id with the callback URL, each\n * under its own version tag.\n * Store-outage successors carry a signed private generation and back off to\n * one day without deriving their deduplication identity from the local clock.\n * `handler(...servers)` verifies the signature over the raw request body\n * before parsing it, then applies the same drain protocol as the Vercel\n * scheduler adapter.\n *\n * `@upstash/qstash` is an optional peer dependency, imported lazily. The\n * structural `transport` option keeps its types out of A2's public surface and\n * gives tests a provider-faithful injection seam.\n */\n\nimport {\n assertSchedulerTargets,\n classifySchedulerSendFailure,\n DRAIN_TIMINGS,\n serverInternals,\n type DrainResult,\n} from './internal.ts'\nimport { A2Error } from './errors.ts'\nimport { platformVercelOidcToken } from './platform.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport { normalizeSchedulerTask, type SchedulerTask } from './scheduler-task.ts'\nimport {\n deliverSchedulerAppend,\n deliverSchedulerClear,\n type A2Scheduler,\n type DrainableServer,\n} from './server.ts'\nimport { SYSTEM_CLOCK, type Clock } from './store.ts'\n\ntype QStashStoreOutage = {\n version: 1\n /** Zero is the first successor created for a store outage. */\n generation: number\n /** The first failing drain task's due time. Append and clear identity lives on the task. */\n rootDueAt?: number\n}\n\ntype QStashMessage = SchedulerTask & {\n /** Provider-private state authenticated as part of QStash's signed body. */\n _a2QStash?: QStashStoreOutage\n}\n\nexport type QStashPublishRequest = {\n url: string\n body: QStashMessage\n /** Headers the transport must deliver to the callback. */\n headers?: Readonly<Record<string, string>>\n /** Header names the transport must redact from provider dashboard/API views. */\n redact?: {\n header: readonly string[]\n }\n /** Whole-second Unix timestamp. Omitted for an immediate delivery. */\n notBefore?: number\n deduplicationId: string\n /** Delivery retries after the initial attempt. */\n retries?: number\n}\n\n/** The request shape passed to an injected `@upstash/qstash` client. */\nexport type QStashClientPublishRequest = Omit<\n QStashPublishRequest,\n 'headers' | 'redact'\n> & {\n headers?: Record<string, string>\n redact?: {\n header: string[]\n }\n}\n\n/** The outbound part of `@upstash/qstash` used by the built-in transport. */\nexport type QStashPublishingClient = {\n publishJSON(request: QStashClientPublishRequest): Promise<unknown>\n}\n\nexport type QStashVerifyRequest = {\n signature: string\n /** The untouched request body covered by the signature. */\n body: string\n /** Exact destination URL, checked against the signature subject. */\n url: string\n upstashRegion?: string\n}\n\n/** The small part of `@upstash/qstash` this adapter needs. */\nexport type QStashTransport = {\n publish(request: QStashPublishRequest): Promise<unknown>\n verify(request: QStashVerifyRequest): Promise<boolean>\n}\n\nexport type QStashSchedulerOptions = {\n /** Exact URL mounted with `scheduler.handler(...)`. Inferred in local dev and on Vercel. */\n url?: string\n /** QStash API token. Default: `QSTASH_TOKEN`. */\n token?: string\n /** QStash signing key. Default: `QSTASH_CURRENT_SIGNING_KEY`. */\n currentSigningKey?: string\n /** Next QStash signing key. Default: `QSTASH_NEXT_SIGNING_KEY`. */\n nextSigningKey?: string\n /** Delivery retries after the initial attempt. Default: provider default. */\n retries?: number\n /** Maximum callback body and outbound task with outage headroom. Default 1 MiB. */\n maxBodyBytes?: number\n /** Static callback headers. QStash receives them; A2 requests dashboard/API redaction. */\n headers?: Readonly<Record<string, string>>\n /** Injectable publishing client. The built-in receiver still verifies callbacks. */\n client?: QStashPublishingClient\n /** Injectable transport (tests). Default: `@upstash/qstash`. */\n transport?: QStashTransport\n /** Injectable clock for due-time calculations (tests). */\n clock?: Clock\n}\n\nconst qstashTransport = async (\n options: Pick<\n QStashSchedulerOptions,\n 'token' | 'currentSigningKey' | 'nextSigningKey' | 'client'\n >,\n developmentMode: boolean | undefined,\n): Promise<QStashTransport> => {\n const sdk = await import('@upstash/qstash').catch(() => {\n throw classifySchedulerSendFailure(\n new Error(\n \"experimental-a2/scheduler-qstash needs the '@upstash/qstash' package (optional peer dependency) — install it, or inject a transport\",\n ),\n 'terminal',\n )\n })\n const developmentOverride =\n developmentMode === undefined ? {} : { devMode: developmentMode }\n const client: QStashPublishingClient =\n options.client ??\n new sdk.Client({\n ...(options.token === undefined ? {} : { token: options.token }),\n ...developmentOverride,\n })\n const receiver = new sdk.Receiver({\n ...(options.currentSigningKey === undefined\n ? {}\n : { currentSigningKey: options.currentSigningKey }),\n ...(options.nextSigningKey === undefined\n ? {}\n : { nextSigningKey: options.nextSigningKey }),\n ...developmentOverride,\n })\n return {\n publish: (request) => {\n const clientRequest: QStashClientPublishRequest = {\n url: request.url,\n body: request.body,\n ...(request.headers === undefined\n ? {}\n : { headers: { ...request.headers } }),\n ...(request.redact === undefined\n ? {}\n : { redact: { header: [...request.redact.header] } }),\n deduplicationId: request.deduplicationId,\n ...(request.retries === undefined ? {} : { retries: request.retries }),\n ...(request.notBefore === undefined\n ? {}\n : { notBefore: request.notBefore }),\n }\n return client.publishJSON(clientRequest)\n },\n verify: (request) => receiver.verify(request),\n }\n}\n\nconst digestId = async (\n parts: readonly (string | number)[],\n): Promise<string> => {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(JSON.stringify(parts)),\n )\n const hex = Array.from(new Uint8Array(digest), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('')\n return `a2_${hex}`\n}\n\ntype ParsedQStashMessage = {\n task: SchedulerTask\n outage?: QStashStoreOutage\n}\n\nconst recordOf = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nconst TRUSTED_SOURCE_HEADER = 'x-vercel-trusted-oidc-idp-token'\nconst TRUSTED_SOURCE_DELIVERY_MARGIN_MS = 60_000\nconst RESERVED_CALLBACK_HEADERS = new Set([\n 'connection',\n 'content-length',\n 'host',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'x-vercel-oidc-token',\n TRUSTED_SOURCE_HEADER,\n])\n\nconst callbackHeaders = (\n value: Readonly<Record<string, string>> | undefined,\n): Readonly<Record<string, string>> | undefined => {\n if (value === undefined) return undefined\n\n let entries: [string, string][]\n try {\n entries = Object.entries(value)\n } catch {\n throw new TypeError('qstash() headers must be an HTTP header record')\n }\n\n const normalized: [string, string][] = []\n const seen = new Set<string>()\n for (const [name, headerValue] of entries) {\n if (typeof headerValue !== 'string') {\n throw new TypeError('qstash() header values must be strings')\n }\n let entry: [string, string] | undefined\n try {\n entry = [...new Headers([[name, headerValue]]).entries()][0]\n } catch {\n throw new TypeError(\n 'qstash() headers must contain valid HTTP header names and values',\n )\n }\n if (entry === undefined) {\n throw new TypeError(\n 'qstash() headers must contain valid HTTP header names and values',\n )\n }\n const [normalizedName, normalizedValue] = entry\n if (seen.has(normalizedName)) {\n throw new TypeError(\n `qstash() headers contain duplicate name '${normalizedName}'`,\n )\n }\n if (\n normalizedName.startsWith('upstash-') ||\n normalizedName.startsWith('content-type') ||\n RESERVED_CALLBACK_HEADERS.has(normalizedName)\n ) {\n throw new TypeError(\n `qstash() headers cannot set reserved header '${normalizedName}'`,\n )\n }\n seen.add(normalizedName)\n normalized.push([normalizedName, normalizedValue])\n }\n\n return normalized.length === 0\n ? undefined\n : Object.freeze(Object.fromEntries(normalized))\n}\n\nconst publishHeaders = (\n configured: Readonly<Record<string, string>> | undefined,\n generated: Pick<QStashPublishRequest, 'headers' | 'redact'> | undefined,\n): Pick<QStashPublishRequest, 'headers' | 'redact'> | undefined => {\n if (configured === undefined && generated === undefined) return undefined\n\n const headers = new Headers()\n const redacted = new Set<string>()\n for (const [name, value] of Object.entries(configured ?? {})) {\n headers.set(name, value)\n redacted.add(name)\n }\n for (const [name, value] of Object.entries(generated?.headers ?? {})) {\n headers.set(name, value)\n redacted.add(name)\n }\n for (const name of generated?.redact?.header ?? []) {\n const normalized = [...new Headers([[name, 'redacted']]).keys()][0]\n if (normalized === undefined) {\n throw new TypeError('qstash generated an invalid redaction header')\n }\n redacted.add(normalized)\n }\n\n const headerEntries = [...headers.entries()]\n return {\n ...(headerEntries.length === 0\n ? {}\n : { headers: Object.fromEntries(headerEntries) }),\n ...(redacted.size === 0\n ? {}\n : { redact: { header: [...redacted].toSorted() } }),\n }\n}\n\nconst jwtExpirationMs = (token: string): number | null => {\n const parts = token.split('.')\n const payloadPart = parts.length === 3 ? parts[1] : undefined\n if (\n payloadPart === undefined ||\n payloadPart.length === 0 ||\n !/^[A-Za-z0-9_-]+$/.test(payloadPart) ||\n payloadPart.length % 4 === 1\n ) {\n return null\n }\n\n try {\n const decode = globalThis.atob\n if (typeof decode !== 'function') return null\n const base64 = payloadPart\n .replaceAll('-', '+')\n .replaceAll('_', '/')\n .padEnd(payloadPart.length + ((4 - (payloadPart.length % 4)) % 4), '=')\n const binary = decode(base64)\n const bytes = Uint8Array.from(binary, (character) =>\n character.charCodeAt(0),\n )\n const payload = recordOf(JSON.parse(new TextDecoder().decode(bytes)))\n const expiration = payload?.['exp']\n if (\n typeof expiration !== 'number' ||\n !Number.isSafeInteger(expiration) ||\n expiration <= 0 ||\n expiration > Number.MAX_SAFE_INTEGER / 1_000\n ) {\n return null\n }\n return expiration * 1_000\n } catch {\n return null\n }\n}\n\nconst previewTrustedSource = (\n firstDeliveryAt: number,\n): Pick<QStashPublishRequest, 'headers' | 'redact'> | undefined => {\n const token = platformVercelOidcToken()\n if (token === null) return undefined\n const expiration = jwtExpirationMs(token)\n if (\n expiration === null ||\n firstDeliveryAt + TRUSTED_SOURCE_DELIVERY_MARGIN_MS > expiration\n ) {\n return undefined\n }\n return {\n headers: { [TRUSTED_SOURCE_HEADER]: token },\n redact: { header: [TRUSTED_SOURCE_HEADER] },\n }\n}\n\nconst RETRYABLE_HTTP_CLIENT_STATUSES = new Set([408, 409, 425, 429])\n\nconst httpStatusOf = (error: unknown): number | undefined => {\n try {\n const record = recordOf(error)\n if (!record) return undefined\n const response = recordOf(record['response'])\n const statuses = [\n record['status'],\n record['statusCode'],\n response?.['status'],\n ].filter(\n (value): value is number =>\n typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= 100 &&\n value <= 599,\n )\n const [status] = statuses\n if (status === undefined) return undefined\n return statuses.every((candidate) => candidate === status)\n ? status\n : undefined\n } catch {\n return undefined\n }\n}\n\nconst classifyQStashPublishFailure = (error: unknown): unknown => {\n const status = httpStatusOf(error)\n const terminal =\n status !== undefined &&\n status >= 400 &&\n status < 500 &&\n !RETRYABLE_HTTP_CLIENT_STATUSES.has(status)\n return classifySchedulerSendFailure(\n error,\n terminal ? 'terminal' : 'retryable',\n )\n}\n\nconst parseTask = (body: string): ParsedQStashMessage | null => {\n let value: unknown\n try {\n value = JSON.parse(body)\n } catch {\n return null\n }\n const task = normalizeSchedulerTask(value)\n const record = recordOf(value)\n if (!task || !record) return null\n\n const encodedOutage = record['_a2QStash']\n if (encodedOutage === undefined) return { task }\n const outage = recordOf(encodedOutage)\n const generation = outage?.['generation']\n if (\n !outage ||\n outage['version'] !== 1 ||\n typeof generation !== 'number' ||\n !Number.isSafeInteger(generation) ||\n generation < 0 ||\n generation >= Number.MAX_SAFE_INTEGER\n ) {\n return null\n }\n\n const rootDueAt = outage['rootDueAt']\n if (task.kind === 'drain') {\n if (\n typeof rootDueAt !== 'number' ||\n !Number.isInteger(rootDueAt) ||\n rootDueAt % 1_000 !== 0\n ) {\n return null\n }\n } else if (rootDueAt !== undefined) {\n return null\n }\n\n return {\n task,\n outage: {\n version: 1,\n generation,\n ...(rootDueAt === undefined ? {} : { rootDueAt }),\n },\n }\n}\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024\nconst STORE_OUTAGE_BASE_DELAY_MS = 30_000\nconst STORE_OUTAGE_MAX_DELAY_MS = 24 * 60 * 60 * 1_000\n\nconst readBoundedBody = async (\n request: Request,\n maxBytes: number,\n): Promise<string | null> => {\n const declared = request.headers.get('content-length')\n if (\n declared !== null &&\n /^\\d+$/.test(declared) &&\n Number(declared) > maxBytes\n ) {\n return null\n }\n\n if (request.body === null) return ''\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let length = 0\n try {\n for (;;) {\n // oxlint-disable-next-line no-await-in-loop\n const { done, value } = await reader.read()\n if (done) break\n length += value.byteLength\n if (length > maxBytes) {\n // oxlint-disable-next-line no-await-in-loop\n await reader.cancel().catch(() => {})\n return null\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n const bytes = new Uint8Array(length)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n return new TextDecoder().decode(bytes)\n}\n\nconst storeUnavailable = (error: unknown): error is A2Error =>\n error instanceof A2Error && error.code === 'STORE_UNAVAILABLE'\n\nconst response = {\n ok: (): Response => new Response('ok'),\n unauthorized: (): Response => new Response('unauthorized', { status: 401 }),\n malformed: (): Response =>\n new Response('invalid a2 scheduler message', {\n status: 489,\n headers: { 'Upstash-NonRetryable-Error': 'true' },\n }),\n tooLarge: (): Response =>\n new Response('a2 scheduler message is too large', {\n status: 489,\n headers: { 'Upstash-NonRetryable-Error': 'true' },\n }),\n}\n\nconst CALLBACK_PATH = '/api/a2/scheduler'\nconst DEFAULT_DEVELOPMENT_PORT = 3_000\n\nconst developmentCallbackUrl = (portValue: string | undefined): string => {\n const port =\n portValue === undefined || portValue === ''\n ? DEFAULT_DEVELOPMENT_PORT\n : Number(portValue)\n if (\n !Number.isSafeInteger(port) ||\n port < 1 ||\n port > 65_535 ||\n (portValue !== undefined && portValue !== '' && !/^\\d+$/.test(portValue))\n ) {\n throw new TypeError(\n 'qstash() could not infer url: PORT must be an integer from 1 to 65535 for local development',\n )\n }\n return `http://localhost:${String(port)}${CALLBACK_PATH}`\n}\n\ntype CallbackResolution = {\n callbackUrl: string\n /** An SDK override. Undefined preserves the SDK's environment behavior. */\n developmentMode?: boolean\n inferredVercelPreview?: true\n}\n\nconst inferCallbackUrl = (): CallbackResolution => {\n const env = typeof process === 'undefined' ? undefined : process.env\n const vercelEnv = env?.['VERCEL_ENV']\n const variable =\n env?.['VERCEL'] !== '1'\n ? null\n : vercelEnv === 'production'\n ? 'VERCEL_PROJECT_PRODUCTION_URL'\n : vercelEnv === 'preview'\n ? 'VERCEL_BRANCH_URL'\n : null\n if (variable !== null) {\n const host = env?.[variable]\n if (!host) {\n throw new TypeError(\n `qstash() could not infer url: ${variable} is missing`,\n )\n }\n let origin: URL\n try {\n origin = new URL(`https://${host}`)\n } catch {\n throw new TypeError(\n `qstash() could not infer url: ${variable} must be a hostname`,\n )\n }\n if (\n origin.host !== host ||\n origin.pathname !== '/' ||\n origin.search !== '' ||\n origin.hash !== '' ||\n origin.username !== '' ||\n origin.password !== '' ||\n origin.port !== ''\n ) {\n throw new TypeError(\n `qstash() could not infer url: ${variable} must be a hostname`,\n )\n }\n return {\n callbackUrl: new URL(CALLBACK_PATH, origin).toString(),\n developmentMode: false,\n ...(vercelEnv === 'preview' ? { inferredVercelPreview: true } : {}),\n }\n }\n\n if (env === undefined) {\n throw new TypeError(\n 'qstash() needs url when the runtime cannot identify local development',\n )\n }\n\n if (env['NODE_ENV'] === 'production') {\n throw new TypeError(\n 'qstash() needs url in production outside a Vercel production or preview deployment',\n )\n }\n\n return {\n callbackUrl: developmentCallbackUrl(env['PORT']),\n developmentMode: true,\n }\n}\n\nexport function qstash(options: QStashSchedulerOptions = {}): A2Scheduler {\n const env = typeof process === 'undefined' ? undefined : process.env\n const resolution: CallbackResolution =\n options.url === undefined\n ? inferCallbackUrl()\n : {\n callbackUrl: options.url,\n ...(env?.['NODE_ENV'] === 'production'\n ? { developmentMode: false }\n : {}),\n }\n const { callbackUrl, developmentMode, inferredVercelPreview } = resolution\n let parsedUrl: URL\n try {\n parsedUrl = new URL(callbackUrl)\n } catch {\n throw new TypeError('qstash() needs an absolute callback url')\n }\n if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {\n throw new TypeError('qstash() callback url must use http or https')\n }\n if (options.client !== undefined && options.transport !== undefined) {\n throw new TypeError('qstash() cannot combine client and transport')\n }\n if (options.client !== undefined && options.token !== undefined) {\n throw new TypeError('qstash() cannot combine client and token')\n }\n const url = parsedUrl.toString()\n const configuredHeaders = callbackHeaders(options.headers)\n const { retries } = options\n if (\n retries !== undefined &&\n (!Number.isSafeInteger(retries) || retries < 0)\n ) {\n throw new TypeError('qstash() retries must be a non-negative integer')\n }\n const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n if (!Number.isSafeInteger(maxBodyBytes) || maxBodyBytes < 1) {\n throw new TypeError('qstash() maxBodyBytes must be a positive integer')\n }\n const clock = options.clock ?? SYSTEM_CLOCK\n\n // Lazy: construction does no I/O and does not require the optional peer.\n const transport = retryableLazy(() =>\n options.transport\n ? Promise.resolve(options.transport)\n : qstashTransport(options, developmentMode),\n ).get\n\n const storeOutageDeduplicationId = (\n task: SchedulerTask,\n outage: QStashStoreOutage,\n ): Promise<string> => {\n if (task.kind === 'clear') {\n return digestId([\n 'a2:scheduler-qstash:store-outage:clear:v1',\n url,\n task.contract,\n task.sessionId,\n task.id,\n outage.generation,\n ])\n }\n if (task.kind === 'append') {\n return digestId([\n 'a2:scheduler-qstash:store-outage:append:v1',\n url,\n task.contract,\n task.sessionId,\n task.id,\n outage.generation,\n ])\n }\n const { rootDueAt } = outage\n if (rootDueAt === undefined) {\n throw new TypeError('qstash store-outage drain needs a root dueAt')\n }\n return digestId([\n 'a2:scheduler-qstash:store-outage:drain:v1',\n url,\n task.contract,\n task.sessionId,\n rootDueAt,\n outage.generation,\n ])\n }\n\n const sendTask = async (\n task: SchedulerTask,\n outage?: QStashStoreOutage,\n ): Promise<void> => {\n const nowMs = clock.now().getTime()\n const requestedDueAt = task.dueAt\n if (!Number.isFinite(requestedDueAt)) {\n throw classifySchedulerSendFailure(\n new TypeError('qstash scheduler dueAt must be a finite number'),\n 'terminal',\n )\n }\n const dueAt =\n requestedDueAt <= nowMs\n ? Math.floor(nowMs / 1_000) * 1_000\n : Math.ceil(requestedDueAt / 1_000) * 1_000\n const normalizedTask: SchedulerTask =\n task.kind === 'append'\n ? {\n version: 1,\n kind: 'append',\n id: task.id,\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt,\n events: task.events,\n ...(task.originIndex === undefined\n ? {}\n : { originIndex: task.originIndex }),\n }\n : task.kind === 'clear'\n ? {\n version: 1,\n kind: 'clear',\n id: task.id,\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt,\n ifLastIndex: task.ifLastIndex,\n }\n : {\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt,\n }\n const message: QStashMessage = {\n ...normalizedTask,\n ...(outage === undefined ? {} : { _a2QStash: outage }),\n }\n const sizeCheckedMessage: QStashMessage =\n outage === undefined\n ? {\n ...normalizedTask,\n _a2QStash:\n normalizedTask.kind !== 'drain'\n ? { version: 1, generation: Number.MAX_SAFE_INTEGER }\n : {\n version: 1,\n generation: Number.MAX_SAFE_INTEGER,\n rootDueAt: dueAt,\n },\n }\n : message\n let bodyBytes: number\n try {\n bodyBytes = new TextEncoder().encode(\n JSON.stringify(sizeCheckedMessage),\n ).byteLength\n } catch (error) {\n throw classifySchedulerSendFailure(error, 'terminal')\n }\n if (bodyBytes > maxBodyBytes) {\n throw classifySchedulerSendFailure(\n new RangeError('qstash scheduler message exceeds maxBodyBytes'),\n 'terminal',\n )\n }\n const deduplicationId =\n outage !== undefined\n ? await storeOutageDeduplicationId(task, outage)\n : task.kind === 'append'\n ? await digestId(['a2:scheduler-qstash:append:v1', url, task.id])\n : task.kind === 'clear'\n ? await digestId(['a2:scheduler-qstash:clear:v1', url, task.id])\n : await digestId([\n 'a2:scheduler-qstash:v1',\n url,\n task.contract,\n task.sessionId,\n dueAt,\n ])\n const trustedSource =\n inferredVercelPreview === true\n ? previewTrustedSource(Math.max(nowMs, dueAt))\n : undefined\n const deliveryHeaders = publishHeaders(configuredHeaders, trustedSource)\n const t = await transport()\n try {\n await t.publish({\n url,\n body: message,\n ...deliveryHeaders,\n ...(dueAt <= nowMs ? {} : { notBefore: dueAt / 1_000 }),\n deduplicationId,\n ...(retries === undefined ? {} : { retries }),\n })\n } catch (error) {\n throw classifyQStashPublishFailure(error)\n }\n }\n\n const sendStoreOutageSuccessor = async (\n task: SchedulerTask,\n outage?: QStashStoreOutage,\n ): Promise<void> => {\n const generation = (outage?.generation ?? -1) + 1\n const exponent = Math.min(generation, 6)\n const delayMs = Math.min(\n STORE_OUTAGE_MAX_DELAY_MS,\n STORE_OUTAGE_BASE_DELAY_MS * 4 ** exponent,\n )\n const nowMs = clock.now().getTime()\n const dueAt = Math.ceil((nowMs + delayMs) / 1_000) * 1_000\n const nextOutage: QStashStoreOutage =\n task.kind !== 'drain'\n ? { version: 1, generation }\n : { version: 1, generation, rootDueAt: outage?.rootDueAt ?? task.dueAt }\n await sendTask({ ...task, dueAt }, nextOutage)\n }\n\n const successorDueAt = (message: { dueAt: number }): number => {\n const minimumDueAt =\n clock.now().getTime() +\n DRAIN_TIMINGS.claimTtlMs +\n DRAIN_TIMINGS.watchdogGraceMs\n const beats = Math.max(\n 1,\n Math.ceil(\n (minimumDueAt - message.dueAt) / DRAIN_TIMINGS.claimHeartbeatMs,\n ),\n )\n return message.dueAt + beats * DRAIN_TIMINGS.claimHeartbeatMs\n }\n\n return {\n async schedule(task) {\n await sendTask(task)\n },\n\n handler(...servers: DrainableServer[]) {\n assertSchedulerTargets(servers)\n const byContract = new Map<string, DrainableServer>()\n for (const server of servers) {\n byContract.set(server.contract.name, server)\n }\n return async (req: Request): Promise<Response> => {\n const signature = req.headers.get('upstash-signature')\n if (!signature) return response.unauthorized()\n\n const body = await readBoundedBody(req, maxBodyBytes)\n if (body === null) return response.tooLarge()\n const upstashRegion = req.headers.get('upstash-region')\n const t = await transport()\n try {\n const valid = await t.verify({\n signature,\n body,\n url,\n ...(upstashRegion === null ? {} : { upstashRegion }),\n })\n if (!valid) return response.unauthorized()\n } catch {\n return response.unauthorized()\n }\n\n const parsed = parseTask(body)\n if (!parsed) return response.malformed()\n const { task, outage } = parsed\n\n const server = byContract.get(task.contract)\n if (!server) {\n throw new Error(\n `a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`,\n )\n }\n const internals = serverInternals.get(server)\n if (task.kind !== 'drain') {\n try {\n if (task.kind === 'append')\n await deliverSchedulerAppend(server, task)\n else await deliverSchedulerClear(server, task)\n } catch (error) {\n if (!storeUnavailable(error)) throw error\n await sendStoreOutageSuccessor(task, outage)\n }\n return response.ok()\n }\n\n const nextDueAt = successorDueAt(task)\n let result: DrainResult | { settled: boolean; outcome: 'unknown' }\n try {\n result = internals\n ? await internals.schedulerDrain(task.sessionId, {\n watchdogDueAt: nextDueAt,\n })\n : {\n ...(await server.drain(task.sessionId)),\n outcome: 'unknown' as const,\n }\n } catch (error) {\n if (!storeUnavailable(error)) throw error\n await sendStoreOutageSuccessor(task, outage)\n return response.ok()\n }\n if (result.settled) return response.ok()\n\n if (result.outcome === 'busy') {\n if (result.schedulerArm) await result.schedulerArm\n else {\n await sendTask({\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt: nextDueAt,\n })\n }\n return response.ok()\n }\n\n // QStash's per-message retry default can be lower than A2's failure\n // budget. Hand stalled work to a fresh durable message so recovery\n // continues until the store reports success or dead-letter. If this\n // publish fails, the throw keeps the current delivery retryable.\n await sendTask({\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt: nextDueAt,\n })\n return response.ok()\n }\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"scheduler-qstash.js","names":[],"sources":["../src/scheduler-qstash.ts"],"sourcesContent":["/**\n * experimental-a2/scheduler-qstash — HTTP scheduler over Upstash QStash.\n *\n * QStash publishes one signed HTTP delivery per scheduler task. Drain tasks\n * use an absolute, whole-second `notBefore` and a fixed-size deduplication id;\n * append and clear tasks hash their stable task id with the callback URL, each\n * under its own version tag.\n * Store-outage successors carry a signed private generation and back off to\n * one day without deriving their deduplication identity from the local clock.\n * `handler(...servers)` verifies the signature over the raw request body\n * before parsing it, then applies the same drain protocol as the Vercel\n * scheduler adapter.\n *\n * `@upstash/qstash` is an optional peer dependency, imported lazily. The\n * structural `transport` option keeps its types out of A2's public surface and\n * gives tests a provider-faithful injection seam.\n */\n\nimport {\n assertSchedulerTargets,\n classifySchedulerSendFailure,\n DRAIN_TIMINGS,\n serverInternals,\n type DrainResult,\n} from './internal.ts'\nimport { A2Error } from './errors.ts'\nimport { platformVercelOidcToken } from './platform.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport { normalizeSchedulerTask, type SchedulerTask } from './scheduler-task.ts'\nimport {\n deliverSchedulerAppend,\n deliverSchedulerClear,\n type A2Scheduler,\n type DrainableServer,\n} from './server.ts'\nimport { SYSTEM_CLOCK, type Clock } from './store.ts'\n\ntype QStashStoreOutage = {\n version: 1\n /** Zero is the first successor created for a store outage. */\n generation: number\n /** The first failing drain task's due time. Append and clear identity lives on the task. */\n rootDueAt?: number\n}\n\ntype QStashMessage = SchedulerTask & {\n /** Provider-private state authenticated as part of QStash's signed body. */\n _a2QStash?: QStashStoreOutage\n}\n\nexport type QStashPublishRequest = {\n url: string\n body: QStashMessage\n /** Headers the transport must deliver to the callback. */\n headers?: Readonly<Record<string, string>>\n /** Header names the transport must redact from provider dashboard/API views. */\n redact?: {\n header: readonly string[]\n }\n /** Whole-second Unix timestamp. Omitted for an immediate delivery. */\n notBefore?: number\n deduplicationId: string\n /** Delivery retries after the initial attempt. */\n retries?: number\n}\n\n/** The request shape passed to an injected `@upstash/qstash` client. */\nexport type QStashClientPublishRequest = Omit<\n QStashPublishRequest,\n 'headers' | 'redact'\n> & {\n headers?: Record<string, string>\n redact?: {\n header: string[]\n }\n}\n\n/** The outbound part of `@upstash/qstash` used by the built-in transport. */\nexport type QStashPublishingClient = {\n publishJSON(request: QStashClientPublishRequest): Promise<unknown>\n}\n\nexport type QStashVerifyRequest = {\n signature: string\n /** The untouched request body covered by the signature. */\n body: string\n /** Exact destination URL, checked against the signature subject. */\n url: string\n upstashRegion?: string\n}\n\n/** The small part of `@upstash/qstash` this adapter needs. */\nexport type QStashTransport = {\n publish(request: QStashPublishRequest): Promise<unknown>\n verify(request: QStashVerifyRequest): Promise<boolean>\n}\n\nexport type QStashSchedulerOptions = {\n /** Exact URL mounted with `scheduler.handler(...)`. Inferred in local dev and on Vercel. */\n url?: string\n /** QStash API token. Default: `QSTASH_TOKEN`. */\n token?: string\n /** QStash signing key. Default: `QSTASH_CURRENT_SIGNING_KEY`. */\n currentSigningKey?: string\n /** Next QStash signing key. Default: `QSTASH_NEXT_SIGNING_KEY`. */\n nextSigningKey?: string\n /** Delivery retries after the initial attempt. Default: provider default. */\n retries?: number\n /** Maximum callback body and outbound task with outage headroom. Default 1 MiB. */\n maxBodyBytes?: number\n /** Static callback headers. QStash receives them; A2 requests dashboard/API redaction. */\n headers?: Readonly<Record<string, string>>\n /** Injectable publishing client. The built-in receiver still verifies callbacks. */\n client?: QStashPublishingClient\n /** Injectable transport (tests). Default: `@upstash/qstash`. */\n transport?: QStashTransport\n /** Injectable clock for due-time calculations (tests). */\n clock?: Clock\n}\n\nconst qstashTransport = async (\n options: Pick<\n QStashSchedulerOptions,\n 'token' | 'currentSigningKey' | 'nextSigningKey' | 'client'\n >,\n developmentMode: boolean | undefined,\n): Promise<QStashTransport> => {\n const sdk = await import('@upstash/qstash').catch(() => {\n throw classifySchedulerSendFailure(\n new Error(\n \"experimental-a2/scheduler-qstash needs the '@upstash/qstash' package (optional peer dependency) — install it, or inject a transport\",\n ),\n 'terminal',\n )\n })\n const developmentOverride =\n developmentMode === undefined ? {} : { devMode: developmentMode }\n const client: QStashPublishingClient =\n options.client ??\n new sdk.Client({\n ...(options.token === undefined ? {} : { token: options.token }),\n ...developmentOverride,\n })\n const receiver = new sdk.Receiver({\n ...(options.currentSigningKey === undefined\n ? {}\n : { currentSigningKey: options.currentSigningKey }),\n ...(options.nextSigningKey === undefined\n ? {}\n : { nextSigningKey: options.nextSigningKey }),\n ...developmentOverride,\n })\n return {\n publish: (request) => {\n const clientRequest: QStashClientPublishRequest = {\n url: request.url,\n body: request.body,\n ...(request.headers === undefined\n ? {}\n : { headers: { ...request.headers } }),\n ...(request.redact === undefined\n ? {}\n : { redact: { header: [...request.redact.header] } }),\n deduplicationId: request.deduplicationId,\n ...(request.retries === undefined ? {} : { retries: request.retries }),\n ...(request.notBefore === undefined\n ? {}\n : { notBefore: request.notBefore }),\n }\n return client.publishJSON(clientRequest)\n },\n verify: (request) => receiver.verify(request),\n }\n}\n\nconst digestId = async (\n parts: readonly (string | number)[],\n): Promise<string> => {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(JSON.stringify(parts)),\n )\n const hex = Array.from(new Uint8Array(digest), (byte) =>\n byte.toString(16).padStart(2, '0'),\n ).join('')\n return `a2_${hex}`\n}\n\ntype ParsedQStashMessage = {\n task: SchedulerTask\n outage?: QStashStoreOutage\n}\n\nconst recordOf = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nconst TRUSTED_SOURCE_HEADER = 'x-vercel-trusted-oidc-idp-token'\nconst TRUSTED_SOURCE_DELIVERY_MARGIN_MS = 60_000\nconst RESERVED_CALLBACK_HEADERS = new Set([\n 'connection',\n 'content-length',\n 'host',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'x-vercel-oidc-token',\n TRUSTED_SOURCE_HEADER,\n])\n\nconst callbackHeaders = (\n value: Readonly<Record<string, string>> | undefined,\n): Readonly<Record<string, string>> | undefined => {\n if (value === undefined) return undefined\n\n let entries: [string, string][]\n try {\n entries = Object.entries(value)\n } catch {\n throw new TypeError('qstash() headers must be an HTTP header record')\n }\n\n const normalized: [string, string][] = []\n const seen = new Set<string>()\n for (const [name, headerValue] of entries) {\n if (typeof headerValue !== 'string') {\n throw new TypeError('qstash() header values must be strings')\n }\n let entry: [string, string] | undefined\n try {\n entry = [...new Headers([[name, headerValue]]).entries()][0]\n } catch {\n throw new TypeError(\n 'qstash() headers must contain valid HTTP header names and values',\n )\n }\n if (entry === undefined) {\n throw new TypeError(\n 'qstash() headers must contain valid HTTP header names and values',\n )\n }\n const [normalizedName, normalizedValue] = entry\n if (seen.has(normalizedName)) {\n throw new TypeError(\n `qstash() headers contain duplicate name '${normalizedName}'`,\n )\n }\n if (\n normalizedName.startsWith('upstash-') ||\n normalizedName.startsWith('content-type') ||\n RESERVED_CALLBACK_HEADERS.has(normalizedName)\n ) {\n throw new TypeError(\n `qstash() headers cannot set reserved header '${normalizedName}'`,\n )\n }\n seen.add(normalizedName)\n normalized.push([normalizedName, normalizedValue])\n }\n\n return normalized.length === 0\n ? undefined\n : Object.freeze(Object.fromEntries(normalized))\n}\n\nconst publishHeaders = (\n configured: Readonly<Record<string, string>> | undefined,\n generated: Pick<QStashPublishRequest, 'headers' | 'redact'> | undefined,\n): Pick<QStashPublishRequest, 'headers' | 'redact'> | undefined => {\n if (configured === undefined && generated === undefined) return undefined\n\n const headers = new Headers()\n const redacted = new Set<string>()\n for (const [name, value] of Object.entries(configured ?? {})) {\n headers.set(name, value)\n redacted.add(name)\n }\n for (const [name, value] of Object.entries(generated?.headers ?? {})) {\n headers.set(name, value)\n redacted.add(name)\n }\n for (const name of generated?.redact?.header ?? []) {\n const normalized = [...new Headers([[name, 'redacted']]).keys()][0]\n if (normalized === undefined) {\n throw new TypeError('qstash generated an invalid redaction header')\n }\n redacted.add(normalized)\n }\n\n const headerEntries = [...headers.entries()]\n return {\n ...(headerEntries.length === 0\n ? {}\n : { headers: Object.fromEntries(headerEntries) }),\n ...(redacted.size === 0\n ? {}\n : { redact: { header: [...redacted].toSorted() } }),\n }\n}\n\nconst jwtExpirationMs = (token: string): number | null => {\n const parts = token.split('.')\n const payloadPart = parts.length === 3 ? parts[1] : undefined\n if (\n payloadPart === undefined ||\n payloadPart.length === 0 ||\n !/^[A-Za-z0-9_-]+$/.test(payloadPart) ||\n payloadPart.length % 4 === 1\n ) {\n return null\n }\n\n try {\n const decode = globalThis.atob\n if (typeof decode !== 'function') return null\n const base64 = payloadPart\n .replaceAll('-', '+')\n .replaceAll('_', '/')\n .padEnd(payloadPart.length + ((4 - (payloadPart.length % 4)) % 4), '=')\n const binary = decode(base64)\n const bytes = Uint8Array.from(binary, (character) =>\n character.charCodeAt(0),\n )\n const payload = recordOf(JSON.parse(new TextDecoder().decode(bytes)))\n const expiration = payload?.['exp']\n if (\n typeof expiration !== 'number' ||\n !Number.isSafeInteger(expiration) ||\n expiration <= 0 ||\n expiration > Number.MAX_SAFE_INTEGER / 1_000\n ) {\n return null\n }\n return expiration * 1_000\n } catch {\n return null\n }\n}\n\nconst previewTrustedSource = (\n firstDeliveryAt: number,\n): Pick<QStashPublishRequest, 'headers' | 'redact'> | undefined => {\n const token = platformVercelOidcToken()\n if (token === null) return undefined\n const expiration = jwtExpirationMs(token)\n if (\n expiration === null ||\n firstDeliveryAt + TRUSTED_SOURCE_DELIVERY_MARGIN_MS > expiration\n ) {\n return undefined\n }\n return {\n headers: { [TRUSTED_SOURCE_HEADER]: token },\n redact: { header: [TRUSTED_SOURCE_HEADER] },\n }\n}\n\nconst RETRYABLE_HTTP_CLIENT_STATUSES = new Set([408, 409, 425, 429])\n\nconst httpStatusOf = (error: unknown): number | undefined => {\n try {\n const record = recordOf(error)\n if (!record) return undefined\n const response = recordOf(record['response'])\n const statuses = [\n record['status'],\n record['statusCode'],\n response?.['status'],\n ].filter(\n (value): value is number =>\n typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= 100 &&\n value <= 599,\n )\n const [status] = statuses\n if (status === undefined) return undefined\n return statuses.every((candidate) => candidate === status)\n ? status\n : undefined\n } catch {\n return undefined\n }\n}\n\nconst classifyQStashPublishFailure = (error: unknown): unknown => {\n const status = httpStatusOf(error)\n const terminal =\n status !== undefined &&\n status >= 400 &&\n status < 500 &&\n !RETRYABLE_HTTP_CLIENT_STATUSES.has(status)\n return classifySchedulerSendFailure(\n error,\n terminal ? 'terminal' : 'retryable',\n )\n}\n\nconst parseTask = (body: string): ParsedQStashMessage | null => {\n let value: unknown\n try {\n value = JSON.parse(body)\n } catch {\n return null\n }\n const task = normalizeSchedulerTask(value)\n const record = recordOf(value)\n if (!task || !record) return null\n\n const encodedOutage = record['_a2QStash']\n if (encodedOutage === undefined) return { task }\n const outage = recordOf(encodedOutage)\n const generation = outage?.['generation']\n if (\n !outage ||\n outage['version'] !== 1 ||\n typeof generation !== 'number' ||\n !Number.isSafeInteger(generation) ||\n generation < 0 ||\n generation >= Number.MAX_SAFE_INTEGER\n ) {\n return null\n }\n\n const rootDueAt = outage['rootDueAt']\n if (task.kind === 'drain') {\n if (\n typeof rootDueAt !== 'number' ||\n !Number.isInteger(rootDueAt) ||\n rootDueAt % 1_000 !== 0\n ) {\n return null\n }\n } else if (rootDueAt !== undefined) {\n return null\n }\n\n return {\n task,\n outage: {\n version: 1,\n generation,\n ...(rootDueAt === undefined ? {} : { rootDueAt }),\n },\n }\n}\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024\nconst STORE_OUTAGE_BASE_DELAY_MS = 30_000\nconst STORE_OUTAGE_MAX_DELAY_MS = 24 * 60 * 60 * 1_000\n\nconst readBoundedBody = async (\n request: Request,\n maxBytes: number,\n): Promise<string | null> => {\n const declared = request.headers.get('content-length')\n if (\n declared !== null &&\n /^\\d+$/.test(declared) &&\n Number(declared) > maxBytes\n ) {\n return null\n }\n\n if (request.body === null) return ''\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let length = 0\n try {\n for (;;) {\n // oxlint-disable-next-line no-await-in-loop\n const { done, value } = await reader.read()\n if (done) break\n length += value.byteLength\n if (length > maxBytes) {\n // oxlint-disable-next-line no-await-in-loop\n await reader.cancel().catch(() => {})\n return null\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n const bytes = new Uint8Array(length)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n return new TextDecoder().decode(bytes)\n}\n\nconst storeUnavailable = (error: unknown): error is A2Error =>\n error instanceof A2Error && error.code === 'STORE_UNAVAILABLE'\n\nconst response = {\n ok: (): Response => new Response('ok'),\n unauthorized: (): Response => new Response('unauthorized', { status: 401 }),\n malformed: (): Response =>\n new Response('invalid a2 scheduler message', {\n status: 489,\n headers: { 'Upstash-NonRetryable-Error': 'true' },\n }),\n tooLarge: (): Response =>\n new Response('a2 scheduler message is too large', {\n status: 489,\n headers: { 'Upstash-NonRetryable-Error': 'true' },\n }),\n}\n\nconst CALLBACK_PATH = '/api/a2/scheduler'\nconst DEFAULT_DEVELOPMENT_PORT = 3_000\n\nconst developmentCallbackUrl = (portValue: string | undefined): string => {\n const port =\n portValue === undefined || portValue === ''\n ? DEFAULT_DEVELOPMENT_PORT\n : Number(portValue)\n if (\n !Number.isSafeInteger(port) ||\n port < 1 ||\n port > 65_535 ||\n (portValue !== undefined && portValue !== '' && !/^\\d+$/.test(portValue))\n ) {\n throw new TypeError(\n 'qstash() could not infer url: PORT must be an integer from 1 to 65535 for local development',\n )\n }\n return `http://localhost:${String(port)}${CALLBACK_PATH}`\n}\n\ntype CallbackResolution = {\n callbackUrl: string\n /** An SDK override. Undefined preserves the SDK's environment behavior. */\n developmentMode?: boolean\n inferredVercelPreview?: true\n}\n\nconst inferCallbackUrl = (): CallbackResolution => {\n const env = typeof process === 'undefined' ? undefined : process.env\n const vercelEnv = env?.['VERCEL_ENV']\n const variable =\n env?.['VERCEL'] !== '1'\n ? null\n : vercelEnv === 'production'\n ? 'VERCEL_PROJECT_PRODUCTION_URL'\n : vercelEnv === 'preview'\n ? 'VERCEL_BRANCH_URL'\n : null\n if (variable !== null) {\n const host = env?.[variable]\n if (!host) {\n throw new TypeError(\n `qstash() could not infer url: ${variable} is missing`,\n )\n }\n let origin: URL\n try {\n origin = new URL(`https://${host}`)\n } catch {\n throw new TypeError(\n `qstash() could not infer url: ${variable} must be a hostname`,\n )\n }\n if (\n origin.host !== host ||\n origin.pathname !== '/' ||\n origin.search !== '' ||\n origin.hash !== '' ||\n origin.username !== '' ||\n origin.password !== '' ||\n origin.port !== ''\n ) {\n throw new TypeError(\n `qstash() could not infer url: ${variable} must be a hostname`,\n )\n }\n return {\n callbackUrl: new URL(CALLBACK_PATH, origin).toString(),\n developmentMode: false,\n ...(vercelEnv === 'preview' ? { inferredVercelPreview: true } : {}),\n }\n }\n\n if (env === undefined) {\n throw new TypeError(\n 'qstash() needs url when the runtime cannot identify local development',\n )\n }\n\n if (env['NODE_ENV'] === 'production') {\n throw new TypeError(\n 'qstash() needs url in production outside a Vercel production or preview deployment',\n )\n }\n\n return {\n callbackUrl: developmentCallbackUrl(env['PORT']),\n developmentMode: true,\n }\n}\n\nexport function qstash(options: QStashSchedulerOptions = {}): A2Scheduler {\n const env = typeof process === 'undefined' ? undefined : process.env\n const resolution: CallbackResolution =\n options.url === undefined\n ? inferCallbackUrl()\n : {\n callbackUrl: options.url,\n ...(env?.['NODE_ENV'] === 'production'\n ? { developmentMode: false }\n : {}),\n }\n const { callbackUrl, developmentMode, inferredVercelPreview } = resolution\n let parsedUrl: URL\n try {\n parsedUrl = new URL(callbackUrl)\n } catch {\n throw new TypeError('qstash() needs an absolute callback url')\n }\n if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {\n throw new TypeError('qstash() callback url must use http or https')\n }\n if (options.client !== undefined && options.transport !== undefined) {\n throw new TypeError('qstash() cannot combine client and transport')\n }\n if (options.client !== undefined && options.token !== undefined) {\n throw new TypeError('qstash() cannot combine client and token')\n }\n const url = parsedUrl.toString()\n const configuredHeaders = callbackHeaders(options.headers)\n const { retries } = options\n if (\n retries !== undefined &&\n (!Number.isSafeInteger(retries) || retries < 0)\n ) {\n throw new TypeError('qstash() retries must be a non-negative integer')\n }\n const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n if (!Number.isSafeInteger(maxBodyBytes) || maxBodyBytes < 1) {\n throw new TypeError('qstash() maxBodyBytes must be a positive integer')\n }\n const clock = options.clock ?? SYSTEM_CLOCK\n\n // Lazy: construction does no I/O and does not require the optional peer.\n const transport = retryableLazy(() =>\n options.transport\n ? Promise.resolve(options.transport)\n : qstashTransport(options, developmentMode),\n ).get\n\n const storeOutageDeduplicationId = (\n task: SchedulerTask,\n outage: QStashStoreOutage,\n ): Promise<string> => {\n if (task.kind === 'clear') {\n return digestId([\n 'a2:scheduler-qstash:store-outage:clear:v1',\n url,\n task.contract,\n task.sessionId,\n task.id,\n outage.generation,\n ])\n }\n if (task.kind === 'append') {\n return digestId([\n 'a2:scheduler-qstash:store-outage:append:v1',\n url,\n task.contract,\n task.sessionId,\n task.id,\n outage.generation,\n ])\n }\n const { rootDueAt } = outage\n if (rootDueAt === undefined) {\n throw new TypeError('qstash store-outage drain needs a root dueAt')\n }\n return digestId([\n 'a2:scheduler-qstash:store-outage:drain:v1',\n url,\n task.contract,\n task.sessionId,\n rootDueAt,\n outage.generation,\n ])\n }\n\n const sendTask = async (\n task: SchedulerTask,\n outage?: QStashStoreOutage,\n ): Promise<void> => {\n const nowMs = clock.now().getTime()\n const requestedDueAt = task.dueAt\n if (!Number.isFinite(requestedDueAt)) {\n throw classifySchedulerSendFailure(\n new TypeError('qstash scheduler dueAt must be a finite number'),\n 'terminal',\n )\n }\n const dueAt =\n requestedDueAt <= nowMs\n ? Math.floor(nowMs / 1_000) * 1_000\n : Math.ceil(requestedDueAt / 1_000) * 1_000\n const normalizedTask: SchedulerTask =\n task.kind === 'append'\n ? {\n version: 1,\n kind: 'append',\n id: task.id,\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt,\n events: task.events,\n ...(task.originIndex === undefined\n ? {}\n : { originIndex: task.originIndex }),\n }\n : task.kind === 'clear'\n ? {\n version: 1,\n kind: 'clear',\n id: task.id,\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt,\n ifLastIndex: task.ifLastIndex,\n }\n : {\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt,\n }\n const message: QStashMessage = {\n ...normalizedTask,\n ...(outage === undefined ? {} : { _a2QStash: outage }),\n }\n const sizeCheckedMessage: QStashMessage =\n outage === undefined\n ? {\n ...normalizedTask,\n _a2QStash:\n normalizedTask.kind !== 'drain'\n ? { version: 1, generation: Number.MAX_SAFE_INTEGER }\n : {\n version: 1,\n generation: Number.MAX_SAFE_INTEGER,\n rootDueAt: dueAt,\n },\n }\n : message\n let bodyBytes: number\n try {\n bodyBytes = new TextEncoder().encode(\n JSON.stringify(sizeCheckedMessage),\n ).byteLength\n } catch (error) {\n throw classifySchedulerSendFailure(error, 'terminal')\n }\n if (bodyBytes > maxBodyBytes) {\n throw classifySchedulerSendFailure(\n new RangeError('qstash scheduler message exceeds maxBodyBytes'),\n 'terminal',\n )\n }\n const deduplicationId =\n outage !== undefined\n ? await storeOutageDeduplicationId(task, outage)\n : task.kind === 'append'\n ? await digestId(['a2:scheduler-qstash:append:v1', url, task.id])\n : task.kind === 'clear'\n ? await digestId(['a2:scheduler-qstash:clear:v1', url, task.id])\n : await digestId([\n 'a2:scheduler-qstash:v1',\n url,\n task.contract,\n task.sessionId,\n dueAt,\n ])\n const trustedSource =\n inferredVercelPreview === true\n ? previewTrustedSource(Math.max(nowMs, dueAt))\n : undefined\n const deliveryHeaders = publishHeaders(configuredHeaders, trustedSource)\n const t = await transport()\n try {\n await t.publish({\n url,\n body: message,\n ...deliveryHeaders,\n ...(dueAt <= nowMs ? {} : { notBefore: dueAt / 1_000 }),\n deduplicationId,\n ...(retries === undefined ? {} : { retries }),\n })\n } catch (error) {\n throw classifyQStashPublishFailure(error)\n }\n }\n\n const sendStoreOutageSuccessor = async (\n task: SchedulerTask,\n outage?: QStashStoreOutage,\n ): Promise<void> => {\n const generation = (outage?.generation ?? -1) + 1\n const exponent = Math.min(generation, 6)\n const delayMs = Math.min(\n STORE_OUTAGE_MAX_DELAY_MS,\n STORE_OUTAGE_BASE_DELAY_MS * 4 ** exponent,\n )\n const nowMs = clock.now().getTime()\n const dueAt = Math.ceil((nowMs + delayMs) / 1_000) * 1_000\n const nextOutage: QStashStoreOutage =\n task.kind !== 'drain'\n ? { version: 1, generation }\n : { version: 1, generation, rootDueAt: outage?.rootDueAt ?? task.dueAt }\n await sendTask({ ...task, dueAt }, nextOutage)\n }\n\n const successorDueAt = (message: { dueAt: number }): number => {\n const minimumDueAt =\n clock.now().getTime() +\n DRAIN_TIMINGS.claimTtlMs +\n DRAIN_TIMINGS.watchdogGraceMs\n const beats = Math.max(\n 1,\n Math.ceil(\n (minimumDueAt - message.dueAt) / DRAIN_TIMINGS.claimHeartbeatMs,\n ),\n )\n return message.dueAt + beats * DRAIN_TIMINGS.claimHeartbeatMs\n }\n\n return {\n async schedule(task) {\n await sendTask(task)\n },\n\n handler(...servers: DrainableServer[]) {\n assertSchedulerTargets(servers)\n const byContract = new Map<string, DrainableServer>()\n for (const server of servers) {\n byContract.set(server.contract.name, server)\n }\n return async (req: Request): Promise<Response> => {\n const signature = req.headers.get('upstash-signature')\n if (!signature) return response.unauthorized()\n\n const body = await readBoundedBody(req, maxBodyBytes)\n if (body === null) return response.tooLarge()\n const upstashRegion = req.headers.get('upstash-region')\n const t = await transport()\n try {\n const valid = await t.verify({\n signature,\n body,\n url,\n ...(upstashRegion === null ? {} : { upstashRegion }),\n })\n if (!valid) return response.unauthorized()\n } catch {\n return response.unauthorized()\n }\n\n const parsed = parseTask(body)\n if (!parsed) return response.malformed()\n const { task, outage } = parsed\n\n const server = byContract.get(task.contract)\n if (!server) {\n throw new Error(\n `a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`,\n )\n }\n const internals = serverInternals.get(server)\n if (task.kind !== 'drain') {\n try {\n if (task.kind === 'append')\n await deliverSchedulerAppend(server, task)\n else await deliverSchedulerClear(server, task)\n } catch (error) {\n if (!storeUnavailable(error)) throw error\n await sendStoreOutageSuccessor(task, outage)\n }\n return response.ok()\n }\n\n const nextDueAt = successorDueAt(task)\n let result: DrainResult | { settled: boolean; outcome: 'unknown' }\n try {\n result = internals\n ? await internals.schedulerDrain(task.sessionId, {\n watchdogDueAt: nextDueAt,\n })\n : {\n ...(await server.drain(task.sessionId)),\n outcome: 'unknown' as const,\n }\n } catch (error) {\n if (!storeUnavailable(error)) throw error\n await sendStoreOutageSuccessor(task, outage)\n return response.ok()\n }\n if (result.settled) return response.ok()\n\n if (result.outcome === 'busy' || result.outcome === 'stalled') {\n if (result.schedulerArm) await result.schedulerArm\n else {\n await sendTask({\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt: nextDueAt,\n })\n }\n return response.ok()\n }\n\n // A drain whose outcome is unknown hands its work to a fresh durable\n // message: QStash's per-message retry default can be lower than A2's\n // failure budget. If this publish fails, the throw keeps the current\n // delivery retryable.\n await sendTask({\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt: nextDueAt,\n })\n return response.ok()\n }\n },\n }\n}\n"],"mappings":"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"}
@@ -1,13 +1,13 @@
1
- import { r as Clock } from "./store-Djdv0bnZ.js";
2
- import { P as SchedulerTask, t as A2Scheduler } from "./server-En7cbi3v.js";
1
+ import { r as Clock } from "./store-CyLUy86v.js";
2
+ import { P as SchedulerTask, t as A2Scheduler } from "./server-DYAATFuT.js";
3
3
  //#region src/scheduler-vercel.d.ts
4
4
  /**
5
- * The "not settled yet" signal for a stalled handler — an expected state,
6
- * not a transport failure. The default transport classifies it in
7
- * `@vercel/queue`'s `retry` option and reschedules the delivery quietly
5
+ * The "not settled yet" signal for a drain whose outcome is unknown — an
6
+ * expected state, not a transport failure. The default transport classifies
7
+ * it in `@vercel/queue`'s `retry` option and reschedules the delivery quietly
8
8
  * (`{ afterSeconds }` → visibility change → 200 response, no error log)
9
- * instead of letting it surface as a thrown 500. Busy deliveries use a
10
- * fresh watchdog instead. Custom transports should treat this class the
9
+ * instead of letting it surface as a thrown 500. Busy and stalled deliveries
10
+ * use a fresh watchdog instead. Custom transports should treat this class the
11
11
  * same way when their protocol has a polite reschedule.
12
12
  */
13
13
  declare class DrainUnsettled extends Error {}
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler-vercel.d.ts","names":[],"sources":["../src/scheduler-vercel.ts"],"mappings":";;;;;;;;;;;;cAsDa,uBAAuB;KAExB;EACV;EACA;;;KAIU;EACV,KACE,eACA,SAAS,eACT;IACE;IACA;IACA;MAED;;;;;;;;EAQH,eACE,UACE,kBACA,UAAU,0BACP,iBACH,KAAK,YAAY,QAAQ;;EAE7B,YAAY;;KAGF;;EAEV;;EAEA;;EAEA,YAAY;;EAEZ,QAAQ;;iBA8HM,aAAa,UAAS,sBAA2B"}
1
+ {"version":3,"file":"scheduler-vercel.d.ts","names":[],"sources":["../src/scheduler-vercel.ts"],"mappings":";;;;;;;;;;;;cAqDa,uBAAuB;KAExB;EACV;EACA;;;KAIU;EACV,KACE,eACA,SAAS,eACT;IACE;IACA;IACA;MAED;;;;;;;;EAQH,eACE,UACE,kBACA,UAAU,0BACP,iBACH,KAAK,YAAY,QAAQ;;EAE7B,YAAY;;KAGF;;EAEV;;EAEA;;EAEA,YAAY;;EAEZ,QAAQ;;iBA8HM,aAAa,UAAS,sBAA2B"}
@@ -1,5 +1,5 @@
1
- import { d as assertSchedulerTargets, f as classifySchedulerSendFailure, t as DRAIN_TIMINGS, v as serverInternals } from "./internal-B2T3U4Rq.js";
2
- import { n as deliverSchedulerAppend, r as deliverSchedulerClear } from "./server-Dw_k_7kO.js";
1
+ import { d as assertSchedulerTargets, f as classifySchedulerSendFailure, t as DRAIN_TIMINGS, v as serverInternals } from "./internal-CscSY7K_.js";
2
+ import { n as deliverSchedulerAppend, r as deliverSchedulerClear } from "./server-D-Y4pqwu.js";
3
3
  import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
4
4
  import { n as SYSTEM_CLOCK } from "./store-N8PXxDAS.js";
5
5
  import { t as normalizeSchedulerTask } from "./scheduler-task-X1SToDjP.js";
@@ -21,20 +21,19 @@ import { createHash } from "node:crypto";
21
21
  * the configured deployment scope for sending and receiving.
22
22
  * - `handler(...servers)` is the delivery route: look up the server for
23
23
  * the contract named in the message and drain it. Settled drains ack;
24
- * contention arms a later watchdog before acking; handler failure keeps
25
- * the current message and redelivers with backoff.
24
+ * contention and handler failure arm a later watchdog before acking.
26
25
  *
27
26
  * `@vercel/queue` is an optional peer dependency, imported lazily. The
28
27
  * `transport` option is the injection seam — tests run a real little
29
28
  * in-memory queue through the same interface.
30
29
  */
31
30
  /**
32
- * The "not settled yet" signal for a stalled handler — an expected state,
33
- * not a transport failure. The default transport classifies it in
34
- * `@vercel/queue`'s `retry` option and reschedules the delivery quietly
31
+ * The "not settled yet" signal for a drain whose outcome is unknown — an
32
+ * expected state, not a transport failure. The default transport classifies
33
+ * it in `@vercel/queue`'s `retry` option and reschedules the delivery quietly
35
34
  * (`{ afterSeconds }` → visibility change → 200 response, no error log)
36
- * instead of letting it surface as a thrown 500. Busy deliveries use a
37
- * fresh watchdog instead. Custom transports should treat this class the
35
+ * instead of letting it surface as a thrown 500. Busy and stalled deliveries
36
+ * use a fresh watchdog instead. Custom transports should treat this class the
38
37
  * same way when their protocol has a polite reschedule.
39
38
  */
40
39
  var DrainUnsettled = class extends Error {};
@@ -186,7 +185,7 @@ function vercelQueues(options = {}) {
186
185
  outcome: "unknown"
187
186
  };
188
187
  if (result.settled) return;
189
- if (result.outcome === "busy") {
188
+ if (result.outcome === "busy" || result.outcome === "stalled") {
190
189
  if (result.schedulerArm) await result.schedulerArm;
191
190
  else await sendTask({
192
191
  version: 1,
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler-vercel.js","names":[],"sources":["../src/scheduler-vercel.ts"],"sourcesContent":["/**\n * experimental-a2/scheduler-vercel — queue-backed scheduler over Vercel Queues.\n *\n * `vercelQueues()` returns an `A2Scheduler` (a2-implementation.md §9):\n *\n * - drain tasks round the requested watchdog time up to a one-second slot.\n * One message per `(topic, contract, session, dueAt)` coalesces appends,\n * claim renewals and racing callbacks. `DuplicateMessageError` is\n * success because that due-time slot is already durable.\n * - append tasks hash their stable task id with the topic for provider\n * deduplication. Their six-day maximum delay leaves a full day for\n * delivery and retries inside the provider's seven-day retention.\n * Clear tasks follow the same identity and delay rules. All tasks use\n * the configured deployment scope for sending and receiving.\n * - `handler(...servers)` is the delivery route: look up the server for\n * the contract named in the message and drain it. Settled drains ack;\n * contention arms a later watchdog before acking; handler failure keeps\n * the current message and redelivers with backoff.\n *\n * `@vercel/queue` is an optional peer dependency, imported lazily. The\n * `transport` option is the injection seam — tests run a real little\n * in-memory queue through the same interface.\n */\n\nimport { createHash } from 'node:crypto'\nimport { SYSTEM_CLOCK, type Clock } from './store.ts'\nimport {\n assertSchedulerTargets,\n classifySchedulerSendFailure,\n DRAIN_TIMINGS,\n serverInternals,\n} from './internal.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport {\n normalizeSchedulerTask,\n type SchedulerDrainTask,\n type SchedulerTask,\n} from './scheduler-task.ts'\nimport {\n deliverSchedulerAppend,\n deliverSchedulerClear,\n type A2Scheduler,\n type DrainableServer,\n} from './server.ts'\n\n/**\n * The \"not settled yet\" signal for a stalled handler — an expected state,\n * not a transport failure. The default transport classifies it in\n * `@vercel/queue`'s `retry` option and reschedules the delivery quietly\n * (`{ afterSeconds }` → visibility change → 200 response, no error log)\n * instead of letting it surface as a thrown 500. Busy deliveries use a\n * fresh watchdog instead. Custom transports should treat this class the\n * same way when their protocol has a polite reschedule.\n */\nexport class DrainUnsettled extends Error {}\n\nexport type QueueDeliveryMetadata = {\n messageId: string\n deliveryCount: number\n}\n\n/** What this module needs from a Vercel Queues-compatible transport. */\nexport type QueueTransport = {\n send(\n topic: string,\n message: SchedulerTask,\n options?: {\n delaySeconds?: number\n idempotencyKey?: string\n retentionSeconds?: number\n },\n ): Promise<unknown>\n /**\n * Wrap the delivery handler into a route. The handler throws\n * `DrainUnsettled` when the session needs redelivery — transports\n * with a polite reschedule (visibility change) should use it there\n * instead of surfacing an error; anything else thrown is a genuine\n * failure and should stay loud.\n */\n handleCallback(\n handler: (\n message: unknown,\n metadata: QueueDeliveryMetadata,\n ) => Promise<void>,\n ): (req: Request) => Promise<Response>\n /** Is this send error the idempotency-key collision? (= success) */\n isDuplicate(error: unknown): boolean\n}\n\nexport type VercelQueuesOptions = {\n /** Queue topic matching `[A-Za-z0-9_-]+`. Default `'a2'`. */\n topic?: string\n /** Queue SDK deployment scope for all tasks. Default: VERCEL_DEPLOYMENT_ID; null opts out. */\n deploymentId?: string | null\n /** Injectable transport (tests). Default: `@vercel/queue`. */\n transport?: QueueTransport\n /** Injectable clock for due-time calculations (tests). */\n clock?: Clock\n}\n\n/** Vercel Queues rejects shorter visibility changes. */\nconst MIN_VISIBILITY_SECONDS = 30\n\n/** Vercel Queues' maximum message retention and delivery delay. */\nconst MAX_RETENTION_SECONDS = 7 * 24 * 60 * 60\n\n/** Leave the final retention day for delivery and retries. */\nconst MAX_DELAY_SECONDS = 6 * 24 * 60 * 60\n\nconst TOPIC_PATTERN = /^[A-Za-z0-9_-]+$/\nconst RETRYABLE_HTTP_CLIENT_STATUSES = new Set([408, 409, 425, 429])\n/** The SDK's send path maps these statuses to name-only error instances. */\nconst TERMINAL_VERCEL_SEND_ERROR_NAMES = new Set([\n 'BadRequestError',\n 'UnauthorizedError',\n 'ForbiddenError',\n])\n\nconst recordOf = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nconst httpStatusOf = (error: unknown): number | undefined => {\n try {\n const record = recordOf(error)\n if (!record) return undefined\n const response = recordOf(record['response'])\n const statuses = [\n record['status'],\n record['statusCode'],\n response?.['status'],\n ].filter(\n (value): value is number =>\n typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= 100 &&\n value <= 599,\n )\n const [status] = statuses\n if (status === undefined) return undefined\n return statuses.every((candidate) => candidate === status)\n ? status\n : undefined\n } catch {\n return undefined\n }\n}\n\nconst errorNameOf = (error: unknown): string | undefined => {\n try {\n const name = recordOf(error)?.['name']\n return typeof name === 'string' ? name : undefined\n } catch {\n return undefined\n }\n}\n\nconst classifyVercelSendFailure = (error: unknown): unknown => {\n const status = httpStatusOf(error)\n const terminal =\n status === undefined\n ? TERMINAL_VERCEL_SEND_ERROR_NAMES.has(errorNameOf(error) ?? '')\n : status >= 400 &&\n status < 500 &&\n !RETRYABLE_HTTP_CLIENT_STATUSES.has(status)\n return classifySchedulerSendFailure(\n error,\n terminal ? 'terminal' : 'retryable',\n )\n}\n\nconst digestId = (parts: readonly string[]): string =>\n `a2_${createHash('sha256').update(JSON.stringify(parts)).digest('hex')}`\n\n/** Base of the unsettled redelivery backoff, doubled per delivery. */\nconst UNSETTLED_BACKOFF_BASE_SECONDS = 5\n\n/** Redelivery backoff for retry states: base, 2×, 4×… bounded. */\nconst unsettledBackoffSeconds = (deliveryCount: number): number =>\n Math.min(\n Math.max(\n UNSETTLED_BACKOFF_BASE_SECONDS * 2 ** Math.max(deliveryCount - 1, 0),\n MIN_VISIBILITY_SECONDS,\n ),\n 60,\n )\n\nconst vercelQueueTransport = async (\n clientOptions: Pick<VercelQueuesOptions, 'deploymentId'>,\n): Promise<QueueTransport> => {\n const queue = await import('@vercel/queue').catch(() => {\n throw classifySchedulerSendFailure(\n new Error(\n \"experimental-a2/scheduler-vercel needs the '@vercel/queue' package (optional peer dependency) — install it, or inject a transport\",\n ),\n 'terminal',\n )\n })\n const client = new queue.QueueClient(clientOptions)\n return {\n send: (topic, message, options) => client.send(topic, message, options),\n handleCallback: (handler) =>\n client.handleCallback(handler, {\n // A hard-killed callback normally has a fresh claim watchdog. If\n // its successor send also failed, make the old unacked delivery's\n // fallback as prompt as Vercel Queues permits.\n visibilityTimeoutSeconds: MIN_VISIBILITY_SECONDS,\n // Not-settled-yet is an expected state: reschedule the (still\n // unacked) delivery via a visibility change — 200, no error\n // log. Everything else returns undefined → the SDK's default\n // throw path, which is loud on purpose.\n retry: (error, metadata) =>\n error instanceof DrainUnsettled\n ? {\n afterSeconds: unsettledBackoffSeconds(metadata.deliveryCount),\n }\n : undefined,\n }) as (req: Request) => Promise<Response>,\n isDuplicate: (error) => error instanceof queue.DuplicateMessageError,\n }\n}\n\nexport function vercelQueues(options: VercelQueuesOptions = {}): A2Scheduler {\n const topic = options.topic ?? 'a2'\n if (typeof topic !== 'string' || !TOPIC_PATTERN.test(topic)) {\n throw new TypeError('vercelQueues() topic must match [A-Za-z0-9_-]+')\n }\n const clock = options.clock ?? SYSTEM_CLOCK\n const clientOptions =\n options.deploymentId === undefined\n ? {}\n : { deploymentId: options.deploymentId }\n\n // Lazy: constructing the scheduler value does no I/O and works without\n // the peer dep until the first task or delivery.\n const transport = retryableLazy(() =>\n options.transport\n ? Promise.resolve(options.transport)\n : vercelQueueTransport(clientOptions),\n ).get\n\n const sendTask = async (task: SchedulerTask): Promise<void> => {\n const nowMs = clock.now().getTime()\n const requestedDueAt = task.dueAt\n if (!Number.isFinite(requestedDueAt)) {\n throw classifySchedulerSendFailure(\n new TypeError('vercel scheduler dueAt must be a finite number'),\n 'terminal',\n )\n }\n const dueAt =\n requestedDueAt <= nowMs\n ? Math.floor(nowMs / 1_000) * 1_000\n : Math.ceil(requestedDueAt / 1_000) * 1_000\n const deliveryTargetAt = task.kind === 'drain' ? dueAt : requestedDueAt\n const deliveryDelaySeconds = Math.max(\n 0,\n Math.ceil((deliveryTargetAt - nowMs) / 1_000),\n )\n if (deliveryDelaySeconds > MAX_DELAY_SECONDS) {\n throw classifySchedulerSendFailure(\n new RangeError(\n 'vercelQueues() tasks cannot be delayed more than 6 days',\n ),\n 'terminal',\n )\n }\n const message: SchedulerTask = { ...task, dueAt }\n const idempotencyKey =\n task.kind === 'drain'\n ? digestId([\n 'a2:scheduler-vercel:drain:v1',\n topic,\n task.contract,\n task.sessionId,\n String(dueAt),\n ])\n : task.kind === 'append'\n ? digestId(['a2:scheduler-vercel:append:v1', topic, task.id])\n : digestId(['a2:scheduler-vercel:clear:v1', topic, task.id])\n const t = await transport()\n try {\n await t.send(topic, message, {\n delaySeconds: deliveryDelaySeconds,\n idempotencyKey,\n retentionSeconds: MAX_RETENTION_SECONDS,\n })\n } catch (err) {\n if (t.isDuplicate(err)) return\n throw classifyVercelSendFailure(err)\n }\n }\n\n const successorDueAt = (message: SchedulerDrainTask): number => {\n const minimumDueAt =\n clock.now().getTime() +\n DRAIN_TIMINGS.claimTtlMs +\n DRAIN_TIMINGS.watchdogGraceMs\n const beats = Math.max(\n 1,\n Math.ceil(\n (minimumDueAt - message.dueAt) / DRAIN_TIMINGS.claimHeartbeatMs,\n ),\n )\n return message.dueAt + beats * DRAIN_TIMINGS.claimHeartbeatMs\n }\n\n return {\n async schedule(task) {\n await sendTask(task)\n },\n\n handler(...servers: DrainableServer[]) {\n assertSchedulerTargets(servers)\n const byContract = new Map<string, DrainableServer>()\n for (const server of servers) {\n byContract.set(server.contract.name, server)\n }\n return async (req: Request): Promise<Response> => {\n const t = await transport()\n return t.handleCallback(async (delivered) => {\n const task = normalizeSchedulerTask(delivered)\n if (!task) {\n throw new TypeError('a2 scheduler received an invalid task')\n }\n const server = byContract.get(task.contract)\n if (!server) {\n // A message for a contract this route doesn't serve is a\n // wiring bug — throw so it redelivers and stays visible in\n // queue observability instead of vanishing on an ack.\n throw new Error(\n `a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`,\n )\n }\n const internals = serverInternals.get(server)\n if (task.kind === 'append') {\n await deliverSchedulerAppend(server, task)\n return\n }\n if (task.kind === 'clear') {\n await deliverSchedulerClear(server, task)\n return\n }\n\n const nextDueAt = successorDueAt(task)\n const result = internals\n ? await internals.schedulerDrain(task.sessionId, {\n watchdogDueAt: nextDueAt,\n })\n : {\n ...(await server.drain(task.sessionId)),\n outcome: 'unknown' as const,\n }\n if (result.settled) return\n\n if (result.outcome === 'busy') {\n // The live owner normally arms its own claim windows.\n // Ensure a later slot here as well before relinquishing this\n // durable delivery. If the queue is down, the throw leaves the\n // current message unacked for visibility-timeout redelivery.\n if (result.schedulerArm) await result.schedulerArm\n else {\n await sendTask({\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt: nextDueAt,\n })\n }\n return\n }\n\n // Handler failure keeps the current durable message and uses the\n // transport retry path. Dead-lettered sessions count as settled\n // above, so retries stop there.\n throw new DrainUnsettled(\n `a2 scheduler: session '${task.sessionId}' of '${task.contract}' not settled yet (${result.outcome}) — rescheduled for redelivery`,\n )\n })(req)\n }\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"scheduler-vercel.js","names":[],"sources":["../src/scheduler-vercel.ts"],"sourcesContent":["/**\n * experimental-a2/scheduler-vercel — queue-backed scheduler over Vercel Queues.\n *\n * `vercelQueues()` returns an `A2Scheduler` (a2-implementation.md §9):\n *\n * - drain tasks round the requested watchdog time up to a one-second slot.\n * One message per `(topic, contract, session, dueAt)` coalesces appends,\n * claim renewals and racing callbacks. `DuplicateMessageError` is\n * success because that due-time slot is already durable.\n * - append tasks hash their stable task id with the topic for provider\n * deduplication. Their six-day maximum delay leaves a full day for\n * delivery and retries inside the provider's seven-day retention.\n * Clear tasks follow the same identity and delay rules. All tasks use\n * the configured deployment scope for sending and receiving.\n * - `handler(...servers)` is the delivery route: look up the server for\n * the contract named in the message and drain it. Settled drains ack;\n * contention and handler failure arm a later watchdog before acking.\n *\n * `@vercel/queue` is an optional peer dependency, imported lazily. The\n * `transport` option is the injection seam — tests run a real little\n * in-memory queue through the same interface.\n */\n\nimport { createHash } from 'node:crypto'\nimport { SYSTEM_CLOCK, type Clock } from './store.ts'\nimport {\n assertSchedulerTargets,\n classifySchedulerSendFailure,\n DRAIN_TIMINGS,\n serverInternals,\n} from './internal.ts'\nimport { retryableLazy } from './retryable-lazy.ts'\nimport {\n normalizeSchedulerTask,\n type SchedulerDrainTask,\n type SchedulerTask,\n} from './scheduler-task.ts'\nimport {\n deliverSchedulerAppend,\n deliverSchedulerClear,\n type A2Scheduler,\n type DrainableServer,\n} from './server.ts'\n\n/**\n * The \"not settled yet\" signal for a drain whose outcome is unknown — an\n * expected state, not a transport failure. The default transport classifies\n * it in `@vercel/queue`'s `retry` option and reschedules the delivery quietly\n * (`{ afterSeconds }` → visibility change → 200 response, no error log)\n * instead of letting it surface as a thrown 500. Busy and stalled deliveries\n * use a fresh watchdog instead. Custom transports should treat this class the\n * same way when their protocol has a polite reschedule.\n */\nexport class DrainUnsettled extends Error {}\n\nexport type QueueDeliveryMetadata = {\n messageId: string\n deliveryCount: number\n}\n\n/** What this module needs from a Vercel Queues-compatible transport. */\nexport type QueueTransport = {\n send(\n topic: string,\n message: SchedulerTask,\n options?: {\n delaySeconds?: number\n idempotencyKey?: string\n retentionSeconds?: number\n },\n ): Promise<unknown>\n /**\n * Wrap the delivery handler into a route. The handler throws\n * `DrainUnsettled` when the session needs redelivery — transports\n * with a polite reschedule (visibility change) should use it there\n * instead of surfacing an error; anything else thrown is a genuine\n * failure and should stay loud.\n */\n handleCallback(\n handler: (\n message: unknown,\n metadata: QueueDeliveryMetadata,\n ) => Promise<void>,\n ): (req: Request) => Promise<Response>\n /** Is this send error the idempotency-key collision? (= success) */\n isDuplicate(error: unknown): boolean\n}\n\nexport type VercelQueuesOptions = {\n /** Queue topic matching `[A-Za-z0-9_-]+`. Default `'a2'`. */\n topic?: string\n /** Queue SDK deployment scope for all tasks. Default: VERCEL_DEPLOYMENT_ID; null opts out. */\n deploymentId?: string | null\n /** Injectable transport (tests). Default: `@vercel/queue`. */\n transport?: QueueTransport\n /** Injectable clock for due-time calculations (tests). */\n clock?: Clock\n}\n\n/** Vercel Queues rejects shorter visibility changes. */\nconst MIN_VISIBILITY_SECONDS = 30\n\n/** Vercel Queues' maximum message retention and delivery delay. */\nconst MAX_RETENTION_SECONDS = 7 * 24 * 60 * 60\n\n/** Leave the final retention day for delivery and retries. */\nconst MAX_DELAY_SECONDS = 6 * 24 * 60 * 60\n\nconst TOPIC_PATTERN = /^[A-Za-z0-9_-]+$/\nconst RETRYABLE_HTTP_CLIENT_STATUSES = new Set([408, 409, 425, 429])\n/** The SDK's send path maps these statuses to name-only error instances. */\nconst TERMINAL_VERCEL_SEND_ERROR_NAMES = new Set([\n 'BadRequestError',\n 'UnauthorizedError',\n 'ForbiddenError',\n])\n\nconst recordOf = (value: unknown): Record<string, unknown> | null =>\n value !== null && typeof value === 'object' && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nconst httpStatusOf = (error: unknown): number | undefined => {\n try {\n const record = recordOf(error)\n if (!record) return undefined\n const response = recordOf(record['response'])\n const statuses = [\n record['status'],\n record['statusCode'],\n response?.['status'],\n ].filter(\n (value): value is number =>\n typeof value === 'number' &&\n Number.isInteger(value) &&\n value >= 100 &&\n value <= 599,\n )\n const [status] = statuses\n if (status === undefined) return undefined\n return statuses.every((candidate) => candidate === status)\n ? status\n : undefined\n } catch {\n return undefined\n }\n}\n\nconst errorNameOf = (error: unknown): string | undefined => {\n try {\n const name = recordOf(error)?.['name']\n return typeof name === 'string' ? name : undefined\n } catch {\n return undefined\n }\n}\n\nconst classifyVercelSendFailure = (error: unknown): unknown => {\n const status = httpStatusOf(error)\n const terminal =\n status === undefined\n ? TERMINAL_VERCEL_SEND_ERROR_NAMES.has(errorNameOf(error) ?? '')\n : status >= 400 &&\n status < 500 &&\n !RETRYABLE_HTTP_CLIENT_STATUSES.has(status)\n return classifySchedulerSendFailure(\n error,\n terminal ? 'terminal' : 'retryable',\n )\n}\n\nconst digestId = (parts: readonly string[]): string =>\n `a2_${createHash('sha256').update(JSON.stringify(parts)).digest('hex')}`\n\n/** Base of the unsettled redelivery backoff, doubled per delivery. */\nconst UNSETTLED_BACKOFF_BASE_SECONDS = 5\n\n/** Redelivery backoff for retry states: base, 2×, 4×… bounded. */\nconst unsettledBackoffSeconds = (deliveryCount: number): number =>\n Math.min(\n Math.max(\n UNSETTLED_BACKOFF_BASE_SECONDS * 2 ** Math.max(deliveryCount - 1, 0),\n MIN_VISIBILITY_SECONDS,\n ),\n 60,\n )\n\nconst vercelQueueTransport = async (\n clientOptions: Pick<VercelQueuesOptions, 'deploymentId'>,\n): Promise<QueueTransport> => {\n const queue = await import('@vercel/queue').catch(() => {\n throw classifySchedulerSendFailure(\n new Error(\n \"experimental-a2/scheduler-vercel needs the '@vercel/queue' package (optional peer dependency) — install it, or inject a transport\",\n ),\n 'terminal',\n )\n })\n const client = new queue.QueueClient(clientOptions)\n return {\n send: (topic, message, options) => client.send(topic, message, options),\n handleCallback: (handler) =>\n client.handleCallback(handler, {\n // A hard-killed callback normally has a fresh claim watchdog. If\n // its successor send also failed, make the old unacked delivery's\n // fallback as prompt as Vercel Queues permits.\n visibilityTimeoutSeconds: MIN_VISIBILITY_SECONDS,\n // Not-settled-yet is an expected state: reschedule the (still\n // unacked) delivery via a visibility change — 200, no error\n // log. Everything else returns undefined → the SDK's default\n // throw path, which is loud on purpose.\n retry: (error, metadata) =>\n error instanceof DrainUnsettled\n ? {\n afterSeconds: unsettledBackoffSeconds(metadata.deliveryCount),\n }\n : undefined,\n }) as (req: Request) => Promise<Response>,\n isDuplicate: (error) => error instanceof queue.DuplicateMessageError,\n }\n}\n\nexport function vercelQueues(options: VercelQueuesOptions = {}): A2Scheduler {\n const topic = options.topic ?? 'a2'\n if (typeof topic !== 'string' || !TOPIC_PATTERN.test(topic)) {\n throw new TypeError('vercelQueues() topic must match [A-Za-z0-9_-]+')\n }\n const clock = options.clock ?? SYSTEM_CLOCK\n const clientOptions =\n options.deploymentId === undefined\n ? {}\n : { deploymentId: options.deploymentId }\n\n // Lazy: constructing the scheduler value does no I/O and works without\n // the peer dep until the first task or delivery.\n const transport = retryableLazy(() =>\n options.transport\n ? Promise.resolve(options.transport)\n : vercelQueueTransport(clientOptions),\n ).get\n\n const sendTask = async (task: SchedulerTask): Promise<void> => {\n const nowMs = clock.now().getTime()\n const requestedDueAt = task.dueAt\n if (!Number.isFinite(requestedDueAt)) {\n throw classifySchedulerSendFailure(\n new TypeError('vercel scheduler dueAt must be a finite number'),\n 'terminal',\n )\n }\n const dueAt =\n requestedDueAt <= nowMs\n ? Math.floor(nowMs / 1_000) * 1_000\n : Math.ceil(requestedDueAt / 1_000) * 1_000\n const deliveryTargetAt = task.kind === 'drain' ? dueAt : requestedDueAt\n const deliveryDelaySeconds = Math.max(\n 0,\n Math.ceil((deliveryTargetAt - nowMs) / 1_000),\n )\n if (deliveryDelaySeconds > MAX_DELAY_SECONDS) {\n throw classifySchedulerSendFailure(\n new RangeError(\n 'vercelQueues() tasks cannot be delayed more than 6 days',\n ),\n 'terminal',\n )\n }\n const message: SchedulerTask = { ...task, dueAt }\n const idempotencyKey =\n task.kind === 'drain'\n ? digestId([\n 'a2:scheduler-vercel:drain:v1',\n topic,\n task.contract,\n task.sessionId,\n String(dueAt),\n ])\n : task.kind === 'append'\n ? digestId(['a2:scheduler-vercel:append:v1', topic, task.id])\n : digestId(['a2:scheduler-vercel:clear:v1', topic, task.id])\n const t = await transport()\n try {\n await t.send(topic, message, {\n delaySeconds: deliveryDelaySeconds,\n idempotencyKey,\n retentionSeconds: MAX_RETENTION_SECONDS,\n })\n } catch (err) {\n if (t.isDuplicate(err)) return\n throw classifyVercelSendFailure(err)\n }\n }\n\n const successorDueAt = (message: SchedulerDrainTask): number => {\n const minimumDueAt =\n clock.now().getTime() +\n DRAIN_TIMINGS.claimTtlMs +\n DRAIN_TIMINGS.watchdogGraceMs\n const beats = Math.max(\n 1,\n Math.ceil(\n (minimumDueAt - message.dueAt) / DRAIN_TIMINGS.claimHeartbeatMs,\n ),\n )\n return message.dueAt + beats * DRAIN_TIMINGS.claimHeartbeatMs\n }\n\n return {\n async schedule(task) {\n await sendTask(task)\n },\n\n handler(...servers: DrainableServer[]) {\n assertSchedulerTargets(servers)\n const byContract = new Map<string, DrainableServer>()\n for (const server of servers) {\n byContract.set(server.contract.name, server)\n }\n return async (req: Request): Promise<Response> => {\n const t = await transport()\n return t.handleCallback(async (delivered) => {\n const task = normalizeSchedulerTask(delivered)\n if (!task) {\n throw new TypeError('a2 scheduler received an invalid task')\n }\n const server = byContract.get(task.contract)\n if (!server) {\n // A message for a contract this route doesn't serve is a\n // wiring bug — throw so it redelivers and stays visible in\n // queue observability instead of vanishing on an ack.\n throw new Error(\n `a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`,\n )\n }\n const internals = serverInternals.get(server)\n if (task.kind === 'append') {\n await deliverSchedulerAppend(server, task)\n return\n }\n if (task.kind === 'clear') {\n await deliverSchedulerClear(server, task)\n return\n }\n\n const nextDueAt = successorDueAt(task)\n const result = internals\n ? await internals.schedulerDrain(task.sessionId, {\n watchdogDueAt: nextDueAt,\n })\n : {\n ...(await server.drain(task.sessionId)),\n outcome: 'unknown' as const,\n }\n if (result.settled) return\n\n if (result.outcome === 'busy' || result.outcome === 'stalled') {\n // The live owner arms its own claim windows, and the store holds\n // a failed event until its retry time. Ensure a later slot here\n // as well before relinquishing this durable delivery. If the queue is down, the\n // throw leaves the current message unacked for visibility-timeout\n // redelivery.\n if (result.schedulerArm) await result.schedulerArm\n else {\n await sendTask({\n version: 1,\n kind: 'drain',\n contract: task.contract,\n sessionId: task.sessionId,\n dueAt: nextDueAt,\n })\n }\n return\n }\n\n // A drain whose outcome is unknown keeps the current durable message\n // and uses the transport retry path.\n throw new DrainUnsettled(\n `a2 scheduler: session '${task.sessionId}' of '${task.contract}' not settled yet (${result.outcome}) — rescheduled for redelivery`,\n )\n })(req)\n }\n },\n }\n}\n"],"mappings":"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"}