inngest 4.1.1 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/components/connect/config.cjs +1 -1
- package/components/connect/config.cjs.map +1 -1
- package/components/connect/config.js +1 -1
- package/components/connect/config.js.map +1 -1
- package/components/connect/strategies/core/connection.cjs +36 -5
- package/components/connect/strategies/core/connection.cjs.map +1 -1
- package/components/connect/strategies/core/connection.js +36 -5
- package/components/connect/strategies/core/connection.js.map +1 -1
- package/components/connect/strategies/core/handshake.cjs +1 -0
- package/components/connect/strategies/core/handshake.cjs.map +1 -1
- package/components/connect/strategies/core/handshake.js +1 -0
- package/components/connect/strategies/core/handshake.js.map +1 -1
- package/components/connect/strategies/sameThread/index.cjs +2 -2
- package/components/connect/strategies/sameThread/index.cjs.map +1 -1
- package/components/connect/strategies/sameThread/index.js +2 -2
- package/components/connect/strategies/sameThread/index.js.map +1 -1
- package/components/connect/strategies/workerThread/index.cjs +13 -7
- package/components/connect/strategies/workerThread/index.cjs.map +1 -1
- package/components/connect/strategies/workerThread/index.js +13 -7
- package/components/connect/strategies/workerThread/index.js.map +1 -1
- package/components/connect/strategies/workerThread/runner.cjs +2 -2
- package/components/connect/strategies/workerThread/runner.cjs.map +1 -1
- package/components/connect/strategies/workerThread/runner.js +2 -2
- package/components/connect/strategies/workerThread/runner.js.map +1 -1
- package/package.json +1 -1
- package/react.d.cts.map +1 -1
- package/version.cjs +1 -1
- package/version.cjs.map +1 -1
- package/version.d.cts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# inngest
|
|
2
2
|
|
|
3
|
+
## 4.1.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1435](https://github.com/inngest/inngest-js/pull/1435) [`d8fc84b2`](https://github.com/inngest/inngest-js/commit/d8fc84b2f54e1b4725318593fdcc7b4004398eba) Thanks [@KiKoS0](https://github.com/KiKoS0)! - Fix Connect worker blocking flushing & termination issues
|
|
8
|
+
|
|
9
|
+
- [#1437](https://github.com/inngest/inngest-js/pull/1437) [`ad7ae27a`](https://github.com/inngest/inngest-js/commit/ad7ae27a7e20f60ed8aaf1ff80cdc98f2e405f1a) Thanks [@amh4r](https://github.com/amh4r)! - Fix worker thread strategy missing graceful shutdown
|
|
10
|
+
|
|
3
11
|
## 4.1.1
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -66,7 +66,7 @@ function prepareConnectionConfig(apps, inngest) {
|
|
|
66
66
|
});
|
|
67
67
|
}) }, "Prepared sync data");
|
|
68
68
|
const connectionData = {
|
|
69
|
-
manualReadinessAck:
|
|
69
|
+
manualReadinessAck: true,
|
|
70
70
|
marshaledCapabilities: JSON.stringify(capabilities),
|
|
71
71
|
apps: Object.entries(functionConfigs).map(([appId, { client, functions: fns }]) => ({
|
|
72
72
|
appName: appId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.cjs","names":["result: Record<\n string,\n { client: Inngest.Like; functions: InngestFunction.Any[] }\n >","getEnvironmentName","hashSigningKey","capabilities: Capabilities","functionConfigs: Record<\n string,\n { client: Inngest.Like; functions: FunctionConfig[] }\n >","internalLoggerSymbol","connectionData: ConnectionEstablishData","requestHandlers: Record<string, RequestHandler>","inngestCommHandler: ConnectCommHandler","InngestCommHandler","parseFnData","parseTraceCtx","headerKeys","sdkResponseStatus: SDKResponseStatus","SDKResponseStatus","SDKResponse","version","PREFERRED_ASYNC_EXECUTION_VERSION","queryKeys"],"sources":["../../../src/components/connect/config.ts"],"sourcesContent":["/**\n * Connection configuration preparation.\n *\n * Encapsulates signing key hashing, function config building,\n * InngestCommHandler creation, and connection data assembly — everything\n * needed before handing off to a connection strategy.\n */\n\nimport { envKeys, headerKeys, queryKeys } from \"../../helpers/consts.ts\";\nimport { allProcessEnv, getEnvironmentName } from \"../../helpers/env.ts\";\nimport { parseFnData } from \"../../helpers/functions.ts\";\nimport { hashSigningKey } from \"../../helpers/strings.ts\";\nimport {\n type GatewayExecutorRequestData,\n SDKResponse,\n SDKResponseStatus,\n} from \"../../proto/src/components/connect/protobuf/connect.ts\";\nimport type { Capabilities, FunctionConfig } from \"../../types.ts\";\nimport { version } from \"../../version.ts\";\nimport { PREFERRED_ASYNC_EXECUTION_VERSION } from \"../execution/InngestExecution.ts\";\nimport { type Inngest, internalLoggerSymbol } from \"../Inngest.ts\";\nimport { InngestCommHandler } from \"../InngestCommHandler.ts\";\nimport type { InngestFunction } from \"../InngestFunction.ts\";\nimport type {\n ConnectionEstablishData,\n RequestHandler,\n} from \"./strategies/index.ts\";\nimport type { ConnectApp } from \"./types.ts\";\nimport { parseTraceCtx } from \"./util.ts\";\n\nconst InngestBranchEnvironmentSigningKeyPrefix = \"signkey-branch-\";\n\ntype ConnectCommHandler = InngestCommHandler<\n [GatewayExecutorRequestData],\n SDKResponse,\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n any\n>;\n\nexport interface PreparedConnectionConfig {\n hashedSigningKey: string | undefined;\n hashedFallbackKey: string | undefined;\n envName: string | undefined;\n connectionData: ConnectionEstablishData;\n requestHandlers: Record<string, RequestHandler>;\n}\n\n/**\n * Collect per-app client + functions from ConnectApp definitions.\n */\nfunction collectFunctions(\n apps: ConnectApp[],\n): Record<string, { client: Inngest.Like; functions: InngestFunction.Any[] }> {\n const result: Record<\n string,\n { client: Inngest.Like; functions: InngestFunction.Any[] }\n > = {};\n\n for (const app of apps) {\n const client = app.client as Inngest.Any;\n if (result[client.id]) {\n throw new Error(`Duplicate app id: ${client.id}`);\n }\n result[client.id] = {\n client: app.client,\n functions: (app.functions as InngestFunction.Any[]) ?? client.funcs,\n };\n }\n\n return result;\n}\n\n/**\n * Prepare all connection configuration: signing keys, function configs,\n * connection data, and request handlers.\n */\nexport function prepareConnectionConfig(\n apps: ConnectApp[],\n inngest: Inngest.Any,\n): PreparedConnectionConfig {\n const envName = inngest.env ?? getEnvironmentName();\n\n const hashedSigningKey = inngest.signingKey\n ? hashSigningKey(inngest.signingKey)\n : undefined;\n\n if (\n inngest.signingKey &&\n inngest.signingKey.startsWith(InngestBranchEnvironmentSigningKeyPrefix) &&\n !envName\n ) {\n throw new Error(\n \"Environment is required when using branch environment signing keys\",\n );\n }\n\n const hashedFallbackKey = inngest.signingKeyFallback\n ? hashSigningKey(inngest.signingKeyFallback)\n : undefined;\n\n // Build capabilities\n const capabilities: Capabilities = {\n trust_probe: \"v1\",\n connect: \"v1\",\n };\n\n const functions = collectFunctions(apps);\n\n // Build function configs\n const functionConfigs: Record<\n string,\n { client: Inngest.Like; functions: FunctionConfig[] }\n > = {};\n for (const [appId, { client, functions: fns }] of Object.entries(functions)) {\n functionConfigs[appId] = {\n client: client,\n functions: fns.flatMap((f) =>\n f[\"getConfig\"]({\n baseUrl: new URL(\"wss://connect\"),\n appPrefix: (client as Inngest.Any).id,\n isConnect: true,\n }),\n ),\n };\n }\n\n inngest[internalLoggerSymbol].debug(\n {\n functionSlugs: Object.entries(functionConfigs).map(\n ([appId, { functions: fns }]) => {\n return JSON.stringify({\n appId,\n functions: fns.map((f) => ({\n id: f.id,\n stepUrls: Object.values(f.steps).map((s) => s.runtime[\"url\"]),\n })),\n });\n },\n ),\n },\n \"Prepared sync data\",\n );\n\n // Build connection establish data\n const connectionData: ConnectionEstablishData = {\n manualReadinessAck: false,\n marshaledCapabilities: JSON.stringify(capabilities),\n apps: Object.entries(functionConfigs).map(\n ([appId, { client, functions: fns }]) => ({\n appName: appId,\n appVersion: (client as Inngest.Any).appVersion,\n functions: new TextEncoder().encode(JSON.stringify(fns)),\n }),\n ),\n };\n\n // Build request handlers\n const requestHandlers: Record<string, RequestHandler> = {};\n for (const [appId, { client, functions: fns }] of Object.entries(functions)) {\n const inngestCommHandler: ConnectCommHandler = new InngestCommHandler({\n client: client,\n functions: fns,\n frameworkName: \"connect\",\n skipSignatureValidation: true,\n handler: (msg: GatewayExecutorRequestData) => {\n const asString = new TextDecoder().decode(msg.requestPayload);\n const parsed = parseFnData(\n JSON.parse(asString),\n undefined,\n inngest[internalLoggerSymbol],\n );\n\n const userTraceCtx = parseTraceCtx(msg.userTraceCtx);\n\n return {\n body() {\n return parsed;\n },\n method() {\n return \"POST\";\n },\n headers(key) {\n switch (key) {\n case headerKeys.ContentLength.toString():\n return asString.length.toString();\n case headerKeys.InngestExpectedServerKind.toString():\n return \"connect\";\n case headerKeys.RequestVersion.toString():\n return parsed.version.toString();\n case headerKeys.Signature.toString():\n return null;\n case headerKeys.TraceParent.toString():\n return userTraceCtx?.traceParent ?? null;\n case headerKeys.TraceState.toString():\n return userTraceCtx?.traceState ?? null;\n default:\n return null;\n }\n },\n transformResponse({ body, headers, status }) {\n let sdkResponseStatus: SDKResponseStatus = SDKResponseStatus.DONE;\n switch (status) {\n case 200:\n sdkResponseStatus = SDKResponseStatus.DONE;\n break;\n case 206:\n sdkResponseStatus = SDKResponseStatus.NOT_COMPLETED;\n break;\n case 500:\n sdkResponseStatus = SDKResponseStatus.ERROR;\n break;\n }\n\n return SDKResponse.create({\n requestId: msg.requestId,\n accountId: msg.accountId,\n envId: msg.envId,\n appId: msg.appId,\n status: sdkResponseStatus,\n body: new TextEncoder().encode(body),\n noRetry: headers[headerKeys.NoRetry] === \"true\",\n retryAfter: headers[headerKeys.RetryAfter],\n sdkVersion: `inngest-js:v${version}`,\n requestVersion: parseInt(\n headers[headerKeys.RequestVersion] ??\n PREFERRED_ASYNC_EXECUTION_VERSION.toString(),\n 10,\n ),\n systemTraceCtx: msg.systemTraceCtx,\n userTraceCtx: msg.userTraceCtx,\n runId: msg.runId,\n });\n },\n url() {\n const baseUrl = new URL(\"http://connect.inngest.com\");\n\n baseUrl.searchParams.set(queryKeys.FnId, msg.functionSlug);\n\n if (msg.stepId) {\n baseUrl.searchParams.set(queryKeys.StepId, msg.stepId);\n }\n\n return baseUrl;\n },\n };\n },\n });\n\n if (!inngestCommHandler.checkModeConfiguration()) {\n throw new Error(\"Signing key is required\");\n }\n\n const requestHandler = inngestCommHandler.createHandler();\n requestHandlers[appId] = requestHandler;\n }\n\n return {\n hashedSigningKey,\n hashedFallbackKey,\n envName,\n connectionData,\n requestHandlers,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.cjs","names":["result: Record<\n string,\n { client: Inngest.Like; functions: InngestFunction.Any[] }\n >","getEnvironmentName","hashSigningKey","capabilities: Capabilities","functionConfigs: Record<\n string,\n { client: Inngest.Like; functions: FunctionConfig[] }\n >","internalLoggerSymbol","connectionData: ConnectionEstablishData","requestHandlers: Record<string, RequestHandler>","inngestCommHandler: ConnectCommHandler","InngestCommHandler","parseFnData","parseTraceCtx","headerKeys","sdkResponseStatus: SDKResponseStatus","SDKResponseStatus","SDKResponse","version","PREFERRED_ASYNC_EXECUTION_VERSION","queryKeys"],"sources":["../../../src/components/connect/config.ts"],"sourcesContent":["/**\n * Connection configuration preparation.\n *\n * Encapsulates signing key hashing, function config building,\n * InngestCommHandler creation, and connection data assembly — everything\n * needed before handing off to a connection strategy.\n */\n\nimport { envKeys, headerKeys, queryKeys } from \"../../helpers/consts.ts\";\nimport { allProcessEnv, getEnvironmentName } from \"../../helpers/env.ts\";\nimport { parseFnData } from \"../../helpers/functions.ts\";\nimport { hashSigningKey } from \"../../helpers/strings.ts\";\nimport {\n type GatewayExecutorRequestData,\n SDKResponse,\n SDKResponseStatus,\n} from \"../../proto/src/components/connect/protobuf/connect.ts\";\nimport type { Capabilities, FunctionConfig } from \"../../types.ts\";\nimport { version } from \"../../version.ts\";\nimport { PREFERRED_ASYNC_EXECUTION_VERSION } from \"../execution/InngestExecution.ts\";\nimport { type Inngest, internalLoggerSymbol } from \"../Inngest.ts\";\nimport { InngestCommHandler } from \"../InngestCommHandler.ts\";\nimport type { InngestFunction } from \"../InngestFunction.ts\";\nimport type {\n ConnectionEstablishData,\n RequestHandler,\n} from \"./strategies/index.ts\";\nimport type { ConnectApp } from \"./types.ts\";\nimport { parseTraceCtx } from \"./util.ts\";\n\nconst InngestBranchEnvironmentSigningKeyPrefix = \"signkey-branch-\";\n\ntype ConnectCommHandler = InngestCommHandler<\n [GatewayExecutorRequestData],\n SDKResponse,\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n any\n>;\n\nexport interface PreparedConnectionConfig {\n hashedSigningKey: string | undefined;\n hashedFallbackKey: string | undefined;\n envName: string | undefined;\n connectionData: ConnectionEstablishData;\n requestHandlers: Record<string, RequestHandler>;\n}\n\n/**\n * Collect per-app client + functions from ConnectApp definitions.\n */\nfunction collectFunctions(\n apps: ConnectApp[],\n): Record<string, { client: Inngest.Like; functions: InngestFunction.Any[] }> {\n const result: Record<\n string,\n { client: Inngest.Like; functions: InngestFunction.Any[] }\n > = {};\n\n for (const app of apps) {\n const client = app.client as Inngest.Any;\n if (result[client.id]) {\n throw new Error(`Duplicate app id: ${client.id}`);\n }\n result[client.id] = {\n client: app.client,\n functions: (app.functions as InngestFunction.Any[]) ?? client.funcs,\n };\n }\n\n return result;\n}\n\n/**\n * Prepare all connection configuration: signing keys, function configs,\n * connection data, and request handlers.\n */\nexport function prepareConnectionConfig(\n apps: ConnectApp[],\n inngest: Inngest.Any,\n): PreparedConnectionConfig {\n const envName = inngest.env ?? getEnvironmentName();\n\n const hashedSigningKey = inngest.signingKey\n ? hashSigningKey(inngest.signingKey)\n : undefined;\n\n if (\n inngest.signingKey &&\n inngest.signingKey.startsWith(InngestBranchEnvironmentSigningKeyPrefix) &&\n !envName\n ) {\n throw new Error(\n \"Environment is required when using branch environment signing keys\",\n );\n }\n\n const hashedFallbackKey = inngest.signingKeyFallback\n ? hashSigningKey(inngest.signingKeyFallback)\n : undefined;\n\n // Build capabilities\n const capabilities: Capabilities = {\n trust_probe: \"v1\",\n connect: \"v1\",\n };\n\n const functions = collectFunctions(apps);\n\n // Build function configs\n const functionConfigs: Record<\n string,\n { client: Inngest.Like; functions: FunctionConfig[] }\n > = {};\n for (const [appId, { client, functions: fns }] of Object.entries(functions)) {\n functionConfigs[appId] = {\n client: client,\n functions: fns.flatMap((f) =>\n f[\"getConfig\"]({\n baseUrl: new URL(\"wss://connect\"),\n appPrefix: (client as Inngest.Any).id,\n isConnect: true,\n }),\n ),\n };\n }\n\n inngest[internalLoggerSymbol].debug(\n {\n functionSlugs: Object.entries(functionConfigs).map(\n ([appId, { functions: fns }]) => {\n return JSON.stringify({\n appId,\n functions: fns.map((f) => ({\n id: f.id,\n stepUrls: Object.values(f.steps).map((s) => s.runtime[\"url\"]),\n })),\n });\n },\n ),\n },\n \"Prepared sync data\",\n );\n\n // Build connection establish data\n const connectionData: ConnectionEstablishData = {\n manualReadinessAck: true,\n marshaledCapabilities: JSON.stringify(capabilities),\n apps: Object.entries(functionConfigs).map(\n ([appId, { client, functions: fns }]) => ({\n appName: appId,\n appVersion: (client as Inngest.Any).appVersion,\n functions: new TextEncoder().encode(JSON.stringify(fns)),\n }),\n ),\n };\n\n // Build request handlers\n const requestHandlers: Record<string, RequestHandler> = {};\n for (const [appId, { client, functions: fns }] of Object.entries(functions)) {\n const inngestCommHandler: ConnectCommHandler = new InngestCommHandler({\n client: client,\n functions: fns,\n frameworkName: \"connect\",\n skipSignatureValidation: true,\n handler: (msg: GatewayExecutorRequestData) => {\n const asString = new TextDecoder().decode(msg.requestPayload);\n const parsed = parseFnData(\n JSON.parse(asString),\n undefined,\n inngest[internalLoggerSymbol],\n );\n\n const userTraceCtx = parseTraceCtx(msg.userTraceCtx);\n\n return {\n body() {\n return parsed;\n },\n method() {\n return \"POST\";\n },\n headers(key) {\n switch (key) {\n case headerKeys.ContentLength.toString():\n return asString.length.toString();\n case headerKeys.InngestExpectedServerKind.toString():\n return \"connect\";\n case headerKeys.RequestVersion.toString():\n return parsed.version.toString();\n case headerKeys.Signature.toString():\n return null;\n case headerKeys.TraceParent.toString():\n return userTraceCtx?.traceParent ?? null;\n case headerKeys.TraceState.toString():\n return userTraceCtx?.traceState ?? null;\n default:\n return null;\n }\n },\n transformResponse({ body, headers, status }) {\n let sdkResponseStatus: SDKResponseStatus = SDKResponseStatus.DONE;\n switch (status) {\n case 200:\n sdkResponseStatus = SDKResponseStatus.DONE;\n break;\n case 206:\n sdkResponseStatus = SDKResponseStatus.NOT_COMPLETED;\n break;\n case 500:\n sdkResponseStatus = SDKResponseStatus.ERROR;\n break;\n }\n\n return SDKResponse.create({\n requestId: msg.requestId,\n accountId: msg.accountId,\n envId: msg.envId,\n appId: msg.appId,\n status: sdkResponseStatus,\n body: new TextEncoder().encode(body),\n noRetry: headers[headerKeys.NoRetry] === \"true\",\n retryAfter: headers[headerKeys.RetryAfter],\n sdkVersion: `inngest-js:v${version}`,\n requestVersion: parseInt(\n headers[headerKeys.RequestVersion] ??\n PREFERRED_ASYNC_EXECUTION_VERSION.toString(),\n 10,\n ),\n systemTraceCtx: msg.systemTraceCtx,\n userTraceCtx: msg.userTraceCtx,\n runId: msg.runId,\n });\n },\n url() {\n const baseUrl = new URL(\"http://connect.inngest.com\");\n\n baseUrl.searchParams.set(queryKeys.FnId, msg.functionSlug);\n\n if (msg.stepId) {\n baseUrl.searchParams.set(queryKeys.StepId, msg.stepId);\n }\n\n return baseUrl;\n },\n };\n },\n });\n\n if (!inngestCommHandler.checkModeConfiguration()) {\n throw new Error(\"Signing key is required\");\n }\n\n const requestHandler = inngestCommHandler.createHandler();\n requestHandlers[appId] = requestHandler;\n }\n\n return {\n hashedSigningKey,\n hashedFallbackKey,\n envName,\n connectionData,\n requestHandlers,\n };\n}\n"],"mappings":"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"}
|
|
@@ -66,7 +66,7 @@ function prepareConnectionConfig(apps, inngest) {
|
|
|
66
66
|
});
|
|
67
67
|
}) }, "Prepared sync data");
|
|
68
68
|
const connectionData = {
|
|
69
|
-
manualReadinessAck:
|
|
69
|
+
manualReadinessAck: true,
|
|
70
70
|
marshaledCapabilities: JSON.stringify(capabilities),
|
|
71
71
|
apps: Object.entries(functionConfigs).map(([appId, { client, functions: fns }]) => ({
|
|
72
72
|
appName: appId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","names":["result: Record<\n string,\n { client: Inngest.Like; functions: InngestFunction.Any[] }\n >","capabilities: Capabilities","functionConfigs: Record<\n string,\n { client: Inngest.Like; functions: FunctionConfig[] }\n >","connectionData: ConnectionEstablishData","requestHandlers: Record<string, RequestHandler>","inngestCommHandler: ConnectCommHandler","sdkResponseStatus: SDKResponseStatus"],"sources":["../../../src/components/connect/config.ts"],"sourcesContent":["/**\n * Connection configuration preparation.\n *\n * Encapsulates signing key hashing, function config building,\n * InngestCommHandler creation, and connection data assembly — everything\n * needed before handing off to a connection strategy.\n */\n\nimport { envKeys, headerKeys, queryKeys } from \"../../helpers/consts.ts\";\nimport { allProcessEnv, getEnvironmentName } from \"../../helpers/env.ts\";\nimport { parseFnData } from \"../../helpers/functions.ts\";\nimport { hashSigningKey } from \"../../helpers/strings.ts\";\nimport {\n type GatewayExecutorRequestData,\n SDKResponse,\n SDKResponseStatus,\n} from \"../../proto/src/components/connect/protobuf/connect.ts\";\nimport type { Capabilities, FunctionConfig } from \"../../types.ts\";\nimport { version } from \"../../version.ts\";\nimport { PREFERRED_ASYNC_EXECUTION_VERSION } from \"../execution/InngestExecution.ts\";\nimport { type Inngest, internalLoggerSymbol } from \"../Inngest.ts\";\nimport { InngestCommHandler } from \"../InngestCommHandler.ts\";\nimport type { InngestFunction } from \"../InngestFunction.ts\";\nimport type {\n ConnectionEstablishData,\n RequestHandler,\n} from \"./strategies/index.ts\";\nimport type { ConnectApp } from \"./types.ts\";\nimport { parseTraceCtx } from \"./util.ts\";\n\nconst InngestBranchEnvironmentSigningKeyPrefix = \"signkey-branch-\";\n\ntype ConnectCommHandler = InngestCommHandler<\n [GatewayExecutorRequestData],\n SDKResponse,\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n any\n>;\n\nexport interface PreparedConnectionConfig {\n hashedSigningKey: string | undefined;\n hashedFallbackKey: string | undefined;\n envName: string | undefined;\n connectionData: ConnectionEstablishData;\n requestHandlers: Record<string, RequestHandler>;\n}\n\n/**\n * Collect per-app client + functions from ConnectApp definitions.\n */\nfunction collectFunctions(\n apps: ConnectApp[],\n): Record<string, { client: Inngest.Like; functions: InngestFunction.Any[] }> {\n const result: Record<\n string,\n { client: Inngest.Like; functions: InngestFunction.Any[] }\n > = {};\n\n for (const app of apps) {\n const client = app.client as Inngest.Any;\n if (result[client.id]) {\n throw new Error(`Duplicate app id: ${client.id}`);\n }\n result[client.id] = {\n client: app.client,\n functions: (app.functions as InngestFunction.Any[]) ?? client.funcs,\n };\n }\n\n return result;\n}\n\n/**\n * Prepare all connection configuration: signing keys, function configs,\n * connection data, and request handlers.\n */\nexport function prepareConnectionConfig(\n apps: ConnectApp[],\n inngest: Inngest.Any,\n): PreparedConnectionConfig {\n const envName = inngest.env ?? getEnvironmentName();\n\n const hashedSigningKey = inngest.signingKey\n ? hashSigningKey(inngest.signingKey)\n : undefined;\n\n if (\n inngest.signingKey &&\n inngest.signingKey.startsWith(InngestBranchEnvironmentSigningKeyPrefix) &&\n !envName\n ) {\n throw new Error(\n \"Environment is required when using branch environment signing keys\",\n );\n }\n\n const hashedFallbackKey = inngest.signingKeyFallback\n ? hashSigningKey(inngest.signingKeyFallback)\n : undefined;\n\n // Build capabilities\n const capabilities: Capabilities = {\n trust_probe: \"v1\",\n connect: \"v1\",\n };\n\n const functions = collectFunctions(apps);\n\n // Build function configs\n const functionConfigs: Record<\n string,\n { client: Inngest.Like; functions: FunctionConfig[] }\n > = {};\n for (const [appId, { client, functions: fns }] of Object.entries(functions)) {\n functionConfigs[appId] = {\n client: client,\n functions: fns.flatMap((f) =>\n f[\"getConfig\"]({\n baseUrl: new URL(\"wss://connect\"),\n appPrefix: (client as Inngest.Any).id,\n isConnect: true,\n }),\n ),\n };\n }\n\n inngest[internalLoggerSymbol].debug(\n {\n functionSlugs: Object.entries(functionConfigs).map(\n ([appId, { functions: fns }]) => {\n return JSON.stringify({\n appId,\n functions: fns.map((f) => ({\n id: f.id,\n stepUrls: Object.values(f.steps).map((s) => s.runtime[\"url\"]),\n })),\n });\n },\n ),\n },\n \"Prepared sync data\",\n );\n\n // Build connection establish data\n const connectionData: ConnectionEstablishData = {\n manualReadinessAck: false,\n marshaledCapabilities: JSON.stringify(capabilities),\n apps: Object.entries(functionConfigs).map(\n ([appId, { client, functions: fns }]) => ({\n appName: appId,\n appVersion: (client as Inngest.Any).appVersion,\n functions: new TextEncoder().encode(JSON.stringify(fns)),\n }),\n ),\n };\n\n // Build request handlers\n const requestHandlers: Record<string, RequestHandler> = {};\n for (const [appId, { client, functions: fns }] of Object.entries(functions)) {\n const inngestCommHandler: ConnectCommHandler = new InngestCommHandler({\n client: client,\n functions: fns,\n frameworkName: \"connect\",\n skipSignatureValidation: true,\n handler: (msg: GatewayExecutorRequestData) => {\n const asString = new TextDecoder().decode(msg.requestPayload);\n const parsed = parseFnData(\n JSON.parse(asString),\n undefined,\n inngest[internalLoggerSymbol],\n );\n\n const userTraceCtx = parseTraceCtx(msg.userTraceCtx);\n\n return {\n body() {\n return parsed;\n },\n method() {\n return \"POST\";\n },\n headers(key) {\n switch (key) {\n case headerKeys.ContentLength.toString():\n return asString.length.toString();\n case headerKeys.InngestExpectedServerKind.toString():\n return \"connect\";\n case headerKeys.RequestVersion.toString():\n return parsed.version.toString();\n case headerKeys.Signature.toString():\n return null;\n case headerKeys.TraceParent.toString():\n return userTraceCtx?.traceParent ?? null;\n case headerKeys.TraceState.toString():\n return userTraceCtx?.traceState ?? null;\n default:\n return null;\n }\n },\n transformResponse({ body, headers, status }) {\n let sdkResponseStatus: SDKResponseStatus = SDKResponseStatus.DONE;\n switch (status) {\n case 200:\n sdkResponseStatus = SDKResponseStatus.DONE;\n break;\n case 206:\n sdkResponseStatus = SDKResponseStatus.NOT_COMPLETED;\n break;\n case 500:\n sdkResponseStatus = SDKResponseStatus.ERROR;\n break;\n }\n\n return SDKResponse.create({\n requestId: msg.requestId,\n accountId: msg.accountId,\n envId: msg.envId,\n appId: msg.appId,\n status: sdkResponseStatus,\n body: new TextEncoder().encode(body),\n noRetry: headers[headerKeys.NoRetry] === \"true\",\n retryAfter: headers[headerKeys.RetryAfter],\n sdkVersion: `inngest-js:v${version}`,\n requestVersion: parseInt(\n headers[headerKeys.RequestVersion] ??\n PREFERRED_ASYNC_EXECUTION_VERSION.toString(),\n 10,\n ),\n systemTraceCtx: msg.systemTraceCtx,\n userTraceCtx: msg.userTraceCtx,\n runId: msg.runId,\n });\n },\n url() {\n const baseUrl = new URL(\"http://connect.inngest.com\");\n\n baseUrl.searchParams.set(queryKeys.FnId, msg.functionSlug);\n\n if (msg.stepId) {\n baseUrl.searchParams.set(queryKeys.StepId, msg.stepId);\n }\n\n return baseUrl;\n },\n };\n },\n });\n\n if (!inngestCommHandler.checkModeConfiguration()) {\n throw new Error(\"Signing key is required\");\n }\n\n const requestHandler = inngestCommHandler.createHandler();\n requestHandlers[appId] = requestHandler;\n }\n\n return {\n hashedSigningKey,\n hashedFallbackKey,\n envName,\n connectionData,\n requestHandlers,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.js","names":["result: Record<\n string,\n { client: Inngest.Like; functions: InngestFunction.Any[] }\n >","capabilities: Capabilities","functionConfigs: Record<\n string,\n { client: Inngest.Like; functions: FunctionConfig[] }\n >","connectionData: ConnectionEstablishData","requestHandlers: Record<string, RequestHandler>","inngestCommHandler: ConnectCommHandler","sdkResponseStatus: SDKResponseStatus"],"sources":["../../../src/components/connect/config.ts"],"sourcesContent":["/**\n * Connection configuration preparation.\n *\n * Encapsulates signing key hashing, function config building,\n * InngestCommHandler creation, and connection data assembly — everything\n * needed before handing off to a connection strategy.\n */\n\nimport { envKeys, headerKeys, queryKeys } from \"../../helpers/consts.ts\";\nimport { allProcessEnv, getEnvironmentName } from \"../../helpers/env.ts\";\nimport { parseFnData } from \"../../helpers/functions.ts\";\nimport { hashSigningKey } from \"../../helpers/strings.ts\";\nimport {\n type GatewayExecutorRequestData,\n SDKResponse,\n SDKResponseStatus,\n} from \"../../proto/src/components/connect/protobuf/connect.ts\";\nimport type { Capabilities, FunctionConfig } from \"../../types.ts\";\nimport { version } from \"../../version.ts\";\nimport { PREFERRED_ASYNC_EXECUTION_VERSION } from \"../execution/InngestExecution.ts\";\nimport { type Inngest, internalLoggerSymbol } from \"../Inngest.ts\";\nimport { InngestCommHandler } from \"../InngestCommHandler.ts\";\nimport type { InngestFunction } from \"../InngestFunction.ts\";\nimport type {\n ConnectionEstablishData,\n RequestHandler,\n} from \"./strategies/index.ts\";\nimport type { ConnectApp } from \"./types.ts\";\nimport { parseTraceCtx } from \"./util.ts\";\n\nconst InngestBranchEnvironmentSigningKeyPrefix = \"signkey-branch-\";\n\ntype ConnectCommHandler = InngestCommHandler<\n [GatewayExecutorRequestData],\n SDKResponse,\n // biome-ignore lint/suspicious/noExplicitAny: intentional\n any\n>;\n\nexport interface PreparedConnectionConfig {\n hashedSigningKey: string | undefined;\n hashedFallbackKey: string | undefined;\n envName: string | undefined;\n connectionData: ConnectionEstablishData;\n requestHandlers: Record<string, RequestHandler>;\n}\n\n/**\n * Collect per-app client + functions from ConnectApp definitions.\n */\nfunction collectFunctions(\n apps: ConnectApp[],\n): Record<string, { client: Inngest.Like; functions: InngestFunction.Any[] }> {\n const result: Record<\n string,\n { client: Inngest.Like; functions: InngestFunction.Any[] }\n > = {};\n\n for (const app of apps) {\n const client = app.client as Inngest.Any;\n if (result[client.id]) {\n throw new Error(`Duplicate app id: ${client.id}`);\n }\n result[client.id] = {\n client: app.client,\n functions: (app.functions as InngestFunction.Any[]) ?? client.funcs,\n };\n }\n\n return result;\n}\n\n/**\n * Prepare all connection configuration: signing keys, function configs,\n * connection data, and request handlers.\n */\nexport function prepareConnectionConfig(\n apps: ConnectApp[],\n inngest: Inngest.Any,\n): PreparedConnectionConfig {\n const envName = inngest.env ?? getEnvironmentName();\n\n const hashedSigningKey = inngest.signingKey\n ? hashSigningKey(inngest.signingKey)\n : undefined;\n\n if (\n inngest.signingKey &&\n inngest.signingKey.startsWith(InngestBranchEnvironmentSigningKeyPrefix) &&\n !envName\n ) {\n throw new Error(\n \"Environment is required when using branch environment signing keys\",\n );\n }\n\n const hashedFallbackKey = inngest.signingKeyFallback\n ? hashSigningKey(inngest.signingKeyFallback)\n : undefined;\n\n // Build capabilities\n const capabilities: Capabilities = {\n trust_probe: \"v1\",\n connect: \"v1\",\n };\n\n const functions = collectFunctions(apps);\n\n // Build function configs\n const functionConfigs: Record<\n string,\n { client: Inngest.Like; functions: FunctionConfig[] }\n > = {};\n for (const [appId, { client, functions: fns }] of Object.entries(functions)) {\n functionConfigs[appId] = {\n client: client,\n functions: fns.flatMap((f) =>\n f[\"getConfig\"]({\n baseUrl: new URL(\"wss://connect\"),\n appPrefix: (client as Inngest.Any).id,\n isConnect: true,\n }),\n ),\n };\n }\n\n inngest[internalLoggerSymbol].debug(\n {\n functionSlugs: Object.entries(functionConfigs).map(\n ([appId, { functions: fns }]) => {\n return JSON.stringify({\n appId,\n functions: fns.map((f) => ({\n id: f.id,\n stepUrls: Object.values(f.steps).map((s) => s.runtime[\"url\"]),\n })),\n });\n },\n ),\n },\n \"Prepared sync data\",\n );\n\n // Build connection establish data\n const connectionData: ConnectionEstablishData = {\n manualReadinessAck: true,\n marshaledCapabilities: JSON.stringify(capabilities),\n apps: Object.entries(functionConfigs).map(\n ([appId, { client, functions: fns }]) => ({\n appName: appId,\n appVersion: (client as Inngest.Any).appVersion,\n functions: new TextEncoder().encode(JSON.stringify(fns)),\n }),\n ),\n };\n\n // Build request handlers\n const requestHandlers: Record<string, RequestHandler> = {};\n for (const [appId, { client, functions: fns }] of Object.entries(functions)) {\n const inngestCommHandler: ConnectCommHandler = new InngestCommHandler({\n client: client,\n functions: fns,\n frameworkName: \"connect\",\n skipSignatureValidation: true,\n handler: (msg: GatewayExecutorRequestData) => {\n const asString = new TextDecoder().decode(msg.requestPayload);\n const parsed = parseFnData(\n JSON.parse(asString),\n undefined,\n inngest[internalLoggerSymbol],\n );\n\n const userTraceCtx = parseTraceCtx(msg.userTraceCtx);\n\n return {\n body() {\n return parsed;\n },\n method() {\n return \"POST\";\n },\n headers(key) {\n switch (key) {\n case headerKeys.ContentLength.toString():\n return asString.length.toString();\n case headerKeys.InngestExpectedServerKind.toString():\n return \"connect\";\n case headerKeys.RequestVersion.toString():\n return parsed.version.toString();\n case headerKeys.Signature.toString():\n return null;\n case headerKeys.TraceParent.toString():\n return userTraceCtx?.traceParent ?? null;\n case headerKeys.TraceState.toString():\n return userTraceCtx?.traceState ?? null;\n default:\n return null;\n }\n },\n transformResponse({ body, headers, status }) {\n let sdkResponseStatus: SDKResponseStatus = SDKResponseStatus.DONE;\n switch (status) {\n case 200:\n sdkResponseStatus = SDKResponseStatus.DONE;\n break;\n case 206:\n sdkResponseStatus = SDKResponseStatus.NOT_COMPLETED;\n break;\n case 500:\n sdkResponseStatus = SDKResponseStatus.ERROR;\n break;\n }\n\n return SDKResponse.create({\n requestId: msg.requestId,\n accountId: msg.accountId,\n envId: msg.envId,\n appId: msg.appId,\n status: sdkResponseStatus,\n body: new TextEncoder().encode(body),\n noRetry: headers[headerKeys.NoRetry] === \"true\",\n retryAfter: headers[headerKeys.RetryAfter],\n sdkVersion: `inngest-js:v${version}`,\n requestVersion: parseInt(\n headers[headerKeys.RequestVersion] ??\n PREFERRED_ASYNC_EXECUTION_VERSION.toString(),\n 10,\n ),\n systemTraceCtx: msg.systemTraceCtx,\n userTraceCtx: msg.userTraceCtx,\n runId: msg.runId,\n });\n },\n url() {\n const baseUrl = new URL(\"http://connect.inngest.com\");\n\n baseUrl.searchParams.set(queryKeys.FnId, msg.functionSlug);\n\n if (msg.stepId) {\n baseUrl.searchParams.set(queryKeys.StepId, msg.stepId);\n }\n\n return baseUrl;\n },\n };\n },\n });\n\n if (!inngestCommHandler.checkModeConfiguration()) {\n throw new Error(\"Signing key is required\");\n }\n\n const requestHandler = inngestCommHandler.createHandler();\n requestHandlers[appId] = requestHandler;\n }\n\n return {\n hashedSigningKey,\n hashedFallbackKey,\n envName,\n connectionData,\n requestHandlers,\n };\n}\n"],"mappings":"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"}
|
|
@@ -196,7 +196,6 @@ var ConnectionCore = class {
|
|
|
196
196
|
else this.callbacks.logger.info("Connecting");
|
|
197
197
|
this.callbacks.onStateChange(this.hasConnectedBefore ? require_types.ConnectionState.RECONNECTING : require_types.ConnectionState.CONNECTING);
|
|
198
198
|
try {
|
|
199
|
-
if (this.callbacks.beforeConnect) await this.callbacks.beforeConnect(this.useSigningKey);
|
|
200
199
|
const { conn, gatewayGroup } = await require_handshake.establishConnection(this.config, this.useSigningKey, attempt, this.excludeGateways, this.callbacks.logger);
|
|
201
200
|
this.attachHandlers(conn, gatewayGroup);
|
|
202
201
|
if (this._drainingConnection) {
|
|
@@ -217,6 +216,11 @@ var ConnectionCore = class {
|
|
|
217
216
|
gatewayGroup
|
|
218
217
|
}, "Connection active");
|
|
219
218
|
this.callbacks.onStateChange(require_types.ConnectionState.ACTIVE);
|
|
219
|
+
if (this._shutdownRequested) {
|
|
220
|
+
conn.ws.send(require_buffer.ensureUnsharedArrayBuffer(require_connect.ConnectMessage.encode(require_connect.ConnectMessage.create({ kind: require_connect.GatewayMessageType.WORKER_PAUSE })).finish()));
|
|
221
|
+
this.callbacks.logger.info({ connectionId: conn.id }, "Sent WORKER_PAUSE on reconnect during shutdown");
|
|
222
|
+
} else conn.ws.send(require_buffer.ensureUnsharedArrayBuffer(require_connect.ConnectMessage.encode(require_connect.ConnectMessage.create({ kind: require_connect.GatewayMessageType.WORKER_READY })).finish()));
|
|
223
|
+
this.callbacks.onConnectionActive?.(this.useSigningKey);
|
|
220
224
|
this.resolveFirstReady?.();
|
|
221
225
|
this.resolveFirstReady = void 0;
|
|
222
226
|
this.rejectFirstReady = void 0;
|
|
@@ -225,6 +229,18 @@ var ConnectionCore = class {
|
|
|
225
229
|
attempt = err.attempt + 1;
|
|
226
230
|
if (err instanceof require_util.AuthError) this.switchAuthKey();
|
|
227
231
|
if (err instanceof require_util.ConnectionLimitError) this.callbacks.logger.error("Max concurrent connections reached");
|
|
232
|
+
if (err.message?.includes("connect_gateway_closing")) {
|
|
233
|
+
const jitter = 500 + Math.random() * 1e3;
|
|
234
|
+
this.callbacks.logger.info({
|
|
235
|
+
attempt,
|
|
236
|
+
delay: Math.round(jitter),
|
|
237
|
+
error: err.message
|
|
238
|
+
}, "Gateway draining, retrying");
|
|
239
|
+
if (await require_util.waitWithCancel(jitter, () => {
|
|
240
|
+
return this._shutdownRequested && !this.hasInFlightRequests();
|
|
241
|
+
})) break;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
228
244
|
const delay = require_util.expBackoff(attempt);
|
|
229
245
|
this.callbacks.logger.info({
|
|
230
246
|
attempt,
|
|
@@ -256,9 +272,15 @@ var ConnectionCore = class {
|
|
|
256
272
|
attachHandlers(conn, gatewayGroup) {
|
|
257
273
|
const { ws } = conn;
|
|
258
274
|
const connectionId = conn.id;
|
|
259
|
-
ws.onerror = () => {
|
|
275
|
+
ws.onerror = (ev) => {
|
|
260
276
|
if (conn.dead) return;
|
|
261
|
-
|
|
277
|
+
const uptimeMs = Date.now() - conn.connectedAt;
|
|
278
|
+
this.callbacks.logger.warn({
|
|
279
|
+
connectionId,
|
|
280
|
+
gatewayGroup,
|
|
281
|
+
uptimeMs,
|
|
282
|
+
error: ev?.message
|
|
283
|
+
}, "Connection lost (error)");
|
|
262
284
|
conn.dead = true;
|
|
263
285
|
this.excludeGateways.add(gatewayGroup);
|
|
264
286
|
if (this._activeConnection?.id === connectionId) this._activeConnection = void 0;
|
|
@@ -266,10 +288,14 @@ var ConnectionCore = class {
|
|
|
266
288
|
};
|
|
267
289
|
ws.onclose = (ev) => {
|
|
268
290
|
if (conn.dead) return;
|
|
291
|
+
const uptimeMs = Date.now() - conn.connectedAt;
|
|
269
292
|
this.callbacks.logger.warn({
|
|
270
293
|
connectionId,
|
|
294
|
+
gatewayGroup,
|
|
295
|
+
uptimeMs,
|
|
296
|
+
code: ev.code,
|
|
271
297
|
reason: ev.reason
|
|
272
|
-
}, "Connection lost");
|
|
298
|
+
}, "Connection lost (close)");
|
|
273
299
|
conn.dead = true;
|
|
274
300
|
this.excludeGateways.add(gatewayGroup);
|
|
275
301
|
if (this._activeConnection?.id === connectionId) this._activeConnection = void 0;
|
|
@@ -279,7 +305,12 @@ var ConnectionCore = class {
|
|
|
279
305
|
this._lastMessageReceivedAt = Date.now();
|
|
280
306
|
const connectMessage = require_messages.parseConnectMessage(new Uint8Array(event.data));
|
|
281
307
|
if (connectMessage.kind === require_connect.GatewayMessageType.GATEWAY_CLOSING) {
|
|
282
|
-
|
|
308
|
+
const uptimeMs = Date.now() - conn.connectedAt;
|
|
309
|
+
this.callbacks.logger.info({
|
|
310
|
+
connectionId: conn.id,
|
|
311
|
+
gatewayGroup,
|
|
312
|
+
uptimeMs
|
|
313
|
+
}, "Gateway draining, opening new connection");
|
|
283
314
|
this._drainingConnection = this._activeConnection;
|
|
284
315
|
this._activeConnection = void 0;
|
|
285
316
|
this.wake();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.cjs","names":["WaitGroup","resolve: () => void","HeartbeatManager","StatusReporter","RequestProcessor","ConnectionState","ensureUnsharedArrayBuffer","ConnectMessage","GatewayMessageType","resolveApiBaseUrl","establishConnection","ReconnectError","AuthError","ConnectionLimitError","expBackoff","waitWithCancel","parseConnectMessage","gatewayMessageTypeToJSON"],"sources":["../../../../../src/components/connect/strategies/core/connection.ts"],"sourcesContent":["/**\n * Shared connection core logic used by both SameThreadStrategy and\n * WorkerThreadStrategy.\n *\n * This module uses a **reconcile loop** that continuously ensures a live\n * WebSocket connection is open. Reconnection, drain, and shutdown are\n * expressed as state changes that wake the loop rather than recursive\n * calls or callback-driven control flow.\n *\n * Domain-specific logic is delegated to focused sub-modules:\n * - {@link HeartbeatManager} — periodic heartbeat pings\n * - {@link RequestProcessor} — executor requests, lease extensions, reply ACKs\n * - {@link establishConnection} — HTTP start + WebSocket handshake\n */\n\nimport { WaitGroup } from \"@jpwilliams/waitgroup\";\nimport { resolveApiBaseUrl } from \"../../../../helpers/url.ts\";\nimport type { Logger } from \"../../../../middleware/logger.ts\";\nimport type { GatewayExecutorRequestData } from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport {\n ConnectMessage,\n GatewayMessageType,\n gatewayMessageTypeToJSON,\n} from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport { ensureUnsharedArrayBuffer } from \"../../buffer.ts\";\nimport { parseConnectMessage } from \"../../messages.ts\";\nimport {\n type ConnectDebugState,\n ConnectionState,\n type InFlightRequest,\n} from \"../../types.ts\";\nimport {\n AuthError,\n ConnectionLimitError,\n expBackoff,\n ReconnectError,\n waitWithCancel,\n} from \"../../util.ts\";\nimport { establishConnection } from \"./handshake.ts\";\nimport { HeartbeatManager } from \"./heartbeat.ts\";\nimport { RequestProcessor } from \"./requestProcessor.ts\";\nimport { StatusReporter } from \"./statusReporter.ts\";\nimport type { BaseConnectionConfig } from \"./types.ts\";\n\n/**\n * Connection object representing an active WebSocket connection.\n */\nexport interface Connection {\n id: string;\n ws: WebSocket;\n pendingHeartbeats: number;\n /** When true the connection is considered unusable and the reconcile loop\n * will establish a replacement. */\n dead: boolean;\n heartbeatIntervalMs: number;\n extendLeaseIntervalMs: number;\n statusIntervalMs: number;\n /** Disable all handlers and close the underlying WebSocket. */\n close(): void;\n}\n\n/**\n * Configuration for the connection core.\n * Extends BaseConnectionConfig with connection-specific options.\n */\nexport interface ConnectionCoreConfig extends BaseConnectionConfig {\n instanceId?: string;\n maxWorkerConcurrency?: number;\n gatewayUrl?: string;\n appIds: string[];\n}\n\n/**\n * Callbacks for connection core events.\n */\nexport interface ConnectionCoreCallbacks {\n logger: Logger;\n onStateChange: (state: ConnectionState) => void;\n getState: () => ConnectionState;\n handleExecutionRequest: (\n request: GatewayExecutorRequestData,\n ) => Promise<Uint8Array>;\n onReplyAck?: (requestId: string) => void;\n onBufferResponse?: (requestId: string, responseBytes: Uint8Array) => void;\n beforeConnect?: (signingKey: string | undefined) => Promise<void>;\n}\n\n/**\n * Core connection manager that handles WebSocket connection lifecycle,\n * handshake, heartbeat, lease extension, and reconnection.\n *\n * Uses a reconcile loop that:\n * - Ensures a WebSocket connection is always open\n * - Manages a single heartbeat interval targeting the active connection\n * - Handles reconnection, drain, and shutdown as state changes\n */\nexport class ConnectionCore {\n private config: ConnectionCoreConfig;\n private callbacks: ConnectionCoreCallbacks;\n\n // Exposed via ConnectionAccessor for sub-modules\n private _activeConnection: Connection | undefined;\n private _drainingConnection: Connection | undefined;\n private _shutdownRequested = false;\n private _inProgressRequests: {\n wg: WaitGroup;\n requestLeases: Record<string, string>;\n requestMeta: Record<string, InFlightRequest>;\n } = {\n wg: new WaitGroup(),\n requestLeases: {},\n requestMeta: {},\n };\n\n private _lastHeartbeatSentAt: number | undefined;\n private _lastHeartbeatReceivedAt: number | undefined;\n private _lastMessageReceivedAt: number | undefined;\n\n private excludeGateways: Set<string> = new Set();\n\n // Wake signal for the reconcile loop\n private wakeSignal: { promise: Promise<void>; resolve: () => void };\n\n // Whether we've ever successfully connected (used to distinguish\n // CONNECTING from RECONNECTING state transitions).\n private hasConnectedBefore = false;\n\n // Loop promise — resolved when the reconcile loop exits\n private loopPromise: Promise<void> | undefined;\n\n // First-ready resolution — resolves start() when first connection is ready\n private resolveFirstReady: (() => void) | undefined;\n private rejectFirstReady: ((err: unknown) => void) | undefined;\n\n // Signing key management\n private useSigningKey: string | undefined;\n\n // Sub-modules\n private readonly heartbeatManager: HeartbeatManager;\n private readonly statusReporter: StatusReporter;\n private readonly requestProcessor: RequestProcessor;\n\n constructor(\n config: ConnectionCoreConfig,\n callbacks: ConnectionCoreCallbacks,\n ) {\n this.config = config;\n this.callbacks = callbacks;\n this.useSigningKey = config.hashedSigningKey;\n\n // Initialize the wake signal\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n\n // Build a ConnectionAccessor view for sub-modules\n const accessor = {\n get activeConnection() {\n return self._activeConnection;\n },\n get drainingConnection() {\n return self._drainingConnection;\n },\n get shutdownRequested() {\n return self._shutdownRequested;\n },\n get inProgressRequests() {\n return self._inProgressRequests;\n },\n get appIds() {\n return self.config.appIds;\n },\n };\n\n const wakeSignalRef = { wake: () => this.wake() };\n\n const self = this;\n\n this.heartbeatManager = new HeartbeatManager(\n accessor,\n wakeSignalRef,\n callbacks.logger,\n );\n this.heartbeatManager.onHeartbeatSent = () => {\n this._lastHeartbeatSentAt = Date.now();\n };\n\n this.statusReporter = new StatusReporter(accessor, callbacks.logger);\n\n this.requestProcessor = new RequestProcessor(\n accessor,\n wakeSignalRef,\n callbacks,\n callbacks.logger,\n );\n }\n\n get connectionId(): string | undefined {\n return this._activeConnection?.id;\n }\n\n /**\n * Wait for all in-progress requests to complete.\n */\n async waitForInProgress(): Promise<void> {\n await this._inProgressRequests.wg.wait();\n }\n\n /**\n * Return a snapshot of debug/health information for this connection.\n */\n getDebugState(): ConnectDebugState {\n return {\n state: this.callbacks.getState(),\n activeConnectionId: this._activeConnection?.id,\n drainingConnectionId: this._drainingConnection?.id,\n lastHeartbeatSentAt: this._lastHeartbeatSentAt,\n lastHeartbeatReceivedAt: this._lastHeartbeatReceivedAt,\n lastMessageReceivedAt: this._lastMessageReceivedAt,\n shutdownRequested: this._shutdownRequested,\n inFlightRequestCount: Object.keys(this._inProgressRequests.requestLeases)\n .length,\n inFlightRequests: Object.values(this._inProgressRequests.requestMeta),\n };\n }\n\n /**\n * Start the reconcile loop. Resolves when the first connection is active.\n * The loop continues running in the background.\n */\n async start(attempt = 0): Promise<void> {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSockets not supported in current environment\");\n }\n\n const state = this.callbacks.getState();\n if (state === ConnectionState.CLOSED) {\n throw new Error(\"Connection already closed\");\n }\n\n const firstReadyPromise = new Promise<void>((resolve, reject) => {\n this.resolveFirstReady = resolve;\n this.rejectFirstReady = reject;\n });\n\n this.loopPromise = this.reconcileLoop(attempt);\n\n // If the loop ends before firstReady resolves, propagate any error\n this.loopPromise.catch((err) => {\n this.rejectFirstReady?.(err);\n });\n\n await firstReadyPromise;\n }\n\n /**\n * Request graceful shutdown. Resolves when fully closed (in-flight done,\n * connection closed).\n */\n async close(): Promise<void> {\n const inFlightCount = Object.keys(\n this._inProgressRequests.requestLeases,\n ).length;\n this.callbacks.logger.info(\n { inFlightCount },\n \"Shutting down, waiting for in-flight requests\",\n );\n this._shutdownRequested = true;\n\n if (this._activeConnection?.ws.readyState === WebSocket.OPEN) {\n this._activeConnection.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: this._activeConnection.id },\n \"Sent WORKER_PAUSE, draining\",\n );\n }\n\n this.wake();\n\n if (this.loopPromise) {\n await this.loopPromise;\n }\n\n this.callbacks.logger.info(\"Connection closed\");\n }\n\n async getApiBaseUrl(): Promise<string> {\n return resolveApiBaseUrl({\n apiBaseUrl: this.config.apiBaseUrl,\n mode: this.config.mode,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Wake signal\n // ---------------------------------------------------------------------------\n\n private resetWakeSignal(): void {\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n }\n\n private wake(): void {\n this.wakeSignal.resolve();\n this.resetWakeSignal();\n }\n\n // ---------------------------------------------------------------------------\n // Signing key management\n // ---------------------------------------------------------------------------\n\n private switchAuthKey(): void {\n const switchToFallback =\n this.useSigningKey === this.config.hashedSigningKey;\n if (switchToFallback) {\n this.callbacks.logger.debug(\"Switching to fallback signing key\");\n }\n this.useSigningKey = switchToFallback\n ? this.config.hashedFallbackKey\n : this.config.hashedSigningKey;\n }\n\n // ---------------------------------------------------------------------------\n // In-flight helpers\n // ---------------------------------------------------------------------------\n\n private hasInFlightRequests(): boolean {\n return Object.keys(this._inProgressRequests.requestLeases).length > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Reconcile loop\n // ---------------------------------------------------------------------------\n\n private async reconcileLoop(initialAttempt: number): Promise<void> {\n let attempt = initialAttempt;\n\n while (true) {\n // Exit condition: shutdown requested + no in-flight requests\n if (this._shutdownRequested && !this.hasInFlightRequests()) {\n break;\n }\n\n // Ensure we have a live connection\n if (!this._activeConnection || this._activeConnection.dead) {\n this.callbacks.logger.debug(\n {\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n hasDrainingConnection: !!this._drainingConnection,\n drainingConnectionId: this._drainingConnection?.id,\n },\n \"No active connection\",\n );\n\n if (this.hasConnectedBefore) {\n this.callbacks.logger.info({ attempt }, \"Reconnecting\");\n } else {\n this.callbacks.logger.info(\"Connecting\");\n }\n\n this.callbacks.onStateChange(\n this.hasConnectedBefore\n ? ConnectionState.RECONNECTING\n : ConnectionState.CONNECTING,\n );\n\n try {\n // Flush any pending messages before attempting connection\n if (this.callbacks.beforeConnect) {\n await this.callbacks.beforeConnect(this.useSigningKey);\n }\n\n const { conn, gatewayGroup } = await establishConnection(\n this.config,\n this.useSigningKey,\n attempt,\n this.excludeGateways,\n this.callbacks.logger,\n );\n\n // Attach post-handshake handlers\n this.attachHandlers(conn, gatewayGroup);\n\n // Clean up draining connection after new one is ready\n if (this._drainingConnection) {\n this.callbacks.logger.info(\n {\n oldConnectionId: this._drainingConnection.id,\n newConnectionId: conn.id,\n },\n \"Replaced draining connection\",\n );\n this._drainingConnection.close();\n this._drainingConnection = undefined;\n }\n\n this._activeConnection = conn;\n this.heartbeatManager.updateInterval(conn.heartbeatIntervalMs);\n this.statusReporter.updateInterval(conn.statusIntervalMs);\n attempt = 0;\n this.hasConnectedBefore = true;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup },\n \"Connection active\",\n );\n this.callbacks.onStateChange(ConnectionState.ACTIVE);\n this.resolveFirstReady?.();\n this.resolveFirstReady = undefined;\n this.rejectFirstReady = undefined;\n } catch (err) {\n if (!(err instanceof ReconnectError)) throw err;\n\n attempt = err.attempt + 1;\n if (err instanceof AuthError) this.switchAuthKey();\n if (err instanceof ConnectionLimitError) {\n this.callbacks.logger.error(\"Max concurrent connections reached\");\n }\n\n const delay = expBackoff(attempt);\n this.callbacks.logger.info(\n { attempt, delay },\n \"Reconnecting after failure\",\n );\n\n const cancelled = await waitWithCancel(delay, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n }\n\n // Wait for something to change\n await this.wakeSignal.promise;\n this.callbacks.logger.debug(\n {\n shutdownRequested: this._shutdownRequested,\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n },\n \"Reconcile loop woken\",\n );\n }\n\n // Teardown\n this.heartbeatManager.stop();\n this.statusReporter.stop();\n this._activeConnection?.close();\n this._activeConnection = undefined;\n this._drainingConnection?.close();\n this._drainingConnection = undefined;\n }\n\n // ---------------------------------------------------------------------------\n // Post-handshake handler attachment\n // ---------------------------------------------------------------------------\n\n /**\n * Wire up error, close, and message handlers on a newly-handshaked connection.\n */\n private attachHandlers(conn: Connection, gatewayGroup: string): void {\n const { ws } = conn;\n const connectionId = conn.id;\n\n // Error/close handlers: mark connection as dead and wake the loop\n ws.onerror = () => {\n if (conn.dead) return;\n this.callbacks.logger.warn({ connectionId }, \"Connection lost\");\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n ws.onclose = (ev) => {\n if (conn.dead) return;\n this.callbacks.logger.warn(\n { connectionId, reason: ev.reason },\n \"Connection lost\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n // Message handler for post-handshake messages\n ws.onmessage = async (event) => {\n this._lastMessageReceivedAt = Date.now();\n\n const messageBytes = new Uint8Array(event.data as ArrayBuffer);\n const connectMessage = parseConnectMessage(messageBytes);\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_CLOSING) {\n this.callbacks.logger.info(\n { connectionId: conn.id },\n \"Gateway draining, opening new connection\",\n );\n // Move current connection to draining, clear active so the loop\n // establishes a replacement.\n this._drainingConnection = this._activeConnection;\n this._activeConnection = undefined;\n this.wake();\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_HEARTBEAT) {\n this._lastHeartbeatReceivedAt = Date.now();\n conn.pendingHeartbeats = 0;\n this.callbacks.logger.debug(\n { connectionId },\n \"Handled gateway heartbeat\",\n );\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_EXECUTOR_REQUEST) {\n await this.requestProcessor.handleExecutorRequest(connectMessage, conn);\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.WORKER_REPLY_ACK) {\n this.requestProcessor.handleReplyAck(connectMessage, connectionId);\n return;\n }\n\n if (\n connectMessage.kind ===\n GatewayMessageType.WORKER_REQUEST_EXTEND_LEASE_ACK\n ) {\n this.requestProcessor.handleExtendLeaseAck(\n connectMessage,\n connectionId,\n );\n return;\n }\n\n this.callbacks.logger.warn(\n {\n kind: gatewayMessageTypeToJSON(connectMessage.kind),\n rawKind: connectMessage.kind,\n state: this.callbacks.getState(),\n connectionId,\n },\n \"Unexpected message type\",\n );\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"connection.cjs","names":["WaitGroup","resolve: () => void","HeartbeatManager","StatusReporter","RequestProcessor","ConnectionState","ensureUnsharedArrayBuffer","ConnectMessage","GatewayMessageType","resolveApiBaseUrl","establishConnection","ReconnectError","AuthError","ConnectionLimitError","waitWithCancel","expBackoff","parseConnectMessage","gatewayMessageTypeToJSON"],"sources":["../../../../../src/components/connect/strategies/core/connection.ts"],"sourcesContent":["/**\n * Shared connection core logic used by both SameThreadStrategy and\n * WorkerThreadStrategy.\n *\n * This module uses a **reconcile loop** that continuously ensures a live\n * WebSocket connection is open. Reconnection, drain, and shutdown are\n * expressed as state changes that wake the loop rather than recursive\n * calls or callback-driven control flow.\n *\n * Domain-specific logic is delegated to focused sub-modules:\n * - {@link HeartbeatManager} — periodic heartbeat pings\n * - {@link RequestProcessor} — executor requests, lease extensions, reply ACKs\n * - {@link establishConnection} — HTTP start + WebSocket handshake\n */\n\nimport { WaitGroup } from \"@jpwilliams/waitgroup\";\nimport { resolveApiBaseUrl } from \"../../../../helpers/url.ts\";\nimport type { Logger } from \"../../../../middleware/logger.ts\";\nimport type { GatewayExecutorRequestData } from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport {\n ConnectMessage,\n GatewayMessageType,\n gatewayMessageTypeToJSON,\n} from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport { ensureUnsharedArrayBuffer } from \"../../buffer.ts\";\nimport { parseConnectMessage } from \"../../messages.ts\";\nimport {\n type ConnectDebugState,\n ConnectionState,\n type InFlightRequest,\n} from \"../../types.ts\";\nimport {\n AuthError,\n ConnectionLimitError,\n expBackoff,\n ReconnectError,\n waitWithCancel,\n} from \"../../util.ts\";\nimport { establishConnection } from \"./handshake.ts\";\nimport { HeartbeatManager } from \"./heartbeat.ts\";\nimport { RequestProcessor } from \"./requestProcessor.ts\";\nimport { StatusReporter } from \"./statusReporter.ts\";\nimport type { BaseConnectionConfig } from \"./types.ts\";\n\n/**\n * Connection object representing an active WebSocket connection.\n */\nexport interface Connection {\n id: string;\n ws: WebSocket;\n pendingHeartbeats: number;\n /** When true the connection is considered unusable and the reconcile loop\n * will establish a replacement. */\n dead: boolean;\n heartbeatIntervalMs: number;\n extendLeaseIntervalMs: number;\n statusIntervalMs: number;\n /** Timestamp (ms) when the connection was established. */\n connectedAt: number;\n /** Disable all handlers and close the underlying WebSocket. */\n close(): void;\n}\n\n/**\n * Configuration for the connection core.\n * Extends BaseConnectionConfig with connection-specific options.\n */\nexport interface ConnectionCoreConfig extends BaseConnectionConfig {\n instanceId?: string;\n maxWorkerConcurrency?: number;\n gatewayUrl?: string;\n appIds: string[];\n}\n\n/**\n * Callbacks for connection core events.\n */\nexport interface ConnectionCoreCallbacks {\n logger: Logger;\n onStateChange: (state: ConnectionState) => void;\n getState: () => ConnectionState;\n handleExecutionRequest: (\n request: GatewayExecutorRequestData,\n ) => Promise<Uint8Array>;\n onReplyAck?: (requestId: string) => void;\n onBufferResponse?: (requestId: string, responseBytes: Uint8Array) => void;\n onConnectionActive?: (signingKey: string | undefined) => void;\n}\n\n/**\n * Core connection manager that handles WebSocket connection lifecycle,\n * handshake, heartbeat, lease extension, and reconnection.\n *\n * Uses a reconcile loop that:\n * - Ensures a WebSocket connection is always open\n * - Manages a single heartbeat interval targeting the active connection\n * - Handles reconnection, drain, and shutdown as state changes\n */\nexport class ConnectionCore {\n private config: ConnectionCoreConfig;\n private callbacks: ConnectionCoreCallbacks;\n\n // Exposed via ConnectionAccessor for sub-modules\n private _activeConnection: Connection | undefined;\n private _drainingConnection: Connection | undefined;\n private _shutdownRequested = false;\n private _inProgressRequests: {\n wg: WaitGroup;\n requestLeases: Record<string, string>;\n requestMeta: Record<string, InFlightRequest>;\n } = {\n wg: new WaitGroup(),\n requestLeases: {},\n requestMeta: {},\n };\n\n private _lastHeartbeatSentAt: number | undefined;\n private _lastHeartbeatReceivedAt: number | undefined;\n private _lastMessageReceivedAt: number | undefined;\n\n private excludeGateways: Set<string> = new Set();\n\n // Wake signal for the reconcile loop\n private wakeSignal: { promise: Promise<void>; resolve: () => void };\n\n // Whether we've ever successfully connected (used to distinguish\n // CONNECTING from RECONNECTING state transitions).\n private hasConnectedBefore = false;\n\n // Loop promise — resolved when the reconcile loop exits\n private loopPromise: Promise<void> | undefined;\n\n // First-ready resolution — resolves start() when first connection is ready\n private resolveFirstReady: (() => void) | undefined;\n private rejectFirstReady: ((err: unknown) => void) | undefined;\n\n // Signing key management\n private useSigningKey: string | undefined;\n\n // Sub-modules\n private readonly heartbeatManager: HeartbeatManager;\n private readonly statusReporter: StatusReporter;\n private readonly requestProcessor: RequestProcessor;\n\n constructor(\n config: ConnectionCoreConfig,\n callbacks: ConnectionCoreCallbacks,\n ) {\n this.config = config;\n this.callbacks = callbacks;\n this.useSigningKey = config.hashedSigningKey;\n\n // Initialize the wake signal\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n\n // Build a ConnectionAccessor view for sub-modules\n const accessor = {\n get activeConnection() {\n return self._activeConnection;\n },\n get drainingConnection() {\n return self._drainingConnection;\n },\n get shutdownRequested() {\n return self._shutdownRequested;\n },\n get inProgressRequests() {\n return self._inProgressRequests;\n },\n get appIds() {\n return self.config.appIds;\n },\n };\n\n const wakeSignalRef = { wake: () => this.wake() };\n\n const self = this;\n\n this.heartbeatManager = new HeartbeatManager(\n accessor,\n wakeSignalRef,\n callbacks.logger,\n );\n this.heartbeatManager.onHeartbeatSent = () => {\n this._lastHeartbeatSentAt = Date.now();\n };\n\n this.statusReporter = new StatusReporter(accessor, callbacks.logger);\n\n this.requestProcessor = new RequestProcessor(\n accessor,\n wakeSignalRef,\n callbacks,\n callbacks.logger,\n );\n }\n\n get connectionId(): string | undefined {\n return this._activeConnection?.id;\n }\n\n /**\n * Wait for all in-progress requests to complete.\n */\n async waitForInProgress(): Promise<void> {\n await this._inProgressRequests.wg.wait();\n }\n\n /**\n * Return a snapshot of debug/health information for this connection.\n */\n getDebugState(): ConnectDebugState {\n return {\n state: this.callbacks.getState(),\n activeConnectionId: this._activeConnection?.id,\n drainingConnectionId: this._drainingConnection?.id,\n lastHeartbeatSentAt: this._lastHeartbeatSentAt,\n lastHeartbeatReceivedAt: this._lastHeartbeatReceivedAt,\n lastMessageReceivedAt: this._lastMessageReceivedAt,\n shutdownRequested: this._shutdownRequested,\n inFlightRequestCount: Object.keys(this._inProgressRequests.requestLeases)\n .length,\n inFlightRequests: Object.values(this._inProgressRequests.requestMeta),\n };\n }\n\n /**\n * Start the reconcile loop. Resolves when the first connection is active.\n * The loop continues running in the background.\n */\n async start(attempt = 0): Promise<void> {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSockets not supported in current environment\");\n }\n\n const state = this.callbacks.getState();\n if (state === ConnectionState.CLOSED) {\n throw new Error(\"Connection already closed\");\n }\n\n const firstReadyPromise = new Promise<void>((resolve, reject) => {\n this.resolveFirstReady = resolve;\n this.rejectFirstReady = reject;\n });\n\n this.loopPromise = this.reconcileLoop(attempt);\n\n // If the loop ends before firstReady resolves, propagate any error\n this.loopPromise.catch((err) => {\n this.rejectFirstReady?.(err);\n });\n\n await firstReadyPromise;\n }\n\n /**\n * Request graceful shutdown. Resolves when fully closed (in-flight done,\n * connection closed).\n */\n async close(): Promise<void> {\n const inFlightCount = Object.keys(\n this._inProgressRequests.requestLeases,\n ).length;\n this.callbacks.logger.info(\n { inFlightCount },\n \"Shutting down, waiting for in-flight requests\",\n );\n this._shutdownRequested = true;\n\n if (this._activeConnection?.ws.readyState === WebSocket.OPEN) {\n this._activeConnection.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: this._activeConnection.id },\n \"Sent WORKER_PAUSE, draining\",\n );\n }\n\n this.wake();\n\n if (this.loopPromise) {\n await this.loopPromise;\n }\n\n this.callbacks.logger.info(\"Connection closed\");\n }\n\n async getApiBaseUrl(): Promise<string> {\n return resolveApiBaseUrl({\n apiBaseUrl: this.config.apiBaseUrl,\n mode: this.config.mode,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Wake signal\n // ---------------------------------------------------------------------------\n\n private resetWakeSignal(): void {\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n }\n\n private wake(): void {\n this.wakeSignal.resolve();\n this.resetWakeSignal();\n }\n\n // ---------------------------------------------------------------------------\n // Signing key management\n // ---------------------------------------------------------------------------\n\n private switchAuthKey(): void {\n const switchToFallback =\n this.useSigningKey === this.config.hashedSigningKey;\n if (switchToFallback) {\n this.callbacks.logger.debug(\"Switching to fallback signing key\");\n }\n this.useSigningKey = switchToFallback\n ? this.config.hashedFallbackKey\n : this.config.hashedSigningKey;\n }\n\n // ---------------------------------------------------------------------------\n // In-flight helpers\n // ---------------------------------------------------------------------------\n\n private hasInFlightRequests(): boolean {\n return Object.keys(this._inProgressRequests.requestLeases).length > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Reconcile loop\n // ---------------------------------------------------------------------------\n\n private async reconcileLoop(initialAttempt: number): Promise<void> {\n let attempt = initialAttempt;\n\n while (true) {\n // Exit condition: shutdown requested + no in-flight requests\n if (this._shutdownRequested && !this.hasInFlightRequests()) {\n break;\n }\n\n // Ensure we have a live connection\n if (!this._activeConnection || this._activeConnection.dead) {\n this.callbacks.logger.debug(\n {\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n hasDrainingConnection: !!this._drainingConnection,\n drainingConnectionId: this._drainingConnection?.id,\n },\n \"No active connection\",\n );\n\n if (this.hasConnectedBefore) {\n this.callbacks.logger.info({ attempt }, \"Reconnecting\");\n } else {\n this.callbacks.logger.info(\"Connecting\");\n }\n\n this.callbacks.onStateChange(\n this.hasConnectedBefore\n ? ConnectionState.RECONNECTING\n : ConnectionState.CONNECTING,\n );\n\n try {\n const { conn, gatewayGroup } = await establishConnection(\n this.config,\n this.useSigningKey,\n attempt,\n this.excludeGateways,\n this.callbacks.logger,\n );\n\n // Attach post-handshake handlers\n this.attachHandlers(conn, gatewayGroup);\n\n // Clean up draining connection after new one is ready\n if (this._drainingConnection) {\n this.callbacks.logger.info(\n {\n oldConnectionId: this._drainingConnection.id,\n newConnectionId: conn.id,\n },\n \"Replaced draining connection\",\n );\n this._drainingConnection.close();\n this._drainingConnection = undefined;\n }\n\n this._activeConnection = conn;\n this.heartbeatManager.updateInterval(conn.heartbeatIntervalMs);\n this.statusReporter.updateInterval(conn.statusIntervalMs);\n attempt = 0;\n this.hasConnectedBefore = true;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup },\n \"Connection active\",\n );\n this.callbacks.onStateChange(ConnectionState.ACTIVE);\n\n if (this._shutdownRequested) {\n // Reconnected during shutdown to keep in-flight requests alive.\n // Send WORKER_PAUSE instead of WORKER_READY so no new work is routed.\n conn.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: conn.id },\n \"Sent WORKER_PAUSE on reconnect during shutdown\",\n );\n } else {\n // Signal the gateway that we're ready to receive requests.\n // This must happen after ACTIVE so the gateway doesn't route\n // requests before handlers are fully attached.\n conn.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_READY,\n }),\n ).finish(),\n ),\n );\n }\n\n // Flush any buffered responses via HTTP now that we're active.\n this.callbacks.onConnectionActive?.(this.useSigningKey);\n\n this.resolveFirstReady?.();\n this.resolveFirstReady = undefined;\n this.rejectFirstReady = undefined;\n } catch (err) {\n if (!(err instanceof ReconnectError)) throw err;\n\n attempt = err.attempt + 1;\n if (err instanceof AuthError) this.switchAuthKey();\n if (err instanceof ConnectionLimitError) {\n this.callbacks.logger.error(\"Max concurrent connections reached\");\n }\n\n // Gateway is draining, we should retry much faster\n if (err.message?.includes(\"connect_gateway_closing\")) {\n const jitter = 500 + Math.random() * 1000;\n this.callbacks.logger.info(\n { attempt, delay: Math.round(jitter), error: err.message },\n \"Gateway draining, retrying\",\n );\n const cancelled = await waitWithCancel(jitter, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n\n const delay = expBackoff(attempt);\n this.callbacks.logger.info(\n { attempt, delay },\n \"Reconnecting after failure\",\n );\n\n const cancelled = await waitWithCancel(delay, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n }\n\n // Wait for something to change\n await this.wakeSignal.promise;\n this.callbacks.logger.debug(\n {\n shutdownRequested: this._shutdownRequested,\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n },\n \"Reconcile loop woken\",\n );\n }\n\n // Teardown\n this.heartbeatManager.stop();\n this.statusReporter.stop();\n this._activeConnection?.close();\n this._activeConnection = undefined;\n this._drainingConnection?.close();\n this._drainingConnection = undefined;\n }\n\n // ---------------------------------------------------------------------------\n // Post-handshake handler attachment\n // ---------------------------------------------------------------------------\n\n /**\n * Wire up error, close, and message handlers on a newly-handshaked connection.\n */\n private attachHandlers(conn: Connection, gatewayGroup: string): void {\n const { ws } = conn;\n const connectionId = conn.id;\n\n // Error/close handlers: mark connection as dead and wake the loop\n ws.onerror = (ev) => {\n if (conn.dead) return;\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.warn(\n {\n connectionId,\n gatewayGroup,\n uptimeMs,\n error: (ev as ErrorEvent)?.message,\n },\n \"Connection lost (error)\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n ws.onclose = (ev) => {\n if (conn.dead) return;\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.warn(\n {\n connectionId,\n gatewayGroup,\n uptimeMs,\n code: ev.code,\n reason: ev.reason,\n },\n \"Connection lost (close)\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n // Message handler for post-handshake messages\n ws.onmessage = async (event) => {\n this._lastMessageReceivedAt = Date.now();\n\n const messageBytes = new Uint8Array(event.data as ArrayBuffer);\n const connectMessage = parseConnectMessage(messageBytes);\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_CLOSING) {\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup, uptimeMs },\n \"Gateway draining, opening new connection\",\n );\n // Move current connection to draining, clear active so the loop\n // establishes a replacement.\n this._drainingConnection = this._activeConnection;\n this._activeConnection = undefined;\n this.wake();\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_HEARTBEAT) {\n this._lastHeartbeatReceivedAt = Date.now();\n conn.pendingHeartbeats = 0;\n this.callbacks.logger.debug(\n { connectionId },\n \"Handled gateway heartbeat\",\n );\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_EXECUTOR_REQUEST) {\n await this.requestProcessor.handleExecutorRequest(connectMessage, conn);\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.WORKER_REPLY_ACK) {\n this.requestProcessor.handleReplyAck(connectMessage, connectionId);\n return;\n }\n\n if (\n connectMessage.kind ===\n GatewayMessageType.WORKER_REQUEST_EXTEND_LEASE_ACK\n ) {\n this.requestProcessor.handleExtendLeaseAck(\n connectMessage,\n connectionId,\n );\n return;\n }\n\n this.callbacks.logger.warn(\n {\n kind: gatewayMessageTypeToJSON(connectMessage.kind),\n rawKind: connectMessage.kind,\n state: this.callbacks.getState(),\n connectionId,\n },\n \"Unexpected message type\",\n );\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -195,7 +195,6 @@ var ConnectionCore = class {
|
|
|
195
195
|
else this.callbacks.logger.info("Connecting");
|
|
196
196
|
this.callbacks.onStateChange(this.hasConnectedBefore ? ConnectionState.RECONNECTING : ConnectionState.CONNECTING);
|
|
197
197
|
try {
|
|
198
|
-
if (this.callbacks.beforeConnect) await this.callbacks.beforeConnect(this.useSigningKey);
|
|
199
198
|
const { conn, gatewayGroup } = await establishConnection(this.config, this.useSigningKey, attempt, this.excludeGateways, this.callbacks.logger);
|
|
200
199
|
this.attachHandlers(conn, gatewayGroup);
|
|
201
200
|
if (this._drainingConnection) {
|
|
@@ -216,6 +215,11 @@ var ConnectionCore = class {
|
|
|
216
215
|
gatewayGroup
|
|
217
216
|
}, "Connection active");
|
|
218
217
|
this.callbacks.onStateChange(ConnectionState.ACTIVE);
|
|
218
|
+
if (this._shutdownRequested) {
|
|
219
|
+
conn.ws.send(ensureUnsharedArrayBuffer(ConnectMessage.encode(ConnectMessage.create({ kind: GatewayMessageType.WORKER_PAUSE })).finish()));
|
|
220
|
+
this.callbacks.logger.info({ connectionId: conn.id }, "Sent WORKER_PAUSE on reconnect during shutdown");
|
|
221
|
+
} else conn.ws.send(ensureUnsharedArrayBuffer(ConnectMessage.encode(ConnectMessage.create({ kind: GatewayMessageType.WORKER_READY })).finish()));
|
|
222
|
+
this.callbacks.onConnectionActive?.(this.useSigningKey);
|
|
219
223
|
this.resolveFirstReady?.();
|
|
220
224
|
this.resolveFirstReady = void 0;
|
|
221
225
|
this.rejectFirstReady = void 0;
|
|
@@ -224,6 +228,18 @@ var ConnectionCore = class {
|
|
|
224
228
|
attempt = err.attempt + 1;
|
|
225
229
|
if (err instanceof AuthError) this.switchAuthKey();
|
|
226
230
|
if (err instanceof ConnectionLimitError) this.callbacks.logger.error("Max concurrent connections reached");
|
|
231
|
+
if (err.message?.includes("connect_gateway_closing")) {
|
|
232
|
+
const jitter = 500 + Math.random() * 1e3;
|
|
233
|
+
this.callbacks.logger.info({
|
|
234
|
+
attempt,
|
|
235
|
+
delay: Math.round(jitter),
|
|
236
|
+
error: err.message
|
|
237
|
+
}, "Gateway draining, retrying");
|
|
238
|
+
if (await waitWithCancel(jitter, () => {
|
|
239
|
+
return this._shutdownRequested && !this.hasInFlightRequests();
|
|
240
|
+
})) break;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
227
243
|
const delay = expBackoff(attempt);
|
|
228
244
|
this.callbacks.logger.info({
|
|
229
245
|
attempt,
|
|
@@ -255,9 +271,15 @@ var ConnectionCore = class {
|
|
|
255
271
|
attachHandlers(conn, gatewayGroup) {
|
|
256
272
|
const { ws } = conn;
|
|
257
273
|
const connectionId = conn.id;
|
|
258
|
-
ws.onerror = () => {
|
|
274
|
+
ws.onerror = (ev) => {
|
|
259
275
|
if (conn.dead) return;
|
|
260
|
-
|
|
276
|
+
const uptimeMs = Date.now() - conn.connectedAt;
|
|
277
|
+
this.callbacks.logger.warn({
|
|
278
|
+
connectionId,
|
|
279
|
+
gatewayGroup,
|
|
280
|
+
uptimeMs,
|
|
281
|
+
error: ev?.message
|
|
282
|
+
}, "Connection lost (error)");
|
|
261
283
|
conn.dead = true;
|
|
262
284
|
this.excludeGateways.add(gatewayGroup);
|
|
263
285
|
if (this._activeConnection?.id === connectionId) this._activeConnection = void 0;
|
|
@@ -265,10 +287,14 @@ var ConnectionCore = class {
|
|
|
265
287
|
};
|
|
266
288
|
ws.onclose = (ev) => {
|
|
267
289
|
if (conn.dead) return;
|
|
290
|
+
const uptimeMs = Date.now() - conn.connectedAt;
|
|
268
291
|
this.callbacks.logger.warn({
|
|
269
292
|
connectionId,
|
|
293
|
+
gatewayGroup,
|
|
294
|
+
uptimeMs,
|
|
295
|
+
code: ev.code,
|
|
270
296
|
reason: ev.reason
|
|
271
|
-
}, "Connection lost");
|
|
297
|
+
}, "Connection lost (close)");
|
|
272
298
|
conn.dead = true;
|
|
273
299
|
this.excludeGateways.add(gatewayGroup);
|
|
274
300
|
if (this._activeConnection?.id === connectionId) this._activeConnection = void 0;
|
|
@@ -278,7 +304,12 @@ var ConnectionCore = class {
|
|
|
278
304
|
this._lastMessageReceivedAt = Date.now();
|
|
279
305
|
const connectMessage = parseConnectMessage(new Uint8Array(event.data));
|
|
280
306
|
if (connectMessage.kind === GatewayMessageType.GATEWAY_CLOSING) {
|
|
281
|
-
|
|
307
|
+
const uptimeMs = Date.now() - conn.connectedAt;
|
|
308
|
+
this.callbacks.logger.info({
|
|
309
|
+
connectionId: conn.id,
|
|
310
|
+
gatewayGroup,
|
|
311
|
+
uptimeMs
|
|
312
|
+
}, "Gateway draining, opening new connection");
|
|
282
313
|
this._drainingConnection = this._activeConnection;
|
|
283
314
|
this._activeConnection = void 0;
|
|
284
315
|
this.wake();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","names":["resolve: () => void"],"sources":["../../../../../src/components/connect/strategies/core/connection.ts"],"sourcesContent":["/**\n * Shared connection core logic used by both SameThreadStrategy and\n * WorkerThreadStrategy.\n *\n * This module uses a **reconcile loop** that continuously ensures a live\n * WebSocket connection is open. Reconnection, drain, and shutdown are\n * expressed as state changes that wake the loop rather than recursive\n * calls or callback-driven control flow.\n *\n * Domain-specific logic is delegated to focused sub-modules:\n * - {@link HeartbeatManager} — periodic heartbeat pings\n * - {@link RequestProcessor} — executor requests, lease extensions, reply ACKs\n * - {@link establishConnection} — HTTP start + WebSocket handshake\n */\n\nimport { WaitGroup } from \"@jpwilliams/waitgroup\";\nimport { resolveApiBaseUrl } from \"../../../../helpers/url.ts\";\nimport type { Logger } from \"../../../../middleware/logger.ts\";\nimport type { GatewayExecutorRequestData } from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport {\n ConnectMessage,\n GatewayMessageType,\n gatewayMessageTypeToJSON,\n} from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport { ensureUnsharedArrayBuffer } from \"../../buffer.ts\";\nimport { parseConnectMessage } from \"../../messages.ts\";\nimport {\n type ConnectDebugState,\n ConnectionState,\n type InFlightRequest,\n} from \"../../types.ts\";\nimport {\n AuthError,\n ConnectionLimitError,\n expBackoff,\n ReconnectError,\n waitWithCancel,\n} from \"../../util.ts\";\nimport { establishConnection } from \"./handshake.ts\";\nimport { HeartbeatManager } from \"./heartbeat.ts\";\nimport { RequestProcessor } from \"./requestProcessor.ts\";\nimport { StatusReporter } from \"./statusReporter.ts\";\nimport type { BaseConnectionConfig } from \"./types.ts\";\n\n/**\n * Connection object representing an active WebSocket connection.\n */\nexport interface Connection {\n id: string;\n ws: WebSocket;\n pendingHeartbeats: number;\n /** When true the connection is considered unusable and the reconcile loop\n * will establish a replacement. */\n dead: boolean;\n heartbeatIntervalMs: number;\n extendLeaseIntervalMs: number;\n statusIntervalMs: number;\n /** Disable all handlers and close the underlying WebSocket. */\n close(): void;\n}\n\n/**\n * Configuration for the connection core.\n * Extends BaseConnectionConfig with connection-specific options.\n */\nexport interface ConnectionCoreConfig extends BaseConnectionConfig {\n instanceId?: string;\n maxWorkerConcurrency?: number;\n gatewayUrl?: string;\n appIds: string[];\n}\n\n/**\n * Callbacks for connection core events.\n */\nexport interface ConnectionCoreCallbacks {\n logger: Logger;\n onStateChange: (state: ConnectionState) => void;\n getState: () => ConnectionState;\n handleExecutionRequest: (\n request: GatewayExecutorRequestData,\n ) => Promise<Uint8Array>;\n onReplyAck?: (requestId: string) => void;\n onBufferResponse?: (requestId: string, responseBytes: Uint8Array) => void;\n beforeConnect?: (signingKey: string | undefined) => Promise<void>;\n}\n\n/**\n * Core connection manager that handles WebSocket connection lifecycle,\n * handshake, heartbeat, lease extension, and reconnection.\n *\n * Uses a reconcile loop that:\n * - Ensures a WebSocket connection is always open\n * - Manages a single heartbeat interval targeting the active connection\n * - Handles reconnection, drain, and shutdown as state changes\n */\nexport class ConnectionCore {\n private config: ConnectionCoreConfig;\n private callbacks: ConnectionCoreCallbacks;\n\n // Exposed via ConnectionAccessor for sub-modules\n private _activeConnection: Connection | undefined;\n private _drainingConnection: Connection | undefined;\n private _shutdownRequested = false;\n private _inProgressRequests: {\n wg: WaitGroup;\n requestLeases: Record<string, string>;\n requestMeta: Record<string, InFlightRequest>;\n } = {\n wg: new WaitGroup(),\n requestLeases: {},\n requestMeta: {},\n };\n\n private _lastHeartbeatSentAt: number | undefined;\n private _lastHeartbeatReceivedAt: number | undefined;\n private _lastMessageReceivedAt: number | undefined;\n\n private excludeGateways: Set<string> = new Set();\n\n // Wake signal for the reconcile loop\n private wakeSignal: { promise: Promise<void>; resolve: () => void };\n\n // Whether we've ever successfully connected (used to distinguish\n // CONNECTING from RECONNECTING state transitions).\n private hasConnectedBefore = false;\n\n // Loop promise — resolved when the reconcile loop exits\n private loopPromise: Promise<void> | undefined;\n\n // First-ready resolution — resolves start() when first connection is ready\n private resolveFirstReady: (() => void) | undefined;\n private rejectFirstReady: ((err: unknown) => void) | undefined;\n\n // Signing key management\n private useSigningKey: string | undefined;\n\n // Sub-modules\n private readonly heartbeatManager: HeartbeatManager;\n private readonly statusReporter: StatusReporter;\n private readonly requestProcessor: RequestProcessor;\n\n constructor(\n config: ConnectionCoreConfig,\n callbacks: ConnectionCoreCallbacks,\n ) {\n this.config = config;\n this.callbacks = callbacks;\n this.useSigningKey = config.hashedSigningKey;\n\n // Initialize the wake signal\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n\n // Build a ConnectionAccessor view for sub-modules\n const accessor = {\n get activeConnection() {\n return self._activeConnection;\n },\n get drainingConnection() {\n return self._drainingConnection;\n },\n get shutdownRequested() {\n return self._shutdownRequested;\n },\n get inProgressRequests() {\n return self._inProgressRequests;\n },\n get appIds() {\n return self.config.appIds;\n },\n };\n\n const wakeSignalRef = { wake: () => this.wake() };\n\n const self = this;\n\n this.heartbeatManager = new HeartbeatManager(\n accessor,\n wakeSignalRef,\n callbacks.logger,\n );\n this.heartbeatManager.onHeartbeatSent = () => {\n this._lastHeartbeatSentAt = Date.now();\n };\n\n this.statusReporter = new StatusReporter(accessor, callbacks.logger);\n\n this.requestProcessor = new RequestProcessor(\n accessor,\n wakeSignalRef,\n callbacks,\n callbacks.logger,\n );\n }\n\n get connectionId(): string | undefined {\n return this._activeConnection?.id;\n }\n\n /**\n * Wait for all in-progress requests to complete.\n */\n async waitForInProgress(): Promise<void> {\n await this._inProgressRequests.wg.wait();\n }\n\n /**\n * Return a snapshot of debug/health information for this connection.\n */\n getDebugState(): ConnectDebugState {\n return {\n state: this.callbacks.getState(),\n activeConnectionId: this._activeConnection?.id,\n drainingConnectionId: this._drainingConnection?.id,\n lastHeartbeatSentAt: this._lastHeartbeatSentAt,\n lastHeartbeatReceivedAt: this._lastHeartbeatReceivedAt,\n lastMessageReceivedAt: this._lastMessageReceivedAt,\n shutdownRequested: this._shutdownRequested,\n inFlightRequestCount: Object.keys(this._inProgressRequests.requestLeases)\n .length,\n inFlightRequests: Object.values(this._inProgressRequests.requestMeta),\n };\n }\n\n /**\n * Start the reconcile loop. Resolves when the first connection is active.\n * The loop continues running in the background.\n */\n async start(attempt = 0): Promise<void> {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSockets not supported in current environment\");\n }\n\n const state = this.callbacks.getState();\n if (state === ConnectionState.CLOSED) {\n throw new Error(\"Connection already closed\");\n }\n\n const firstReadyPromise = new Promise<void>((resolve, reject) => {\n this.resolveFirstReady = resolve;\n this.rejectFirstReady = reject;\n });\n\n this.loopPromise = this.reconcileLoop(attempt);\n\n // If the loop ends before firstReady resolves, propagate any error\n this.loopPromise.catch((err) => {\n this.rejectFirstReady?.(err);\n });\n\n await firstReadyPromise;\n }\n\n /**\n * Request graceful shutdown. Resolves when fully closed (in-flight done,\n * connection closed).\n */\n async close(): Promise<void> {\n const inFlightCount = Object.keys(\n this._inProgressRequests.requestLeases,\n ).length;\n this.callbacks.logger.info(\n { inFlightCount },\n \"Shutting down, waiting for in-flight requests\",\n );\n this._shutdownRequested = true;\n\n if (this._activeConnection?.ws.readyState === WebSocket.OPEN) {\n this._activeConnection.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: this._activeConnection.id },\n \"Sent WORKER_PAUSE, draining\",\n );\n }\n\n this.wake();\n\n if (this.loopPromise) {\n await this.loopPromise;\n }\n\n this.callbacks.logger.info(\"Connection closed\");\n }\n\n async getApiBaseUrl(): Promise<string> {\n return resolveApiBaseUrl({\n apiBaseUrl: this.config.apiBaseUrl,\n mode: this.config.mode,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Wake signal\n // ---------------------------------------------------------------------------\n\n private resetWakeSignal(): void {\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n }\n\n private wake(): void {\n this.wakeSignal.resolve();\n this.resetWakeSignal();\n }\n\n // ---------------------------------------------------------------------------\n // Signing key management\n // ---------------------------------------------------------------------------\n\n private switchAuthKey(): void {\n const switchToFallback =\n this.useSigningKey === this.config.hashedSigningKey;\n if (switchToFallback) {\n this.callbacks.logger.debug(\"Switching to fallback signing key\");\n }\n this.useSigningKey = switchToFallback\n ? this.config.hashedFallbackKey\n : this.config.hashedSigningKey;\n }\n\n // ---------------------------------------------------------------------------\n // In-flight helpers\n // ---------------------------------------------------------------------------\n\n private hasInFlightRequests(): boolean {\n return Object.keys(this._inProgressRequests.requestLeases).length > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Reconcile loop\n // ---------------------------------------------------------------------------\n\n private async reconcileLoop(initialAttempt: number): Promise<void> {\n let attempt = initialAttempt;\n\n while (true) {\n // Exit condition: shutdown requested + no in-flight requests\n if (this._shutdownRequested && !this.hasInFlightRequests()) {\n break;\n }\n\n // Ensure we have a live connection\n if (!this._activeConnection || this._activeConnection.dead) {\n this.callbacks.logger.debug(\n {\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n hasDrainingConnection: !!this._drainingConnection,\n drainingConnectionId: this._drainingConnection?.id,\n },\n \"No active connection\",\n );\n\n if (this.hasConnectedBefore) {\n this.callbacks.logger.info({ attempt }, \"Reconnecting\");\n } else {\n this.callbacks.logger.info(\"Connecting\");\n }\n\n this.callbacks.onStateChange(\n this.hasConnectedBefore\n ? ConnectionState.RECONNECTING\n : ConnectionState.CONNECTING,\n );\n\n try {\n // Flush any pending messages before attempting connection\n if (this.callbacks.beforeConnect) {\n await this.callbacks.beforeConnect(this.useSigningKey);\n }\n\n const { conn, gatewayGroup } = await establishConnection(\n this.config,\n this.useSigningKey,\n attempt,\n this.excludeGateways,\n this.callbacks.logger,\n );\n\n // Attach post-handshake handlers\n this.attachHandlers(conn, gatewayGroup);\n\n // Clean up draining connection after new one is ready\n if (this._drainingConnection) {\n this.callbacks.logger.info(\n {\n oldConnectionId: this._drainingConnection.id,\n newConnectionId: conn.id,\n },\n \"Replaced draining connection\",\n );\n this._drainingConnection.close();\n this._drainingConnection = undefined;\n }\n\n this._activeConnection = conn;\n this.heartbeatManager.updateInterval(conn.heartbeatIntervalMs);\n this.statusReporter.updateInterval(conn.statusIntervalMs);\n attempt = 0;\n this.hasConnectedBefore = true;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup },\n \"Connection active\",\n );\n this.callbacks.onStateChange(ConnectionState.ACTIVE);\n this.resolveFirstReady?.();\n this.resolveFirstReady = undefined;\n this.rejectFirstReady = undefined;\n } catch (err) {\n if (!(err instanceof ReconnectError)) throw err;\n\n attempt = err.attempt + 1;\n if (err instanceof AuthError) this.switchAuthKey();\n if (err instanceof ConnectionLimitError) {\n this.callbacks.logger.error(\"Max concurrent connections reached\");\n }\n\n const delay = expBackoff(attempt);\n this.callbacks.logger.info(\n { attempt, delay },\n \"Reconnecting after failure\",\n );\n\n const cancelled = await waitWithCancel(delay, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n }\n\n // Wait for something to change\n await this.wakeSignal.promise;\n this.callbacks.logger.debug(\n {\n shutdownRequested: this._shutdownRequested,\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n },\n \"Reconcile loop woken\",\n );\n }\n\n // Teardown\n this.heartbeatManager.stop();\n this.statusReporter.stop();\n this._activeConnection?.close();\n this._activeConnection = undefined;\n this._drainingConnection?.close();\n this._drainingConnection = undefined;\n }\n\n // ---------------------------------------------------------------------------\n // Post-handshake handler attachment\n // ---------------------------------------------------------------------------\n\n /**\n * Wire up error, close, and message handlers on a newly-handshaked connection.\n */\n private attachHandlers(conn: Connection, gatewayGroup: string): void {\n const { ws } = conn;\n const connectionId = conn.id;\n\n // Error/close handlers: mark connection as dead and wake the loop\n ws.onerror = () => {\n if (conn.dead) return;\n this.callbacks.logger.warn({ connectionId }, \"Connection lost\");\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n ws.onclose = (ev) => {\n if (conn.dead) return;\n this.callbacks.logger.warn(\n { connectionId, reason: ev.reason },\n \"Connection lost\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n // Message handler for post-handshake messages\n ws.onmessage = async (event) => {\n this._lastMessageReceivedAt = Date.now();\n\n const messageBytes = new Uint8Array(event.data as ArrayBuffer);\n const connectMessage = parseConnectMessage(messageBytes);\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_CLOSING) {\n this.callbacks.logger.info(\n { connectionId: conn.id },\n \"Gateway draining, opening new connection\",\n );\n // Move current connection to draining, clear active so the loop\n // establishes a replacement.\n this._drainingConnection = this._activeConnection;\n this._activeConnection = undefined;\n this.wake();\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_HEARTBEAT) {\n this._lastHeartbeatReceivedAt = Date.now();\n conn.pendingHeartbeats = 0;\n this.callbacks.logger.debug(\n { connectionId },\n \"Handled gateway heartbeat\",\n );\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_EXECUTOR_REQUEST) {\n await this.requestProcessor.handleExecutorRequest(connectMessage, conn);\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.WORKER_REPLY_ACK) {\n this.requestProcessor.handleReplyAck(connectMessage, connectionId);\n return;\n }\n\n if (\n connectMessage.kind ===\n GatewayMessageType.WORKER_REQUEST_EXTEND_LEASE_ACK\n ) {\n this.requestProcessor.handleExtendLeaseAck(\n connectMessage,\n connectionId,\n );\n return;\n }\n\n this.callbacks.logger.warn(\n {\n kind: gatewayMessageTypeToJSON(connectMessage.kind),\n rawKind: connectMessage.kind,\n state: this.callbacks.getState(),\n connectionId,\n },\n \"Unexpected message type\",\n );\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgGA,IAAa,iBAAb,MAA4B;CAC1B,AAAQ;CACR,AAAQ;CAGR,AAAQ;CACR,AAAQ;CACR,AAAQ,qBAAqB;CAC7B,AAAQ,sBAIJ;EACF,IAAI,IAAI,WAAW;EACnB,eAAe,EAAE;EACjB,aAAa,EAAE;EAChB;CAED,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,AAAQ,kCAA+B,IAAI,KAAK;CAGhD,AAAQ;CAIR,AAAQ,qBAAqB;CAG7B,AAAQ;CAGR,AAAQ;CACR,AAAQ;CAGR,AAAQ;CAGR,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,YACE,QACA,WACA;AACA,OAAK,SAAS;AACd,OAAK,YAAY;AACjB,OAAK,gBAAgB,OAAO;EAG5B,IAAIA;AAIJ,OAAK,aAAa;GAAE,SAHJ,IAAI,SAAe,MAAM;AACvC,cAAU;KACV;GACoC;GAAU;EAGhD,MAAM,WAAW;GACf,IAAI,mBAAmB;AACrB,WAAO,KAAK;;GAEd,IAAI,qBAAqB;AACvB,WAAO,KAAK;;GAEd,IAAI,oBAAoB;AACtB,WAAO,KAAK;;GAEd,IAAI,qBAAqB;AACvB,WAAO,KAAK;;GAEd,IAAI,SAAS;AACX,WAAO,KAAK,OAAO;;GAEtB;EAED,MAAM,gBAAgB,EAAE,YAAY,KAAK,MAAM,EAAE;EAEjD,MAAM,OAAO;AAEb,OAAK,mBAAmB,IAAI,iBAC1B,UACA,eACA,UAAU,OACX;AACD,OAAK,iBAAiB,wBAAwB;AAC5C,QAAK,uBAAuB,KAAK,KAAK;;AAGxC,OAAK,iBAAiB,IAAI,eAAe,UAAU,UAAU,OAAO;AAEpE,OAAK,mBAAmB,IAAI,iBAC1B,UACA,eACA,WACA,UAAU,OACX;;CAGH,IAAI,eAAmC;AACrC,SAAO,KAAK,mBAAmB;;;;;CAMjC,MAAM,oBAAmC;AACvC,QAAM,KAAK,oBAAoB,GAAG,MAAM;;;;;CAM1C,gBAAmC;AACjC,SAAO;GACL,OAAO,KAAK,UAAU,UAAU;GAChC,oBAAoB,KAAK,mBAAmB;GAC5C,sBAAsB,KAAK,qBAAqB;GAChD,qBAAqB,KAAK;GAC1B,yBAAyB,KAAK;GAC9B,uBAAuB,KAAK;GAC5B,mBAAmB,KAAK;GACxB,sBAAsB,OAAO,KAAK,KAAK,oBAAoB,cAAc,CACtE;GACH,kBAAkB,OAAO,OAAO,KAAK,oBAAoB,YAAY;GACtE;;;;;;CAOH,MAAM,MAAM,UAAU,GAAkB;AACtC,MAAI,OAAO,cAAc,YACvB,OAAM,IAAI,MAAM,kDAAkD;AAIpE,MADc,KAAK,UAAU,UAAU,KACzB,gBAAgB,OAC5B,OAAM,IAAI,MAAM,4BAA4B;EAG9C,MAAM,oBAAoB,IAAI,SAAe,SAAS,WAAW;AAC/D,QAAK,oBAAoB;AACzB,QAAK,mBAAmB;IACxB;AAEF,OAAK,cAAc,KAAK,cAAc,QAAQ;AAG9C,OAAK,YAAY,OAAO,QAAQ;AAC9B,QAAK,mBAAmB,IAAI;IAC5B;AAEF,QAAM;;;;;;CAOR,MAAM,QAAuB;EAC3B,MAAM,gBAAgB,OAAO,KAC3B,KAAK,oBAAoB,cAC1B,CAAC;AACF,OAAK,UAAU,OAAO,KACpB,EAAE,eAAe,EACjB,gDACD;AACD,OAAK,qBAAqB;AAE1B,MAAI,KAAK,mBAAmB,GAAG,eAAe,UAAU,MAAM;AAC5D,QAAK,kBAAkB,GAAG,KACxB,0BACE,eAAe,OACb,eAAe,OAAO,EACpB,MAAM,mBAAmB,cAC1B,CAAC,CACH,CAAC,QAAQ,CACX,CACF;AACD,QAAK,UAAU,OAAO,KACpB,EAAE,cAAc,KAAK,kBAAkB,IAAI,EAC3C,8BACD;;AAGH,OAAK,MAAM;AAEX,MAAI,KAAK,YACP,OAAM,KAAK;AAGb,OAAK,UAAU,OAAO,KAAK,oBAAoB;;CAGjD,MAAM,gBAAiC;AACrC,SAAO,kBAAkB;GACvB,YAAY,KAAK,OAAO;GACxB,MAAM,KAAK,OAAO;GACnB,CAAC;;CAOJ,AAAQ,kBAAwB;EAC9B,IAAIA;AAIJ,OAAK,aAAa;GAAE,SAHJ,IAAI,SAAe,MAAM;AACvC,cAAU;KACV;GACoC;GAAU;;CAGlD,AAAQ,OAAa;AACnB,OAAK,WAAW,SAAS;AACzB,OAAK,iBAAiB;;CAOxB,AAAQ,gBAAsB;EAC5B,MAAM,mBACJ,KAAK,kBAAkB,KAAK,OAAO;AACrC,MAAI,iBACF,MAAK,UAAU,OAAO,MAAM,oCAAoC;AAElE,OAAK,gBAAgB,mBACjB,KAAK,OAAO,oBACZ,KAAK,OAAO;;CAOlB,AAAQ,sBAA+B;AACrC,SAAO,OAAO,KAAK,KAAK,oBAAoB,cAAc,CAAC,SAAS;;CAOtE,MAAc,cAAc,gBAAuC;EACjE,IAAI,UAAU;AAEd,SAAO,MAAM;AAEX,OAAI,KAAK,sBAAsB,CAAC,KAAK,qBAAqB,CACxD;AAIF,OAAI,CAAC,KAAK,qBAAqB,KAAK,kBAAkB,MAAM;AAC1D,SAAK,UAAU,OAAO,MACpB;KACE,qBAAqB,CAAC,CAAC,KAAK;KAC5B,sBAAsB,KAAK,mBAAmB;KAC9C,uBAAuB,CAAC,CAAC,KAAK;KAC9B,sBAAsB,KAAK,qBAAqB;KACjD,EACD,uBACD;AAED,QAAI,KAAK,mBACP,MAAK,UAAU,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe;QAEvD,MAAK,UAAU,OAAO,KAAK,aAAa;AAG1C,SAAK,UAAU,cACb,KAAK,qBACD,gBAAgB,eAChB,gBAAgB,WACrB;AAED,QAAI;AAEF,SAAI,KAAK,UAAU,cACjB,OAAM,KAAK,UAAU,cAAc,KAAK,cAAc;KAGxD,MAAM,EAAE,MAAM,iBAAiB,MAAM,oBACnC,KAAK,QACL,KAAK,eACL,SACA,KAAK,iBACL,KAAK,UAAU,OAChB;AAGD,UAAK,eAAe,MAAM,aAAa;AAGvC,SAAI,KAAK,qBAAqB;AAC5B,WAAK,UAAU,OAAO,KACpB;OACE,iBAAiB,KAAK,oBAAoB;OAC1C,iBAAiB,KAAK;OACvB,EACD,+BACD;AACD,WAAK,oBAAoB,OAAO;AAChC,WAAK,sBAAsB;;AAG7B,UAAK,oBAAoB;AACzB,UAAK,iBAAiB,eAAe,KAAK,oBAAoB;AAC9D,UAAK,eAAe,eAAe,KAAK,iBAAiB;AACzD,eAAU;AACV,UAAK,qBAAqB;AAC1B,UAAK,UAAU,OAAO,KACpB;MAAE,cAAc,KAAK;MAAI;MAAc,EACvC,oBACD;AACD,UAAK,UAAU,cAAc,gBAAgB,OAAO;AACpD,UAAK,qBAAqB;AAC1B,UAAK,oBAAoB;AACzB,UAAK,mBAAmB;aACjB,KAAK;AACZ,SAAI,EAAE,eAAe,gBAAiB,OAAM;AAE5C,eAAU,IAAI,UAAU;AACxB,SAAI,eAAe,UAAW,MAAK,eAAe;AAClD,SAAI,eAAe,qBACjB,MAAK,UAAU,OAAO,MAAM,qCAAqC;KAGnE,MAAM,QAAQ,WAAW,QAAQ;AACjC,UAAK,UAAU,OAAO,KACpB;MAAE;MAAS;MAAO,EAClB,6BACD;AAKD,SAHkB,MAAM,eAAe,aAAa;AAClD,aAAO,KAAK,sBAAsB,CAAC,KAAK,qBAAqB;OAC7D,CACa;AACf;;;AAKJ,SAAM,KAAK,WAAW;AACtB,QAAK,UAAU,OAAO,MACpB;IACE,mBAAmB,KAAK;IACxB,qBAAqB,CAAC,CAAC,KAAK;IAC5B,sBAAsB,KAAK,mBAAmB;IAC/C,EACD,uBACD;;AAIH,OAAK,iBAAiB,MAAM;AAC5B,OAAK,eAAe,MAAM;AAC1B,OAAK,mBAAmB,OAAO;AAC/B,OAAK,oBAAoB;AACzB,OAAK,qBAAqB,OAAO;AACjC,OAAK,sBAAsB;;;;;CAU7B,AAAQ,eAAe,MAAkB,cAA4B;EACnE,MAAM,EAAE,OAAO;EACf,MAAM,eAAe,KAAK;AAG1B,KAAG,gBAAgB;AACjB,OAAI,KAAK,KAAM;AACf,QAAK,UAAU,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB;AAC/D,QAAK,OAAO;AACZ,QAAK,gBAAgB,IAAI,aAAa;AACtC,OAAI,KAAK,mBAAmB,OAAO,aACjC,MAAK,oBAAoB;AAE3B,QAAK,MAAM;;AAGb,KAAG,WAAW,OAAO;AACnB,OAAI,KAAK,KAAM;AACf,QAAK,UAAU,OAAO,KACpB;IAAE;IAAc,QAAQ,GAAG;IAAQ,EACnC,kBACD;AACD,QAAK,OAAO;AACZ,QAAK,gBAAgB,IAAI,aAAa;AACtC,OAAI,KAAK,mBAAmB,OAAO,aACjC,MAAK,oBAAoB;AAE3B,QAAK,MAAM;;AAIb,KAAG,YAAY,OAAO,UAAU;AAC9B,QAAK,yBAAyB,KAAK,KAAK;GAGxC,MAAM,iBAAiB,oBADF,IAAI,WAAW,MAAM,KAAoB,CACN;AAExD,OAAI,eAAe,SAAS,mBAAmB,iBAAiB;AAC9D,SAAK,UAAU,OAAO,KACpB,EAAE,cAAc,KAAK,IAAI,EACzB,2CACD;AAGD,SAAK,sBAAsB,KAAK;AAChC,SAAK,oBAAoB;AACzB,SAAK,MAAM;AACX;;AAGF,OAAI,eAAe,SAAS,mBAAmB,mBAAmB;AAChE,SAAK,2BAA2B,KAAK,KAAK;AAC1C,SAAK,oBAAoB;AACzB,SAAK,UAAU,OAAO,MACpB,EAAE,cAAc,EAChB,4BACD;AACD;;AAGF,OAAI,eAAe,SAAS,mBAAmB,0BAA0B;AACvE,UAAM,KAAK,iBAAiB,sBAAsB,gBAAgB,KAAK;AACvE;;AAGF,OAAI,eAAe,SAAS,mBAAmB,kBAAkB;AAC/D,SAAK,iBAAiB,eAAe,gBAAgB,aAAa;AAClE;;AAGF,OACE,eAAe,SACf,mBAAmB,iCACnB;AACA,SAAK,iBAAiB,qBACpB,gBACA,aACD;AACD;;AAGF,QAAK,UAAU,OAAO,KACpB;IACE,MAAM,yBAAyB,eAAe,KAAK;IACnD,SAAS,eAAe;IACxB,OAAO,KAAK,UAAU,UAAU;IAChC;IACD,EACD,0BACD"}
|
|
1
|
+
{"version":3,"file":"connection.js","names":["resolve: () => void"],"sources":["../../../../../src/components/connect/strategies/core/connection.ts"],"sourcesContent":["/**\n * Shared connection core logic used by both SameThreadStrategy and\n * WorkerThreadStrategy.\n *\n * This module uses a **reconcile loop** that continuously ensures a live\n * WebSocket connection is open. Reconnection, drain, and shutdown are\n * expressed as state changes that wake the loop rather than recursive\n * calls or callback-driven control flow.\n *\n * Domain-specific logic is delegated to focused sub-modules:\n * - {@link HeartbeatManager} — periodic heartbeat pings\n * - {@link RequestProcessor} — executor requests, lease extensions, reply ACKs\n * - {@link establishConnection} — HTTP start + WebSocket handshake\n */\n\nimport { WaitGroup } from \"@jpwilliams/waitgroup\";\nimport { resolveApiBaseUrl } from \"../../../../helpers/url.ts\";\nimport type { Logger } from \"../../../../middleware/logger.ts\";\nimport type { GatewayExecutorRequestData } from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport {\n ConnectMessage,\n GatewayMessageType,\n gatewayMessageTypeToJSON,\n} from \"../../../../proto/src/components/connect/protobuf/connect.ts\";\nimport { ensureUnsharedArrayBuffer } from \"../../buffer.ts\";\nimport { parseConnectMessage } from \"../../messages.ts\";\nimport {\n type ConnectDebugState,\n ConnectionState,\n type InFlightRequest,\n} from \"../../types.ts\";\nimport {\n AuthError,\n ConnectionLimitError,\n expBackoff,\n ReconnectError,\n waitWithCancel,\n} from \"../../util.ts\";\nimport { establishConnection } from \"./handshake.ts\";\nimport { HeartbeatManager } from \"./heartbeat.ts\";\nimport { RequestProcessor } from \"./requestProcessor.ts\";\nimport { StatusReporter } from \"./statusReporter.ts\";\nimport type { BaseConnectionConfig } from \"./types.ts\";\n\n/**\n * Connection object representing an active WebSocket connection.\n */\nexport interface Connection {\n id: string;\n ws: WebSocket;\n pendingHeartbeats: number;\n /** When true the connection is considered unusable and the reconcile loop\n * will establish a replacement. */\n dead: boolean;\n heartbeatIntervalMs: number;\n extendLeaseIntervalMs: number;\n statusIntervalMs: number;\n /** Timestamp (ms) when the connection was established. */\n connectedAt: number;\n /** Disable all handlers and close the underlying WebSocket. */\n close(): void;\n}\n\n/**\n * Configuration for the connection core.\n * Extends BaseConnectionConfig with connection-specific options.\n */\nexport interface ConnectionCoreConfig extends BaseConnectionConfig {\n instanceId?: string;\n maxWorkerConcurrency?: number;\n gatewayUrl?: string;\n appIds: string[];\n}\n\n/**\n * Callbacks for connection core events.\n */\nexport interface ConnectionCoreCallbacks {\n logger: Logger;\n onStateChange: (state: ConnectionState) => void;\n getState: () => ConnectionState;\n handleExecutionRequest: (\n request: GatewayExecutorRequestData,\n ) => Promise<Uint8Array>;\n onReplyAck?: (requestId: string) => void;\n onBufferResponse?: (requestId: string, responseBytes: Uint8Array) => void;\n onConnectionActive?: (signingKey: string | undefined) => void;\n}\n\n/**\n * Core connection manager that handles WebSocket connection lifecycle,\n * handshake, heartbeat, lease extension, and reconnection.\n *\n * Uses a reconcile loop that:\n * - Ensures a WebSocket connection is always open\n * - Manages a single heartbeat interval targeting the active connection\n * - Handles reconnection, drain, and shutdown as state changes\n */\nexport class ConnectionCore {\n private config: ConnectionCoreConfig;\n private callbacks: ConnectionCoreCallbacks;\n\n // Exposed via ConnectionAccessor for sub-modules\n private _activeConnection: Connection | undefined;\n private _drainingConnection: Connection | undefined;\n private _shutdownRequested = false;\n private _inProgressRequests: {\n wg: WaitGroup;\n requestLeases: Record<string, string>;\n requestMeta: Record<string, InFlightRequest>;\n } = {\n wg: new WaitGroup(),\n requestLeases: {},\n requestMeta: {},\n };\n\n private _lastHeartbeatSentAt: number | undefined;\n private _lastHeartbeatReceivedAt: number | undefined;\n private _lastMessageReceivedAt: number | undefined;\n\n private excludeGateways: Set<string> = new Set();\n\n // Wake signal for the reconcile loop\n private wakeSignal: { promise: Promise<void>; resolve: () => void };\n\n // Whether we've ever successfully connected (used to distinguish\n // CONNECTING from RECONNECTING state transitions).\n private hasConnectedBefore = false;\n\n // Loop promise — resolved when the reconcile loop exits\n private loopPromise: Promise<void> | undefined;\n\n // First-ready resolution — resolves start() when first connection is ready\n private resolveFirstReady: (() => void) | undefined;\n private rejectFirstReady: ((err: unknown) => void) | undefined;\n\n // Signing key management\n private useSigningKey: string | undefined;\n\n // Sub-modules\n private readonly heartbeatManager: HeartbeatManager;\n private readonly statusReporter: StatusReporter;\n private readonly requestProcessor: RequestProcessor;\n\n constructor(\n config: ConnectionCoreConfig,\n callbacks: ConnectionCoreCallbacks,\n ) {\n this.config = config;\n this.callbacks = callbacks;\n this.useSigningKey = config.hashedSigningKey;\n\n // Initialize the wake signal\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n\n // Build a ConnectionAccessor view for sub-modules\n const accessor = {\n get activeConnection() {\n return self._activeConnection;\n },\n get drainingConnection() {\n return self._drainingConnection;\n },\n get shutdownRequested() {\n return self._shutdownRequested;\n },\n get inProgressRequests() {\n return self._inProgressRequests;\n },\n get appIds() {\n return self.config.appIds;\n },\n };\n\n const wakeSignalRef = { wake: () => this.wake() };\n\n const self = this;\n\n this.heartbeatManager = new HeartbeatManager(\n accessor,\n wakeSignalRef,\n callbacks.logger,\n );\n this.heartbeatManager.onHeartbeatSent = () => {\n this._lastHeartbeatSentAt = Date.now();\n };\n\n this.statusReporter = new StatusReporter(accessor, callbacks.logger);\n\n this.requestProcessor = new RequestProcessor(\n accessor,\n wakeSignalRef,\n callbacks,\n callbacks.logger,\n );\n }\n\n get connectionId(): string | undefined {\n return this._activeConnection?.id;\n }\n\n /**\n * Wait for all in-progress requests to complete.\n */\n async waitForInProgress(): Promise<void> {\n await this._inProgressRequests.wg.wait();\n }\n\n /**\n * Return a snapshot of debug/health information for this connection.\n */\n getDebugState(): ConnectDebugState {\n return {\n state: this.callbacks.getState(),\n activeConnectionId: this._activeConnection?.id,\n drainingConnectionId: this._drainingConnection?.id,\n lastHeartbeatSentAt: this._lastHeartbeatSentAt,\n lastHeartbeatReceivedAt: this._lastHeartbeatReceivedAt,\n lastMessageReceivedAt: this._lastMessageReceivedAt,\n shutdownRequested: this._shutdownRequested,\n inFlightRequestCount: Object.keys(this._inProgressRequests.requestLeases)\n .length,\n inFlightRequests: Object.values(this._inProgressRequests.requestMeta),\n };\n }\n\n /**\n * Start the reconcile loop. Resolves when the first connection is active.\n * The loop continues running in the background.\n */\n async start(attempt = 0): Promise<void> {\n if (typeof WebSocket === \"undefined\") {\n throw new Error(\"WebSockets not supported in current environment\");\n }\n\n const state = this.callbacks.getState();\n if (state === ConnectionState.CLOSED) {\n throw new Error(\"Connection already closed\");\n }\n\n const firstReadyPromise = new Promise<void>((resolve, reject) => {\n this.resolveFirstReady = resolve;\n this.rejectFirstReady = reject;\n });\n\n this.loopPromise = this.reconcileLoop(attempt);\n\n // If the loop ends before firstReady resolves, propagate any error\n this.loopPromise.catch((err) => {\n this.rejectFirstReady?.(err);\n });\n\n await firstReadyPromise;\n }\n\n /**\n * Request graceful shutdown. Resolves when fully closed (in-flight done,\n * connection closed).\n */\n async close(): Promise<void> {\n const inFlightCount = Object.keys(\n this._inProgressRequests.requestLeases,\n ).length;\n this.callbacks.logger.info(\n { inFlightCount },\n \"Shutting down, waiting for in-flight requests\",\n );\n this._shutdownRequested = true;\n\n if (this._activeConnection?.ws.readyState === WebSocket.OPEN) {\n this._activeConnection.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: this._activeConnection.id },\n \"Sent WORKER_PAUSE, draining\",\n );\n }\n\n this.wake();\n\n if (this.loopPromise) {\n await this.loopPromise;\n }\n\n this.callbacks.logger.info(\"Connection closed\");\n }\n\n async getApiBaseUrl(): Promise<string> {\n return resolveApiBaseUrl({\n apiBaseUrl: this.config.apiBaseUrl,\n mode: this.config.mode,\n });\n }\n\n // ---------------------------------------------------------------------------\n // Wake signal\n // ---------------------------------------------------------------------------\n\n private resetWakeSignal(): void {\n let resolve: () => void;\n const promise = new Promise<void>((r) => {\n resolve = r;\n });\n this.wakeSignal = { promise, resolve: resolve! };\n }\n\n private wake(): void {\n this.wakeSignal.resolve();\n this.resetWakeSignal();\n }\n\n // ---------------------------------------------------------------------------\n // Signing key management\n // ---------------------------------------------------------------------------\n\n private switchAuthKey(): void {\n const switchToFallback =\n this.useSigningKey === this.config.hashedSigningKey;\n if (switchToFallback) {\n this.callbacks.logger.debug(\"Switching to fallback signing key\");\n }\n this.useSigningKey = switchToFallback\n ? this.config.hashedFallbackKey\n : this.config.hashedSigningKey;\n }\n\n // ---------------------------------------------------------------------------\n // In-flight helpers\n // ---------------------------------------------------------------------------\n\n private hasInFlightRequests(): boolean {\n return Object.keys(this._inProgressRequests.requestLeases).length > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Reconcile loop\n // ---------------------------------------------------------------------------\n\n private async reconcileLoop(initialAttempt: number): Promise<void> {\n let attempt = initialAttempt;\n\n while (true) {\n // Exit condition: shutdown requested + no in-flight requests\n if (this._shutdownRequested && !this.hasInFlightRequests()) {\n break;\n }\n\n // Ensure we have a live connection\n if (!this._activeConnection || this._activeConnection.dead) {\n this.callbacks.logger.debug(\n {\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n hasDrainingConnection: !!this._drainingConnection,\n drainingConnectionId: this._drainingConnection?.id,\n },\n \"No active connection\",\n );\n\n if (this.hasConnectedBefore) {\n this.callbacks.logger.info({ attempt }, \"Reconnecting\");\n } else {\n this.callbacks.logger.info(\"Connecting\");\n }\n\n this.callbacks.onStateChange(\n this.hasConnectedBefore\n ? ConnectionState.RECONNECTING\n : ConnectionState.CONNECTING,\n );\n\n try {\n const { conn, gatewayGroup } = await establishConnection(\n this.config,\n this.useSigningKey,\n attempt,\n this.excludeGateways,\n this.callbacks.logger,\n );\n\n // Attach post-handshake handlers\n this.attachHandlers(conn, gatewayGroup);\n\n // Clean up draining connection after new one is ready\n if (this._drainingConnection) {\n this.callbacks.logger.info(\n {\n oldConnectionId: this._drainingConnection.id,\n newConnectionId: conn.id,\n },\n \"Replaced draining connection\",\n );\n this._drainingConnection.close();\n this._drainingConnection = undefined;\n }\n\n this._activeConnection = conn;\n this.heartbeatManager.updateInterval(conn.heartbeatIntervalMs);\n this.statusReporter.updateInterval(conn.statusIntervalMs);\n attempt = 0;\n this.hasConnectedBefore = true;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup },\n \"Connection active\",\n );\n this.callbacks.onStateChange(ConnectionState.ACTIVE);\n\n if (this._shutdownRequested) {\n // Reconnected during shutdown to keep in-flight requests alive.\n // Send WORKER_PAUSE instead of WORKER_READY so no new work is routed.\n conn.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_PAUSE,\n }),\n ).finish(),\n ),\n );\n this.callbacks.logger.info(\n { connectionId: conn.id },\n \"Sent WORKER_PAUSE on reconnect during shutdown\",\n );\n } else {\n // Signal the gateway that we're ready to receive requests.\n // This must happen after ACTIVE so the gateway doesn't route\n // requests before handlers are fully attached.\n conn.ws.send(\n ensureUnsharedArrayBuffer(\n ConnectMessage.encode(\n ConnectMessage.create({\n kind: GatewayMessageType.WORKER_READY,\n }),\n ).finish(),\n ),\n );\n }\n\n // Flush any buffered responses via HTTP now that we're active.\n this.callbacks.onConnectionActive?.(this.useSigningKey);\n\n this.resolveFirstReady?.();\n this.resolveFirstReady = undefined;\n this.rejectFirstReady = undefined;\n } catch (err) {\n if (!(err instanceof ReconnectError)) throw err;\n\n attempt = err.attempt + 1;\n if (err instanceof AuthError) this.switchAuthKey();\n if (err instanceof ConnectionLimitError) {\n this.callbacks.logger.error(\"Max concurrent connections reached\");\n }\n\n // Gateway is draining, we should retry much faster\n if (err.message?.includes(\"connect_gateway_closing\")) {\n const jitter = 500 + Math.random() * 1000;\n this.callbacks.logger.info(\n { attempt, delay: Math.round(jitter), error: err.message },\n \"Gateway draining, retrying\",\n );\n const cancelled = await waitWithCancel(jitter, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n\n const delay = expBackoff(attempt);\n this.callbacks.logger.info(\n { attempt, delay },\n \"Reconnecting after failure\",\n );\n\n const cancelled = await waitWithCancel(delay, () => {\n return this._shutdownRequested && !this.hasInFlightRequests();\n });\n if (cancelled) break;\n continue;\n }\n }\n\n // Wait for something to change\n await this.wakeSignal.promise;\n this.callbacks.logger.debug(\n {\n shutdownRequested: this._shutdownRequested,\n hasActiveConnection: !!this._activeConnection,\n activeConnectionDead: this._activeConnection?.dead,\n },\n \"Reconcile loop woken\",\n );\n }\n\n // Teardown\n this.heartbeatManager.stop();\n this.statusReporter.stop();\n this._activeConnection?.close();\n this._activeConnection = undefined;\n this._drainingConnection?.close();\n this._drainingConnection = undefined;\n }\n\n // ---------------------------------------------------------------------------\n // Post-handshake handler attachment\n // ---------------------------------------------------------------------------\n\n /**\n * Wire up error, close, and message handlers on a newly-handshaked connection.\n */\n private attachHandlers(conn: Connection, gatewayGroup: string): void {\n const { ws } = conn;\n const connectionId = conn.id;\n\n // Error/close handlers: mark connection as dead and wake the loop\n ws.onerror = (ev) => {\n if (conn.dead) return;\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.warn(\n {\n connectionId,\n gatewayGroup,\n uptimeMs,\n error: (ev as ErrorEvent)?.message,\n },\n \"Connection lost (error)\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n ws.onclose = (ev) => {\n if (conn.dead) return;\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.warn(\n {\n connectionId,\n gatewayGroup,\n uptimeMs,\n code: ev.code,\n reason: ev.reason,\n },\n \"Connection lost (close)\",\n );\n conn.dead = true;\n this.excludeGateways.add(gatewayGroup);\n if (this._activeConnection?.id === connectionId) {\n this._activeConnection = undefined;\n }\n this.wake();\n };\n\n // Message handler for post-handshake messages\n ws.onmessage = async (event) => {\n this._lastMessageReceivedAt = Date.now();\n\n const messageBytes = new Uint8Array(event.data as ArrayBuffer);\n const connectMessage = parseConnectMessage(messageBytes);\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_CLOSING) {\n const uptimeMs = Date.now() - conn.connectedAt;\n this.callbacks.logger.info(\n { connectionId: conn.id, gatewayGroup, uptimeMs },\n \"Gateway draining, opening new connection\",\n );\n // Move current connection to draining, clear active so the loop\n // establishes a replacement.\n this._drainingConnection = this._activeConnection;\n this._activeConnection = undefined;\n this.wake();\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_HEARTBEAT) {\n this._lastHeartbeatReceivedAt = Date.now();\n conn.pendingHeartbeats = 0;\n this.callbacks.logger.debug(\n { connectionId },\n \"Handled gateway heartbeat\",\n );\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.GATEWAY_EXECUTOR_REQUEST) {\n await this.requestProcessor.handleExecutorRequest(connectMessage, conn);\n return;\n }\n\n if (connectMessage.kind === GatewayMessageType.WORKER_REPLY_ACK) {\n this.requestProcessor.handleReplyAck(connectMessage, connectionId);\n return;\n }\n\n if (\n connectMessage.kind ===\n GatewayMessageType.WORKER_REQUEST_EXTEND_LEASE_ACK\n ) {\n this.requestProcessor.handleExtendLeaseAck(\n connectMessage,\n connectionId,\n );\n return;\n }\n\n this.callbacks.logger.warn(\n {\n kind: gatewayMessageTypeToJSON(connectMessage.kind),\n rawKind: connectMessage.kind,\n state: this.callbacks.getState(),\n connectionId,\n },\n \"Unexpected message type\",\n );\n };\n }\n}\n"],"mappings":"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"}
|
|
@@ -185,6 +185,7 @@ async function establishConnection(config, hashedSigningKey, attempt, excludeGat
|
|
|
185
185
|
heartbeatIntervalMs: heartbeatIntervalMs ?? 1e4,
|
|
186
186
|
extendLeaseIntervalMs: extendLeaseIntervalMs ?? 5e3,
|
|
187
187
|
statusIntervalMs: statusIntervalMs ?? 0,
|
|
188
|
+
connectedAt: Date.now(),
|
|
188
189
|
close: () => {
|
|
189
190
|
if (conn.dead) return;
|
|
190
191
|
conn.dead = true;
|