UNPKG

inngest

Version:

Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.

217 lines (215 loc) • 8.38 kB
import { envKeys, headerKeys, queryKeys } from "../../helpers/consts.js"; import { version } from "../../version.js"; import { allProcessEnv, getEnvironmentName } from "../../helpers/env.js"; import { PREFERRED_ASYNC_EXECUTION_VERSION } from "../execution/InngestExecution.js"; import { parseFnData } from "../../helpers/functions.js"; import { hashSigningKey } from "../../helpers/strings.js"; import { internalLoggerSymbol } from "../Inngest.js"; import { InngestCommHandler } from "../InngestCommHandler.js"; import { SDKResponse, SDKResponseStatus } from "../../proto/src/components/connect/protobuf/connect.js"; import { parseTraceCtx } from "./util.js"; import { ConnectionState, DEFAULT_SHUTDOWN_SIGNALS } from "./types.js"; import { createStrategy } from "./strategies/index.js"; //#region src/components/connect/index.ts const InngestBranchEnvironmentSigningKeyPrefix = "signkey-branch-"; /** * WebSocket worker connection that implements the WorkerConnection interface. * * This class acts as a facade that delegates to a connection strategy. * The strategy determines how the WebSocket connection, heartbeater, and * lease extender are managed (same thread vs worker thread). */ var WebSocketWorkerConnection = class { inngest; options; strategy; constructor(options) { if (!Array.isArray(options.apps) || options.apps.length === 0 || !options.apps[0]) throw new Error("No apps provided"); this.inngest = options.apps[0].client; for (const app of options.apps) { const client = app.client; if (client.env !== this.inngest.env) throw new Error(`All apps must be configured to the same environment. ${client.id} is configured to ${client.env} but ${this.inngest.id} is configured to ${this.inngest.env}`); } this.options = this.applyDefaults(options); } get functions() { const functions = {}; for (const app of this.options.apps) { const client = app.client; if (functions[client.id]) throw new Error(`Duplicate app id: ${client.id}`); functions[client.id] = { client: app.client, functions: app.functions ?? client.funcs }; } return functions; } applyDefaults(opts) { const options = { ...opts }; if (!Array.isArray(options.handleShutdownSignals)) options.handleShutdownSignals = DEFAULT_SHUTDOWN_SIGNALS; const env = allProcessEnv(); if (options.maxWorkerConcurrency === void 0) { const envValue = env[envKeys.InngestConnectMaxWorkerConcurrency]; if (envValue) { const parsed = Number.parseInt(envValue, 10); if (!Number.isNaN(parsed) && parsed > 0) options.maxWorkerConcurrency = parsed; } } if (options.isolateExecution === void 0) { const envValue = env[envKeys.InngestConnectIsolateExecution]; if (envValue === "0" || envValue === "false") options.isolateExecution = false; } if (options.gatewayUrl === void 0) { const envValue = env[envKeys.InngestConnectGatewayUrl]; if (envValue) options.gatewayUrl = envValue; } return options; } get state() { return this.strategy?.state ?? ConnectionState.CONNECTING; } get connectionId() { if (!this.strategy?.connectionId) throw new Error("Connection not prepared"); return this.strategy.connectionId; } get closed() { if (!this.strategy) throw new Error("No connection established"); return this.strategy.closed; } async close() { if (!this.strategy) return; return this.strategy.close(); } /** * Establish a persistent connection to the gateway. */ async connect(attempt = 0) { this.inngest[internalLoggerSymbol].debug({ attempt }, "Establishing connection"); const envName = this.inngest.env ?? getEnvironmentName(); const hashedSigningKey = this.inngest.signingKey ? hashSigningKey(this.inngest.signingKey) : void 0; if (this.inngest.signingKey && this.inngest.signingKey.startsWith(InngestBranchEnvironmentSigningKeyPrefix) && !envName) throw new Error("Environment is required when using branch environment signing keys"); const hashedFallbackKey = this.inngest.signingKeyFallback ? hashSigningKey(this.inngest.signingKeyFallback) : void 0; const capabilities = { trust_probe: "v1", connect: "v1" }; const functionConfigs = {}; for (const [appId, { client, functions }] of Object.entries(this.functions)) functionConfigs[appId] = { client, functions: functions.flatMap((f) => f["getConfig"]({ baseUrl: new URL("wss://connect"), appPrefix: client.id, isConnect: true })) }; this.inngest[internalLoggerSymbol].debug({ functionSlugs: Object.entries(functionConfigs).map(([appId, { functions }]) => { return JSON.stringify({ appId, functions: functions.map((f) => ({ id: f.id, stepUrls: Object.values(f.steps).map((s) => s.runtime["url"]) })) }); }) }, "Prepared sync data"); const connectionData = { manualReadinessAck: false, marshaledCapabilities: JSON.stringify(capabilities), apps: Object.entries(functionConfigs).map(([appId, { client, functions }]) => ({ appName: appId, appVersion: client.appVersion, functions: new TextEncoder().encode(JSON.stringify(functions)) })) }; const requestHandlers = {}; for (const [appId, { client, functions }] of Object.entries(this.functions)) { const inngestCommHandler = new InngestCommHandler({ client, functions, frameworkName: "connect", skipSignatureValidation: true, handler: (msg) => { const asString = new TextDecoder().decode(msg.requestPayload); const parsed = parseFnData(JSON.parse(asString), void 0, this.inngest[internalLoggerSymbol]); const userTraceCtx = parseTraceCtx(msg.userTraceCtx); return { body() { return parsed; }, method() { return "POST"; }, headers(key) { switch (key) { case headerKeys.ContentLength.toString(): return asString.length.toString(); case headerKeys.InngestExpectedServerKind.toString(): return "connect"; case headerKeys.RequestVersion.toString(): return parsed.version.toString(); case headerKeys.Signature.toString(): return null; case headerKeys.TraceParent.toString(): return userTraceCtx?.traceParent ?? null; case headerKeys.TraceState.toString(): return userTraceCtx?.traceState ?? null; default: return null; } }, transformResponse({ body, headers, status }) { let sdkResponseStatus = SDKResponseStatus.DONE; switch (status) { case 200: sdkResponseStatus = SDKResponseStatus.DONE; break; case 206: sdkResponseStatus = SDKResponseStatus.NOT_COMPLETED; break; case 500: sdkResponseStatus = SDKResponseStatus.ERROR; break; } return SDKResponse.create({ requestId: msg.requestId, accountId: msg.accountId, envId: msg.envId, appId: msg.appId, status: sdkResponseStatus, body: new TextEncoder().encode(body), noRetry: headers[headerKeys.NoRetry] === "true", retryAfter: headers[headerKeys.RetryAfter], sdkVersion: `inngest-js:v${version}`, requestVersion: parseInt(headers[headerKeys.RequestVersion] ?? PREFERRED_ASYNC_EXECUTION_VERSION.toString(), 10), systemTraceCtx: msg.systemTraceCtx, userTraceCtx: msg.userTraceCtx, runId: msg.runId }); }, url() { const baseUrl = new URL("http://connect.inngest.com"); baseUrl.searchParams.set(queryKeys.FnId, msg.functionSlug); if (msg.stepId) baseUrl.searchParams.set(queryKeys.StepId, msg.stepId); return baseUrl; } }; } }); if (!inngestCommHandler.checkModeConfiguration()) throw new Error("Signing key is required"); requestHandlers[appId] = inngestCommHandler.createHandler(); } this.strategy = await createStrategy({ hashedSigningKey, hashedFallbackKey, internalLogger: this.inngest[internalLoggerSymbol], envName, connectionData, requestHandlers, options: this.options, apiBaseUrl: this.inngest.apiBaseUrl, mode: this.inngest["mode"] }, this.options); await this.strategy.connect(attempt); } }; const connect = async (options) => { if (options.apps.length === 0) throw new Error("No apps provided"); const conn = new WebSocketWorkerConnection(options); await conn.connect(); return conn; }; //#endregion export { connect }; //# sourceMappingURL=index.js.map