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
JavaScript
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