@bunworks/inngest-realtime 0.1.0 → 0.1.5

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 (101) hide show
  1. package/CHANGELOG.md +6 -6
  2. package/README.md +1 -1
  3. package/_virtual/_rolldown/runtime.cjs +29 -0
  4. package/api.cjs +45 -0
  5. package/api.cjs.map +1 -0
  6. package/api.mjs +44 -0
  7. package/api.mjs.map +1 -0
  8. package/channel.cjs +67 -0
  9. package/channel.cjs.map +1 -0
  10. package/channel.d.cts +19 -0
  11. package/channel.d.cts.map +1 -0
  12. package/channel.d.mts +19 -0
  13. package/channel.d.mts.map +1 -0
  14. package/channel.mjs +66 -0
  15. package/channel.mjs.map +1 -0
  16. package/env.cjs +72 -0
  17. package/env.cjs.map +1 -0
  18. package/env.mjs +71 -0
  19. package/env.mjs.map +1 -0
  20. package/hooks.cjs +147 -0
  21. package/hooks.cjs.map +1 -0
  22. package/hooks.d.cts +70 -0
  23. package/hooks.d.cts.map +1 -0
  24. package/hooks.d.mts +70 -0
  25. package/hooks.d.mts.map +1 -0
  26. package/hooks.mjs +144 -0
  27. package/hooks.mjs.map +1 -0
  28. package/index.cjs +19 -0
  29. package/index.d.cts +5 -0
  30. package/index.d.mts +6 -0
  31. package/index.mjs +7 -0
  32. package/middleware.cjs +41 -0
  33. package/middleware.cjs.map +1 -0
  34. package/middleware.d.cts +29 -0
  35. package/middleware.d.cts.map +1 -0
  36. package/middleware.d.mts +29 -0
  37. package/middleware.d.mts.map +1 -0
  38. package/middleware.mjs +34 -0
  39. package/middleware.mjs.map +1 -0
  40. package/package.json +6 -5
  41. package/subscribe/StreamFanout.cjs +47 -0
  42. package/subscribe/StreamFanout.cjs.map +1 -0
  43. package/subscribe/StreamFanout.mjs +46 -0
  44. package/subscribe/StreamFanout.mjs.map +1 -0
  45. package/subscribe/TokenSubscription.cjs +438 -0
  46. package/subscribe/TokenSubscription.cjs.map +1 -0
  47. package/subscribe/TokenSubscription.mjs +436 -0
  48. package/subscribe/TokenSubscription.mjs.map +1 -0
  49. package/subscribe/helpers.cjs +41 -0
  50. package/subscribe/helpers.cjs.map +1 -0
  51. package/subscribe/helpers.d.cts +64 -0
  52. package/subscribe/helpers.d.cts.map +1 -0
  53. package/subscribe/helpers.d.mts +64 -0
  54. package/subscribe/helpers.d.mts.map +1 -0
  55. package/subscribe/helpers.mjs +40 -0
  56. package/subscribe/helpers.mjs.map +1 -0
  57. package/subscribe/index.cjs +1 -0
  58. package/subscribe/index.d.mts +1 -0
  59. package/subscribe/index.mjs +3 -0
  60. package/topic.cjs +30 -0
  61. package/topic.cjs.map +1 -0
  62. package/topic.d.cts +19 -0
  63. package/topic.d.cts.map +1 -0
  64. package/topic.d.mts +19 -0
  65. package/topic.d.mts.map +1 -0
  66. package/topic.mjs +28 -0
  67. package/topic.mjs.map +1 -0
  68. package/types.cjs +45 -0
  69. package/types.cjs.map +1 -0
  70. package/types.d.cts +241 -0
  71. package/types.d.cts.map +1 -0
  72. package/types.d.mts +241 -0
  73. package/types.d.mts.map +1 -0
  74. package/types.mjs +39 -0
  75. package/types.mjs.map +1 -0
  76. package/util.cjs +72 -0
  77. package/util.cjs.map +1 -0
  78. package/util.mjs +69 -0
  79. package/util.mjs.map +1 -0
  80. package/CLAUDE.md +0 -69
  81. package/bun.lock +0 -527
  82. package/eslint.config.mjs +0 -19
  83. package/src/api.ts +0 -83
  84. package/src/channel.ts +0 -122
  85. package/src/env.d.ts +0 -15
  86. package/src/env.ts +0 -152
  87. package/src/hooks.ts +0 -256
  88. package/src/index.test.ts +0 -840
  89. package/src/index.ts +0 -4
  90. package/src/middleware.ts +0 -68
  91. package/src/subscribe/StreamFanout.ts +0 -82
  92. package/src/subscribe/TokenSubscription.ts +0 -524
  93. package/src/subscribe/helpers.ts +0 -153
  94. package/src/subscribe/index.ts +0 -1
  95. package/src/topic.ts +0 -51
  96. package/src/types.ts +0 -499
  97. package/src/util.ts +0 -104
  98. package/tsconfig.build.json +0 -7
  99. package/tsconfig.json +0 -114
  100. package/tsdown.config.ts +0 -18
  101. package/vitest.config.ts +0 -22
package/middleware.mjs ADDED
@@ -0,0 +1,34 @@
1
+ import { Realtime } from "./types.mjs";
2
+
3
+ //#region src/middleware.ts
4
+ const realtimeMiddleware = () => {
5
+ return {
6
+ name: "publish",
7
+ init({ client }) {
8
+ return { onFunctionRun({ ctx: { runId } }) {
9
+ return { transformInput({ ctx: { step } }) {
10
+ const publish = async (input) => {
11
+ const { topic, channel, data } = await input;
12
+ const publishOpts = {
13
+ topics: [topic],
14
+ channel,
15
+ runId
16
+ };
17
+ const action = async () => {
18
+ const result = await client["api"].publish(publishOpts, data);
19
+ if (!result.ok) throw new Error(`Failed to publish event: ${result.error?.error}`);
20
+ };
21
+ return step.run(`publish:${publishOpts.channel}`, action).then(() => {
22
+ return data;
23
+ });
24
+ };
25
+ return { ctx: { publish } };
26
+ } };
27
+ } };
28
+ }
29
+ };
30
+ };
31
+
32
+ //#endregion
33
+ export { Realtime, realtimeMiddleware };
34
+ //# sourceMappingURL=middleware.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.mjs","names":[],"sources":["../src/middleware.ts"],"sourcesContent":["import type { Realtime } from \"./types\";\r\n\r\nexport interface InngestMiddleware {\r\n name: string;\r\n init: (config: { client: any }) => {\r\n onFunctionRun: (config: { ctx: { runId: string } }) => {\r\n transformInput: (config: { ctx: { step: any } }) => {\r\n ctx: {\r\n publish: Realtime.PublishFn;\r\n };\r\n };\r\n };\r\n };\r\n}\r\n\r\nexport const realtimeMiddleware = () => {\r\n return {\r\n name: \"publish\",\r\n init({ client }: { client: any }) {\r\n return {\r\n onFunctionRun({ ctx: { runId } }: { ctx: { runId: string } }) {\r\n return {\r\n transformInput({ ctx: { step } }: { ctx: { step: any } }) {\r\n const publish: Realtime.PublishFn = async (input) => {\r\n const { topic, channel, data } = await input;\r\n\r\n const publishOpts = {\r\n topics: [topic],\r\n channel,\r\n runId,\r\n };\r\n\r\n const action = async () => {\r\n const result = await client[\"api\"].publish(publishOpts, data);\r\n\r\n if (!result.ok) {\r\n throw new Error(\r\n `Failed to publish event: ${result.error?.error}`,\r\n );\r\n }\r\n };\r\n\r\n return step\r\n .run(`publish:${publishOpts.channel}`, action)\r\n .then(() => {\r\n return data;\r\n });\r\n };\r\n\r\n return {\r\n ctx: {\r\n /**\r\n * Function to publish messages to realtime channel\r\n */\r\n publish,\r\n },\r\n };\r\n },\r\n };\r\n },\r\n };\r\n },\r\n } as InngestMiddleware;\r\n};\r\n\r\n// Re-export types from here, as this is used as a separate entrypoint now\r\nexport * from \"./types\";\r\n"],"mappings":";;;AAeA,MAAa,2BAA2B;AACtC,QAAO;EACL,MAAM;EACN,KAAK,EAAE,UAA2B;AAChC,UAAO,EACL,cAAc,EAAE,KAAK,EAAE,WAAuC;AAC5D,WAAO,EACL,eAAe,EAAE,KAAK,EAAE,UAAkC;KACxD,MAAM,UAA8B,OAAO,UAAU;MACnD,MAAM,EAAE,OAAO,SAAS,SAAS,MAAM;MAEvC,MAAM,cAAc;OAClB,QAAQ,CAAC,MAAM;OACf;OACA;OACD;MAED,MAAM,SAAS,YAAY;OACzB,MAAM,SAAS,MAAM,OAAO,OAAO,QAAQ,aAAa,KAAK;AAE7D,WAAI,CAAC,OAAO,GACV,OAAM,IAAI,MACR,4BAA4B,OAAO,OAAO,QAC3C;;AAIL,aAAO,KACJ,IAAI,WAAW,YAAY,WAAW,OAAO,CAC7C,WAAW;AACV,cAAO;QACP;;AAGN,YAAO,EACL,KAAK,EAIH,SACD,EACF;OAEJ;MAEJ;;EAEJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bunworks/inngest-realtime",
3
- "version": "0.1.0",
3
+ "version": "0.1.5",
4
4
  "description": "Realtime messaging для @bunworks",
5
5
  "main": "./index.js",
6
6
  "publishConfig": {
@@ -10,9 +10,10 @@
10
10
  "scripts": {
11
11
  "test": "vitest run",
12
12
  "build": "tsc -p tsconfig.build.json && tsdown --config tsdown.config.ts",
13
- "postversion": "pnpm run build",
13
+ "postversion": "bun run build",
14
14
  "release": "cross-env DIST_DIR=dist node ../../scripts/release/publish.js",
15
- "pack": "pnpm run build && mv $(npm pack ./dist --pack-destination . --silent) inngest-realtime.tgz"
15
+ "pack": "bun run build && mv $(npm pack ./dist --pack-destination . --silent) inngest-realtime.tgz",
16
+ "publish:patch": "npm version patch --no-git-tag-version && npm publish ./dist --access public"
16
17
  },
17
18
  "exports": {
18
19
  ".": {
@@ -52,10 +53,10 @@
52
53
  "websocket",
53
54
  "messaging"
54
55
  ],
55
- "homepage": "https://github.com/bunworks/bunworks#readme",
56
+ "homepage": "https://github.com/bunworks/inngest-realtime#readme",
56
57
  "repository": {
57
58
  "type": "git",
58
- "url": "git+https://github.com/bunworks/bunworks.git",
59
+ "url": "git+https://github.com/bunworks/inngest-realtime.git",
59
60
  "directory": "external/realtime"
60
61
  },
61
62
  "author": "Bunworks Team",
@@ -0,0 +1,47 @@
1
+
2
+ //#region src/subscribe/StreamFanout.ts
3
+ /**
4
+ * Fan out a single input stream to multiple output streams
5
+ */
6
+ var StreamFanout = class {
7
+ #writers = /* @__PURE__ */ new Set();
8
+ /**
9
+ * Create a new output stream with optional transformation
10
+ */
11
+ createStream(transform) {
12
+ const { readable, writable } = new TransformStream({ transform: (chunk, controller) => {
13
+ controller.enqueue(transform ? transform(chunk) : chunk);
14
+ } });
15
+ const writer = writable.getWriter();
16
+ this.#writers.add(writer);
17
+ writer.closed.catch(() => {}).finally(() => {
18
+ this.#writers.delete(writer);
19
+ });
20
+ return readable;
21
+ }
22
+ /**
23
+ * Write a chunk to all active streams
24
+ */
25
+ write(chunk) {
26
+ for (const writer of this.#writers) writer.ready.then(() => writer.write(chunk)).catch(() => this.#writers.delete(writer));
27
+ }
28
+ /**
29
+ * Close all active streams
30
+ */
31
+ close() {
32
+ for (const writer of this.#writers) try {
33
+ writer.close();
34
+ } catch {}
35
+ this.#writers.clear();
36
+ }
37
+ /**
38
+ * Get the number of active streams
39
+ */
40
+ size() {
41
+ return this.#writers.size;
42
+ }
43
+ };
44
+
45
+ //#endregion
46
+ exports.StreamFanout = StreamFanout;
47
+ //# sourceMappingURL=StreamFanout.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamFanout.cjs","names":["#writers"],"sources":["../../src/subscribe/StreamFanout.ts"],"sourcesContent":["/**\r\n * Fan out a single input stream to multiple output streams\r\n */\r\nexport class StreamFanout<TInput = unknown> {\r\n #writers = new Set<WritableStreamDefaultWriter<TInput>>();\r\n\r\n /**\r\n * Create a new output stream with optional transformation\r\n */\r\n createStream<TOutput = TInput>(\r\n /**\r\n * Optional transform function for each chunk\r\n */\r\n transform?: (\r\n /**\r\n * Input chunk\r\n */\r\n chunk: TInput,\r\n ) => TOutput,\r\n ): ReadableStream<TOutput> {\r\n const { readable, writable } = new TransformStream<TInput, TOutput>({\r\n transform: (chunk, controller) => {\r\n controller.enqueue(\r\n transform ? transform(chunk) : (chunk as unknown as TOutput),\r\n );\r\n },\r\n });\r\n\r\n const writer = writable.getWriter();\r\n this.#writers.add(writer);\r\n\r\n // Eagerly remove the writer is the stream is closed\r\n writer.closed\r\n .catch(() => {}) // Suppress unhandled promise rejection to avoid noisy logs\r\n .finally(() => {\r\n this.#writers.delete(writer);\r\n });\r\n\r\n return readable;\r\n }\r\n\r\n /**\r\n * Write a chunk to all active streams\r\n */\r\n write(\r\n /**\r\n * Chunk to write\r\n */\r\n chunk: TInput,\r\n ) {\r\n for (const writer of this.#writers) {\r\n writer.ready\r\n .then(() => writer.write(chunk))\r\n // Dereference the writer if we fail, as this means it's closed\r\n .catch(() => this.#writers.delete(writer));\r\n }\r\n }\r\n\r\n /**\r\n * Close all active streams\r\n */\r\n close() {\r\n for (const writer of this.#writers) {\r\n try {\r\n writer.close();\r\n } catch {\r\n // Ignore errors, as we are closing the stream and the writer may\r\n // already be closed, especially if the stream is closed before the\r\n // writer is closed or if the stream is cancelled.\r\n }\r\n }\r\n\r\n this.#writers.clear();\r\n }\r\n\r\n /**\r\n * Get the number of active streams\r\n */\r\n size() {\r\n return this.#writers.size;\r\n }\r\n}\r\n"],"mappings":";;;;;AAGA,IAAa,eAAb,MAA4C;CAC1C,2BAAW,IAAI,KAA0C;;;;CAKzD,aAIE,WAMyB;EACzB,MAAM,EAAE,UAAU,aAAa,IAAI,gBAAiC,EAClE,YAAY,OAAO,eAAe;AAChC,cAAW,QACT,YAAY,UAAU,MAAM,GAAI,MACjC;KAEJ,CAAC;EAEF,MAAM,SAAS,SAAS,WAAW;AACnC,QAAKA,QAAS,IAAI,OAAO;AAGzB,SAAO,OACJ,YAAY,GAAG,CACf,cAAc;AACb,SAAKA,QAAS,OAAO,OAAO;IAC5B;AAEJ,SAAO;;;;;CAMT,MAIE,OACA;AACA,OAAK,MAAM,UAAU,MAAKA,QACxB,QAAO,MACJ,WAAW,OAAO,MAAM,MAAM,CAAC,CAE/B,YAAY,MAAKA,QAAS,OAAO,OAAO,CAAC;;;;;CAOhD,QAAQ;AACN,OAAK,MAAM,UAAU,MAAKA,QACxB,KAAI;AACF,UAAO,OAAO;UACR;AAOV,QAAKA,QAAS,OAAO;;;;;CAMvB,OAAO;AACL,SAAO,MAAKA,QAAS"}
@@ -0,0 +1,46 @@
1
+ //#region src/subscribe/StreamFanout.ts
2
+ /**
3
+ * Fan out a single input stream to multiple output streams
4
+ */
5
+ var StreamFanout = class {
6
+ #writers = /* @__PURE__ */ new Set();
7
+ /**
8
+ * Create a new output stream with optional transformation
9
+ */
10
+ createStream(transform) {
11
+ const { readable, writable } = new TransformStream({ transform: (chunk, controller) => {
12
+ controller.enqueue(transform ? transform(chunk) : chunk);
13
+ } });
14
+ const writer = writable.getWriter();
15
+ this.#writers.add(writer);
16
+ writer.closed.catch(() => {}).finally(() => {
17
+ this.#writers.delete(writer);
18
+ });
19
+ return readable;
20
+ }
21
+ /**
22
+ * Write a chunk to all active streams
23
+ */
24
+ write(chunk) {
25
+ for (const writer of this.#writers) writer.ready.then(() => writer.write(chunk)).catch(() => this.#writers.delete(writer));
26
+ }
27
+ /**
28
+ * Close all active streams
29
+ */
30
+ close() {
31
+ for (const writer of this.#writers) try {
32
+ writer.close();
33
+ } catch {}
34
+ this.#writers.clear();
35
+ }
36
+ /**
37
+ * Get the number of active streams
38
+ */
39
+ size() {
40
+ return this.#writers.size;
41
+ }
42
+ };
43
+
44
+ //#endregion
45
+ export { StreamFanout };
46
+ //# sourceMappingURL=StreamFanout.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StreamFanout.mjs","names":["#writers"],"sources":["../../src/subscribe/StreamFanout.ts"],"sourcesContent":["/**\r\n * Fan out a single input stream to multiple output streams\r\n */\r\nexport class StreamFanout<TInput = unknown> {\r\n #writers = new Set<WritableStreamDefaultWriter<TInput>>();\r\n\r\n /**\r\n * Create a new output stream with optional transformation\r\n */\r\n createStream<TOutput = TInput>(\r\n /**\r\n * Optional transform function for each chunk\r\n */\r\n transform?: (\r\n /**\r\n * Input chunk\r\n */\r\n chunk: TInput,\r\n ) => TOutput,\r\n ): ReadableStream<TOutput> {\r\n const { readable, writable } = new TransformStream<TInput, TOutput>({\r\n transform: (chunk, controller) => {\r\n controller.enqueue(\r\n transform ? transform(chunk) : (chunk as unknown as TOutput),\r\n );\r\n },\r\n });\r\n\r\n const writer = writable.getWriter();\r\n this.#writers.add(writer);\r\n\r\n // Eagerly remove the writer is the stream is closed\r\n writer.closed\r\n .catch(() => {}) // Suppress unhandled promise rejection to avoid noisy logs\r\n .finally(() => {\r\n this.#writers.delete(writer);\r\n });\r\n\r\n return readable;\r\n }\r\n\r\n /**\r\n * Write a chunk to all active streams\r\n */\r\n write(\r\n /**\r\n * Chunk to write\r\n */\r\n chunk: TInput,\r\n ) {\r\n for (const writer of this.#writers) {\r\n writer.ready\r\n .then(() => writer.write(chunk))\r\n // Dereference the writer if we fail, as this means it's closed\r\n .catch(() => this.#writers.delete(writer));\r\n }\r\n }\r\n\r\n /**\r\n * Close all active streams\r\n */\r\n close() {\r\n for (const writer of this.#writers) {\r\n try {\r\n writer.close();\r\n } catch {\r\n // Ignore errors, as we are closing the stream and the writer may\r\n // already be closed, especially if the stream is closed before the\r\n // writer is closed or if the stream is cancelled.\r\n }\r\n }\r\n\r\n this.#writers.clear();\r\n }\r\n\r\n /**\r\n * Get the number of active streams\r\n */\r\n size() {\r\n return this.#writers.size;\r\n }\r\n}\r\n"],"mappings":";;;;AAGA,IAAa,eAAb,MAA4C;CAC1C,2BAAW,IAAI,KAA0C;;;;CAKzD,aAIE,WAMyB;EACzB,MAAM,EAAE,UAAU,aAAa,IAAI,gBAAiC,EAClE,YAAY,OAAO,eAAe;AAChC,cAAW,QACT,YAAY,UAAU,MAAM,GAAI,MACjC;KAEJ,CAAC;EAEF,MAAM,SAAS,SAAS,WAAW;AACnC,QAAKA,QAAS,IAAI,OAAO;AAGzB,SAAO,OACJ,YAAY,GAAG,CACf,cAAc;AACb,SAAKA,QAAS,OAAO,OAAO;IAC5B;AAEJ,SAAO;;;;;CAMT,MAIE,OACA;AACA,OAAK,MAAM,UAAU,MAAKA,QACxB,QAAO,MACJ,WAAW,OAAO,MAAM,MAAM,CAAC,CAE/B,YAAY,MAAKA,QAAS,OAAO,OAAO,CAAC;;;;;CAOhD,QAAQ;AACN,OAAK,MAAM,UAAU,MAAKA,QACxB,KAAI;AACF,UAAO,OAAO;UACR;AAOV,QAAKA,QAAS,OAAO;;;;;CAMvB,OAAO;AACL,SAAO,MAAKA,QAAS"}
@@ -0,0 +1,438 @@
1
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_topic = require('../topic.cjs');
3
+ const require_env = require('../env.cjs');
4
+ const require_util = require('../util.cjs');
5
+ const require_api = require('../api.cjs');
6
+ const require_types = require('../types.cjs');
7
+ const require_StreamFanout = require('./StreamFanout.cjs');
8
+ let debug = require("debug");
9
+ debug = require_runtime.__toESM(debug);
10
+
11
+ //#region src/subscribe/TokenSubscription.ts
12
+ /**
13
+ * Realtime channel subscription via WebSocket
14
+ */
15
+ var TokenSubscription = class {
16
+ #apiBaseUrl;
17
+ #channelId;
18
+ #debug = (0, debug.default)("inngest:realtime");
19
+ #encoder = new TextEncoder();
20
+ #fanout = new require_StreamFanout.StreamFanout();
21
+ #running = false;
22
+ #topics;
23
+ #ws = null;
24
+ #signingKey;
25
+ #signingKeyFallback;
26
+ #reconnectAttempts = 0;
27
+ #maxReconnectAttempts = 5;
28
+ #reconnectDelay = 1e3;
29
+ #connectionPromise = null;
30
+ #reconnectTimer = null;
31
+ /**
32
+ * Map of stream IDs to their streams and controllers
33
+ */
34
+ #chunkStreams = /* @__PURE__ */ new Map();
35
+ constructor(token, apiBaseUrl, signingKey, signingKeyFallback) {
36
+ this.token = token;
37
+ this.#apiBaseUrl = apiBaseUrl;
38
+ this.#signingKey = signingKey;
39
+ this.#signingKeyFallback = signingKeyFallback;
40
+ if (typeof token.channel === "string") {
41
+ this.#channelId = token.channel;
42
+ this.#topics = this.token.topics.reduce((acc, name) => {
43
+ acc.set(name, require_topic.topic(name));
44
+ return acc;
45
+ }, /* @__PURE__ */ new Map());
46
+ } else {
47
+ this.#channelId = token.channel.name;
48
+ this.#topics = this.token.topics.reduce((acc, name) => {
49
+ acc.set(name, token.channel.topics[name] ?? require_topic.topic(name));
50
+ return acc;
51
+ }, /* @__PURE__ */ new Map());
52
+ }
53
+ }
54
+ async getWsUrl(token) {
55
+ let url;
56
+ const path = "/v1/realtime/connect";
57
+ const devEnvVar = require_env.getEnvVar("INNGEST_DEV");
58
+ if (this.#apiBaseUrl) url = new URL(path, this.#apiBaseUrl);
59
+ else if (devEnvVar) try {
60
+ const devUrl = new URL(devEnvVar);
61
+ url = new URL(path, devUrl);
62
+ } catch {
63
+ if (require_util.parseAsBoolean(devEnvVar)) url = new URL(path, "http://localhost:8288/");
64
+ else url = new URL(path, "https://api.inngest.com/");
65
+ }
66
+ else url = new URL(path, require_env.getEnvVar("NODE_ENV") === "production" ? "https://api.inngest.com/" : "http://localhost:8288/");
67
+ url.protocol = url.protocol === "http:" ? "ws:" : "wss:";
68
+ url.searchParams.set("token", token);
69
+ return url;
70
+ }
71
+ /**
72
+ * Establish WebSocket connection
73
+ */
74
+ async connect() {
75
+ if (this.#connectionPromise) return this.#connectionPromise;
76
+ if (!this.#running && this.#reconnectAttempts === 0) return;
77
+ this.#connectionPromise = this.#connect();
78
+ return this.#connectionPromise;
79
+ }
80
+ async #connect() {
81
+ if (this.#running && this.#ws?.readyState === WebSocket.OPEN) return;
82
+ this.#debug(`Establishing connection to channel "${this.#channelId}" with topics ${JSON.stringify([...this.#topics.keys()])}...`);
83
+ if (typeof WebSocket === "undefined") throw new Error("WebSocket is not supported in current environment");
84
+ let key = this.token.key;
85
+ if (!key) {
86
+ this.#debug("No subscription token key passed; attempting to retrieve one automatically...");
87
+ key = (await this.lazilyGetSubscriptionToken({
88
+ ...this.token,
89
+ signingKey: this.#signingKey,
90
+ signingKeyFallback: this.#signingKeyFallback
91
+ })).key;
92
+ if (!key) throw new Error("No subscription token key provided and failed to retrieve one automatically");
93
+ }
94
+ const ret = require_util.createDeferredPromise();
95
+ try {
96
+ if (this.#ws) this.#cleanupWebSocket();
97
+ this.#ws = new WebSocket(await this.getWsUrl(key));
98
+ this.#ws.onopen = () => {
99
+ this.#debug("WebSocket connection established");
100
+ this.#reconnectAttempts = 0;
101
+ this.#running = true;
102
+ this.#connectionPromise = null;
103
+ ret.resolve();
104
+ };
105
+ this.#ws.onmessage = async (event) => {
106
+ await this.#handleMessage(event);
107
+ };
108
+ this.#ws.onerror = (event) => {
109
+ this.#debug("WebSocket error observed:", event);
110
+ ret.reject(/* @__PURE__ */ new Error("WebSocket connection error"));
111
+ };
112
+ this.#ws.onclose = (event) => {
113
+ this.#debug("WebSocket closed:", event.code, event.reason);
114
+ this.#handleClose(event);
115
+ };
116
+ } catch (err) {
117
+ this.#connectionPromise = null;
118
+ ret.reject(err);
119
+ }
120
+ return ret.promise;
121
+ }
122
+ #cleanupWebSocket() {
123
+ if (!this.#ws) return;
124
+ try {
125
+ this.#ws.onopen = null;
126
+ this.#ws.onmessage = null;
127
+ this.#ws.onerror = null;
128
+ this.#ws.onclose = null;
129
+ if (this.#ws.readyState === WebSocket.OPEN || this.#ws.readyState === WebSocket.CONNECTING) this.#ws.close(1e3, "Cleaning up connection");
130
+ } catch (err) {
131
+ this.#debug("Error cleaning up WebSocket:", err);
132
+ }
133
+ this.#ws = null;
134
+ }
135
+ #handleClose(event) {
136
+ const wasRunning = this.#running;
137
+ this.#running = false;
138
+ for (const [streamId, stream] of this.#chunkStreams.entries()) try {
139
+ stream.controller.close();
140
+ } catch (err) {
141
+ this.#debug(`Error closing stream ${streamId}:`, err);
142
+ }
143
+ this.#chunkStreams.clear();
144
+ if (event.code === 1e3 || !wasRunning) {
145
+ this.#debug("Connection closed normally");
146
+ this.#connectionPromise = null;
147
+ this.#fanout.close();
148
+ return;
149
+ }
150
+ if (this.#reconnectAttempts < this.#maxReconnectAttempts) {
151
+ this.#reconnectAttempts++;
152
+ const delay = this.#reconnectDelay * Math.pow(2, this.#reconnectAttempts - 1);
153
+ this.#debug(`Attempting reconnection ${this.#reconnectAttempts}/${this.#maxReconnectAttempts} in ${delay}ms...`);
154
+ this.#connectionPromise = null;
155
+ this.#reconnectTimer = setTimeout(() => {
156
+ this.#reconnectTimer = null;
157
+ this.connect().catch((err) => {
158
+ this.#debug("Reconnection failed:", err);
159
+ if (this.#reconnectAttempts >= this.#maxReconnectAttempts) {
160
+ this.#debug("Max reconnection attempts reached, closing streams");
161
+ this.#connectionPromise = null;
162
+ this.#fanout.close();
163
+ }
164
+ });
165
+ }, delay);
166
+ } else {
167
+ this.#debug("Max reconnection attempts reached");
168
+ this.#connectionPromise = null;
169
+ this.#fanout.close();
170
+ }
171
+ }
172
+ async #handleMessage(event) {
173
+ let parsedData;
174
+ try {
175
+ parsedData = JSON.parse(event.data);
176
+ } catch (err) {
177
+ this.#debug("Failed to parse JSON from WebSocket message:", err);
178
+ this.#debug("Raw payload:", event.data);
179
+ return;
180
+ }
181
+ const parseRes = await require_types.Realtime.messageSchema.safeParseAsync(parsedData);
182
+ if (!parseRes.success) {
183
+ this.#debug("Received invalid message:", parseRes.error);
184
+ return;
185
+ }
186
+ const msg = parseRes.data;
187
+ if (!this.#running) {
188
+ this.#debug(`Received message on channel "${msg.channel}" for topic "${msg.topic}" but stream is closed`);
189
+ return;
190
+ }
191
+ switch (msg.kind) {
192
+ case "data":
193
+ await this.#handleDataMessage(msg);
194
+ break;
195
+ case "datastream-start":
196
+ this.#handleDataStreamStart(msg);
197
+ break;
198
+ case "datastream-end":
199
+ this.#handleDataStreamEnd(msg);
200
+ break;
201
+ case "chunk":
202
+ this.#handleChunk(msg);
203
+ break;
204
+ case "ping":
205
+ if (this.#ws?.readyState === WebSocket.OPEN) this.#ws.send(JSON.stringify({ kind: "pong" }));
206
+ break;
207
+ case "closing":
208
+ this.#debug("Server is closing connection, will reconnect...");
209
+ break;
210
+ default: this.#debug(`Received message on channel "${msg.channel}" with unhandled kind "${msg.kind}"`);
211
+ }
212
+ }
213
+ async #handleDataMessage(msg) {
214
+ if (!msg.channel) {
215
+ this.#debug(`Received message with no channel`);
216
+ return;
217
+ }
218
+ if (!msg.topic) {
219
+ this.#debug(`Received message on channel "${msg.channel}" with no topic`);
220
+ return;
221
+ }
222
+ const topic = this.#topics.get(msg.topic);
223
+ if (!topic) {
224
+ this.#debug(`Received message on channel "${msg.channel}" for unknown topic "${msg.topic}"`);
225
+ return;
226
+ }
227
+ const schema = topic.getSchema();
228
+ if (schema) {
229
+ const validateRes = await schema["~standard"].validate(msg.data);
230
+ if (validateRes.issues) {
231
+ console.error(`Received message on channel "${msg.channel}" for topic "${msg.topic}" that failed schema validation:`, validateRes.issues);
232
+ return;
233
+ }
234
+ msg.data = validateRes.value;
235
+ }
236
+ this.#debug(`Received message on channel "${msg.channel}" for topic "${msg.topic}":`, msg.data);
237
+ this.#fanout.write({
238
+ channel: msg.channel,
239
+ topic: msg.topic,
240
+ data: msg.data,
241
+ fnId: msg.fn_id,
242
+ createdAt: msg.created_at || /* @__PURE__ */ new Date(),
243
+ runId: msg.run_id,
244
+ kind: "data",
245
+ envId: msg.env_id
246
+ });
247
+ }
248
+ #handleDataStreamStart(msg) {
249
+ if (!msg.channel || !msg.topic) {
250
+ this.#debug(`Received datastream-start with missing channel or topic`);
251
+ return;
252
+ }
253
+ const streamId = msg.data;
254
+ if (typeof streamId !== "string" || !streamId) {
255
+ this.#debug(`Received datastream-start with invalid stream ID`);
256
+ return;
257
+ }
258
+ if (this.#chunkStreams.has(streamId)) {
259
+ this.#debug(`Received datastream-start for stream ID "${streamId}" that already exists`);
260
+ return;
261
+ }
262
+ let holderStream;
263
+ let holderController;
264
+ holderStream = new ReadableStream({
265
+ start: (controller) => {
266
+ holderController = controller;
267
+ this.#chunkStreams.set(streamId, {
268
+ stream: holderStream,
269
+ controller: holderController
270
+ });
271
+ },
272
+ cancel: () => {
273
+ this.#chunkStreams.delete(streamId);
274
+ }
275
+ });
276
+ this.#debug(`Created stream ID "${streamId}" on channel "${msg.channel}"`);
277
+ this.#fanout.write({
278
+ channel: msg.channel,
279
+ topic: msg.topic,
280
+ kind: "datastream-start",
281
+ data: streamId,
282
+ streamId,
283
+ fnId: msg.fn_id,
284
+ runId: msg.run_id,
285
+ stream: holderStream
286
+ });
287
+ }
288
+ #handleDataStreamEnd(msg) {
289
+ if (!msg.channel || !msg.topic) {
290
+ this.#debug(`Received datastream-end with missing channel or topic`);
291
+ return;
292
+ }
293
+ const streamId = msg.data;
294
+ if (typeof streamId !== "string" || !streamId) {
295
+ this.#debug(`Received datastream-end with invalid stream ID`);
296
+ return;
297
+ }
298
+ const stream = this.#chunkStreams.get(streamId);
299
+ if (!stream) {
300
+ this.#debug(`Received datastream-end for stream ID "${streamId}" that doesn't exist`);
301
+ return;
302
+ }
303
+ try {
304
+ stream.controller.close();
305
+ } catch (err) {
306
+ this.#debug(`Error closing stream ${streamId}:`, err);
307
+ }
308
+ this.#chunkStreams.delete(streamId);
309
+ this.#debug(`Closed stream ID "${streamId}" on channel "${msg.channel}"`);
310
+ this.#fanout.write({
311
+ channel: msg.channel,
312
+ topic: msg.topic,
313
+ kind: "datastream-end",
314
+ data: streamId,
315
+ streamId,
316
+ fnId: msg.fn_id,
317
+ runId: msg.run_id,
318
+ stream: stream.stream
319
+ });
320
+ }
321
+ #handleChunk(msg) {
322
+ if (!msg.channel || !msg.topic) {
323
+ this.#debug(`Received chunk with missing channel or topic`);
324
+ return;
325
+ }
326
+ if (!msg.stream_id) {
327
+ this.#debug(`Received chunk with no stream ID`);
328
+ return;
329
+ }
330
+ const stream = this.#chunkStreams.get(msg.stream_id);
331
+ if (!stream) {
332
+ this.#debug(`Received chunk for unknown stream ID "${msg.stream_id}"`);
333
+ return;
334
+ }
335
+ this.#debug(`Received chunk on channel "${msg.channel}" for stream ID "${msg.stream_id}":`, msg.data);
336
+ try {
337
+ stream.controller.enqueue(msg.data);
338
+ } catch (err) {
339
+ this.#debug(`Error enqueueing chunk to stream ${msg.stream_id}:`, err);
340
+ }
341
+ this.#fanout.write({
342
+ channel: msg.channel,
343
+ topic: msg.topic,
344
+ kind: "chunk",
345
+ data: msg.data,
346
+ streamId: msg.stream_id,
347
+ fnId: msg.fn_id,
348
+ runId: msg.run_id,
349
+ stream: stream.stream
350
+ });
351
+ }
352
+ /**
353
+ * Lazily get a subscription token if not provided
354
+ */
355
+ async lazilyGetSubscriptionToken(args) {
356
+ const channelId = typeof args.channel === "string" ? args.channel : args.channel.name;
357
+ if (!channelId) throw new Error("Channel ID is required to create a subscription token");
358
+ const key = await require_api.api.getSubscriptionToken({
359
+ channel: channelId,
360
+ topics: args.topics,
361
+ signingKey: args.signingKey,
362
+ signingKeyFallback: args.signingKeyFallback,
363
+ apiBaseUrl: this.#apiBaseUrl
364
+ });
365
+ return {
366
+ channel: channelId,
367
+ topics: args.topics,
368
+ key
369
+ };
370
+ }
371
+ /**
372
+ * Close the connection and cleanup resources
373
+ */
374
+ close(reason = "Userland closed connection") {
375
+ if (!this.#running && !this.#ws) return;
376
+ this.#debug("close() called; closing connection...");
377
+ this.#running = false;
378
+ if (this.#reconnectTimer) {
379
+ clearTimeout(this.#reconnectTimer);
380
+ this.#reconnectTimer = null;
381
+ }
382
+ this.#reconnectAttempts = this.#maxReconnectAttempts;
383
+ this.#cleanupWebSocket();
384
+ for (const [streamId, stream] of this.#chunkStreams.entries()) try {
385
+ stream.controller.close();
386
+ } catch (err) {
387
+ this.#debug(`Error closing stream ${streamId}:`, err);
388
+ }
389
+ this.#chunkStreams.clear();
390
+ this.#debug(`Closing ${this.#fanout.size()} streams...`);
391
+ this.#fanout.close();
392
+ }
393
+ /**
394
+ * Get a new JSON stream from the subscription
395
+ */
396
+ getJsonStream() {
397
+ return this.#fanout.createStream();
398
+ }
399
+ /**
400
+ * Get a new encoded stream (SSE-compatible) from the subscription
401
+ */
402
+ getEncodedStream() {
403
+ return this.#fanout.createStream((chunk) => {
404
+ return this.#encoder.encode(`${JSON.stringify(chunk)}\n`);
405
+ });
406
+ }
407
+ /**
408
+ * Use a callback to handle messages from the stream
409
+ */
410
+ useCallback(callback, stream = this.getJsonStream()) {
411
+ (async () => {
412
+ const reader = stream.getReader();
413
+ try {
414
+ while (this.#running) {
415
+ const { done, value } = await reader.read();
416
+ if (done || !this.#running) break;
417
+ try {
418
+ callback(value);
419
+ } catch (err) {
420
+ this.#debug("Error in callback:", err);
421
+ }
422
+ }
423
+ } catch (err) {
424
+ this.#debug("Error reading from stream:", err);
425
+ } finally {
426
+ try {
427
+ reader.releaseLock();
428
+ } catch (err) {
429
+ this.#debug("Error releasing reader lock:", err);
430
+ }
431
+ }
432
+ })();
433
+ }
434
+ };
435
+
436
+ //#endregion
437
+ exports.TokenSubscription = TokenSubscription;
438
+ //# sourceMappingURL=TokenSubscription.cjs.map