inngest 4.1.2 → 4.2.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.
Files changed (93) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/api/api.cjs +30 -1
  3. package/api/api.cjs.map +1 -1
  4. package/api/api.d.cts +19 -0
  5. package/api/api.d.cts.map +1 -1
  6. package/api/api.d.ts +19 -0
  7. package/api/api.d.ts.map +1 -1
  8. package/api/api.js +30 -1
  9. package/api/api.js.map +1 -1
  10. package/components/InngestCommHandler.cjs +9 -1
  11. package/components/InngestCommHandler.cjs.map +1 -1
  12. package/components/InngestCommHandler.d.cts.map +1 -1
  13. package/components/InngestCommHandler.d.ts.map +1 -1
  14. package/components/InngestCommHandler.js +9 -1
  15. package/components/InngestCommHandler.js.map +1 -1
  16. package/components/StreamTools.cjs +241 -0
  17. package/components/StreamTools.cjs.map +1 -0
  18. package/components/StreamTools.d.cts +161 -0
  19. package/components/StreamTools.d.cts.map +1 -0
  20. package/components/StreamTools.d.ts +161 -0
  21. package/components/StreamTools.d.ts.map +1 -0
  22. package/components/StreamTools.js +240 -0
  23. package/components/StreamTools.js.map +1 -0
  24. package/components/createWebApiCommHandler.cjs +46 -0
  25. package/components/createWebApiCommHandler.cjs.map +1 -0
  26. package/components/createWebApiCommHandler.js +46 -0
  27. package/components/createWebApiCommHandler.js.map +1 -0
  28. package/components/execution/InngestExecution.cjs.map +1 -1
  29. package/components/execution/InngestExecution.d.cts +6 -0
  30. package/components/execution/InngestExecution.d.cts.map +1 -1
  31. package/components/execution/InngestExecution.d.ts +6 -0
  32. package/components/execution/InngestExecution.d.ts.map +1 -1
  33. package/components/execution/InngestExecution.js.map +1 -1
  34. package/components/execution/als.cjs.map +1 -1
  35. package/components/execution/als.d.cts +9 -1
  36. package/components/execution/als.d.cts.map +1 -1
  37. package/components/execution/als.d.ts +9 -1
  38. package/components/execution/als.d.ts.map +1 -1
  39. package/components/execution/als.js.map +1 -1
  40. package/components/execution/engine.cjs +334 -26
  41. package/components/execution/engine.cjs.map +1 -1
  42. package/components/execution/engine.d.cts +1 -0
  43. package/components/execution/engine.d.cts.map +1 -1
  44. package/components/execution/engine.d.ts +1 -0
  45. package/components/execution/engine.d.ts.map +1 -1
  46. package/components/execution/engine.js +334 -26
  47. package/components/execution/engine.js.map +1 -1
  48. package/components/execution/streaming.cjs +208 -0
  49. package/components/execution/streaming.cjs.map +1 -0
  50. package/components/execution/streaming.d.cts +12 -0
  51. package/components/execution/streaming.d.cts.map +1 -0
  52. package/components/execution/streaming.d.ts +12 -0
  53. package/components/execution/streaming.d.ts.map +1 -0
  54. package/components/execution/streaming.js +198 -0
  55. package/components/execution/streaming.js.map +1 -0
  56. package/edge.cjs +19 -32
  57. package/edge.cjs.map +1 -1
  58. package/edge.d.cts +1 -1
  59. package/edge.d.cts.map +1 -1
  60. package/edge.d.ts +1 -1
  61. package/edge.d.ts.map +1 -1
  62. package/edge.js +19 -32
  63. package/edge.js.map +1 -1
  64. package/experimental/durable-endpoints/client.cjs +114 -0
  65. package/experimental/durable-endpoints/client.cjs.map +1 -0
  66. package/experimental/durable-endpoints/client.d.cts +49 -0
  67. package/experimental/durable-endpoints/client.d.cts.map +1 -0
  68. package/experimental/durable-endpoints/client.d.ts +49 -0
  69. package/experimental/durable-endpoints/client.d.ts.map +1 -0
  70. package/experimental/durable-endpoints/client.js +114 -0
  71. package/experimental/durable-endpoints/client.js.map +1 -0
  72. package/experimental/durable-endpoints/index.cjs +3 -0
  73. package/experimental/durable-endpoints/index.d.cts +2 -0
  74. package/experimental/durable-endpoints/index.d.ts +2 -0
  75. package/experimental/durable-endpoints/index.js +3 -0
  76. package/helpers/promises.cjs.map +1 -1
  77. package/helpers/promises.js.map +1 -1
  78. package/node.cjs +97 -0
  79. package/node.cjs.map +1 -1
  80. package/node.d.cts +34 -2
  81. package/node.d.cts.map +1 -1
  82. package/node.d.ts +34 -2
  83. package/node.d.ts.map +1 -1
  84. package/node.js +95 -2
  85. package/node.js.map +1 -1
  86. package/package.json +17 -1
  87. package/react.d.cts.map +1 -1
  88. package/version.cjs +1 -1
  89. package/version.cjs.map +1 -1
  90. package/version.d.cts +1 -1
  91. package/version.d.ts +1 -1
  92. package/version.js +1 -1
  93. package/version.js.map +1 -1
package/edge.js CHANGED
@@ -1,45 +1,32 @@
1
- import { InngestCommHandler } from "./components/InngestCommHandler.js";
1
+ import { createWebApiCommHandler } from "./components/createWebApiCommHandler.js";
2
2
  import { handleDurableEndpointProxyRequest } from "./components/InngestDurableEndpointProxy.js";
3
3
  import { InngestEndpointAdapter } from "./components/InngestEndpointAdapter.js";
4
4
 
5
5
  //#region src/edge.ts
6
6
  /**
7
+ * An adapter for any request that handles standard Web APIs such as `fetch`,
8
+ * `Request,` and `Response` to serve and register any declared functions with
9
+ * Inngest, making them available to be triggered by events.
10
+ *
11
+ * This is reused by many other adapters, but can be used directly.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { serve } from "inngest/edge";
16
+ * import functions from "~/inngest";
17
+ *
18
+ * export const handler = serve({ id: "my-edge-app", functions });
19
+ * ```
20
+ *
21
+ * @module
22
+ */
23
+ /**
7
24
  * The name of the framework, used to identify the framework in Inngest
8
25
  * dashboards and during testing.
9
26
  */
10
27
  const frameworkName = "edge";
11
28
  const commHandler = (options, syncOptions) => {
12
- return new InngestCommHandler({
13
- frameworkName,
14
- ...options,
15
- syncOptions,
16
- handler: (req) => {
17
- return {
18
- body: () => req.text(),
19
- headers: (key) => req.headers.get(key),
20
- method: () => req.method,
21
- url: () => new URL(req.url, `https://${req.headers.get("host") || ""}`),
22
- transformResponse: ({ body, status, headers }) => {
23
- return new Response(body, {
24
- status,
25
- headers
26
- });
27
- },
28
- experimentalTransformSyncResponse: async (data) => {
29
- const res = data;
30
- const headers = {};
31
- res.headers.forEach((v, k) => {
32
- headers[k] = v;
33
- });
34
- return {
35
- headers,
36
- status: res.status,
37
- body: await res.clone().text()
38
- };
39
- }
40
- };
41
- }
42
- });
29
+ return createWebApiCommHandler(frameworkName, options, syncOptions);
43
30
  };
44
31
  /**
45
32
  * In an edge runtime, serve and register any declared functions with Inngest,
package/edge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"edge.js","names":["frameworkName: SupportedFrameworkName","headers: Record<string, string>"],"sources":["../src/edge.ts"],"sourcesContent":["/**\n * An adapter for any request that handles standard Web APIs such as `fetch`,\n * `Request,` and `Response` to serve and register any declared functions with\n * Inngest, making them available to be triggered by events.\n *\n * This is reused by many other adapters, but can be used directly.\n *\n * @example\n * ```ts\n * import { serve } from \"inngest/edge\";\n * import functions from \"~/inngest\";\n *\n * export const handler = serve({ id: \"my-edge-app\", functions });\n * ```\n *\n * @module\n */\n\nimport type { Inngest } from \"./components/Inngest.ts\";\nimport {\n InngestCommHandler,\n type ServeHandlerOptions,\n type SyncHandlerOptions,\n} from \"./components/InngestCommHandler.ts\";\nimport { handleDurableEndpointProxyRequest } from \"./components/InngestDurableEndpointProxy.ts\";\nimport { InngestEndpointAdapter } from \"./components/InngestEndpointAdapter.ts\";\nimport type { RegisterOptions, SupportedFrameworkName } from \"./types.ts\";\n\n/**\n * The name of the framework, used to identify the framework in Inngest\n * dashboards and during testing.\n */\nexport const frameworkName: SupportedFrameworkName = \"edge\";\n\nexport type EdgeHandler = (req: Request) => Promise<Response>;\n\nconst commHandler = (\n options: RegisterOptions & { client: Inngest.Like },\n syncOptions?: SyncHandlerOptions,\n) => {\n const handler = new InngestCommHandler({\n frameworkName,\n ...options,\n syncOptions,\n handler: (req: Request) => {\n return {\n body: () => req.text(),\n headers: (key: string) => req.headers.get(key),\n method: () => req.method,\n url: () => new URL(req.url, `https://${req.headers.get(\"host\") || \"\"}`),\n transformResponse: ({ body, status, headers }) => {\n return new Response(body, { status, headers });\n },\n experimentalTransformSyncResponse: async (data) => {\n const res = data as Response;\n\n const headers: Record<string, string> = {};\n res.headers.forEach((v, k) => {\n headers[k] = v;\n });\n\n return {\n headers: headers,\n status: res.status,\n body: await res.clone().text(),\n };\n },\n };\n },\n });\n\n return handler;\n};\n\n/**\n * In an edge runtime, serve and register any declared functions with Inngest,\n * making them available to be triggered by events.\n *\n * The edge runtime is a generic term for any serverless runtime that supports\n * only standard Web APIs such as `fetch`, `Request`, and `Response`, such as\n * Cloudflare Workers, Vercel Edge Functions, and AWS Lambda@Edge.\n *\n * @example\n * ```ts\n * import { serve } from \"inngest/edge\";\n * import functions from \"~/inngest\";\n *\n * export const handler = serve({ id: \"my-edge-app\", functions });\n * ```\n *\n * @public\n */\n// Has explicit return type to avoid JSR-defined \"slow types\"\nexport const serve = (options: ServeHandlerOptions): EdgeHandler => {\n return commHandler(options).createHandler();\n};\n\n/**\n * Creates a durable endpoint proxy handler for edge environments.\n *\n * This handler extracts `runId` and `token` from query parameters,\n * fetches the run output from Inngest, decrypts it via middleware\n * (if configured), and returns it with CORS headers.\n */\nconst createDurableEndpointProxyHandler = (\n options: InngestEndpointAdapter.ProxyHandlerOptions,\n): EdgeHandler => {\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n\n const result = await handleDurableEndpointProxyRequest(\n options.client as Inngest.Any,\n {\n runId: url.searchParams.get(\"runId\"),\n token: url.searchParams.get(\"token\"),\n method: req.method,\n },\n );\n\n return new Response(result.body, {\n status: result.status,\n headers: result.headers,\n });\n };\n};\n\n/**\n * In an edge runtime, create a function that can wrap any endpoint to be able\n * to use steps seamlessly within that API.\n *\n * The edge runtime is a generic term for any serverless runtime that supports\n * only standard Web APIs such as `fetch`, `Request`, and `Response`, such as\n * Cloudflare Workers, Vercel Edge Functions, and AWS Lambda@Edge.\n *\n * @example\n * ```ts\n * import { Inngest, step } from \"inngest\";\n * import { endpointAdapter } from \"inngest/edge\";\n *\n * const inngest = new Inngest({\n * id: \"my-app\",\n * endpointAdapter,\n * });\n *\n * Bun.serve({\n * routes: {\n * \"/\": inngest.endpoint(async (req) => {\n * const foo = await step.run(\"my-step\", () => ({ foo: \"bar\" }));\n *\n * return new Response(`Result: ${JSON.stringify(foo)}`);\n * }),\n * },\n * });\n * ```\n *\n * You can also configure a custom redirect URL and create a proxy endpoint:\n *\n * @example\n * ```ts\n * import { Inngest } from \"inngest\";\n * import { endpointAdapter } from \"inngest/edge\";\n *\n * const inngest = new Inngest({\n * id: \"my-app\",\n * endpointAdapter: endpointAdapter.withOptions({\n * asyncRedirectUrl: \"/api/inngest/poll\",\n * }),\n * });\n *\n * // Your durable endpoint\n * export const GET = inngest.endpoint(async (req) => {\n * const result = await step.run(\"work\", () => \"done\");\n * return new Response(result);\n * });\n *\n * // Proxy endpoint at /api/inngest/poll - handles CORS and decryption\n * export const GET = inngest.endpointProxy();\n * ```\n */\nexport const endpointAdapter = InngestEndpointAdapter.create((options) => {\n return commHandler(options, options).createSyncHandler();\n}, createDurableEndpointProxyHandler);\n"],"mappings":";;;;;;;;;AAgCA,MAAaA,gBAAwC;AAIrD,MAAM,eACJ,SACA,gBACG;AAgCH,QA/BgB,IAAI,mBAAmB;EACrC;EACA,GAAG;EACH;EACA,UAAU,QAAiB;AACzB,UAAO;IACL,YAAY,IAAI,MAAM;IACtB,UAAU,QAAgB,IAAI,QAAQ,IAAI,IAAI;IAC9C,cAAc,IAAI;IAClB,WAAW,IAAI,IAAI,IAAI,KAAK,WAAW,IAAI,QAAQ,IAAI,OAAO,IAAI,KAAK;IACvE,oBAAoB,EAAE,MAAM,QAAQ,cAAc;AAChD,YAAO,IAAI,SAAS,MAAM;MAAE;MAAQ;MAAS,CAAC;;IAEhD,mCAAmC,OAAO,SAAS;KACjD,MAAM,MAAM;KAEZ,MAAMC,UAAkC,EAAE;AAC1C,SAAI,QAAQ,SAAS,GAAG,MAAM;AAC5B,cAAQ,KAAK;OACb;AAEF,YAAO;MACI;MACT,QAAQ,IAAI;MACZ,MAAM,MAAM,IAAI,OAAO,CAAC,MAAM;MAC/B;;IAEJ;;EAEJ,CAAC;;;;;;;;;;;;;;;;;;;;AAwBJ,MAAa,SAAS,YAA8C;AAClE,QAAO,YAAY,QAAQ,CAAC,eAAe;;;;;;;;;AAU7C,MAAM,qCACJ,YACgB;AAChB,QAAO,OAAO,QAAoC;EAChD,MAAM,MAAM,IAAI,IAAI,IAAI,IAAI;EAE5B,MAAM,SAAS,MAAM,kCACnB,QAAQ,QACR;GACE,OAAO,IAAI,aAAa,IAAI,QAAQ;GACpC,OAAO,IAAI,aAAa,IAAI,QAAQ;GACpC,QAAQ,IAAI;GACb,CACF;AAED,SAAO,IAAI,SAAS,OAAO,MAAM;GAC/B,QAAQ,OAAO;GACf,SAAS,OAAO;GACjB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDN,MAAa,kBAAkB,uBAAuB,QAAQ,YAAY;AACxE,QAAO,YAAY,SAAS,QAAQ,CAAC,mBAAmB;GACvD,kCAAkC"}
1
+ {"version":3,"file":"edge.js","names":["frameworkName: SupportedFrameworkName"],"sources":["../src/edge.ts"],"sourcesContent":["/**\n * An adapter for any request that handles standard Web APIs such as `fetch`,\n * `Request,` and `Response` to serve and register any declared functions with\n * Inngest, making them available to be triggered by events.\n *\n * This is reused by many other adapters, but can be used directly.\n *\n * @example\n * ```ts\n * import { serve } from \"inngest/edge\";\n * import functions from \"~/inngest\";\n *\n * export const handler = serve({ id: \"my-edge-app\", functions });\n * ```\n *\n * @module\n */\n\nimport { createWebApiCommHandler } from \"./components/createWebApiCommHandler.ts\";\nimport type { Inngest } from \"./components/Inngest.ts\";\nimport type {\n ServeHandlerOptions,\n SyncHandlerOptions,\n} from \"./components/InngestCommHandler.ts\";\nimport { handleDurableEndpointProxyRequest } from \"./components/InngestDurableEndpointProxy.ts\";\nimport { InngestEndpointAdapter } from \"./components/InngestEndpointAdapter.ts\";\nimport type { RegisterOptions, SupportedFrameworkName } from \"./types.ts\";\n\n/**\n * The name of the framework, used to identify the framework in Inngest\n * dashboards and during testing.\n */\nexport const frameworkName: SupportedFrameworkName = \"edge\";\n\nexport type EdgeHandler = (req: Request) => Promise<Response>;\n\nconst commHandler = (\n options: RegisterOptions & { client: Inngest.Like },\n syncOptions?: SyncHandlerOptions,\n) => {\n return createWebApiCommHandler(frameworkName, options, syncOptions);\n};\n\n/**\n * In an edge runtime, serve and register any declared functions with Inngest,\n * making them available to be triggered by events.\n *\n * The edge runtime is a generic term for any serverless runtime that supports\n * only standard Web APIs such as `fetch`, `Request`, and `Response`, such as\n * Cloudflare Workers, Vercel Edge Functions, and AWS Lambda@Edge.\n *\n * @example\n * ```ts\n * import { serve } from \"inngest/edge\";\n * import functions from \"~/inngest\";\n *\n * export const handler = serve({ id: \"my-edge-app\", functions });\n * ```\n *\n * @public\n */\n// Has explicit return type to avoid JSR-defined \"slow types\"\nexport const serve = (options: ServeHandlerOptions): EdgeHandler => {\n return commHandler(options).createHandler();\n};\n\n/**\n * Creates a durable endpoint proxy handler for edge environments.\n *\n * This handler extracts `runId` and `token` from query parameters,\n * fetches the run output from Inngest, decrypts it via middleware\n * (if configured), and returns it with CORS headers.\n */\nconst createDurableEndpointProxyHandler = (\n options: InngestEndpointAdapter.ProxyHandlerOptions,\n): EdgeHandler => {\n return async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n\n const result = await handleDurableEndpointProxyRequest(\n options.client as Inngest.Any,\n {\n runId: url.searchParams.get(\"runId\"),\n token: url.searchParams.get(\"token\"),\n method: req.method,\n },\n );\n\n return new Response(result.body, {\n status: result.status,\n headers: result.headers,\n });\n };\n};\n\n/**\n * In an edge runtime, create a function that can wrap any endpoint to be able\n * to use steps seamlessly within that API.\n *\n * The edge runtime is a generic term for any serverless runtime that supports\n * only standard Web APIs such as `fetch`, `Request`, and `Response`, such as\n * Cloudflare Workers, Vercel Edge Functions, and AWS Lambda@Edge.\n *\n * @example\n * ```ts\n * import { Inngest, step } from \"inngest\";\n * import { endpointAdapter } from \"inngest/edge\";\n *\n * const inngest = new Inngest({\n * id: \"my-app\",\n * endpointAdapter,\n * });\n *\n * Bun.serve({\n * routes: {\n * \"/\": inngest.endpoint(async (req) => {\n * const foo = await step.run(\"my-step\", () => ({ foo: \"bar\" }));\n *\n * return new Response(`Result: ${JSON.stringify(foo)}`);\n * }),\n * },\n * });\n * ```\n *\n * You can also configure a custom redirect URL and create a proxy endpoint:\n *\n * @example\n * ```ts\n * import { Inngest } from \"inngest\";\n * import { endpointAdapter } from \"inngest/edge\";\n *\n * const inngest = new Inngest({\n * id: \"my-app\",\n * endpointAdapter: endpointAdapter.withOptions({\n * asyncRedirectUrl: \"/api/inngest/poll\",\n * }),\n * });\n *\n * // Your durable endpoint\n * export const GET = inngest.endpoint(async (req) => {\n * const result = await step.run(\"work\", () => \"done\");\n * return new Response(result);\n * });\n *\n * // Proxy endpoint at /api/inngest/poll - handles CORS and decryption\n * export const GET = inngest.endpointProxy();\n * ```\n */\nexport const endpointAdapter = InngestEndpointAdapter.create((options) => {\n return commHandler(options, options).createSyncHandler();\n}, createDurableEndpointProxyHandler);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAaA,gBAAwC;AAIrD,MAAM,eACJ,SACA,gBACG;AACH,QAAO,wBAAwB,eAAe,SAAS,YAAY;;;;;;;;;;;;;;;;;;;;AAsBrE,MAAa,SAAS,YAA8C;AAClE,QAAO,YAAY,QAAQ,CAAC,eAAe;;;;;;;;;AAU7C,MAAM,qCACJ,YACgB;AAChB,QAAO,OAAO,QAAoC;EAChD,MAAM,MAAM,IAAI,IAAI,IAAI,IAAI;EAE5B,MAAM,SAAS,MAAM,kCACnB,QAAQ,QACR;GACE,OAAO,IAAI,aAAa,IAAI,QAAQ;GACpC,OAAO,IAAI,aAAa,IAAI,QAAQ;GACpC,QAAQ,IAAI;GACb,CACF;AAED,SAAO,IAAI,SAAS,OAAO,MAAM;GAC/B,QAAQ,OAAO;GACf,SAAS,OAAO;GACjB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDN,MAAa,kBAAkB,uBAAuB,QAAQ,YAAY;AACxE,QAAO,YAAY,SAAS,QAAQ,CAAC,mBAAmB;GACvD,kCAAkC"}
@@ -0,0 +1,114 @@
1
+ const require_streaming = require('../../components/execution/streaming.cjs');
2
+
3
+ //#region src/experimental/durable-endpoints/client.ts
4
+ /**
5
+ * Entrypoint file for client-side Durable Endpoint utilities.
6
+ */
7
+ /**
8
+ * Fetch a durable endpoint URL and consume its SSE stream, dispatching
9
+ * lifecycle callbacks (metadata, data, commit, rollback) as
10
+ * events arrive. Returns the final `Response` reconstructed from the
11
+ * terminal `inngest.response` SSE event.
12
+ *
13
+ * If the server does not respond with `text/event-stream`, the raw
14
+ * `Response` is returned as-is (non-streaming path).
15
+ */
16
+ async function fetchWithStream(url, opts) {
17
+ const fetchFn = opts?.fetch ?? globalThis.fetch;
18
+ const baseHeaders = {};
19
+ if (opts?.fetchOpts?.headers) new Headers(opts.fetchOpts.headers).forEach((value, key) => {
20
+ baseHeaders[key] = value;
21
+ });
22
+ const initialRes = await fetchFn(url, {
23
+ ...opts?.fetchOpts,
24
+ headers: {
25
+ ...baseHeaders,
26
+ Accept: "text/event-stream"
27
+ }
28
+ });
29
+ if (!(initialRes.headers.get("content-type") ?? "").includes("text/event-stream")) return initialRes;
30
+ if (!initialRes.body) throw new Error("No response body");
31
+ let resp;
32
+ const source = iterSseFollowRedirects(initialRes.body, fetchFn, opts?.fetchOpts?.signal ?? void 0);
33
+ outer: for await (const sseEvent of source) switch (sseEvent.type) {
34
+ case "inngest.stream":
35
+ opts?.onData?.({
36
+ data: sseEvent.data,
37
+ hashedStepId: sseEvent.hashedStepId ?? null
38
+ });
39
+ break;
40
+ case "inngest.commit":
41
+ opts?.onCommit?.({ hashedStepId: sseEvent.hashedStepId });
42
+ break;
43
+ case "inngest.rollback":
44
+ opts?.onRollback?.({ hashedStepId: sseEvent.hashedStepId });
45
+ break;
46
+ case "inngest.response":
47
+ resp = new Response(sseEvent.response.body, {
48
+ status: sseEvent.response.statusCode,
49
+ headers: sseEvent.response.headers
50
+ });
51
+ break outer;
52
+ case "inngest.metadata":
53
+ opts?.onMetadata?.({ runId: sseEvent.runId });
54
+ break;
55
+ default: break;
56
+ }
57
+ if (!resp) throw new Error("No response");
58
+ return resp;
59
+ }
60
+ /**
61
+ * Async generator that yields parsed SSE events from an already-fetched
62
+ * response body, following `inngest.redirect_info` redirects.
63
+ *
64
+ * When a redirect event arrives, the redirect URL is fetched eagerly in the
65
+ * background so the connection is already established by the time the direct
66
+ * stream closes. This minimizes the window for late-joiner data loss.
67
+ */
68
+ async function* iterSseFollowRedirects(body, fetchFn, signal) {
69
+ const fetchOpts = {
70
+ headers: { Accept: "text/event-stream" },
71
+ signal
72
+ };
73
+ let redirectUrl;
74
+ let eagerResponse;
75
+ try {
76
+ for await (const raw of require_streaming.iterSse(body)) {
77
+ const sseEvent = require_streaming.parseSseEvent(raw);
78
+ if (!sseEvent) continue;
79
+ if (sseEvent.type === "inngest.redirect_info") {
80
+ redirectUrl = sseEvent.url;
81
+ if (sseEvent.url && !eagerResponse) eagerResponse = fetchFn(sseEvent.url, fetchOpts).catch(() => void 0);
82
+ yield sseEvent;
83
+ continue;
84
+ }
85
+ yield sseEvent;
86
+ }
87
+ if (!redirectUrl) return;
88
+ let redirectRes;
89
+ if (eagerResponse) {
90
+ const eager = await eagerResponse;
91
+ if (eager?.ok && eager.body) redirectRes = eager;
92
+ else await eager?.body?.cancel();
93
+ eagerResponse = void 0;
94
+ }
95
+ if (!redirectRes) {
96
+ if (signal?.aborted) throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
97
+ const fallback = await fetchFn(redirectUrl, fetchOpts);
98
+ if (!fallback.ok) throw new Error(`Stream request failed: ${fallback.status} ${fallback.statusText}`);
99
+ if (!fallback.body) throw new Error("No response body");
100
+ redirectRes = fallback;
101
+ }
102
+ for await (const raw of require_streaming.iterSse(redirectRes.body)) {
103
+ const sseEvent = require_streaming.parseSseEvent(raw);
104
+ if (!sseEvent) continue;
105
+ yield sseEvent;
106
+ }
107
+ } finally {
108
+ if (eagerResponse) eagerResponse.then((r) => r?.body?.cancel()).catch(() => {});
109
+ }
110
+ }
111
+
112
+ //#endregion
113
+ exports.fetchWithStream = fetchWithStream;
114
+ //# sourceMappingURL=client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.cjs","names":["baseHeaders: Record<string, string>","resp: Response | undefined","redirectUrl: string | undefined","eagerResponse: Promise<Response | undefined> | undefined","iterSse","parseSseEvent","redirectRes: Response | undefined"],"sources":["../../../src/experimental/durable-endpoints/client.ts"],"sourcesContent":["/**\n * Entrypoint file for client-side Durable Endpoint utilities.\n */\n\nimport {\n iterSse,\n parseSseEvent,\n type SseEvent,\n} from \"../../components/execution/streaming.ts\";\n\ninterface FetchDurableEndpointOptions {\n /** Fetch function. */\n fetch?: typeof globalThis.fetch;\n\n /** Options passed to the fetch function. */\n fetchOpts?: RequestInit;\n\n /** Called when run metadata is received (e.g. run ID). */\n onMetadata?: (args: { runId: string }) => void;\n\n /**\n * Called for each streamed chunk. Should be considered uncommitted until a\n * commit or rollback event is received.\n */\n onData?: (args: { data: unknown; hashedStepId: string | null }) => void;\n\n /**\n * Called when uncommitted stream data should be rolled back, since a retry\n * will happen.\n */\n onRollback?: (args: { hashedStepId: string | null }) => void;\n\n /**\n * Called when uncommitted stream data should be committed, since it can no longer be\n * rolled back.\n */\n onCommit?: (args: { hashedStepId: string | null }) => void;\n}\n\n/**\n * Fetch a durable endpoint URL and consume its SSE stream, dispatching\n * lifecycle callbacks (metadata, data, commit, rollback) as\n * events arrive. Returns the final `Response` reconstructed from the\n * terminal `inngest.response` SSE event.\n *\n * If the server does not respond with `text/event-stream`, the raw\n * `Response` is returned as-is (non-streaming path).\n */\nexport async function fetchWithStream(\n url: string,\n opts?: FetchDurableEndpointOptions,\n): Promise<Response> {\n const fetchFn = opts?.fetch ?? globalThis.fetch;\n\n const baseHeaders: Record<string, string> = {};\n if (opts?.fetchOpts?.headers) {\n new Headers(opts.fetchOpts.headers).forEach((value, key) => {\n baseHeaders[key] = value;\n });\n }\n\n const initialRes = await fetchFn(url, {\n ...opts?.fetchOpts,\n headers: {\n ...baseHeaders,\n Accept: \"text/event-stream\",\n },\n });\n\n const contentType = initialRes.headers.get(\"content-type\") ?? \"\";\n if (!contentType.includes(\"text/event-stream\")) {\n return initialRes;\n }\n\n if (!initialRes.body) {\n throw new Error(\"No response body\");\n }\n\n let resp: Response | undefined;\n\n const source = iterSseFollowRedirects(\n initialRes.body,\n fetchFn,\n opts?.fetchOpts?.signal ?? undefined,\n );\n\n outer: for await (const sseEvent of source) {\n switch (sseEvent.type) {\n case \"inngest.stream\": {\n opts?.onData?.({\n data: sseEvent.data,\n hashedStepId: sseEvent.hashedStepId ?? null,\n });\n break;\n }\n case \"inngest.commit\": {\n opts?.onCommit?.({ hashedStepId: sseEvent.hashedStepId });\n break;\n }\n case \"inngest.rollback\": {\n opts?.onRollback?.({ hashedStepId: sseEvent.hashedStepId });\n break;\n }\n case \"inngest.response\": {\n resp = new Response(sseEvent.response.body, {\n status: sseEvent.response.statusCode,\n headers: sseEvent.response.headers,\n });\n\n break outer;\n }\n case \"inngest.metadata\": {\n opts?.onMetadata?.({ runId: sseEvent.runId });\n break;\n }\n default:\n break;\n }\n }\n\n if (!resp) {\n throw new Error(\"No response\");\n }\n\n return resp;\n}\n\n/**\n * Async generator that yields parsed SSE events from an already-fetched\n * response body, following `inngest.redirect_info` redirects.\n *\n * When a redirect event arrives, the redirect URL is fetched eagerly in the\n * background so the connection is already established by the time the direct\n * stream closes. This minimizes the window for late-joiner data loss.\n */\nasync function* iterSseFollowRedirects(\n body: ReadableStream<Uint8Array>,\n fetchFn: typeof globalThis.fetch,\n signal?: AbortSignal,\n): AsyncGenerator<SseEvent> {\n const fetchOpts = { headers: { Accept: \"text/event-stream\" }, signal };\n let redirectUrl: string | undefined;\n let eagerResponse: Promise<Response | undefined> | undefined;\n\n try {\n for await (const raw of iterSse(body)) {\n const sseEvent = parseSseEvent(raw);\n if (!sseEvent) continue;\n\n if (sseEvent.type === \"inngest.redirect_info\") {\n redirectUrl = sseEvent.url;\n\n // Start the redirect connection immediately (once only).\n if (sseEvent.url && !eagerResponse) {\n eagerResponse = fetchFn(sseEvent.url, fetchOpts).catch(\n () => undefined,\n );\n }\n\n yield sseEvent;\n continue;\n }\n\n yield sseEvent;\n }\n\n if (!redirectUrl) return;\n\n let redirectRes: Response | undefined;\n\n if (eagerResponse) {\n const eager = await eagerResponse;\n if (eager?.ok && eager.body) {\n redirectRes = eager;\n } else {\n await eager?.body?.cancel();\n }\n eagerResponse = undefined;\n }\n\n if (!redirectRes) {\n if (signal?.aborted) {\n throw (\n signal.reason ??\n new DOMException(\"The operation was aborted.\", \"AbortError\")\n );\n }\n\n const fallback = await fetchFn(redirectUrl, fetchOpts);\n if (!fallback.ok) {\n throw new Error(\n `Stream request failed: ${fallback.status} ${fallback.statusText}`,\n );\n }\n if (!fallback.body) {\n throw new Error(\"No response body\");\n }\n redirectRes = fallback;\n }\n\n for await (const raw of iterSse(redirectRes.body!)) {\n const sseEvent = parseSseEvent(raw);\n if (!sseEvent) continue;\n yield sseEvent;\n }\n } finally {\n // Cancel any unconsumed eager response body to release the connection.\n if (eagerResponse) {\n void eagerResponse.then((r) => r?.body?.cancel()).catch(() => {});\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgDA,eAAsB,gBACpB,KACA,MACmB;CACnB,MAAM,UAAU,MAAM,SAAS,WAAW;CAE1C,MAAMA,cAAsC,EAAE;AAC9C,KAAI,MAAM,WAAW,QACnB,KAAI,QAAQ,KAAK,UAAU,QAAQ,CAAC,SAAS,OAAO,QAAQ;AAC1D,cAAY,OAAO;GACnB;CAGJ,MAAM,aAAa,MAAM,QAAQ,KAAK;EACpC,GAAG,MAAM;EACT,SAAS;GACP,GAAG;GACH,QAAQ;GACT;EACF,CAAC;AAGF,KAAI,EADgB,WAAW,QAAQ,IAAI,eAAe,IAAI,IAC7C,SAAS,oBAAoB,CAC5C,QAAO;AAGT,KAAI,CAAC,WAAW,KACd,OAAM,IAAI,MAAM,mBAAmB;CAGrC,IAAIC;CAEJ,MAAM,SAAS,uBACb,WAAW,MACX,SACA,MAAM,WAAW,UAAU,OAC5B;AAED,OAAO,YAAW,MAAM,YAAY,OAClC,SAAQ,SAAS,MAAjB;EACE,KAAK;AACH,SAAM,SAAS;IACb,MAAM,SAAS;IACf,cAAc,SAAS,gBAAgB;IACxC,CAAC;AACF;EAEF,KAAK;AACH,SAAM,WAAW,EAAE,cAAc,SAAS,cAAc,CAAC;AACzD;EAEF,KAAK;AACH,SAAM,aAAa,EAAE,cAAc,SAAS,cAAc,CAAC;AAC3D;EAEF,KAAK;AACH,UAAO,IAAI,SAAS,SAAS,SAAS,MAAM;IAC1C,QAAQ,SAAS,SAAS;IAC1B,SAAS,SAAS,SAAS;IAC5B,CAAC;AAEF,SAAM;EAER,KAAK;AACH,SAAM,aAAa,EAAE,OAAO,SAAS,OAAO,CAAC;AAC7C;EAEF,QACE;;AAIN,KAAI,CAAC,KACH,OAAM,IAAI,MAAM,cAAc;AAGhC,QAAO;;;;;;;;;;AAWT,gBAAgB,uBACd,MACA,SACA,QAC0B;CAC1B,MAAM,YAAY;EAAE,SAAS,EAAE,QAAQ,qBAAqB;EAAE;EAAQ;CACtE,IAAIC;CACJ,IAAIC;AAEJ,KAAI;AACF,aAAW,MAAM,OAAOC,0BAAQ,KAAK,EAAE;GACrC,MAAM,WAAWC,gCAAc,IAAI;AACnC,OAAI,CAAC,SAAU;AAEf,OAAI,SAAS,SAAS,yBAAyB;AAC7C,kBAAc,SAAS;AAGvB,QAAI,SAAS,OAAO,CAAC,cACnB,iBAAgB,QAAQ,SAAS,KAAK,UAAU,CAAC,YACzC,OACP;AAGH,UAAM;AACN;;AAGF,SAAM;;AAGR,MAAI,CAAC,YAAa;EAElB,IAAIC;AAEJ,MAAI,eAAe;GACjB,MAAM,QAAQ,MAAM;AACpB,OAAI,OAAO,MAAM,MAAM,KACrB,eAAc;OAEd,OAAM,OAAO,MAAM,QAAQ;AAE7B,mBAAgB;;AAGlB,MAAI,CAAC,aAAa;AAChB,OAAI,QAAQ,QACV,OACE,OAAO,UACP,IAAI,aAAa,8BAA8B,aAAa;GAIhE,MAAM,WAAW,MAAM,QAAQ,aAAa,UAAU;AACtD,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MACR,0BAA0B,SAAS,OAAO,GAAG,SAAS,aACvD;AAEH,OAAI,CAAC,SAAS,KACZ,OAAM,IAAI,MAAM,mBAAmB;AAErC,iBAAc;;AAGhB,aAAW,MAAM,OAAOF,0BAAQ,YAAY,KAAM,EAAE;GAClD,MAAM,WAAWC,gCAAc,IAAI;AACnC,OAAI,CAAC,SAAU;AACf,SAAM;;WAEA;AAER,MAAI,cACF,CAAK,cAAc,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,CAAC,YAAY,GAAG"}
@@ -0,0 +1,49 @@
1
+ //#region src/experimental/durable-endpoints/client.d.ts
2
+ /**
3
+ * Entrypoint file for client-side Durable Endpoint utilities.
4
+ */
5
+ interface FetchDurableEndpointOptions {
6
+ /** Fetch function. */
7
+ fetch?: typeof globalThis.fetch;
8
+ /** Options passed to the fetch function. */
9
+ fetchOpts?: RequestInit;
10
+ /** Called when run metadata is received (e.g. run ID). */
11
+ onMetadata?: (args: {
12
+ runId: string;
13
+ }) => void;
14
+ /**
15
+ * Called for each streamed chunk. Should be considered uncommitted until a
16
+ * commit or rollback event is received.
17
+ */
18
+ onData?: (args: {
19
+ data: unknown;
20
+ hashedStepId: string | null;
21
+ }) => void;
22
+ /**
23
+ * Called when uncommitted stream data should be rolled back, since a retry
24
+ * will happen.
25
+ */
26
+ onRollback?: (args: {
27
+ hashedStepId: string | null;
28
+ }) => void;
29
+ /**
30
+ * Called when uncommitted stream data should be committed, since it can no longer be
31
+ * rolled back.
32
+ */
33
+ onCommit?: (args: {
34
+ hashedStepId: string | null;
35
+ }) => void;
36
+ }
37
+ /**
38
+ * Fetch a durable endpoint URL and consume its SSE stream, dispatching
39
+ * lifecycle callbacks (metadata, data, commit, rollback) as
40
+ * events arrive. Returns the final `Response` reconstructed from the
41
+ * terminal `inngest.response` SSE event.
42
+ *
43
+ * If the server does not respond with `text/event-stream`, the raw
44
+ * `Response` is returned as-is (non-streaming path).
45
+ */
46
+ declare function fetchWithStream(url: string, opts?: FetchDurableEndpointOptions): Promise<Response>;
47
+ //#endregion
48
+ export { fetchWithStream };
49
+ //# sourceMappingURL=client.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.cts","names":[],"sources":["../../../src/experimental/durable-endpoints/client.ts"],"sourcesContent":[],"mappings":";;;;UAUU,2BAAA,CAEkB;;EAGH,KAAA,CAAA,EAAA,OAHR,UAAA,CAAW,KAGH;EAiCH;EAAe,SAAA,CAAA,EAjCvB,WAiCuB;;YAG1B,CAAA,EAAA,CAAA,IAAA,EAAA;IAAR,KAAA,EAAA,MAAA;EAAO,CAAA,EAAA,GAAA,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAHY,eAAA,qBAEb,8BACN,QAAQ"}
@@ -0,0 +1,49 @@
1
+ //#region src/experimental/durable-endpoints/client.d.ts
2
+ /**
3
+ * Entrypoint file for client-side Durable Endpoint utilities.
4
+ */
5
+ interface FetchDurableEndpointOptions {
6
+ /** Fetch function. */
7
+ fetch?: typeof globalThis.fetch;
8
+ /** Options passed to the fetch function. */
9
+ fetchOpts?: RequestInit;
10
+ /** Called when run metadata is received (e.g. run ID). */
11
+ onMetadata?: (args: {
12
+ runId: string;
13
+ }) => void;
14
+ /**
15
+ * Called for each streamed chunk. Should be considered uncommitted until a
16
+ * commit or rollback event is received.
17
+ */
18
+ onData?: (args: {
19
+ data: unknown;
20
+ hashedStepId: string | null;
21
+ }) => void;
22
+ /**
23
+ * Called when uncommitted stream data should be rolled back, since a retry
24
+ * will happen.
25
+ */
26
+ onRollback?: (args: {
27
+ hashedStepId: string | null;
28
+ }) => void;
29
+ /**
30
+ * Called when uncommitted stream data should be committed, since it can no longer be
31
+ * rolled back.
32
+ */
33
+ onCommit?: (args: {
34
+ hashedStepId: string | null;
35
+ }) => void;
36
+ }
37
+ /**
38
+ * Fetch a durable endpoint URL and consume its SSE stream, dispatching
39
+ * lifecycle callbacks (metadata, data, commit, rollback) as
40
+ * events arrive. Returns the final `Response` reconstructed from the
41
+ * terminal `inngest.response` SSE event.
42
+ *
43
+ * If the server does not respond with `text/event-stream`, the raw
44
+ * `Response` is returned as-is (non-streaming path).
45
+ */
46
+ declare function fetchWithStream(url: string, opts?: FetchDurableEndpointOptions): Promise<Response>;
47
+ //#endregion
48
+ export { fetchWithStream };
49
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","names":[],"sources":["../../../src/experimental/durable-endpoints/client.ts"],"sourcesContent":[],"mappings":";;;;UAUU,2BAAA,CAEkB;;EAGH,KAAA,CAAA,EAAA,OAHR,UAAA,CAAW,KAGH;EAiCH;EAAe,SAAA,CAAA,EAjCvB,WAiCuB;;YAG1B,CAAA,EAAA,CAAA,IAAA,EAAA;IAAR,KAAA,EAAA,MAAA;EAAO,CAAA,EAAA,GAAA,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAHY,eAAA,qBAEb,8BACN,QAAQ"}
@@ -0,0 +1,114 @@
1
+ import { iterSse, parseSseEvent } from "../../components/execution/streaming.js";
2
+
3
+ //#region src/experimental/durable-endpoints/client.ts
4
+ /**
5
+ * Entrypoint file for client-side Durable Endpoint utilities.
6
+ */
7
+ /**
8
+ * Fetch a durable endpoint URL and consume its SSE stream, dispatching
9
+ * lifecycle callbacks (metadata, data, commit, rollback) as
10
+ * events arrive. Returns the final `Response` reconstructed from the
11
+ * terminal `inngest.response` SSE event.
12
+ *
13
+ * If the server does not respond with `text/event-stream`, the raw
14
+ * `Response` is returned as-is (non-streaming path).
15
+ */
16
+ async function fetchWithStream(url, opts) {
17
+ const fetchFn = opts?.fetch ?? globalThis.fetch;
18
+ const baseHeaders = {};
19
+ if (opts?.fetchOpts?.headers) new Headers(opts.fetchOpts.headers).forEach((value, key) => {
20
+ baseHeaders[key] = value;
21
+ });
22
+ const initialRes = await fetchFn(url, {
23
+ ...opts?.fetchOpts,
24
+ headers: {
25
+ ...baseHeaders,
26
+ Accept: "text/event-stream"
27
+ }
28
+ });
29
+ if (!(initialRes.headers.get("content-type") ?? "").includes("text/event-stream")) return initialRes;
30
+ if (!initialRes.body) throw new Error("No response body");
31
+ let resp;
32
+ const source = iterSseFollowRedirects(initialRes.body, fetchFn, opts?.fetchOpts?.signal ?? void 0);
33
+ outer: for await (const sseEvent of source) switch (sseEvent.type) {
34
+ case "inngest.stream":
35
+ opts?.onData?.({
36
+ data: sseEvent.data,
37
+ hashedStepId: sseEvent.hashedStepId ?? null
38
+ });
39
+ break;
40
+ case "inngest.commit":
41
+ opts?.onCommit?.({ hashedStepId: sseEvent.hashedStepId });
42
+ break;
43
+ case "inngest.rollback":
44
+ opts?.onRollback?.({ hashedStepId: sseEvent.hashedStepId });
45
+ break;
46
+ case "inngest.response":
47
+ resp = new Response(sseEvent.response.body, {
48
+ status: sseEvent.response.statusCode,
49
+ headers: sseEvent.response.headers
50
+ });
51
+ break outer;
52
+ case "inngest.metadata":
53
+ opts?.onMetadata?.({ runId: sseEvent.runId });
54
+ break;
55
+ default: break;
56
+ }
57
+ if (!resp) throw new Error("No response");
58
+ return resp;
59
+ }
60
+ /**
61
+ * Async generator that yields parsed SSE events from an already-fetched
62
+ * response body, following `inngest.redirect_info` redirects.
63
+ *
64
+ * When a redirect event arrives, the redirect URL is fetched eagerly in the
65
+ * background so the connection is already established by the time the direct
66
+ * stream closes. This minimizes the window for late-joiner data loss.
67
+ */
68
+ async function* iterSseFollowRedirects(body, fetchFn, signal) {
69
+ const fetchOpts = {
70
+ headers: { Accept: "text/event-stream" },
71
+ signal
72
+ };
73
+ let redirectUrl;
74
+ let eagerResponse;
75
+ try {
76
+ for await (const raw of iterSse(body)) {
77
+ const sseEvent = parseSseEvent(raw);
78
+ if (!sseEvent) continue;
79
+ if (sseEvent.type === "inngest.redirect_info") {
80
+ redirectUrl = sseEvent.url;
81
+ if (sseEvent.url && !eagerResponse) eagerResponse = fetchFn(sseEvent.url, fetchOpts).catch(() => void 0);
82
+ yield sseEvent;
83
+ continue;
84
+ }
85
+ yield sseEvent;
86
+ }
87
+ if (!redirectUrl) return;
88
+ let redirectRes;
89
+ if (eagerResponse) {
90
+ const eager = await eagerResponse;
91
+ if (eager?.ok && eager.body) redirectRes = eager;
92
+ else await eager?.body?.cancel();
93
+ eagerResponse = void 0;
94
+ }
95
+ if (!redirectRes) {
96
+ if (signal?.aborted) throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
97
+ const fallback = await fetchFn(redirectUrl, fetchOpts);
98
+ if (!fallback.ok) throw new Error(`Stream request failed: ${fallback.status} ${fallback.statusText}`);
99
+ if (!fallback.body) throw new Error("No response body");
100
+ redirectRes = fallback;
101
+ }
102
+ for await (const raw of iterSse(redirectRes.body)) {
103
+ const sseEvent = parseSseEvent(raw);
104
+ if (!sseEvent) continue;
105
+ yield sseEvent;
106
+ }
107
+ } finally {
108
+ if (eagerResponse) eagerResponse.then((r) => r?.body?.cancel()).catch(() => {});
109
+ }
110
+ }
111
+
112
+ //#endregion
113
+ export { fetchWithStream };
114
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","names":["baseHeaders: Record<string, string>","resp: Response | undefined","redirectUrl: string | undefined","eagerResponse: Promise<Response | undefined> | undefined","redirectRes: Response | undefined"],"sources":["../../../src/experimental/durable-endpoints/client.ts"],"sourcesContent":["/**\n * Entrypoint file for client-side Durable Endpoint utilities.\n */\n\nimport {\n iterSse,\n parseSseEvent,\n type SseEvent,\n} from \"../../components/execution/streaming.ts\";\n\ninterface FetchDurableEndpointOptions {\n /** Fetch function. */\n fetch?: typeof globalThis.fetch;\n\n /** Options passed to the fetch function. */\n fetchOpts?: RequestInit;\n\n /** Called when run metadata is received (e.g. run ID). */\n onMetadata?: (args: { runId: string }) => void;\n\n /**\n * Called for each streamed chunk. Should be considered uncommitted until a\n * commit or rollback event is received.\n */\n onData?: (args: { data: unknown; hashedStepId: string | null }) => void;\n\n /**\n * Called when uncommitted stream data should be rolled back, since a retry\n * will happen.\n */\n onRollback?: (args: { hashedStepId: string | null }) => void;\n\n /**\n * Called when uncommitted stream data should be committed, since it can no longer be\n * rolled back.\n */\n onCommit?: (args: { hashedStepId: string | null }) => void;\n}\n\n/**\n * Fetch a durable endpoint URL and consume its SSE stream, dispatching\n * lifecycle callbacks (metadata, data, commit, rollback) as\n * events arrive. Returns the final `Response` reconstructed from the\n * terminal `inngest.response` SSE event.\n *\n * If the server does not respond with `text/event-stream`, the raw\n * `Response` is returned as-is (non-streaming path).\n */\nexport async function fetchWithStream(\n url: string,\n opts?: FetchDurableEndpointOptions,\n): Promise<Response> {\n const fetchFn = opts?.fetch ?? globalThis.fetch;\n\n const baseHeaders: Record<string, string> = {};\n if (opts?.fetchOpts?.headers) {\n new Headers(opts.fetchOpts.headers).forEach((value, key) => {\n baseHeaders[key] = value;\n });\n }\n\n const initialRes = await fetchFn(url, {\n ...opts?.fetchOpts,\n headers: {\n ...baseHeaders,\n Accept: \"text/event-stream\",\n },\n });\n\n const contentType = initialRes.headers.get(\"content-type\") ?? \"\";\n if (!contentType.includes(\"text/event-stream\")) {\n return initialRes;\n }\n\n if (!initialRes.body) {\n throw new Error(\"No response body\");\n }\n\n let resp: Response | undefined;\n\n const source = iterSseFollowRedirects(\n initialRes.body,\n fetchFn,\n opts?.fetchOpts?.signal ?? undefined,\n );\n\n outer: for await (const sseEvent of source) {\n switch (sseEvent.type) {\n case \"inngest.stream\": {\n opts?.onData?.({\n data: sseEvent.data,\n hashedStepId: sseEvent.hashedStepId ?? null,\n });\n break;\n }\n case \"inngest.commit\": {\n opts?.onCommit?.({ hashedStepId: sseEvent.hashedStepId });\n break;\n }\n case \"inngest.rollback\": {\n opts?.onRollback?.({ hashedStepId: sseEvent.hashedStepId });\n break;\n }\n case \"inngest.response\": {\n resp = new Response(sseEvent.response.body, {\n status: sseEvent.response.statusCode,\n headers: sseEvent.response.headers,\n });\n\n break outer;\n }\n case \"inngest.metadata\": {\n opts?.onMetadata?.({ runId: sseEvent.runId });\n break;\n }\n default:\n break;\n }\n }\n\n if (!resp) {\n throw new Error(\"No response\");\n }\n\n return resp;\n}\n\n/**\n * Async generator that yields parsed SSE events from an already-fetched\n * response body, following `inngest.redirect_info` redirects.\n *\n * When a redirect event arrives, the redirect URL is fetched eagerly in the\n * background so the connection is already established by the time the direct\n * stream closes. This minimizes the window for late-joiner data loss.\n */\nasync function* iterSseFollowRedirects(\n body: ReadableStream<Uint8Array>,\n fetchFn: typeof globalThis.fetch,\n signal?: AbortSignal,\n): AsyncGenerator<SseEvent> {\n const fetchOpts = { headers: { Accept: \"text/event-stream\" }, signal };\n let redirectUrl: string | undefined;\n let eagerResponse: Promise<Response | undefined> | undefined;\n\n try {\n for await (const raw of iterSse(body)) {\n const sseEvent = parseSseEvent(raw);\n if (!sseEvent) continue;\n\n if (sseEvent.type === \"inngest.redirect_info\") {\n redirectUrl = sseEvent.url;\n\n // Start the redirect connection immediately (once only).\n if (sseEvent.url && !eagerResponse) {\n eagerResponse = fetchFn(sseEvent.url, fetchOpts).catch(\n () => undefined,\n );\n }\n\n yield sseEvent;\n continue;\n }\n\n yield sseEvent;\n }\n\n if (!redirectUrl) return;\n\n let redirectRes: Response | undefined;\n\n if (eagerResponse) {\n const eager = await eagerResponse;\n if (eager?.ok && eager.body) {\n redirectRes = eager;\n } else {\n await eager?.body?.cancel();\n }\n eagerResponse = undefined;\n }\n\n if (!redirectRes) {\n if (signal?.aborted) {\n throw (\n signal.reason ??\n new DOMException(\"The operation was aborted.\", \"AbortError\")\n );\n }\n\n const fallback = await fetchFn(redirectUrl, fetchOpts);\n if (!fallback.ok) {\n throw new Error(\n `Stream request failed: ${fallback.status} ${fallback.statusText}`,\n );\n }\n if (!fallback.body) {\n throw new Error(\"No response body\");\n }\n redirectRes = fallback;\n }\n\n for await (const raw of iterSse(redirectRes.body!)) {\n const sseEvent = parseSseEvent(raw);\n if (!sseEvent) continue;\n yield sseEvent;\n }\n } finally {\n // Cancel any unconsumed eager response body to release the connection.\n if (eagerResponse) {\n void eagerResponse.then((r) => r?.body?.cancel()).catch(() => {});\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgDA,eAAsB,gBACpB,KACA,MACmB;CACnB,MAAM,UAAU,MAAM,SAAS,WAAW;CAE1C,MAAMA,cAAsC,EAAE;AAC9C,KAAI,MAAM,WAAW,QACnB,KAAI,QAAQ,KAAK,UAAU,QAAQ,CAAC,SAAS,OAAO,QAAQ;AAC1D,cAAY,OAAO;GACnB;CAGJ,MAAM,aAAa,MAAM,QAAQ,KAAK;EACpC,GAAG,MAAM;EACT,SAAS;GACP,GAAG;GACH,QAAQ;GACT;EACF,CAAC;AAGF,KAAI,EADgB,WAAW,QAAQ,IAAI,eAAe,IAAI,IAC7C,SAAS,oBAAoB,CAC5C,QAAO;AAGT,KAAI,CAAC,WAAW,KACd,OAAM,IAAI,MAAM,mBAAmB;CAGrC,IAAIC;CAEJ,MAAM,SAAS,uBACb,WAAW,MACX,SACA,MAAM,WAAW,UAAU,OAC5B;AAED,OAAO,YAAW,MAAM,YAAY,OAClC,SAAQ,SAAS,MAAjB;EACE,KAAK;AACH,SAAM,SAAS;IACb,MAAM,SAAS;IACf,cAAc,SAAS,gBAAgB;IACxC,CAAC;AACF;EAEF,KAAK;AACH,SAAM,WAAW,EAAE,cAAc,SAAS,cAAc,CAAC;AACzD;EAEF,KAAK;AACH,SAAM,aAAa,EAAE,cAAc,SAAS,cAAc,CAAC;AAC3D;EAEF,KAAK;AACH,UAAO,IAAI,SAAS,SAAS,SAAS,MAAM;IAC1C,QAAQ,SAAS,SAAS;IAC1B,SAAS,SAAS,SAAS;IAC5B,CAAC;AAEF,SAAM;EAER,KAAK;AACH,SAAM,aAAa,EAAE,OAAO,SAAS,OAAO,CAAC;AAC7C;EAEF,QACE;;AAIN,KAAI,CAAC,KACH,OAAM,IAAI,MAAM,cAAc;AAGhC,QAAO;;;;;;;;;;AAWT,gBAAgB,uBACd,MACA,SACA,QAC0B;CAC1B,MAAM,YAAY;EAAE,SAAS,EAAE,QAAQ,qBAAqB;EAAE;EAAQ;CACtE,IAAIC;CACJ,IAAIC;AAEJ,KAAI;AACF,aAAW,MAAM,OAAO,QAAQ,KAAK,EAAE;GACrC,MAAM,WAAW,cAAc,IAAI;AACnC,OAAI,CAAC,SAAU;AAEf,OAAI,SAAS,SAAS,yBAAyB;AAC7C,kBAAc,SAAS;AAGvB,QAAI,SAAS,OAAO,CAAC,cACnB,iBAAgB,QAAQ,SAAS,KAAK,UAAU,CAAC,YACzC,OACP;AAGH,UAAM;AACN;;AAGF,SAAM;;AAGR,MAAI,CAAC,YAAa;EAElB,IAAIC;AAEJ,MAAI,eAAe;GACjB,MAAM,QAAQ,MAAM;AACpB,OAAI,OAAO,MAAM,MAAM,KACrB,eAAc;OAEd,OAAM,OAAO,MAAM,QAAQ;AAE7B,mBAAgB;;AAGlB,MAAI,CAAC,aAAa;AAChB,OAAI,QAAQ,QACV,OACE,OAAO,UACP,IAAI,aAAa,8BAA8B,aAAa;GAIhE,MAAM,WAAW,MAAM,QAAQ,aAAa,UAAU;AACtD,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MACR,0BAA0B,SAAS,OAAO,GAAG,SAAS,aACvD;AAEH,OAAI,CAAC,SAAS,KACZ,OAAM,IAAI,MAAM,mBAAmB;AAErC,iBAAc;;AAGhB,aAAW,MAAM,OAAO,QAAQ,YAAY,KAAM,EAAE;GAClD,MAAM,WAAW,cAAc,IAAI;AACnC,OAAI,CAAC,SAAU;AACf,SAAM;;WAEA;AAER,MAAI,cACF,CAAK,cAAc,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,CAAC,YAAY,GAAG"}
@@ -0,0 +1,3 @@
1
+ const require_StreamTools = require('../../components/StreamTools.cjs');
2
+
3
+ exports.stream = require_StreamTools.stream;
@@ -0,0 +1,2 @@
1
+ import { stream } from "../../components/StreamTools.cjs";
2
+ export { stream };
@@ -0,0 +1,2 @@
1
+ import { stream } from "../../components/StreamTools.js";
2
+ export { stream };
@@ -0,0 +1,3 @@
1
+ import { stream } from "../../components/StreamTools.js";
2
+
3
+ export { stream };
@@ -1 +1 @@
1
- {"version":3,"file":"promises.cjs","names":["resolve: DeferredPromiseReturn<T>[\"resolve\"]","reject: DeferredPromiseReturn<T>[\"reject\"]","settledPromises: Promise<T>[]","rotateQueue: (value: void) => void","timeout: ReturnType<typeof setTimeout> | undefined","ret: TimeoutPromise"],"sources":["../../src/helpers/promises.ts"],"sourcesContent":["import type { MaybePromise } from \"./types.ts\";\n\n/**\n * Some environments don't allow access to the global queueMicrotask(). While we\n * had assumed this was only true for those powered by earlier versions of Node\n * (<14) that we don't officially support, Vercel's Edge Functions also obscure\n * the function in dev, even though the platform it's based on (Cloudflare\n * Workers) appropriately exposes it. Even worse, production Vercel Edge\n * Functions can see the function, but it immediately blows up the function when\n * used.\n *\n * Therefore, we can fall back to a reasonable alternative of\n * `Promise.resolve().then(fn)` instead. This _may_ be slightly slower in modern\n * environments, but at least we can still work in these environments.\n */\nconst shimQueueMicrotask = (callback: () => void): void => {\n void Promise.resolve().then(callback);\n};\n\n/**\n * A helper function to create a `Promise` that will never settle.\n *\n * It purposefully creates no references to `resolve` or `reject` so that the\n * returned `Promise` will remain unsettled until it falls out of scope and is\n * garbage collected.\n *\n * This should be used within transient closures to fake asynchronous action, so\n * long as it's guaranteed that they will fall out of scope.\n */\nexport const createFrozenPromise = (): Promise<unknown> => {\n return new Promise(() => undefined);\n};\n\n/**\n * Returns a Promise that resolves after the current event loop's microtasks\n * have finished, but before the next event loop tick.\n */\nexport const resolveAfterPending = (count = 100): Promise<void> => {\n /**\n * This uses a brute force implementation that will continue to enqueue\n * microtasks 10 times before resolving. This is to ensure that the microtask\n * queue is drained, even if the microtask queue is being manipulated by other\n * code.\n *\n * While this still doesn't guarantee that the microtask queue is drained,\n * it's our best bet for giving other non-controlled promises a chance to\n * resolve before we continue without resorting to falling in to the next\n * tick.\n */\n return new Promise((resolve) => {\n let i = 0;\n\n const iterate = () => {\n shimQueueMicrotask(() => {\n if (i++ > count) {\n return resolve();\n }\n\n iterate();\n });\n };\n\n iterate();\n });\n};\n\ntype DeferredPromiseReturn<T> = {\n promise: Promise<T>;\n resolve: (value: T) => DeferredPromiseReturn<T>;\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n reject: (reason: any) => DeferredPromiseReturn<T>;\n};\n\n/**\n * Creates and returns Promise that can be resolved or rejected with the\n * returned `resolve` and `reject` functions.\n *\n * Resolving or rejecting the function will return a new set of Promise control\n * functions. These can be ignored if the original Promise is all that's needed.\n */\nexport const createDeferredPromise = <T>(): DeferredPromiseReturn<T> => {\n let resolve: DeferredPromiseReturn<T>[\"resolve\"];\n let reject: DeferredPromiseReturn<T>[\"reject\"];\n\n const promise = new Promise<T>((_resolve, _reject) => {\n resolve = (value: T) => {\n _resolve(value);\n return createDeferredPromise<T>();\n };\n\n reject = (reason) => {\n _reject(reason);\n return createDeferredPromise<T>();\n };\n });\n\n return { promise, resolve: resolve!, reject: reject! };\n};\n\n/**\n * Creates and returns a deferred Promise that can be resolved or rejected with\n * the returned `resolve` and `reject` functions.\n *\n * For each Promise resolved or rejected this way, this will also keep a stack\n * of all unhandled Promises, resolved or rejected.\n *\n * Once a Promise is read, it is removed from the stack.\n */\nexport const createDeferredPromiseWithStack = <T>(): {\n deferred: DeferredPromiseReturn<T>;\n results: AsyncGenerator<Awaited<T>, void, void>;\n} => {\n const settledPromises: Promise<T>[] = [];\n // biome-ignore lint/suspicious/noConfusingVoidType: intentional\n let rotateQueue: (value: void) => void = () => {};\n\n const results = (async function* () {\n while (true) {\n const next = settledPromises.shift();\n\n if (next) {\n yield next;\n } else {\n await new Promise<void>((resolve) => {\n rotateQueue = resolve;\n });\n }\n }\n })();\n\n const shimDeferredPromise = (deferred: DeferredPromiseReturn<T>) => {\n const originalResolve = deferred.resolve;\n const originalReject = deferred.reject;\n\n deferred.resolve = (value: T) => {\n settledPromises.push(deferred.promise);\n rotateQueue();\n return shimDeferredPromise(originalResolve(value));\n };\n\n deferred.reject = (reason) => {\n settledPromises.push(deferred.promise);\n rotateQueue();\n return shimDeferredPromise(originalReject(reason));\n };\n\n return deferred;\n };\n\n const deferred = shimDeferredPromise(createDeferredPromise<T>());\n\n return { deferred, results };\n};\n\ninterface TimeoutPromise extends Promise<void> {\n /**\n * Starts the timeout. If the timer is already started, this does nothing.\n *\n * @returns The promise that will resolve when the timeout expires.\n */\n start: () => TimeoutPromise;\n\n /**\n * Clears the timeout.\n */\n clear: () => void;\n\n /**\n * Clears the timeout and starts it again.\n *\n * @returns The promise that will resolve when the timeout expires.\n */\n reset: () => TimeoutPromise;\n}\n\n/**\n * Creates a Promise that will resolve after the given duration, along with\n * methods to start, clear, and reset the timeout.\n */\nexport const createTimeoutPromise = (duration: number): TimeoutPromise => {\n const { promise, resolve } = createDeferredPromise<void>();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n // biome-ignore lint/style/useConst: intentional\n let ret: TimeoutPromise;\n\n const start = () => {\n if (timeout) return ret;\n\n timeout = setTimeout(() => {\n resolve();\n }, duration);\n\n return ret;\n };\n\n const clear = () => {\n clearTimeout(timeout);\n timeout = undefined;\n };\n\n const reset = () => {\n clear();\n return start();\n };\n\n ret = Object.assign(promise, { start, clear, reset });\n\n return ret;\n};\n\n/**\n * Take any function and safely promisify such that both synchronous and\n * asynchronous errors are caught and returned as a rejected Promise.\n *\n * The passed `fn` can be undefined to support functions that may conditionally\n * be defined.\n */\n// biome-ignore lint/suspicious/noExplicitAny: intentional\nexport const runAsPromise = <T extends (() => any) | undefined>(\n fn: T,\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n): Promise<T extends () => any ? Awaited<ReturnType<T>> : T> => {\n return Promise.resolve().then(fn);\n};\n\n/**\n * Returns a Promise that resolve after the current event loop tick.\n */\nexport const resolveNextTick = (): Promise<void> => {\n return new Promise((resolve) => setTimeout(resolve));\n};\n\nexport const retryWithBackoff = async <T>(\n fn: () => MaybePromise<T>,\n opts?: {\n maxAttempts?: number;\n baseDelay?: number;\n },\n): Promise<T> => {\n const maxAttempts = opts?.maxAttempts || 5;\n const baseDelay = opts?.baseDelay ?? 100;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxAttempts) {\n throw err;\n }\n\n const jitter = Math.random() * baseDelay;\n const delay = baseDelay * Math.pow(2, attempt - 1) + jitter;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw new Error(\"Max retries reached; this should be unreachable.\");\n};\n\nexport type GoInterval = {\n a: number;\n b: number;\n};\n\n/**\n * Given a function, returns a Promise that resolves with the result of the\n * function and a Go-compatible `interval.Interval` timing object.\n */\n// biome-ignore lint/suspicious/noExplicitAny: match any fn\nexport const goIntervalTiming = async <T extends (...args: any[]) => any>(\n fn: T,\n): Promise<{\n resultPromise: Promise<Awaited<ReturnType<T>>>;\n interval: { a: number; b: number };\n}> => {\n // Ideally this would use process.hrtime, but that's not available in all\n // runtimes, so we must revert to less accurate timing and `Date`.\n const start = Date.now();\n const resultPromise = runAsPromise(fn) as Promise<Awaited<ReturnType<T>>>;\n\n // Let the function run to completion.\n try {\n await resultPromise;\n } catch {\n // no-op\n }\n\n const end = Date.now();\n\n const interval = {\n a: start * 1_000_000,\n b: (end - start) * 1_000_000,\n };\n\n return { resultPromise, interval };\n};\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,sBAAsB,aAA+B;AACzD,CAAK,QAAQ,SAAS,CAAC,KAAK,SAAS;;;;;;AAqBvC,MAAa,uBAAuB,QAAQ,QAAuB;;;;;;;;;;;;AAYjE,QAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,IAAI;EAER,MAAM,gBAAgB;AACpB,4BAAyB;AACvB,QAAI,MAAM,MACR,QAAO,SAAS;AAGlB,aAAS;KACT;;AAGJ,WAAS;GACT;;;;;;;;;AAiBJ,MAAa,8BAA2D;CACtE,IAAIA;CACJ,IAAIC;AAcJ,QAAO;EAAE,SAZO,IAAI,SAAY,UAAU,YAAY;AACpD,cAAW,UAAa;AACtB,aAAS,MAAM;AACf,WAAO,uBAA0B;;AAGnC,aAAU,WAAW;AACnB,YAAQ,OAAO;AACf,WAAO,uBAA0B;;IAEnC;EAEyB;EAAkB;EAAS;;;;;;;;;;;AAYxD,MAAa,uCAGR;CACH,MAAMC,kBAAgC,EAAE;CAExC,IAAIC,oBAA2C;CAE/C,MAAM,WAAW,mBAAmB;AAClC,SAAO,MAAM;GACX,MAAM,OAAO,gBAAgB,OAAO;AAEpC,OAAI,KACF,OAAM;OAEN,OAAM,IAAI,SAAe,YAAY;AACnC,kBAAc;KACd;;KAGJ;CAEJ,MAAM,uBAAuB,aAAuC;EAClE,MAAM,kBAAkB,SAAS;EACjC,MAAM,iBAAiB,SAAS;AAEhC,WAAS,WAAW,UAAa;AAC/B,mBAAgB,KAAK,SAAS,QAAQ;AACtC,gBAAa;AACb,UAAO,oBAAoB,gBAAgB,MAAM,CAAC;;AAGpD,WAAS,UAAU,WAAW;AAC5B,mBAAgB,KAAK,SAAS,QAAQ;AACtC,gBAAa;AACb,UAAO,oBAAoB,eAAe,OAAO,CAAC;;AAGpD,SAAO;;AAKT,QAAO;EAAE,UAFQ,oBAAoB,uBAA0B,CAAC;EAE7C;EAAS;;;;;;AA4B9B,MAAa,wBAAwB,aAAqC;CACxE,MAAM,EAAE,SAAS,YAAY,uBAA6B;CAE1D,IAAIC;CAEJ,IAAIC;CAEJ,MAAM,cAAc;AAClB,MAAI,QAAS,QAAO;AAEpB,YAAU,iBAAiB;AACzB,YAAS;KACR,SAAS;AAEZ,SAAO;;CAGT,MAAM,cAAc;AAClB,eAAa,QAAQ;AACrB,YAAU;;CAGZ,MAAM,cAAc;AAClB,SAAO;AACP,SAAO,OAAO;;AAGhB,OAAM,OAAO,OAAO,SAAS;EAAE;EAAO;EAAO;EAAO,CAAC;AAErD,QAAO;;;;;;;;;AAWT,MAAa,gBACX,OAE8D;AAC9D,QAAO,QAAQ,SAAS,CAAC,KAAK,GAAG;;;;;AAMnC,MAAa,wBAAuC;AAClD,QAAO,IAAI,SAAS,YAAY,WAAW,QAAQ,CAAC;;AAGtD,MAAa,mBAAmB,OAC9B,IACA,SAIe;CACf,MAAM,cAAc,MAAM,eAAe;CACzC,MAAM,YAAY,MAAM,aAAa;AAErC,MAAK,IAAI,UAAU,GAAG,WAAW,aAAa,UAC5C,KAAI;AACF,SAAO,MAAM,IAAI;UACV,KAAK;AACZ,MAAI,WAAW,YACb,OAAM;EAGR,MAAM,SAAS,KAAK,QAAQ,GAAG;EAC/B,MAAM,QAAQ,YAAY,KAAK,IAAI,GAAG,UAAU,EAAE,GAAG;AACrD,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;;AAI9D,OAAM,IAAI,MAAM,mDAAmD;;;;;;AAarE,MAAa,mBAAmB,OAC9B,OAII;CAGJ,MAAM,QAAQ,KAAK,KAAK;CACxB,MAAM,gBAAgB,aAAa,GAAG;AAGtC,KAAI;AACF,QAAM;SACA;CAIR,MAAM,MAAM,KAAK,KAAK;AAOtB,QAAO;EAAE;EAAe,UALP;GACf,GAAG,QAAQ;GACX,IAAI,MAAM,SAAS;GACpB;EAEiC"}
1
+ {"version":3,"file":"promises.cjs","names":["resolve: DeferredPromiseReturn<T>[\"resolve\"]","reject: DeferredPromiseReturn<T>[\"reject\"]","settledPromises: Promise<T>[]","rotateQueue: (value: void) => void","timeout: ReturnType<typeof setTimeout> | undefined","ret: TimeoutPromise"],"sources":["../../src/helpers/promises.ts"],"sourcesContent":["import type { MaybePromise } from \"./types.ts\";\n\n/**\n * Some environments don't allow access to the global queueMicrotask(). While we\n * had assumed this was only true for those powered by earlier versions of Node\n * (<14) that we don't officially support, Vercel's Edge Functions also obscure\n * the function in dev, even though the platform it's based on (Cloudflare\n * Workers) appropriately exposes it. Even worse, production Vercel Edge\n * Functions can see the function, but it immediately blows up the function when\n * used.\n *\n * Therefore, we can fall back to a reasonable alternative of\n * `Promise.resolve().then(fn)` instead. This _may_ be slightly slower in modern\n * environments, but at least we can still work in these environments.\n */\nconst shimQueueMicrotask = (callback: () => void): void => {\n void Promise.resolve().then(callback);\n};\n\n/**\n * A helper function to create a `Promise` that will never settle.\n *\n * It purposefully creates no references to `resolve` or `reject` so that the\n * returned `Promise` will remain unsettled until it falls out of scope and is\n * garbage collected.\n *\n * This should be used within transient closures to fake asynchronous action, so\n * long as it's guaranteed that they will fall out of scope.\n */\nexport const createFrozenPromise = (): Promise<unknown> => {\n return new Promise(() => undefined);\n};\n\n/**\n * Returns a Promise that resolves after the current event loop's microtasks\n * have finished, but before the next event loop tick.\n */\nexport const resolveAfterPending = (count = 100): Promise<void> => {\n /**\n * This uses a brute force implementation that will continue to enqueue\n * microtasks 10 times before resolving. This is to ensure that the microtask\n * queue is drained, even if the microtask queue is being manipulated by other\n * code.\n *\n * While this still doesn't guarantee that the microtask queue is drained,\n * it's our best bet for giving other non-controlled promises a chance to\n * resolve before we continue without resorting to falling in to the next\n * tick.\n */\n return new Promise((resolve) => {\n let i = 0;\n\n const iterate = () => {\n shimQueueMicrotask(() => {\n if (i++ > count) {\n return resolve();\n }\n\n iterate();\n });\n };\n\n iterate();\n });\n};\n\nexport type DeferredPromiseReturn<T> = {\n promise: Promise<T>;\n resolve: (value: T) => DeferredPromiseReturn<T>;\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n reject: (reason: any) => DeferredPromiseReturn<T>;\n};\n\n/**\n * Creates and returns Promise that can be resolved or rejected with the\n * returned `resolve` and `reject` functions.\n *\n * Resolving or rejecting the function will return a new set of Promise control\n * functions. These can be ignored if the original Promise is all that's needed.\n */\nexport const createDeferredPromise = <T>(): DeferredPromiseReturn<T> => {\n let resolve: DeferredPromiseReturn<T>[\"resolve\"];\n let reject: DeferredPromiseReturn<T>[\"reject\"];\n\n const promise = new Promise<T>((_resolve, _reject) => {\n resolve = (value: T) => {\n _resolve(value);\n return createDeferredPromise<T>();\n };\n\n reject = (reason) => {\n _reject(reason);\n return createDeferredPromise<T>();\n };\n });\n\n return { promise, resolve: resolve!, reject: reject! };\n};\n\n/**\n * Creates and returns a deferred Promise that can be resolved or rejected with\n * the returned `resolve` and `reject` functions.\n *\n * For each Promise resolved or rejected this way, this will also keep a stack\n * of all unhandled Promises, resolved or rejected.\n *\n * Once a Promise is read, it is removed from the stack.\n */\nexport const createDeferredPromiseWithStack = <T>(): {\n deferred: DeferredPromiseReturn<T>;\n results: AsyncGenerator<Awaited<T>, void, void>;\n} => {\n const settledPromises: Promise<T>[] = [];\n // biome-ignore lint/suspicious/noConfusingVoidType: intentional\n let rotateQueue: (value: void) => void = () => {};\n\n const results = (async function* () {\n while (true) {\n const next = settledPromises.shift();\n\n if (next) {\n yield next;\n } else {\n await new Promise<void>((resolve) => {\n rotateQueue = resolve;\n });\n }\n }\n })();\n\n const shimDeferredPromise = (deferred: DeferredPromiseReturn<T>) => {\n const originalResolve = deferred.resolve;\n const originalReject = deferred.reject;\n\n deferred.resolve = (value: T) => {\n settledPromises.push(deferred.promise);\n rotateQueue();\n return shimDeferredPromise(originalResolve(value));\n };\n\n deferred.reject = (reason) => {\n settledPromises.push(deferred.promise);\n rotateQueue();\n return shimDeferredPromise(originalReject(reason));\n };\n\n return deferred;\n };\n\n const deferred = shimDeferredPromise(createDeferredPromise<T>());\n\n return { deferred, results };\n};\n\ninterface TimeoutPromise extends Promise<void> {\n /**\n * Starts the timeout. If the timer is already started, this does nothing.\n *\n * @returns The promise that will resolve when the timeout expires.\n */\n start: () => TimeoutPromise;\n\n /**\n * Clears the timeout.\n */\n clear: () => void;\n\n /**\n * Clears the timeout and starts it again.\n *\n * @returns The promise that will resolve when the timeout expires.\n */\n reset: () => TimeoutPromise;\n}\n\n/**\n * Creates a Promise that will resolve after the given duration, along with\n * methods to start, clear, and reset the timeout.\n */\nexport const createTimeoutPromise = (duration: number): TimeoutPromise => {\n const { promise, resolve } = createDeferredPromise<void>();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n // biome-ignore lint/style/useConst: intentional\n let ret: TimeoutPromise;\n\n const start = () => {\n if (timeout) return ret;\n\n timeout = setTimeout(() => {\n resolve();\n }, duration);\n\n return ret;\n };\n\n const clear = () => {\n clearTimeout(timeout);\n timeout = undefined;\n };\n\n const reset = () => {\n clear();\n return start();\n };\n\n ret = Object.assign(promise, { start, clear, reset });\n\n return ret;\n};\n\n/**\n * Take any function and safely promisify such that both synchronous and\n * asynchronous errors are caught and returned as a rejected Promise.\n *\n * The passed `fn` can be undefined to support functions that may conditionally\n * be defined.\n */\n// biome-ignore lint/suspicious/noExplicitAny: intentional\nexport const runAsPromise = <T extends (() => any) | undefined>(\n fn: T,\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n): Promise<T extends () => any ? Awaited<ReturnType<T>> : T> => {\n return Promise.resolve().then(fn);\n};\n\n/**\n * Returns a Promise that resolve after the current event loop tick.\n */\nexport const resolveNextTick = (): Promise<void> => {\n return new Promise((resolve) => setTimeout(resolve));\n};\n\nexport const retryWithBackoff = async <T>(\n fn: () => MaybePromise<T>,\n opts?: {\n maxAttempts?: number;\n baseDelay?: number;\n },\n): Promise<T> => {\n const maxAttempts = opts?.maxAttempts || 5;\n const baseDelay = opts?.baseDelay ?? 100;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxAttempts) {\n throw err;\n }\n\n const jitter = Math.random() * baseDelay;\n const delay = baseDelay * Math.pow(2, attempt - 1) + jitter;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw new Error(\"Max retries reached; this should be unreachable.\");\n};\n\nexport type GoInterval = {\n a: number;\n b: number;\n};\n\n/**\n * Given a function, returns a Promise that resolves with the result of the\n * function and a Go-compatible `interval.Interval` timing object.\n */\n// biome-ignore lint/suspicious/noExplicitAny: match any fn\nexport const goIntervalTiming = async <T extends (...args: any[]) => any>(\n fn: T,\n): Promise<{\n resultPromise: Promise<Awaited<ReturnType<T>>>;\n interval: { a: number; b: number };\n}> => {\n // Ideally this would use process.hrtime, but that's not available in all\n // runtimes, so we must revert to less accurate timing and `Date`.\n const start = Date.now();\n const resultPromise = runAsPromise(fn) as Promise<Awaited<ReturnType<T>>>;\n\n // Let the function run to completion.\n try {\n await resultPromise;\n } catch {\n // no-op\n }\n\n const end = Date.now();\n\n const interval = {\n a: start * 1_000_000,\n b: (end - start) * 1_000_000,\n };\n\n return { resultPromise, interval };\n};\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAM,sBAAsB,aAA+B;AACzD,CAAK,QAAQ,SAAS,CAAC,KAAK,SAAS;;;;;;AAqBvC,MAAa,uBAAuB,QAAQ,QAAuB;;;;;;;;;;;;AAYjE,QAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,IAAI;EAER,MAAM,gBAAgB;AACpB,4BAAyB;AACvB,QAAI,MAAM,MACR,QAAO,SAAS;AAGlB,aAAS;KACT;;AAGJ,WAAS;GACT;;;;;;;;;AAiBJ,MAAa,8BAA2D;CACtE,IAAIA;CACJ,IAAIC;AAcJ,QAAO;EAAE,SAZO,IAAI,SAAY,UAAU,YAAY;AACpD,cAAW,UAAa;AACtB,aAAS,MAAM;AACf,WAAO,uBAA0B;;AAGnC,aAAU,WAAW;AACnB,YAAQ,OAAO;AACf,WAAO,uBAA0B;;IAEnC;EAEyB;EAAkB;EAAS;;;;;;;;;;;AAYxD,MAAa,uCAGR;CACH,MAAMC,kBAAgC,EAAE;CAExC,IAAIC,oBAA2C;CAE/C,MAAM,WAAW,mBAAmB;AAClC,SAAO,MAAM;GACX,MAAM,OAAO,gBAAgB,OAAO;AAEpC,OAAI,KACF,OAAM;OAEN,OAAM,IAAI,SAAe,YAAY;AACnC,kBAAc;KACd;;KAGJ;CAEJ,MAAM,uBAAuB,aAAuC;EAClE,MAAM,kBAAkB,SAAS;EACjC,MAAM,iBAAiB,SAAS;AAEhC,WAAS,WAAW,UAAa;AAC/B,mBAAgB,KAAK,SAAS,QAAQ;AACtC,gBAAa;AACb,UAAO,oBAAoB,gBAAgB,MAAM,CAAC;;AAGpD,WAAS,UAAU,WAAW;AAC5B,mBAAgB,KAAK,SAAS,QAAQ;AACtC,gBAAa;AACb,UAAO,oBAAoB,eAAe,OAAO,CAAC;;AAGpD,SAAO;;AAKT,QAAO;EAAE,UAFQ,oBAAoB,uBAA0B,CAAC;EAE7C;EAAS;;;;;;AA4B9B,MAAa,wBAAwB,aAAqC;CACxE,MAAM,EAAE,SAAS,YAAY,uBAA6B;CAE1D,IAAIC;CAEJ,IAAIC;CAEJ,MAAM,cAAc;AAClB,MAAI,QAAS,QAAO;AAEpB,YAAU,iBAAiB;AACzB,YAAS;KACR,SAAS;AAEZ,SAAO;;CAGT,MAAM,cAAc;AAClB,eAAa,QAAQ;AACrB,YAAU;;CAGZ,MAAM,cAAc;AAClB,SAAO;AACP,SAAO,OAAO;;AAGhB,OAAM,OAAO,OAAO,SAAS;EAAE;EAAO;EAAO;EAAO,CAAC;AAErD,QAAO;;;;;;;;;AAWT,MAAa,gBACX,OAE8D;AAC9D,QAAO,QAAQ,SAAS,CAAC,KAAK,GAAG;;;;;AAMnC,MAAa,wBAAuC;AAClD,QAAO,IAAI,SAAS,YAAY,WAAW,QAAQ,CAAC;;AAGtD,MAAa,mBAAmB,OAC9B,IACA,SAIe;CACf,MAAM,cAAc,MAAM,eAAe;CACzC,MAAM,YAAY,MAAM,aAAa;AAErC,MAAK,IAAI,UAAU,GAAG,WAAW,aAAa,UAC5C,KAAI;AACF,SAAO,MAAM,IAAI;UACV,KAAK;AACZ,MAAI,WAAW,YACb,OAAM;EAGR,MAAM,SAAS,KAAK,QAAQ,GAAG;EAC/B,MAAM,QAAQ,YAAY,KAAK,IAAI,GAAG,UAAU,EAAE,GAAG;AACrD,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;;AAI9D,OAAM,IAAI,MAAM,mDAAmD;;;;;;AAarE,MAAa,mBAAmB,OAC9B,OAII;CAGJ,MAAM,QAAQ,KAAK,KAAK;CACxB,MAAM,gBAAgB,aAAa,GAAG;AAGtC,KAAI;AACF,QAAM;SACA;CAIR,MAAM,MAAM,KAAK,KAAK;AAOtB,QAAO;EAAE;EAAe,UALP;GACf,GAAG,QAAQ;GACX,IAAI,MAAM,SAAS;GACpB;EAEiC"}
@@ -1 +1 @@
1
- {"version":3,"file":"promises.js","names":["resolve: DeferredPromiseReturn<T>[\"resolve\"]","reject: DeferredPromiseReturn<T>[\"reject\"]","settledPromises: Promise<T>[]","rotateQueue: (value: void) => void","timeout: ReturnType<typeof setTimeout> | undefined","ret: TimeoutPromise"],"sources":["../../src/helpers/promises.ts"],"sourcesContent":["import type { MaybePromise } from \"./types.ts\";\n\n/**\n * Some environments don't allow access to the global queueMicrotask(). While we\n * had assumed this was only true for those powered by earlier versions of Node\n * (<14) that we don't officially support, Vercel's Edge Functions also obscure\n * the function in dev, even though the platform it's based on (Cloudflare\n * Workers) appropriately exposes it. Even worse, production Vercel Edge\n * Functions can see the function, but it immediately blows up the function when\n * used.\n *\n * Therefore, we can fall back to a reasonable alternative of\n * `Promise.resolve().then(fn)` instead. This _may_ be slightly slower in modern\n * environments, but at least we can still work in these environments.\n */\nconst shimQueueMicrotask = (callback: () => void): void => {\n void Promise.resolve().then(callback);\n};\n\n/**\n * A helper function to create a `Promise` that will never settle.\n *\n * It purposefully creates no references to `resolve` or `reject` so that the\n * returned `Promise` will remain unsettled until it falls out of scope and is\n * garbage collected.\n *\n * This should be used within transient closures to fake asynchronous action, so\n * long as it's guaranteed that they will fall out of scope.\n */\nexport const createFrozenPromise = (): Promise<unknown> => {\n return new Promise(() => undefined);\n};\n\n/**\n * Returns a Promise that resolves after the current event loop's microtasks\n * have finished, but before the next event loop tick.\n */\nexport const resolveAfterPending = (count = 100): Promise<void> => {\n /**\n * This uses a brute force implementation that will continue to enqueue\n * microtasks 10 times before resolving. This is to ensure that the microtask\n * queue is drained, even if the microtask queue is being manipulated by other\n * code.\n *\n * While this still doesn't guarantee that the microtask queue is drained,\n * it's our best bet for giving other non-controlled promises a chance to\n * resolve before we continue without resorting to falling in to the next\n * tick.\n */\n return new Promise((resolve) => {\n let i = 0;\n\n const iterate = () => {\n shimQueueMicrotask(() => {\n if (i++ > count) {\n return resolve();\n }\n\n iterate();\n });\n };\n\n iterate();\n });\n};\n\ntype DeferredPromiseReturn<T> = {\n promise: Promise<T>;\n resolve: (value: T) => DeferredPromiseReturn<T>;\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n reject: (reason: any) => DeferredPromiseReturn<T>;\n};\n\n/**\n * Creates and returns Promise that can be resolved or rejected with the\n * returned `resolve` and `reject` functions.\n *\n * Resolving or rejecting the function will return a new set of Promise control\n * functions. These can be ignored if the original Promise is all that's needed.\n */\nexport const createDeferredPromise = <T>(): DeferredPromiseReturn<T> => {\n let resolve: DeferredPromiseReturn<T>[\"resolve\"];\n let reject: DeferredPromiseReturn<T>[\"reject\"];\n\n const promise = new Promise<T>((_resolve, _reject) => {\n resolve = (value: T) => {\n _resolve(value);\n return createDeferredPromise<T>();\n };\n\n reject = (reason) => {\n _reject(reason);\n return createDeferredPromise<T>();\n };\n });\n\n return { promise, resolve: resolve!, reject: reject! };\n};\n\n/**\n * Creates and returns a deferred Promise that can be resolved or rejected with\n * the returned `resolve` and `reject` functions.\n *\n * For each Promise resolved or rejected this way, this will also keep a stack\n * of all unhandled Promises, resolved or rejected.\n *\n * Once a Promise is read, it is removed from the stack.\n */\nexport const createDeferredPromiseWithStack = <T>(): {\n deferred: DeferredPromiseReturn<T>;\n results: AsyncGenerator<Awaited<T>, void, void>;\n} => {\n const settledPromises: Promise<T>[] = [];\n // biome-ignore lint/suspicious/noConfusingVoidType: intentional\n let rotateQueue: (value: void) => void = () => {};\n\n const results = (async function* () {\n while (true) {\n const next = settledPromises.shift();\n\n if (next) {\n yield next;\n } else {\n await new Promise<void>((resolve) => {\n rotateQueue = resolve;\n });\n }\n }\n })();\n\n const shimDeferredPromise = (deferred: DeferredPromiseReturn<T>) => {\n const originalResolve = deferred.resolve;\n const originalReject = deferred.reject;\n\n deferred.resolve = (value: T) => {\n settledPromises.push(deferred.promise);\n rotateQueue();\n return shimDeferredPromise(originalResolve(value));\n };\n\n deferred.reject = (reason) => {\n settledPromises.push(deferred.promise);\n rotateQueue();\n return shimDeferredPromise(originalReject(reason));\n };\n\n return deferred;\n };\n\n const deferred = shimDeferredPromise(createDeferredPromise<T>());\n\n return { deferred, results };\n};\n\ninterface TimeoutPromise extends Promise<void> {\n /**\n * Starts the timeout. If the timer is already started, this does nothing.\n *\n * @returns The promise that will resolve when the timeout expires.\n */\n start: () => TimeoutPromise;\n\n /**\n * Clears the timeout.\n */\n clear: () => void;\n\n /**\n * Clears the timeout and starts it again.\n *\n * @returns The promise that will resolve when the timeout expires.\n */\n reset: () => TimeoutPromise;\n}\n\n/**\n * Creates a Promise that will resolve after the given duration, along with\n * methods to start, clear, and reset the timeout.\n */\nexport const createTimeoutPromise = (duration: number): TimeoutPromise => {\n const { promise, resolve } = createDeferredPromise<void>();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n // biome-ignore lint/style/useConst: intentional\n let ret: TimeoutPromise;\n\n const start = () => {\n if (timeout) return ret;\n\n timeout = setTimeout(() => {\n resolve();\n }, duration);\n\n return ret;\n };\n\n const clear = () => {\n clearTimeout(timeout);\n timeout = undefined;\n };\n\n const reset = () => {\n clear();\n return start();\n };\n\n ret = Object.assign(promise, { start, clear, reset });\n\n return ret;\n};\n\n/**\n * Take any function and safely promisify such that both synchronous and\n * asynchronous errors are caught and returned as a rejected Promise.\n *\n * The passed `fn` can be undefined to support functions that may conditionally\n * be defined.\n */\n// biome-ignore lint/suspicious/noExplicitAny: intentional\nexport const runAsPromise = <T extends (() => any) | undefined>(\n fn: T,\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n): Promise<T extends () => any ? Awaited<ReturnType<T>> : T> => {\n return Promise.resolve().then(fn);\n};\n\n/**\n * Returns a Promise that resolve after the current event loop tick.\n */\nexport const resolveNextTick = (): Promise<void> => {\n return new Promise((resolve) => setTimeout(resolve));\n};\n\nexport const retryWithBackoff = async <T>(\n fn: () => MaybePromise<T>,\n opts?: {\n maxAttempts?: number;\n baseDelay?: number;\n },\n): Promise<T> => {\n const maxAttempts = opts?.maxAttempts || 5;\n const baseDelay = opts?.baseDelay ?? 100;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxAttempts) {\n throw err;\n }\n\n const jitter = Math.random() * baseDelay;\n const delay = baseDelay * Math.pow(2, attempt - 1) + jitter;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw new Error(\"Max retries reached; this should be unreachable.\");\n};\n\nexport type GoInterval = {\n a: number;\n b: number;\n};\n\n/**\n * Given a function, returns a Promise that resolves with the result of the\n * function and a Go-compatible `interval.Interval` timing object.\n */\n// biome-ignore lint/suspicious/noExplicitAny: match any fn\nexport const goIntervalTiming = async <T extends (...args: any[]) => any>(\n fn: T,\n): Promise<{\n resultPromise: Promise<Awaited<ReturnType<T>>>;\n interval: { a: number; b: number };\n}> => {\n // Ideally this would use process.hrtime, but that's not available in all\n // runtimes, so we must revert to less accurate timing and `Date`.\n const start = Date.now();\n const resultPromise = runAsPromise(fn) as Promise<Awaited<ReturnType<T>>>;\n\n // Let the function run to completion.\n try {\n await resultPromise;\n } catch {\n // no-op\n }\n\n const end = Date.now();\n\n const interval = {\n a: start * 1_000_000,\n b: (end - start) * 1_000_000,\n };\n\n return { resultPromise, interval };\n};\n"],"mappings":";;;;;;;;;;;;;;AAeA,MAAM,sBAAsB,aAA+B;AACzD,CAAK,QAAQ,SAAS,CAAC,KAAK,SAAS;;;;;;AAqBvC,MAAa,uBAAuB,QAAQ,QAAuB;;;;;;;;;;;;AAYjE,QAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,IAAI;EAER,MAAM,gBAAgB;AACpB,4BAAyB;AACvB,QAAI,MAAM,MACR,QAAO,SAAS;AAGlB,aAAS;KACT;;AAGJ,WAAS;GACT;;;;;;;;;AAiBJ,MAAa,8BAA2D;CACtE,IAAIA;CACJ,IAAIC;AAcJ,QAAO;EAAE,SAZO,IAAI,SAAY,UAAU,YAAY;AACpD,cAAW,UAAa;AACtB,aAAS,MAAM;AACf,WAAO,uBAA0B;;AAGnC,aAAU,WAAW;AACnB,YAAQ,OAAO;AACf,WAAO,uBAA0B;;IAEnC;EAEyB;EAAkB;EAAS;;;;;;;;;;;AAYxD,MAAa,uCAGR;CACH,MAAMC,kBAAgC,EAAE;CAExC,IAAIC,oBAA2C;CAE/C,MAAM,WAAW,mBAAmB;AAClC,SAAO,MAAM;GACX,MAAM,OAAO,gBAAgB,OAAO;AAEpC,OAAI,KACF,OAAM;OAEN,OAAM,IAAI,SAAe,YAAY;AACnC,kBAAc;KACd;;KAGJ;CAEJ,MAAM,uBAAuB,aAAuC;EAClE,MAAM,kBAAkB,SAAS;EACjC,MAAM,iBAAiB,SAAS;AAEhC,WAAS,WAAW,UAAa;AAC/B,mBAAgB,KAAK,SAAS,QAAQ;AACtC,gBAAa;AACb,UAAO,oBAAoB,gBAAgB,MAAM,CAAC;;AAGpD,WAAS,UAAU,WAAW;AAC5B,mBAAgB,KAAK,SAAS,QAAQ;AACtC,gBAAa;AACb,UAAO,oBAAoB,eAAe,OAAO,CAAC;;AAGpD,SAAO;;AAKT,QAAO;EAAE,UAFQ,oBAAoB,uBAA0B,CAAC;EAE7C;EAAS;;;;;;AA4B9B,MAAa,wBAAwB,aAAqC;CACxE,MAAM,EAAE,SAAS,YAAY,uBAA6B;CAE1D,IAAIC;CAEJ,IAAIC;CAEJ,MAAM,cAAc;AAClB,MAAI,QAAS,QAAO;AAEpB,YAAU,iBAAiB;AACzB,YAAS;KACR,SAAS;AAEZ,SAAO;;CAGT,MAAM,cAAc;AAClB,eAAa,QAAQ;AACrB,YAAU;;CAGZ,MAAM,cAAc;AAClB,SAAO;AACP,SAAO,OAAO;;AAGhB,OAAM,OAAO,OAAO,SAAS;EAAE;EAAO;EAAO;EAAO,CAAC;AAErD,QAAO;;;;;;;;;AAWT,MAAa,gBACX,OAE8D;AAC9D,QAAO,QAAQ,SAAS,CAAC,KAAK,GAAG;;;;;AAMnC,MAAa,wBAAuC;AAClD,QAAO,IAAI,SAAS,YAAY,WAAW,QAAQ,CAAC;;AAGtD,MAAa,mBAAmB,OAC9B,IACA,SAIe;CACf,MAAM,cAAc,MAAM,eAAe;CACzC,MAAM,YAAY,MAAM,aAAa;AAErC,MAAK,IAAI,UAAU,GAAG,WAAW,aAAa,UAC5C,KAAI;AACF,SAAO,MAAM,IAAI;UACV,KAAK;AACZ,MAAI,WAAW,YACb,OAAM;EAGR,MAAM,SAAS,KAAK,QAAQ,GAAG;EAC/B,MAAM,QAAQ,YAAY,KAAK,IAAI,GAAG,UAAU,EAAE,GAAG;AACrD,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;;AAI9D,OAAM,IAAI,MAAM,mDAAmD;;;;;;AAarE,MAAa,mBAAmB,OAC9B,OAII;CAGJ,MAAM,QAAQ,KAAK,KAAK;CACxB,MAAM,gBAAgB,aAAa,GAAG;AAGtC,KAAI;AACF,QAAM;SACA;CAIR,MAAM,MAAM,KAAK,KAAK;AAOtB,QAAO;EAAE;EAAe,UALP;GACf,GAAG,QAAQ;GACX,IAAI,MAAM,SAAS;GACpB;EAEiC"}
1
+ {"version":3,"file":"promises.js","names":["resolve: DeferredPromiseReturn<T>[\"resolve\"]","reject: DeferredPromiseReturn<T>[\"reject\"]","settledPromises: Promise<T>[]","rotateQueue: (value: void) => void","timeout: ReturnType<typeof setTimeout> | undefined","ret: TimeoutPromise"],"sources":["../../src/helpers/promises.ts"],"sourcesContent":["import type { MaybePromise } from \"./types.ts\";\n\n/**\n * Some environments don't allow access to the global queueMicrotask(). While we\n * had assumed this was only true for those powered by earlier versions of Node\n * (<14) that we don't officially support, Vercel's Edge Functions also obscure\n * the function in dev, even though the platform it's based on (Cloudflare\n * Workers) appropriately exposes it. Even worse, production Vercel Edge\n * Functions can see the function, but it immediately blows up the function when\n * used.\n *\n * Therefore, we can fall back to a reasonable alternative of\n * `Promise.resolve().then(fn)` instead. This _may_ be slightly slower in modern\n * environments, but at least we can still work in these environments.\n */\nconst shimQueueMicrotask = (callback: () => void): void => {\n void Promise.resolve().then(callback);\n};\n\n/**\n * A helper function to create a `Promise` that will never settle.\n *\n * It purposefully creates no references to `resolve` or `reject` so that the\n * returned `Promise` will remain unsettled until it falls out of scope and is\n * garbage collected.\n *\n * This should be used within transient closures to fake asynchronous action, so\n * long as it's guaranteed that they will fall out of scope.\n */\nexport const createFrozenPromise = (): Promise<unknown> => {\n return new Promise(() => undefined);\n};\n\n/**\n * Returns a Promise that resolves after the current event loop's microtasks\n * have finished, but before the next event loop tick.\n */\nexport const resolveAfterPending = (count = 100): Promise<void> => {\n /**\n * This uses a brute force implementation that will continue to enqueue\n * microtasks 10 times before resolving. This is to ensure that the microtask\n * queue is drained, even if the microtask queue is being manipulated by other\n * code.\n *\n * While this still doesn't guarantee that the microtask queue is drained,\n * it's our best bet for giving other non-controlled promises a chance to\n * resolve before we continue without resorting to falling in to the next\n * tick.\n */\n return new Promise((resolve) => {\n let i = 0;\n\n const iterate = () => {\n shimQueueMicrotask(() => {\n if (i++ > count) {\n return resolve();\n }\n\n iterate();\n });\n };\n\n iterate();\n });\n};\n\nexport type DeferredPromiseReturn<T> = {\n promise: Promise<T>;\n resolve: (value: T) => DeferredPromiseReturn<T>;\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n reject: (reason: any) => DeferredPromiseReturn<T>;\n};\n\n/**\n * Creates and returns Promise that can be resolved or rejected with the\n * returned `resolve` and `reject` functions.\n *\n * Resolving or rejecting the function will return a new set of Promise control\n * functions. These can be ignored if the original Promise is all that's needed.\n */\nexport const createDeferredPromise = <T>(): DeferredPromiseReturn<T> => {\n let resolve: DeferredPromiseReturn<T>[\"resolve\"];\n let reject: DeferredPromiseReturn<T>[\"reject\"];\n\n const promise = new Promise<T>((_resolve, _reject) => {\n resolve = (value: T) => {\n _resolve(value);\n return createDeferredPromise<T>();\n };\n\n reject = (reason) => {\n _reject(reason);\n return createDeferredPromise<T>();\n };\n });\n\n return { promise, resolve: resolve!, reject: reject! };\n};\n\n/**\n * Creates and returns a deferred Promise that can be resolved or rejected with\n * the returned `resolve` and `reject` functions.\n *\n * For each Promise resolved or rejected this way, this will also keep a stack\n * of all unhandled Promises, resolved or rejected.\n *\n * Once a Promise is read, it is removed from the stack.\n */\nexport const createDeferredPromiseWithStack = <T>(): {\n deferred: DeferredPromiseReturn<T>;\n results: AsyncGenerator<Awaited<T>, void, void>;\n} => {\n const settledPromises: Promise<T>[] = [];\n // biome-ignore lint/suspicious/noConfusingVoidType: intentional\n let rotateQueue: (value: void) => void = () => {};\n\n const results = (async function* () {\n while (true) {\n const next = settledPromises.shift();\n\n if (next) {\n yield next;\n } else {\n await new Promise<void>((resolve) => {\n rotateQueue = resolve;\n });\n }\n }\n })();\n\n const shimDeferredPromise = (deferred: DeferredPromiseReturn<T>) => {\n const originalResolve = deferred.resolve;\n const originalReject = deferred.reject;\n\n deferred.resolve = (value: T) => {\n settledPromises.push(deferred.promise);\n rotateQueue();\n return shimDeferredPromise(originalResolve(value));\n };\n\n deferred.reject = (reason) => {\n settledPromises.push(deferred.promise);\n rotateQueue();\n return shimDeferredPromise(originalReject(reason));\n };\n\n return deferred;\n };\n\n const deferred = shimDeferredPromise(createDeferredPromise<T>());\n\n return { deferred, results };\n};\n\ninterface TimeoutPromise extends Promise<void> {\n /**\n * Starts the timeout. If the timer is already started, this does nothing.\n *\n * @returns The promise that will resolve when the timeout expires.\n */\n start: () => TimeoutPromise;\n\n /**\n * Clears the timeout.\n */\n clear: () => void;\n\n /**\n * Clears the timeout and starts it again.\n *\n * @returns The promise that will resolve when the timeout expires.\n */\n reset: () => TimeoutPromise;\n}\n\n/**\n * Creates a Promise that will resolve after the given duration, along with\n * methods to start, clear, and reset the timeout.\n */\nexport const createTimeoutPromise = (duration: number): TimeoutPromise => {\n const { promise, resolve } = createDeferredPromise<void>();\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n // biome-ignore lint/style/useConst: intentional\n let ret: TimeoutPromise;\n\n const start = () => {\n if (timeout) return ret;\n\n timeout = setTimeout(() => {\n resolve();\n }, duration);\n\n return ret;\n };\n\n const clear = () => {\n clearTimeout(timeout);\n timeout = undefined;\n };\n\n const reset = () => {\n clear();\n return start();\n };\n\n ret = Object.assign(promise, { start, clear, reset });\n\n return ret;\n};\n\n/**\n * Take any function and safely promisify such that both synchronous and\n * asynchronous errors are caught and returned as a rejected Promise.\n *\n * The passed `fn` can be undefined to support functions that may conditionally\n * be defined.\n */\n// biome-ignore lint/suspicious/noExplicitAny: intentional\nexport const runAsPromise = <T extends (() => any) | undefined>(\n fn: T,\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n): Promise<T extends () => any ? Awaited<ReturnType<T>> : T> => {\n return Promise.resolve().then(fn);\n};\n\n/**\n * Returns a Promise that resolve after the current event loop tick.\n */\nexport const resolveNextTick = (): Promise<void> => {\n return new Promise((resolve) => setTimeout(resolve));\n};\n\nexport const retryWithBackoff = async <T>(\n fn: () => MaybePromise<T>,\n opts?: {\n maxAttempts?: number;\n baseDelay?: number;\n },\n): Promise<T> => {\n const maxAttempts = opts?.maxAttempts || 5;\n const baseDelay = opts?.baseDelay ?? 100;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (err) {\n if (attempt >= maxAttempts) {\n throw err;\n }\n\n const jitter = Math.random() * baseDelay;\n const delay = baseDelay * Math.pow(2, attempt - 1) + jitter;\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n\n throw new Error(\"Max retries reached; this should be unreachable.\");\n};\n\nexport type GoInterval = {\n a: number;\n b: number;\n};\n\n/**\n * Given a function, returns a Promise that resolves with the result of the\n * function and a Go-compatible `interval.Interval` timing object.\n */\n// biome-ignore lint/suspicious/noExplicitAny: match any fn\nexport const goIntervalTiming = async <T extends (...args: any[]) => any>(\n fn: T,\n): Promise<{\n resultPromise: Promise<Awaited<ReturnType<T>>>;\n interval: { a: number; b: number };\n}> => {\n // Ideally this would use process.hrtime, but that's not available in all\n // runtimes, so we must revert to less accurate timing and `Date`.\n const start = Date.now();\n const resultPromise = runAsPromise(fn) as Promise<Awaited<ReturnType<T>>>;\n\n // Let the function run to completion.\n try {\n await resultPromise;\n } catch {\n // no-op\n }\n\n const end = Date.now();\n\n const interval = {\n a: start * 1_000_000,\n b: (end - start) * 1_000_000,\n };\n\n return { resultPromise, interval };\n};\n"],"mappings":";;;;;;;;;;;;;;AAeA,MAAM,sBAAsB,aAA+B;AACzD,CAAK,QAAQ,SAAS,CAAC,KAAK,SAAS;;;;;;AAqBvC,MAAa,uBAAuB,QAAQ,QAAuB;;;;;;;;;;;;AAYjE,QAAO,IAAI,SAAS,YAAY;EAC9B,IAAI,IAAI;EAER,MAAM,gBAAgB;AACpB,4BAAyB;AACvB,QAAI,MAAM,MACR,QAAO,SAAS;AAGlB,aAAS;KACT;;AAGJ,WAAS;GACT;;;;;;;;;AAiBJ,MAAa,8BAA2D;CACtE,IAAIA;CACJ,IAAIC;AAcJ,QAAO;EAAE,SAZO,IAAI,SAAY,UAAU,YAAY;AACpD,cAAW,UAAa;AACtB,aAAS,MAAM;AACf,WAAO,uBAA0B;;AAGnC,aAAU,WAAW;AACnB,YAAQ,OAAO;AACf,WAAO,uBAA0B;;IAEnC;EAEyB;EAAkB;EAAS;;;;;;;;;;;AAYxD,MAAa,uCAGR;CACH,MAAMC,kBAAgC,EAAE;CAExC,IAAIC,oBAA2C;CAE/C,MAAM,WAAW,mBAAmB;AAClC,SAAO,MAAM;GACX,MAAM,OAAO,gBAAgB,OAAO;AAEpC,OAAI,KACF,OAAM;OAEN,OAAM,IAAI,SAAe,YAAY;AACnC,kBAAc;KACd;;KAGJ;CAEJ,MAAM,uBAAuB,aAAuC;EAClE,MAAM,kBAAkB,SAAS;EACjC,MAAM,iBAAiB,SAAS;AAEhC,WAAS,WAAW,UAAa;AAC/B,mBAAgB,KAAK,SAAS,QAAQ;AACtC,gBAAa;AACb,UAAO,oBAAoB,gBAAgB,MAAM,CAAC;;AAGpD,WAAS,UAAU,WAAW;AAC5B,mBAAgB,KAAK,SAAS,QAAQ;AACtC,gBAAa;AACb,UAAO,oBAAoB,eAAe,OAAO,CAAC;;AAGpD,SAAO;;AAKT,QAAO;EAAE,UAFQ,oBAAoB,uBAA0B,CAAC;EAE7C;EAAS;;;;;;AA4B9B,MAAa,wBAAwB,aAAqC;CACxE,MAAM,EAAE,SAAS,YAAY,uBAA6B;CAE1D,IAAIC;CAEJ,IAAIC;CAEJ,MAAM,cAAc;AAClB,MAAI,QAAS,QAAO;AAEpB,YAAU,iBAAiB;AACzB,YAAS;KACR,SAAS;AAEZ,SAAO;;CAGT,MAAM,cAAc;AAClB,eAAa,QAAQ;AACrB,YAAU;;CAGZ,MAAM,cAAc;AAClB,SAAO;AACP,SAAO,OAAO;;AAGhB,OAAM,OAAO,OAAO,SAAS;EAAE;EAAO;EAAO;EAAO,CAAC;AAErD,QAAO;;;;;;;;;AAWT,MAAa,gBACX,OAE8D;AAC9D,QAAO,QAAQ,SAAS,CAAC,KAAK,GAAG;;;;;AAMnC,MAAa,wBAAuC;AAClD,QAAO,IAAI,SAAS,YAAY,WAAW,QAAQ,CAAC;;AAGtD,MAAa,mBAAmB,OAC9B,IACA,SAIe;CACf,MAAM,cAAc,MAAM,eAAe;CACzC,MAAM,YAAY,MAAM,aAAa;AAErC,MAAK,IAAI,UAAU,GAAG,WAAW,aAAa,UAC5C,KAAI;AACF,SAAO,MAAM,IAAI;UACV,KAAK;AACZ,MAAI,WAAW,YACb,OAAM;EAGR,MAAM,SAAS,KAAK,QAAQ,GAAG;EAC/B,MAAM,QAAQ,YAAY,KAAK,IAAI,GAAG,UAAU,EAAE,GAAG;AACrD,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;;AAI9D,OAAM,IAAI,MAAM,mDAAmD;;;;;;AAarE,MAAa,mBAAmB,OAC9B,OAII;CAGJ,MAAM,QAAQ,KAAK,KAAK;CACxB,MAAM,gBAAgB,aAAa,GAAG;AAGtC,KAAI;AACF,QAAM;SACA;CAIR,MAAM,MAAM,KAAK,KAAK;AAOtB,QAAO;EAAE;EAAe,UALP;GACf,GAAG,QAAQ;GACX,IAAI,MAAM,SAAS;GACpB;EAEiC"}