experimental-a2 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/dist/ai-server.d.ts +4 -5
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +8 -7
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +334 -2
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +1 -1
- package/dist/client.d.ts +202 -2
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1025 -1
- package/dist/client.js.map +1 -0
- package/dist/errors-BQuJpe82.js.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/{internal-DstsI6Re.js → internal-DRXJ56EI.js} +5 -28
- package/dist/internal-DRXJ56EI.js.map +1 -0
- package/dist/react.d.ts +41 -7
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +74 -35
- package/dist/react.js.map +1 -1
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.js +4 -5
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +4 -4
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-Duw6MVlB.js → server-286j79Mt.js} +708 -79
- package/dist/server-286j79Mt.js.map +1 -0
- package/dist/{server-DpvjhdoE.d.ts → server-DgXmORIq.d.ts} +67 -49
- package/dist/server-DgXmORIq.d.ts.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/{store-DysUkTH3.d.ts → store-flRz1OWh.d.ts} +2 -57
- package/dist/store-flRz1OWh.d.ts.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +1 -59
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-dSeLxzfb.js → store-polling-6DW7F1DT.js} +2 -2
- package/dist/{store-polling-dSeLxzfb.js.map → store-polling-6DW7F1DT.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +1 -81
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-redis-core-BFLwz0Wj.js → store-redis-core-DEYO8Ryv.js} +48 -134
- package/dist/store-redis-core-DEYO8Ryv.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +2 -3
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +3 -4
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +1 -72
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{wire-BFQmSJ-9.js → wire-B6te_wns.js} +4 -3
- package/dist/wire-B6te_wns.js.map +1 -0
- package/docs/guides/03-react.mdx +118 -29
- package/docs/guides/05-production.mdx +9 -11
- package/docs/guides/06-ai-agents.mdx +8 -14
- package/docs/guides/09-presence.mdx +19 -21
- package/docs/guides/10-transports.mdx +104 -86
- package/docs/reference/01-api.mdx +186 -279
- package/docs/reference/02-errors.mdx +5 -7
- package/package.json +1 -14
- package/src/ai-server.ts +9 -5
- package/src/client.ts +71 -35
- package/src/errors.ts +1 -0
- package/src/internal.ts +3 -62
- package/src/push-envelope.ts +24 -21
- package/src/react.ts +118 -44
- package/src/scheduler-qstash.ts +3 -3
- package/src/scheduler-vercel.ts +2 -2
- package/src/server-fetch.ts +344 -0
- package/src/server.ts +73 -225
- package/src/session-socket.ts +36 -20
- package/src/sse.ts +2 -2
- package/src/store-memory.ts +0 -81
- package/src/store-postgres.ts +0 -100
- package/src/store-redis-core.ts +47 -211
- package/src/store-redis-http.ts +0 -1
- package/src/store-redis.ts +0 -1
- package/src/store-sqlite.ts +0 -119
- package/src/store.ts +0 -60
- package/src/wire.ts +2 -1
- package/dist/ai-D_PGS-JR.d.ts +0 -334
- package/dist/ai-D_PGS-JR.d.ts.map +0 -1
- package/dist/cli-B3VuxoDe.js +0 -599
- package/dist/cli-B3VuxoDe.js.map +0 -1
- package/dist/cli-bin.d.ts +0 -1
- package/dist/cli-bin.js +0 -7
- package/dist/cli-bin.js.map +0 -1
- package/dist/cli.d.ts +0 -20
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2
- package/dist/client-BKlyLiOU.js +0 -1008
- package/dist/client-BKlyLiOU.js.map +0 -1
- package/dist/client-D7mvIXrF.d.ts +0 -191
- package/dist/client-D7mvIXrF.d.ts.map +0 -1
- package/dist/devtools-J_jZ2vQf.d.ts +0 -152
- package/dist/devtools-J_jZ2vQf.d.ts.map +0 -1
- package/dist/devtools-kJJaORn-.js +0 -340
- package/dist/devtools-kJJaORn-.js.map +0 -1
- package/dist/devtools-server.browser.d.ts +0 -1
- package/dist/devtools-server.browser.js +0 -6
- package/dist/devtools-server.browser.js.map +0 -1
- package/dist/devtools-server.d.ts +0 -23
- package/dist/devtools-server.d.ts.map +0 -1
- package/dist/devtools-server.js +0 -1270
- package/dist/devtools-server.js.map +0 -1
- package/dist/devtools.d.ts +0 -2
- package/dist/devtools.js +0 -2
- package/dist/errors-W6nwJ-fm.d.ts +0 -21
- package/dist/errors-W6nwJ-fm.d.ts.map +0 -1
- package/dist/http.d.ts +0 -151
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -706
- package/dist/http.js.map +0 -1
- package/dist/inspection-DaxB5jM2.js +0 -13
- package/dist/inspection-DaxB5jM2.js.map +0 -1
- package/dist/internal-DstsI6Re.js.map +0 -1
- package/dist/platform-B4TnJtWu.js +0 -34
- package/dist/platform-B4TnJtWu.js.map +0 -1
- package/dist/server-DpvjhdoE.d.ts.map +0 -1
- package/dist/server-Duw6MVlB.js.map +0 -1
- package/dist/store-DysUkTH3.d.ts.map +0 -1
- package/dist/store-redis-core-BFLwz0Wj.js.map +0 -1
- package/dist/testing.browser.d.ts +0 -1
- package/dist/testing.browser.js +0 -6
- package/dist/testing.browser.js.map +0 -1
- package/dist/testing.d.ts +0 -32
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -103
- package/dist/testing.js.map +0 -1
- package/dist/wire-BFQmSJ-9.js.map +0 -1
- package/docs/guides/07-devtools.mdx +0 -229
- package/src/cli-bin.ts +0 -5
- package/src/cli.ts +0 -1046
- package/src/devtools-app.ts +0 -989
- package/src/devtools-server.browser.ts +0 -5
- package/src/devtools-server.ts +0 -604
- package/src/devtools.ts +0 -716
- package/src/http.ts +0 -394
- package/src/inspection.ts +0 -39
- package/src/testing.browser.ts +0 -5
- package/src/testing.ts +0 -185
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","names":[],"sources":["../src/react.ts"],"sourcesContent":["'use client'\n/**\n * experimental-a2/react — React bindings over experimental-a2/client.\n *\n * `createReact({ client })` is a factory (like createContext): call it\n * once in a `'use client'` module with a shared A2 client and export the\n * bound pair. Server components import `SessionProvider` from that module\n * as a client reference; client components import `useSession` from the\n * same file. The shared client also makes imperative pushes and provider\n * renders resolve the same live session. Everything is typed by the\n * reducer value — no type arguments, and nothing here ever touches the\n * machine module.\n */\n\nimport {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n createClient,\n type A2Client,\n type Connection,\n type HistoryState,\n type LoadHistoryOptions,\n type PushResult,\n type SessionClient,\n type SessionClientPresence,\n type SessionSnapshot,\n} from './client.ts'\nimport type {\n AppendInput,\n ContractEvent,\n EventDefs,\n PresenceDefs,\n PresenceMap,\n PresencePatch,\n WithPresence,\n} from './contract.ts'\nimport type { Reducer } from './reducer.ts'\n\nexport type SessionProviderProps<D extends EventDefs, S> = {\n /** Which session to subscribe to. */\n sessionId: string\n /** Base path of the route exposing GET (stream) and POST (push). */\n api: string\n /** The server-rendered fold — the first paint, no client JS needed. */\n initialState: S\n /** The fold's frontier — the stream resumes exactly there. */\n initialIndex: number\n /** Server-rendered event history through `initialIndex`. */\n initialEvents?: ContractEvent<D>[]\n /**\n * This client's presence identity — required to call `setPresence`.\n * Caller-supplied (a user id, a tab nonce, a guest name): A2 does\n * not invent an identity story.\n */\n participant?: string\n children?: ReactNode\n}\n\nexport type BoundSessionProviderProps<D extends EventDefs, S> = Omit<\n SessionProviderProps<D, S>,\n 'api'\n>\n\n/**\n * The presence members of the hook result — intersected in via\n * `WithPresence`, so they exist exactly when the contract declares\n * presence fields (the reducer is the client's typed handle on it).\n */\nexport type UseSessionPresence<P extends PresenceDefs> = {\n /** The replicated presence map, this client included (local echo). */\n presence: PresenceMap<P>\n /**\n * Fire-and-forget: validated locally, echoed into the map\n * immediately, coalesced on the wire to a fixed cadence (a leading\n * send immediately, then at most one merged send per interval;\n * later values win per field), stamped with the frontier at send\n * time as `seen`. Requires the provider's `participant`.\n */\n setPresence: (values: PresencePatch<P>['values']) => void\n}\n\nexport type UseSessionResult<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = WithPresence<P, UseSessionPresence<P>> & {\n /** The live view, folded through the shared reducer. */\n state: S\n /** The raw event feed — observed, seeded, or backscrolled. */\n events: ContractEvent<D>[]\n /** The stream frontier — `lastSeenIndex` for cancellation. */\n index: number\n /**\n * The connection, as a discriminated union — `error` exists only\n * while disconnected; \"reconnecting…\" is\n * `status === 'connecting' && reconnects > 0`.\n */\n connection: Connection\n /**\n * Typed optimistic append. Awaiting it gives the server ack;\n * `.confirmed` resolves when the live stream has delivered the batch\n * back. Rejects with the server's A2Error codes.\n */\n push: (...events: AppendInput<D>[]) => PushResult<D>\n /**\n * Backscroll: fetch a bounded slice of older events into `events`.\n * Defaults page backward 50 at a time from the oldest loaded event.\n * Display data only — `state` and the optimistic overlay never move.\n */\n loadHistory: (options?: LoadHistoryOptions) => Promise<ContractEvent<D>[]>\n /** Backscroll progress: `complete` means the feed reaches index 1. */\n history: HistoryState\n}\n\nexport type A2React<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n SessionProvider: (props: SessionProviderProps<D, S>) => ReactElement\n useSession: () => UseSessionResult<D, S, P>\n}\n\nexport type BoundA2React<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n SessionProvider: (props: BoundSessionProviderProps<D, S>) => ReactElement\n useSession: () => UseSessionResult<D, S, P>\n}\n\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(options: { client: A2Client<D, S, P> }): BoundA2React<D, S, P>\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(options: { reducer: Reducer<D, S, P> }): A2React<D, S, P>\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(\n options: { client: A2Client<D, S, P> } | { reducer: Reducer<D, S, P> },\n): BoundA2React<D, S, P> | A2React<D, S, P> {\n const Context = createContext<SessionClient<D, S, P> | null>(null)\n const providerMounts = new WeakMap<SessionClient<D, S, P> & object, number>()\n const resolveSession: (\n sessionId: string,\n api: string | undefined,\n initialState: S,\n initialIndex: number,\n initialEvents: ContractEvent<D>[] | undefined,\n participant: string | undefined,\n ) => SessionClient<D, S, P> =\n 'client' in options\n ? (\n sessionId,\n _api,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n ) =>\n options.client.session(sessionId, {\n initialState,\n initialIndex,\n ...(initialEvents === undefined ? {} : { initialEvents }),\n ...(participant === undefined ? {} : { participant }),\n })\n : (() => {\n const clients = new Map<string, A2Client<D, S, P>>()\n return (\n sessionId: string,\n api: string | undefined,\n initialState: S,\n initialIndex: number,\n initialEvents: ContractEvent<D>[] | undefined,\n participant: string | undefined,\n ) => {\n if (api === undefined) {\n throw new TypeError(\n 'SessionProvider requires api when createReact binds a reducer',\n )\n }\n let client = clients.get(api)\n if (!client) {\n client = createClient({ reducer: options.reducer, api })\n clients.set(api, client)\n }\n return client.session(sessionId, {\n initialState,\n initialIndex,\n ...(initialEvents === undefined ? {} : { initialEvents }),\n ...(participant === undefined ? {} : { participant }),\n })\n }\n })()\n\n function SessionProvider(\n props: SessionProviderProps<D, S> | BoundSessionProviderProps<D, S>,\n ): ReactElement {\n const {\n sessionId,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n } = props\n const api = 'api' in props ? props.api : undefined\n const client = useMemo(\n () =>\n resolveSession(\n sessionId,\n api,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n ),\n [sessionId, api, initialState, initialIndex, initialEvents, participant],\n )\n useEffect(() => {\n const mounts = providerMounts.get(client) ?? 0\n providerMounts.set(client, mounts + 1)\n if (mounts === 0) client.connect()\n return () => {\n const remaining = (providerMounts.get(client) ?? 1) - 1\n if (remaining === 0) {\n providerMounts.delete(client)\n client.close()\n } else {\n providerMounts.set(client, remaining)\n }\n }\n }, [client])\n return createElement(Context.Provider, { value: client }, props.children)\n }\n\n function useSession(): UseSessionResult<D, S, P> {\n const client = useContext(Context)\n if (!client) {\n throw new Error(\n 'useSession must be rendered inside its matching SessionProvider',\n )\n }\n const snapshot: SessionSnapshot<D, S, P> = useSyncExternalStore(\n client.subscribe,\n client.getSnapshot,\n client.getSnapshot,\n )\n return useMemo(() => {\n // Runtime mirror of the WithPresence surface: the client attaches\n // setPresence (and the snapshot carries presence) exactly when the\n // reducer declares presence fields — no runtime-inert members.\n const { setPresence } = client as Partial<SessionClientPresence<P>>\n const { presence } = snapshot as Partial<{ presence: PresenceMap<P> }>\n return {\n state: snapshot.state,\n events: snapshot.events,\n index: snapshot.index,\n connection: snapshot.connection,\n push: client.push,\n loadHistory: client.loadHistory,\n history: snapshot.history,\n ...(setPresence && presence ? { presence, setPresence } : {}),\n } as UseSessionResult<D, S, P>\n }, [snapshot, client])\n }\n\n return { SessionProvider, useSession }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAsJA,SAAgB,YAKd,SAC0C;CAC1C,MAAM,UAAU,cAA6C,IAAI;CACjE,MAAM,iCAAiB,IAAI,QAAiD;CAC5E,MAAM,iBAQJ,YAAY,WAEN,WACA,MACA,cACA,cACA,eACA,gBAEA,QAAQ,OAAO,QAAQ,WAAW;EAChC;EACA;EACA,GAAI,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc;EACvD,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;CACrD,CAAC,WACI;EACL,MAAM,0BAAU,IAAI,IAA+B;EACnD,QACE,WACA,KACA,cACA,cACA,eACA,gBACG;GACH,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,UACR,+DACF;GAEF,IAAI,SAAS,QAAQ,IAAI,GAAG;GAC5B,IAAI,CAAC,QAAQ;IACX,SAAS,aAAa;KAAE,SAAS,QAAQ;KAAS;IAAI,CAAC;IACvD,QAAQ,IAAI,KAAK,MAAM;GACzB;GACA,OAAO,OAAO,QAAQ,WAAW;IAC/B;IACA;IACA,GAAI,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc;IACvD,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;GACrD,CAAC;EACH;CACF,EAAA,CAAG;CAET,SAAS,gBACP,OACc;EACd,MAAM,EACJ,WACA,cACA,cACA,eACA,gBACE;EACJ,MAAM,MAAM,SAAS,QAAQ,MAAM,MAAM,KAAA;EACzC,MAAM,SAAS,cAEX,eACE,WACA,KACA,cACA,cACA,eACA,WACF,GACF;GAAC;GAAW;GAAK;GAAc;GAAc;GAAe;EAAW,CACzE;EACA,gBAAgB;GACd,MAAM,SAAS,eAAe,IAAI,MAAM,KAAK;GAC7C,eAAe,IAAI,QAAQ,SAAS,CAAC;GACrC,IAAI,WAAW,GAAG,OAAO,QAAQ;GACjC,aAAa;IACX,MAAM,aAAa,eAAe,IAAI,MAAM,KAAK,KAAK;IACtD,IAAI,cAAc,GAAG;KACnB,eAAe,OAAO,MAAM;KAC5B,OAAO,MAAM;IACf,OACE,eAAe,IAAI,QAAQ,SAAS;GAExC;EACF,GAAG,CAAC,MAAM,CAAC;EACX,OAAO,cAAc,QAAQ,UAAU,EAAE,OAAO,OAAO,GAAG,MAAM,QAAQ;CAC1E;CAEA,SAAS,aAAwC;EAC/C,MAAM,SAAS,WAAW,OAAO;EACjC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,iEACF;EAEF,MAAM,WAAqC,qBACzC,OAAO,WACP,OAAO,aACP,OAAO,WACT;EACA,OAAO,cAAc;GAInB,MAAM,EAAE,gBAAgB;GACxB,MAAM,EAAE,aAAa;GACrB,OAAO;IACL,OAAO,SAAS;IAChB,QAAQ,SAAS;IACjB,OAAO,SAAS;IAChB,YAAY,SAAS;IACrB,MAAM,OAAO;IACb,aAAa,OAAO;IACpB,SAAS,SAAS;IAClB,GAAI,eAAe,WAAW;KAAE;KAAU;IAAY,IAAI,CAAC;GAC7D;EACF,GAAG,CAAC,UAAU,MAAM,CAAC;CACvB;CAEA,OAAO;EAAE;EAAiB;CAAW;AACvC"}
|
|
1
|
+
{"version":3,"file":"react.js","names":[],"sources":["../src/react.ts"],"sourcesContent":["'use client'\n/**\n * experimental-a2/react — React bindings over experimental-a2/client.\n *\n * `createReact({ client })` is a factory (like createContext): call it\n * once in a `'use client'` module with a shared A2 client and export the\n * bound pair. Server components import `SessionProvider` from that module\n * as a client reference; client components import `useSession` from the\n * same file. The shared client also makes imperative pushes and provider\n * renders resolve the same live session. Everything is typed by the\n * reducer value — no type arguments, and nothing here ever touches the\n * machine module.\n *\n * The standalone `useSession(client, sessionId, options?)` is the\n * provider-less primitive for client-first apps: identity-mapped\n * handles, one leased stream per session, and an app-owned hydration\n * input — the hook holds the stream until the app hands it a\n * `{ state, index }` fold.\n */\n\nimport {\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport {\n createClient,\n type A2Client,\n type Connection,\n type HistoryState,\n type LoadHistoryOptions,\n type PushResult,\n type SessionClient,\n type SessionClientPresence,\n type SessionSnapshot,\n} from './client.ts'\nimport type {\n AppendInput,\n ContractEvent,\n EventDefs,\n PresenceDefs,\n PresenceMap,\n PresencePatch,\n WithPresence,\n} from './contract.ts'\nimport type { Reducer } from './reducer.ts'\n\nexport type UseSessionOptions<S> = {\n /** Overrides the client's `participant` for this session — for apps\n * whose identity is only known at mount time. Bound once per\n * session handle. */\n participant?: string\n /**\n * The server fold this session mounts from: the `{ state, index }`\n * pair `session.state(reducer)` returns, fetched by the app through\n * its own route. Atomic on purpose — the fold and its frontier\n * travel together or not at all. While `undefined` (the app's fetch\n * has not landed), the hook holds the stream; when the value\n * arrives, the session hydrates through the ordinary re-hydration\n * seam and the stream connects at that frontier. To fold from the log's start\n * deliberately, hand the fold's true starting point:\n * `{ state: reducer.initialState, index: 0 }` — state at index 0 is\n * the reducer's seed by definition, so the explicit replay needs no\n * special vocabulary. There is no pending/ready echo on the result:\n * whether the fold has been handed over is this option — the\n * caller's own input — so the data layer's loading state IS the\n * pending state.\n */\n hydrate?: { state: S; index: number } | undefined\n}\n\nexport type SessionProviderProps<D extends EventDefs, S> = {\n /** Which session to subscribe to. */\n sessionId: string\n /** Base path of the route exposing GET (stream) and POST (push). */\n api: string\n /** The server-rendered fold — the first paint, no client JS needed. */\n initialState: S\n /** The fold's frontier — the stream resumes exactly there. */\n initialIndex: number\n /** Server-rendered event history through `initialIndex`. */\n initialEvents?: ContractEvent<D>[]\n /** Overrides the client's `participant` for this session — for apps\n * whose identity is only known at mount time. */\n participant?: string\n children?: ReactNode\n}\n\nexport type BoundSessionProviderProps<D extends EventDefs, S> = Omit<\n SessionProviderProps<D, S>,\n 'api'\n>\n\n/**\n * The presence members of the hook result — intersected in via\n * `WithPresence`, so they exist exactly when the contract declares\n * presence fields (the reducer is the client's typed handle on it).\n */\nexport type UseSessionPresence<P extends PresenceDefs> = {\n /** The replicated presence map, this client included (local echo). */\n presence: PresenceMap<P>\n /**\n * Fire-and-forget: validated locally, echoed into the map\n * immediately, coalesced on the wire to a fixed cadence (a leading\n * send immediately, then at most one merged send per interval;\n * later values win per field), stamped with the frontier at send\n * time as `seen`. Requires the provider's `participant`.\n */\n setPresence: (values: PresencePatch<P>['values']) => void\n}\n\nexport type UseSessionResult<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = WithPresence<P, UseSessionPresence<P>> & {\n /** The live view, folded through the shared reducer. */\n state: S\n /** The raw event feed — observed, seeded, or backscrolled. */\n events: ContractEvent<D>[]\n /** The stream frontier — `lastSeenIndex` for cancellation. */\n index: number\n /**\n * The connection, as a discriminated union — `error` exists only\n * while disconnected; \"reconnecting…\" is\n * `status === 'connecting' && reconnects > 0`.\n */\n connection: Connection\n /**\n * Typed optimistic append. Awaiting it gives the server ack;\n * `.confirmed` resolves when the live stream has delivered the batch\n * back. Rejects with the server's A2Error codes.\n */\n push: (...events: AppendInput<D>[]) => PushResult<D>\n /**\n * Backscroll: fetch a bounded slice of older events into `events`.\n * Defaults page backward 50 at a time from the oldest loaded event.\n * Display data only — `state` and the optimistic overlay never move.\n */\n loadHistory: (options?: LoadHistoryOptions) => Promise<ContractEvent<D>[]>\n /** Backscroll progress: `complete` means the feed reaches index 1. */\n history: HistoryState\n}\n\nexport type A2React<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n SessionProvider: (props: SessionProviderProps<D, S>) => ReactElement\n useSession: () => UseSessionResult<D, S, P>\n}\n\nexport type BoundA2React<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n SessionProvider: (props: BoundSessionProviderProps<D, S>) => ReactElement\n useSession: () => UseSessionResult<D, S, P>\n}\n\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(options: { client: A2Client<D, S, P> }): BoundA2React<D, S, P>\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(options: { reducer: Reducer<D, S, P> }): A2React<D, S, P>\nexport function createReact<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(\n options: { client: A2Client<D, S, P> } | { reducer: Reducer<D, S, P> },\n): BoundA2React<D, S, P> | A2React<D, S, P> {\n const Context = createContext<SessionClient<D, S, P> | null>(null)\n const resolveSession: (\n sessionId: string,\n api: string | undefined,\n initialState: S,\n initialIndex: number,\n initialEvents: ContractEvent<D>[] | undefined,\n participant: string | undefined,\n ) => SessionClient<D, S, P> =\n 'client' in options\n ? (\n sessionId,\n _api,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n ) =>\n options.client.session(sessionId, {\n initialState,\n initialIndex,\n ...(initialEvents === undefined ? {} : { initialEvents }),\n ...(participant === undefined ? {} : { participant }),\n })\n : (() => {\n const clients = new Map<string, A2Client<D, S, P>>()\n return (\n sessionId: string,\n api: string | undefined,\n initialState: S,\n initialIndex: number,\n initialEvents: ContractEvent<D>[] | undefined,\n participant: string | undefined,\n ) => {\n if (api === undefined) {\n throw new TypeError(\n 'SessionProvider requires api when createReact binds a reducer',\n )\n }\n let client = clients.get(api)\n if (!client) {\n client = createClient({ reducer: options.reducer, api })\n clients.set(api, client)\n }\n return client.session(sessionId, {\n initialState,\n initialIndex,\n ...(initialEvents === undefined ? {} : { initialEvents }),\n ...(participant === undefined ? {} : { participant }),\n })\n }\n })()\n\n function SessionProvider(\n props: SessionProviderProps<D, S> | BoundSessionProviderProps<D, S>,\n ): ReactElement {\n const {\n sessionId,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n } = props\n const api = 'api' in props ? props.api : undefined\n const client = useMemo(\n () =>\n resolveSession(\n sessionId,\n api,\n initialState,\n initialIndex,\n initialEvents,\n participant,\n ),\n [sessionId, api, initialState, initialIndex, initialEvents, participant],\n )\n useSessionLifecycle(client, true)\n return createElement(Context.Provider, { value: client }, props.children)\n }\n\n function useBoundSession(): UseSessionResult<D, S, P> {\n const client = useContext(Context)\n if (!client) {\n throw new Error(\n 'useSession must be rendered inside its matching SessionProvider',\n )\n }\n return useSessionResult(client)\n }\n\n return { SessionProvider, useSession: useBoundSession }\n}\n\n// ── the shared session lifecycle ───────────────────────────────\n\n/**\n * Hold a stream lease while mounted and `live` — the client refcounts\n * leases per handle, so however many providers and hooks mount one\n * session, the first connects and the last release closes\n * (StrictMode-safe: the lease is retaken on remount). A standalone\n * hook without a `hydrate` value passes `live: false` and holds the\n * stream — connecting an unhydrated session would replay the whole\n * log, and the library never picks the expensive path silently.\n */\nfunction useSessionLifecycle<D extends EventDefs, S, P extends PresenceDefs>(\n session: SessionClient<D, S, P>,\n live: boolean,\n): void {\n useEffect(() => {\n if (!live) return\n return session.connect()\n }, [session, live])\n}\n\nfunction useSessionResult<D extends EventDefs, S, P extends PresenceDefs>(\n session: SessionClient<D, S, P>,\n): UseSessionResult<D, S, P> {\n const snapshot: SessionSnapshot<D, S, P> = useSyncExternalStore(\n session.subscribe,\n session.getSnapshot,\n session.getSnapshot,\n )\n return useMemo(() => {\n // Runtime mirror of the WithPresence surface: the client attaches\n // setPresence (and the snapshot carries presence) exactly when the\n // reducer declares presence fields — no runtime-inert members.\n const { setPresence } = session as Partial<SessionClientPresence<P>>\n const { presence } = snapshot as Partial<{ presence: PresenceMap<P> }>\n return {\n state: snapshot.state,\n events: snapshot.events,\n index: snapshot.index,\n connection: snapshot.connection,\n push: session.push,\n loadHistory: session.loadHistory,\n history: snapshot.history,\n ...(setPresence && presence ? { presence, setPresence } : {}),\n } as UseSessionResult<D, S, P>\n }, [snapshot, session])\n}\n\n/**\n * The standalone, provider-less hook — for apps whose components reach\n * sessions ad hoc (a sidebar of channel sessions, a user session read\n * from a menu). Takes the shared client and a session id; the handle\n * is identity-mapped, so every hook and provider mounting the same\n * session shares one runtime and one leased stream. Hydration is\n * app-owned: pass `hydrate` when your fetch lands; until then the\n * stream is held, and your data layer's loading state is the pending\n * state.\n */\nexport function useSession<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(\n client: A2Client<D, S, P>,\n sessionId: string,\n options?: UseSessionOptions<S>,\n): UseSessionResult<D, S, P> {\n const { participant, hydrate } = options ?? {}\n const session = useMemo(\n () =>\n client.session(sessionId, {\n ...(hydrate === undefined\n ? {}\n : { initialState: hydrate.state, initialIndex: hydrate.index }),\n ...(participant === undefined ? {} : { participant }),\n }),\n [client, sessionId, hydrate, participant],\n )\n useSessionLifecycle(session, hydrate !== undefined)\n return useSessionResult(session)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiLA,SAAgB,YAKd,SAC0C;CAC1C,MAAM,UAAU,cAA6C,IAAI;CACjE,MAAM,iBAQJ,YAAY,WAEN,WACA,MACA,cACA,cACA,eACA,gBAEA,QAAQ,OAAO,QAAQ,WAAW;EAChC;EACA;EACA,GAAI,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc;EACvD,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;CACrD,CAAC,WACI;EACL,MAAM,0BAAU,IAAI,IAA+B;EACnD,QACE,WACA,KACA,cACA,cACA,eACA,gBACG;GACH,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,UACR,+DACF;GAEF,IAAI,SAAS,QAAQ,IAAI,GAAG;GAC5B,IAAI,CAAC,QAAQ;IACX,SAAS,aAAa;KAAE,SAAS,QAAQ;KAAS;IAAI,CAAC;IACvD,QAAQ,IAAI,KAAK,MAAM;GACzB;GACA,OAAO,OAAO,QAAQ,WAAW;IAC/B;IACA;IACA,GAAI,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc;IACvD,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;GACrD,CAAC;EACH;CACF,EAAA,CAAG;CAET,SAAS,gBACP,OACc;EACd,MAAM,EACJ,WACA,cACA,cACA,eACA,gBACE;EACJ,MAAM,MAAM,SAAS,QAAQ,MAAM,MAAM,KAAA;EACzC,MAAM,SAAS,cAEX,eACE,WACA,KACA,cACA,cACA,eACA,WACF,GACF;GAAC;GAAW;GAAK;GAAc;GAAc;GAAe;EAAW,CACzE;EACA,oBAAoB,QAAQ,IAAI;EAChC,OAAO,cAAc,QAAQ,UAAU,EAAE,OAAO,OAAO,GAAG,MAAM,QAAQ;CAC1E;CAEA,SAAS,kBAA6C;EACpD,MAAM,SAAS,WAAW,OAAO;EACjC,IAAI,CAAC,QACH,MAAM,IAAI,MACR,iEACF;EAEF,OAAO,iBAAiB,MAAM;CAChC;CAEA,OAAO;EAAE;EAAiB,YAAY;CAAgB;AACxD;;;;;;;;;;AAaA,SAAS,oBACP,SACA,MACM;CACN,gBAAgB;EACd,IAAI,CAAC,MAAM;EACX,OAAO,QAAQ,QAAQ;CACzB,GAAG,CAAC,SAAS,IAAI,CAAC;AACpB;AAEA,SAAS,iBACP,SAC2B;CAC3B,MAAM,WAAqC,qBACzC,QAAQ,WACR,QAAQ,aACR,QAAQ,WACV;CACA,OAAO,cAAc;EAInB,MAAM,EAAE,gBAAgB;EACxB,MAAM,EAAE,aAAa;EACrB,OAAO;GACL,OAAO,SAAS;GAChB,QAAQ,SAAS;GACjB,OAAO,SAAS;GAChB,YAAY,SAAS;GACrB,MAAM,QAAQ;GACd,aAAa,QAAQ;GACrB,SAAS,SAAS;GAClB,GAAI,eAAe,WAAW;IAAE;IAAU;GAAY,IAAI,CAAC;EAC7D;CACF,GAAG,CAAC,UAAU,OAAO,CAAC;AACxB;;;;;;;;;;;AAYA,SAAgB,WAKd,QACA,WACA,SAC2B;CAC3B,MAAM,EAAE,aAAa,YAAY,WAAW,CAAC;CAC7C,MAAM,UAAU,cAEZ,OAAO,QAAQ,WAAW;EACxB,GAAI,YAAY,KAAA,IACZ,CAAC,IACD;GAAE,cAAc,QAAQ;GAAO,cAAc,QAAQ;EAAM;EAC/D,GAAI,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY;CACrD,CAAC,GACH;EAAC;EAAQ;EAAW;EAAS;CAAW,CAC1C;CACA,oBAAoB,SAAS,YAAY,KAAA,CAAS;CAClD,OAAO,iBAAiB,OAAO;AACjC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { r as Clock } from "./store-flRz1OWh.js";
|
|
2
|
+
import { t as A2Scheduler, w as SchedulerTask } from "./server-DgXmORIq.js";
|
|
3
3
|
//#region src/scheduler-qstash.d.ts
|
|
4
4
|
type QStashStoreOutage = {
|
|
5
5
|
version: 1;
|
|
@@ -52,7 +52,7 @@ type QStashTransport = {
|
|
|
52
52
|
verify(request: QStashVerifyRequest): Promise<boolean>;
|
|
53
53
|
};
|
|
54
54
|
type QStashSchedulerOptions = {
|
|
55
|
-
/** Exact URL mounted with `
|
|
55
|
+
/** Exact URL mounted with `scheduler.handler(...)`. Inferred in local dev and on Vercel. */
|
|
56
56
|
url?: string;
|
|
57
57
|
/** QStash API token. Default: `QSTASH_TOKEN`. */
|
|
58
58
|
token?: string;
|
package/dist/scheduler-qstash.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { t as A2Error } from "./errors-BQuJpe82.js";
|
|
2
|
-
import {
|
|
3
|
-
import { n as deliverSchedulerAppend } from "./server-
|
|
4
|
-
import { n as platformVercelOidcToken } from "./platform-B4TnJtWu.js";
|
|
2
|
+
import { d as classifySchedulerSendFailure, g as serverInternals, t as DRAIN_TIMINGS, u as assertSchedulerTargets } from "./internal-DRXJ56EI.js";
|
|
3
|
+
import { i as platformVercelOidcToken, n as deliverSchedulerAppend } from "./server-286j79Mt.js";
|
|
5
4
|
import { t as retryableLazy } from "./retryable-lazy-DZWmHpii.js";
|
|
6
5
|
import { n as SYSTEM_CLOCK } from "./store-N8PXxDAS.js";
|
|
7
6
|
import { t as normalizeSchedulerTask } from "./scheduler-task-BpzhPnRS.js";
|
|
@@ -424,7 +423,7 @@ function qstash(options = {}) {
|
|
|
424
423
|
await sendTask(task);
|
|
425
424
|
},
|
|
426
425
|
handler(...servers) {
|
|
427
|
-
assertSchedulerTargets(servers
|
|
426
|
+
assertSchedulerTargets(servers);
|
|
428
427
|
const byContract = /* @__PURE__ */ new Map();
|
|
429
428
|
for (const server of servers) byContract.set(server.contract.name, server);
|
|
430
429
|
return async (req) => {
|
|
@@ -448,7 +447,7 @@ function qstash(options = {}) {
|
|
|
448
447
|
if (!parsed) return response.malformed();
|
|
449
448
|
const { task, outage } = parsed;
|
|
450
449
|
const server = byContract.get(task.contract);
|
|
451
|
-
if (!server) throw new Error(`a2 scheduler: no server for contract '${task.contract}' — pass it to
|
|
450
|
+
if (!server) throw new Error(`a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`);
|
|
452
451
|
const internals = serverInternals.get(server);
|
|
453
452
|
if (task.kind === "append") {
|
|
454
453
|
try {
|
|
@@ -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 tasks hash their stable task id with the callback URL.\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 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 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 `schedulerHandler(...)`. 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 === '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 }\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 === 'append'\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 : 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 === 'append'\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, { allowStructural: true })\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 schedulerHandler(...)`,\n )\n }\n const internals = serverInternals.get(server)\n if (task.kind === 'append') {\n try {\n await deliverSchedulerAppend(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 tasks hash their stable task id with the callback URL.\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 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 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 === '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 }\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 === 'append'\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 : 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 === 'append'\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 === 'append') {\n try {\n await deliverSchedulerAppend(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,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { r as Clock } from "./store-flRz1OWh.js";
|
|
2
|
+
import { t as A2Scheduler, w as SchedulerTask } from "./server-DgXmORIq.js";
|
|
3
3
|
//#region src/scheduler-vercel.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* The "not settled yet" signal for a stalled handler — an expected state,
|
package/dist/scheduler-vercel.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as deliverSchedulerAppend } from "./server-
|
|
1
|
+
import { d as classifySchedulerSendFailure, g as serverInternals, t as DRAIN_TIMINGS, u as assertSchedulerTargets } from "./internal-DRXJ56EI.js";
|
|
2
|
+
import { n as deliverSchedulerAppend } from "./server-286j79Mt.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-BpzhPnRS.js";
|
|
@@ -185,7 +185,7 @@ function vercelQueues(options = {}) {
|
|
|
185
185
|
await sendTask(task);
|
|
186
186
|
},
|
|
187
187
|
handler(...servers) {
|
|
188
|
-
assertSchedulerTargets(servers
|
|
188
|
+
assertSchedulerTargets(servers);
|
|
189
189
|
const byContract = /* @__PURE__ */ new Map();
|
|
190
190
|
for (const server of servers) byContract.set(server.contract.name, server);
|
|
191
191
|
return async (req) => {
|
|
@@ -193,7 +193,7 @@ function vercelQueues(options = {}) {
|
|
|
193
193
|
const task = normalizeSchedulerTask(delivered);
|
|
194
194
|
if (!task) throw new TypeError("a2 scheduler received an invalid task");
|
|
195
195
|
const server = byContract.get(task.contract);
|
|
196
|
-
if (!server) throw new Error(`a2 scheduler: no server for contract '${task.contract}' — pass it to
|
|
196
|
+
if (!server) throw new Error(`a2 scheduler: no server for contract '${task.contract}' — pass it to scheduler.handler(...)`);
|
|
197
197
|
const internals = serverInternals.get(server);
|
|
198
198
|
if (task.kind === "append") {
|
|
199
199
|
await deliverSchedulerAppend(server, task);
|
|
@@ -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 * They target the publishing deployment unless explicitly configured\n * as deploymentless. Drain tasks always remain deployment-scoped.\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 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\ntype VercelQueueMessage = SchedulerTask & {\n /** Provider-private state used to address callback lease operations. */\n _a2Vercel?: {\n version: 1\n deployment: 'deployment' | 'deploymentless'\n }\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 /** Resolved A2 affinity. Custom transports own its provider mapping. */\n routing?: {\n deployment: 'deployment' | 'deploymentless'\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 /**\n * Deployment affinity for scheduled append tasks. Recovery drain tasks\n * always target the publishing deployment. Default `'deployment'`.\n */\n scheduledAppends?: 'deployment' | 'deploymentless'\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 (): 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 deploymentClient = new queue.QueueClient()\n const deploymentlessClient = new queue.QueueClient({ deploymentId: null })\n return {\n send: (topic, message, options, routing) => {\n const deployment = routing?.deployment ?? 'deployment'\n const body: VercelQueueMessage = {\n ...message,\n _a2Vercel: { version: 1, deployment },\n }\n return (\n deployment === 'deploymentless'\n ? deploymentlessClient\n : deploymentClient\n ).send(topic, body, options)\n },\n handleCallback: (handler) => {\n const callbackOptions = {\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: unknown, metadata: QueueDeliveryMetadata) =>\n error instanceof DrainUnsettled\n ? {\n afterSeconds: unsettledBackoffSeconds(metadata.deliveryCount),\n }\n : undefined,\n }\n const deploymentHandler = deploymentClient.handleCallback(\n handler,\n callbackOptions,\n ) as (req: Request) => Promise<Response>\n const deploymentlessHandler = deploymentlessClient.handleCallback(\n handler,\n callbackOptions,\n ) as (req: Request) => Promise<Response>\n return async (req: Request): Promise<Response> => {\n let delivered: unknown\n try {\n delivered = await req.clone().json()\n } catch {\n return deploymentlessHandler(req)\n }\n const metadata = recordOf(recordOf(delivered)?.['_a2Vercel'])\n const routing =\n metadata?.['version'] === 1 ? metadata['deployment'] : undefined\n return routing === 'deployment'\n ? deploymentHandler(req)\n : deploymentlessHandler(req)\n }\n },\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 scheduledAppends = options.scheduledAppends ?? 'deployment'\n if (\n scheduledAppends !== 'deployment' &&\n scheduledAppends !== 'deploymentless'\n ) {\n throw new TypeError(\n \"vercelQueues() scheduledAppends must be 'deployment' or 'deploymentless'\",\n )\n }\n const clock = options.clock ?? SYSTEM_CLOCK\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(),\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 === 'append' ? requestedDueAt : dueAt\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 deployment = task.kind === 'append' ? scheduledAppends : 'deployment'\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 : digestId(['a2:scheduler-vercel:append:v1', topic, task.id])\n const t = await transport()\n try {\n await t.send(\n topic,\n message,\n {\n delaySeconds: deliveryDelaySeconds,\n idempotencyKey,\n retentionSeconds: MAX_RETENTION_SECONDS,\n },\n { deployment },\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, { allowStructural: true })\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 schedulerHandler(...)`,\n )\n }\n const internals = serverInternals.get(server)\n if (task.kind === 'append') {\n await deliverSchedulerAppend(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 * They target the publishing deployment unless explicitly configured\n * as deploymentless. Drain tasks always remain deployment-scoped.\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 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\ntype VercelQueueMessage = SchedulerTask & {\n /** Provider-private state used to address callback lease operations. */\n _a2Vercel?: {\n version: 1\n deployment: 'deployment' | 'deploymentless'\n }\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 /** Resolved A2 affinity. Custom transports own its provider mapping. */\n routing?: {\n deployment: 'deployment' | 'deploymentless'\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 /**\n * Deployment affinity for scheduled append tasks. Recovery drain tasks\n * always target the publishing deployment. Default `'deployment'`.\n */\n scheduledAppends?: 'deployment' | 'deploymentless'\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 (): 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 deploymentClient = new queue.QueueClient()\n const deploymentlessClient = new queue.QueueClient({ deploymentId: null })\n return {\n send: (topic, message, options, routing) => {\n const deployment = routing?.deployment ?? 'deployment'\n const body: VercelQueueMessage = {\n ...message,\n _a2Vercel: { version: 1, deployment },\n }\n return (\n deployment === 'deploymentless'\n ? deploymentlessClient\n : deploymentClient\n ).send(topic, body, options)\n },\n handleCallback: (handler) => {\n const callbackOptions = {\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: unknown, metadata: QueueDeliveryMetadata) =>\n error instanceof DrainUnsettled\n ? {\n afterSeconds: unsettledBackoffSeconds(metadata.deliveryCount),\n }\n : undefined,\n }\n const deploymentHandler = deploymentClient.handleCallback(\n handler,\n callbackOptions,\n ) as (req: Request) => Promise<Response>\n const deploymentlessHandler = deploymentlessClient.handleCallback(\n handler,\n callbackOptions,\n ) as (req: Request) => Promise<Response>\n return async (req: Request): Promise<Response> => {\n let delivered: unknown\n try {\n delivered = await req.clone().json()\n } catch {\n return deploymentlessHandler(req)\n }\n const metadata = recordOf(recordOf(delivered)?.['_a2Vercel'])\n const routing =\n metadata?.['version'] === 1 ? metadata['deployment'] : undefined\n return routing === 'deployment'\n ? deploymentHandler(req)\n : deploymentlessHandler(req)\n }\n },\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 scheduledAppends = options.scheduledAppends ?? 'deployment'\n if (\n scheduledAppends !== 'deployment' &&\n scheduledAppends !== 'deploymentless'\n ) {\n throw new TypeError(\n \"vercelQueues() scheduledAppends must be 'deployment' or 'deploymentless'\",\n )\n }\n const clock = options.clock ?? SYSTEM_CLOCK\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(),\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 === 'append' ? requestedDueAt : dueAt\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 deployment = task.kind === 'append' ? scheduledAppends : 'deployment'\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 : digestId(['a2:scheduler-vercel:append:v1', topic, task.id])\n const t = await transport()\n try {\n await t.send(\n topic,\n message,\n {\n delaySeconds: deliveryDelaySeconds,\n idempotencyKey,\n retentionSeconds: MAX_RETENTION_SECONDS,\n },\n { deployment },\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\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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"}
|