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.
208 lines (206 loc) • 6.35 kB
JavaScript
const require_rolldown_runtime = require('../../../../_virtual/rolldown_runtime.cjs');
const require_connect = require('../../../../proto/src/components/connect/protobuf/connect.cjs');
const require_buffer = require('../../buffer.cjs');
const require_types = require('../../types.cjs');
const require_connection = require('../core/connection.cjs');
let node_worker_threads = require("node:worker_threads");
//#region src/components/connect/strategies/workerThread/runner.ts
/**
* Worker thread runner for Inngest Connect.
*
* This file runs in a separate worker thread and manages:
* - WebSocket connection to the Inngest gateway
* - Heartbeater
* - Lease extender
*
* Userland code execution still happens in the main thread.
*/
/**
* Time in milliseconds to wait for gateway acknowledgment of a response.
* If no ACK is received within this deadline, the response is moved to the
* buffer for later flush via HTTP.
*/
const responseAcknowledgeDeadline = 5e3;
if (node_worker_threads.isMainThread) throw new Error("This file should only be run in a worker thread");
if (!node_worker_threads.parentPort) throw new Error("No parent port available");
function toError(value) {
if (value instanceof Error) return value;
return new Error(String(value));
}
/**
* Parse pino-style (object, string) or plain (string) log args into a
* structured { message, data } pair for sending over postMessage.
*/
function isRecord(value) {
return value != null && typeof value === "object" && !Array.isArray(value);
}
function parsePinoArgs(args) {
if (args.length >= 2 && isRecord(args[0]) && typeof args[1] === "string") return {
data: args[0],
message: args[1]
};
return { message: String(args[0]) };
}
/**
* Worker thread runner state.
*/
var WorkerRunner = class {
config;
state = require_types.ConnectionState.CONNECTING;
core;
messageBuffer;
logger;
constructor() {
this.logger = this.createMessageLogger();
}
/**
* Pending execution responses waiting for user code to complete.
*/
pendingExecutions = /* @__PURE__ */ new Map();
sendMessage(msg) {
node_worker_threads.parentPort?.postMessage(msg);
}
createMessageLogger() {
const sendLog = (level, ...args) => {
const { message, data } = parsePinoArgs(args);
this.sendMessage({
type: "LOG",
level,
message,
data
});
};
return {
debug: (...args) => sendLog("debug", ...args),
info: (...args) => sendLog("info", ...args),
warn: (...args) => sendLog("warn", ...args),
error: (...args) => sendLog("error", ...args)
};
}
setState(state) {
this.state = state;
this.sendMessage({
type: "STATE_CHANGE",
state
});
}
handleMessage(msg) {
switch (msg.type) {
case "INIT":
this.config = msg.config;
this.initializeCore();
this.logger.debug("Worker initialized with config");
break;
case "CONNECT":
if (!this.core) {
this.sendMessage({
type: "ERROR",
error: "Worker not initialized",
fatal: true
});
return;
}
this.core.connect(msg.attempt).catch((err) => {
this.sendMessage({
type: "ERROR",
error: err instanceof Error ? err.message : "Unknown error",
fatal: true
});
});
break;
case "CLOSE":
this.close().catch((err) => {
this.logger.error({ err: toError(err) }, "Error during close");
});
break;
case "EXECUTION_RESPONSE": {
const pending = this.pendingExecutions.get(msg.requestId);
if (pending) {
pending.resolve(msg.response);
this.pendingExecutions.delete(msg.requestId);
}
break;
}
case "EXECUTION_ERROR": {
const pending = this.pendingExecutions.get(msg.requestId);
if (pending) {
pending.reject(new Error(msg.error));
this.pendingExecutions.delete(msg.requestId);
}
break;
}
}
}
initializeCore() {
if (!this.config) throw new Error("Config not set");
this.core = new require_connection.ConnectionCore({
...this.config,
gatewayUrl: this.config.gatewayUrl
}, {
logger: this.logger,
onStateChange: (state) => {
this.setState(state);
if (state === require_types.ConnectionState.ACTIVE && this.core?.connectionId) this.sendMessage({
type: "CONNECTION_READY",
connectionId: this.core.connectionId
});
},
getState: () => this.state,
handleExecutionRequest: async (request) => {
const requestPromise = new Promise((resolve, reject) => {
this.pendingExecutions.set(request.requestId, {
resolve,
reject
});
});
this.sendMessage({
type: "EXECUTION_REQUEST",
requestId: request.requestId,
request: require_connect.GatewayExecutorRequestData.encode(request).finish()
});
const responseBytes = await requestPromise;
this.messageBuffer?.addPending(request.requestId, responseBytes, responseAcknowledgeDeadline);
return responseBytes;
},
onReplyAck: (requestId) => {
this.messageBuffer?.acknowledgePending(requestId);
},
onBufferResponse: (requestId, responseBytes) => {
this.messageBuffer?.append(requestId, responseBytes);
},
beforeConnect: async (signingKey) => {
await this.messageBuffer?.flush(signingKey);
}
});
this.messageBuffer = new require_buffer.MessageBuffer({
envName: this.config.envName,
getApiBaseUrl: () => this.core.getApiBaseUrl(),
logger: this.logger
});
}
async close() {
this.setState(require_types.ConnectionState.CLOSING);
this.logger.debug("Cleaning up connection resources");
if (this.core) await this.core.cleanup();
this.logger.debug("Connection closed");
this.logger.debug("Waiting for in-flight requests to complete");
if (this.core) await this.core.waitForInProgress();
this.logger.debug("Flushing messages before closing");
if (this.messageBuffer) try {
await this.messageBuffer.flush(this.config?.hashedSigningKey);
} catch (err) {
this.logger.debug({ err: toError(err) }, "Failed to flush messages, using fallback key");
await this.messageBuffer.flush(this.config?.hashedFallbackKey);
}
this.setState(require_types.ConnectionState.CLOSED);
this.sendMessage({ type: "CLOSED" });
this.logger.debug("Fully closed");
process.exit(0);
}
};
const runner = new WorkerRunner();
node_worker_threads.parentPort.on("message", (msg) => {
runner.handleMessage(msg);
});
//#endregion
//# sourceMappingURL=runner.cjs.map