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.

414 lines (412 loc) • 15.3 kB
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs'); const require_NonRetriableError = require('./NonRetriableError.cjs'); const require_types = require('../types.cjs'); const require_strings = require('../helpers/strings.cjs'); const require_als = require('./execution/als.cjs'); const require_temporal = require('../helpers/temporal.cjs'); const require_InngestFunction = require('./InngestFunction.cjs'); const require_InngestMetadata = require('./InngestMetadata.cjs'); const require_Fetch = require('./Fetch.cjs'); const require_InngestFunctionReference = require('./InngestFunctionReference.cjs'); const require_Inngest = require('./Inngest.cjs'); let zod_v3 = require("zod/v3"); let __inngest_ai = require("@inngest/ai"); //#region src/components/InngestStepTools.ts const getStepOptions = (options) => { if (typeof options === "string") return { id: options }; return options; }; /** * Suffix used to namespace steps that are automatically indexed. */ const STEP_INDEXING_SUFFIX = ":"; const createStepTools = (client, execution, stepHandler) => { /** * A local helper used to create tools that can be used to submit an op. * * When using this function, a generic type should be provided which is the * function signature exposed to the user. */ const createTool = (matchOp, opts) => { const wrappedMatchOp = (stepOptions, ...rest) => { const op = matchOp(stepOptions, ...rest); const alsCtx = require_als.getAsyncCtxSync()?.execution; if (alsCtx?.insideExperimentSelect) throw new require_NonRetriableError.NonRetriableError("Step tools (step.run, step.sleep, etc.) cannot be called inside an experiment select() callback. Move step calls into variant callbacks instead."); const parallelMode = stepOptions.parallelMode ?? alsCtx?.parallelMode; if (parallelMode) op.opts = { ...op.opts, parallelMode }; const experimentContext = alsCtx?.experimentContext; if (experimentContext) op.opts = { ...op.opts, ...experimentContext }; const tracker = alsCtx?.experimentStepTracker; if (tracker) tracker.found = true; return op; }; return (async (...args) => { return stepHandler({ args, matchOp: wrappedMatchOp, opts }); }); }; /** * Create a new step run tool that can be used to run a step function using * `step.run()` as a shim. */ const createStepRun = (type) => { return createTool(({ id, name }, _fn, ...input) => { const opts = { ...input.length ? { input } : {}, ...type ? { type } : {} }; return { id, mode: require_types.StepMode.Sync, op: require_types.StepOpCode.StepPlanned, name: id, displayName: name ?? id, ...Object.keys(opts).length ? { opts } : {}, userland: { id } }; }, { fn: (_, __, fn, ...input) => fn(...input) }); }; /** * Creates a metadata builder wrapper for step.metadata("id"). * Uses MetadataBuilder for config accumulation, but wraps .update() in tools.run() for memoization. */ const createStepMetadataWrapper = (memoizationId, builder) => { if (!client["experimentalMetadataEnabled"]) throw new Error("step.metadata() is experimental. Enable it by adding metadataMiddleware() from \"inngest/experimental\" to your client middleware."); const withBuilder = (next) => createStepMetadataWrapper(memoizationId, next); if (!builder) builder = new require_InngestMetadata.UnscopedMetadataBuilder(client).run(); return { run: (runId) => withBuilder(builder.run(runId)), step: (stepId, index) => withBuilder(builder.step(stepId, index)), attempt: (attemptIndex) => withBuilder(builder.attempt(attemptIndex)), span: (spanId) => withBuilder(builder.span(spanId)), update: async (values, kind = "default") => { await tools.run(memoizationId, async () => { await builder.update(values, kind); }); }, do: async (fn) => { await tools.run(memoizationId, async () => { await fn(builder); }); } }; }; /** * Define the set of tools the user has access to for their step functions. * * Each key is the function name and is expected to run `createTool` and pass * a generic type for that function as it will appear in the user's code. */ const tools = { sendEvent: createTool(({ id, name }) => { return { id, mode: require_types.StepMode.Sync, op: require_types.StepOpCode.StepPlanned, name: "sendEvent", displayName: name ?? id, opts: { type: "step.sendEvent" }, userland: { id } }; }, { fn: (_ctx, _idOrOptions, payload) => { const fn = execution["options"]["fn"]; return client["_send"]({ payload, headers: execution["options"]["headers"], fnMiddleware: fn.opts.middleware ?? [], fn }); } }), waitForSignal: createTool(({ id, name }, opts) => { return { id, mode: require_types.StepMode.Async, op: require_types.StepOpCode.WaitForSignal, name: opts.signal, displayName: name ?? id, opts: { signal: opts.signal, timeout: require_strings.timeStr(opts.timeout), conflict: opts.onConflict }, userland: { id } }; }), realtime: { publish: createTool(({ id, name }) => { return { id, mode: require_types.StepMode.Sync, op: require_types.StepOpCode.StepPlanned, displayName: name ?? id, opts: { type: "step.realtime.publish" }, userland: { id } }; }, { fn: async (ctx, _idOrOptions, topicRef, data) => { const topicConfig = topicRef.config; if (topicConfig && "schema" in topicConfig && topicConfig.schema) { if ((await topicConfig.schema["~standard"].validate(data)).issues) throw new Error(`Schema validation failed for topic "${topicRef.topic}"`); } const res = await client["inngestApi"].publish({ topics: [topicRef.topic], channel: topicRef.channel, runId: ctx.runId }, data); if (!res.ok) throw new Error(`Failed to publish to realtime: ${res.error?.error || "Unknown error"}`); return data; } }) }, sendSignal: createTool(({ id, name }, opts) => { return { id, mode: require_types.StepMode.Sync, op: require_types.StepOpCode.StepPlanned, name: "sendSignal", displayName: name ?? id, opts: { type: "step.sendSignal", signal: opts.signal }, userland: { id } }; }, { fn: (_ctx, _idOrOptions, opts) => { return client["_sendSignal"]({ signal: opts.signal, data: opts.data, headers: execution["options"]["headers"] }); } }), waitForEvent: createTool(({ id, name }, opts) => { const matchOpts = { timeout: require_strings.timeStr(typeof opts === "string" ? opts : opts.timeout) }; if (typeof opts !== "string") { if (opts?.match) matchOpts.if = `event.${opts.match} == async.${opts.match}`; else if (opts?.if) matchOpts.if = opts.if; } const eventName = typeof opts.event === "string" ? opts.event : opts.event.name; return { id, mode: require_types.StepMode.Async, op: require_types.StepOpCode.WaitForEvent, name: eventName, opts: matchOpts, displayName: name ?? id, userland: { id } }; }), run: createStepRun(), ai: { infer: createTool(({ id, name }, options) => { const { model, body, ...rest } = options; const modelCopy = { ...model }; options.model.onCall?.(modelCopy, options.body); return { id, mode: require_types.StepMode.Async, op: require_types.StepOpCode.AiGateway, displayName: name ?? id, opts: { type: "step.ai.infer", url: modelCopy.url, headers: modelCopy.headers, auth_key: modelCopy.authKey, format: modelCopy.format, body, ...rest }, userland: { id } }; }), wrap: createStepRun("step.ai.wrap"), models: { ...__inngest_ai.models } }, sleep: createTool(({ id, name }, time) => { /** * The presence of this operation in the returned stack indicates that the * sleep is over and we should continue execution. */ const msTimeStr = require_strings.timeStr(require_temporal.isTemporalDuration(time) ? time.total({ unit: "milliseconds" }) : time); return { id, mode: require_types.StepMode.Async, op: require_types.StepOpCode.Sleep, name: msTimeStr, displayName: name ?? id, userland: { id } }; }), sleepUntil: createTool(({ id, name }, time) => { try { const iso = require_temporal.getISOString(time); /** * The presence of this operation in the returned stack indicates that the * sleep is over and we should continue execution. */ return { id, mode: require_types.StepMode.Async, op: require_types.StepOpCode.Sleep, name: iso, displayName: name ?? id, userland: { id } }; } catch (err) { /** * If we're here, it's because the date is invalid. We'll throw a custom * error here to standardise this response. */ client[require_Inngest.internalLoggerSymbol].warn({ err }, "Invalid `Date`, date string, `Temporal.Instant`, or `Temporal.ZonedDateTime` passed to sleepUntil"); throw new Error(`Invalid \`Date\`, date string, \`Temporal.Instant\`, or \`Temporal.ZonedDateTime\` passed to sleepUntil: ${time}`); } }), invoke: createTool(({ id, name }, invokeOpts) => { const optsSchema = invokePayloadSchema.extend({ timeout: zod_v3.z.union([ zod_v3.z.number(), zod_v3.z.string(), zod_v3.z.date() ]).optional() }); const parsedFnOpts = optsSchema.extend({ _type: zod_v3.z.literal("fnInstance").optional().default("fnInstance"), function: zod_v3.z.instanceof(require_InngestFunction.InngestFunction) }).or(optsSchema.extend({ _type: zod_v3.z.literal("refInstance").optional().default("refInstance"), function: zod_v3.z.instanceof(require_InngestFunctionReference.InngestFunctionReference) })).safeParse(invokeOpts); if (!parsedFnOpts.success) throw new Error(`Invalid invocation options passed to invoke; must include a function instance or referenceFunction().`); const { _type, function: fn, data, v, timeout } = parsedFnOpts.data; const opts = { payload: { data, v }, function_id: "", timeout: typeof timeout === "undefined" ? void 0 : require_strings.timeStr(timeout) }; switch (_type) { case "fnInstance": opts.function_id = fn.id(fn["client"].id); break; case "refInstance": opts.function_id = [fn.opts.appId || client.id, fn.opts.functionId].filter(Boolean).join("-"); break; } return { id, mode: require_types.StepMode.Async, op: require_types.StepOpCode.InvokeFunction, displayName: name ?? id, opts, userland: { id } }; }), fetch: require_Fetch.fetch }; tools[require_InngestMetadata.metadataSymbol] = (memoizationId) => createStepMetadataWrapper(memoizationId); tools[experimentStepRunSymbol] = createStepRun("group.experiment"); tools[gatewaySymbol] = createTool(({ id, name }, input, init) => { const url = input instanceof Request ? input.url : input.toString(); const headers = {}; if (input instanceof Request) input.headers.forEach((value, key) => { headers[key] = value; }); else if (init?.headers) new Headers(init.headers).forEach((value, key) => { headers[key] = value; }); return { id, mode: require_types.StepMode.Async, op: require_types.StepOpCode.Gateway, displayName: name ?? id, opts: { url, method: init?.method ?? "GET", headers, body: init?.body }, userland: { id } }; }); return tools; }; const gatewaySymbol = Symbol.for("inngest.step.gateway"); const experimentStepRunSymbol = Symbol.for("inngest.group.experiment"); /** * A generic set of step tools that can be used without typing information about * the client used to create them. * * These tools use AsyncLocalStorage to track the context in which they are * used, and will throw an error if used outside of an Inngest context. * * The intention of these high-level tools is to allow usage of Inngest step * tools within API endpoints, though they can still be used within regular * Inngest functions as well. */ const step = { fetch: null, ai: { infer: (...args) => getDeferredStepTooling().then((tools) => tools.ai.infer(...args)), wrap: (...args) => getDeferredStepTooling().then((tools) => tools.ai.wrap(...args)), models: { ...__inngest_ai.models } }, invoke: (...args) => getDeferredStepTooling().then((tools) => tools.invoke(...args)), run: (...args) => getDeferredStepTooling().then((tools) => tools.run(...args)), sendEvent: (...args) => getDeferredStepTooling().then((tools) => tools.sendEvent(...args)), sendSignal: (...args) => getDeferredStepTooling().then((tools) => tools.sendSignal(...args)), sleep: (...args) => getDeferredStepTooling().then((tools) => tools.sleep(...args)), sleepUntil: (...args) => getDeferredStepTooling().then((tools) => tools.sleepUntil(...args)), waitForEvent: (...args) => getDeferredStepTooling().then((tools) => tools.waitForEvent(...args)), waitForSignal: (...args) => getDeferredStepTooling().then((tools) => tools.waitForSignal(...args)), realtime: { publish: (...args) => getDeferredStepTooling().then((tools) => tools.realtime.publish(...args)) } }; /** * An internal function used to retrieve or create step tooling for the current * execution context. * * Note that this requires an existing context to create the step tooling; * something must declare the Inngest execution context before this can be used. */ const getDeferredStepTooling = async () => { const ctx = await require_als.getAsyncCtx(); if (!ctx) throw new Error("`step` tools can only be used within Inngest function executions; no context was found"); if (!ctx.app) throw new Error("`step` tools can only be used within Inngest function executions; no Inngest client was found in the execution context"); if (!ctx.execution) throw new Error("`step` tools can only be used within Inngest function executions; no execution context was found"); return ctx.execution.ctx.step; }; const getDeferredGroupTooling = async () => { const ctx = await require_als.getAsyncCtx(); if (!ctx) throw new Error("`group` tools can only be used within Inngest function executions; no context was found"); if (!ctx.execution) throw new Error("`group` tools can only be used within Inngest function executions; no execution context was found"); return ctx.execution.ctx.group; }; /** * A deferred proxy for `group` tools that delegates through ALS context. * * @public */ const group = { parallel: (...args) => getDeferredGroupTooling().then((tools) => tools.parallel(...args)), experiment: (...args) => getDeferredGroupTooling().then((tools) => tools.experiment(...args)) }; /** * The event payload portion of the options for `step.invoke()`. This does not * include non-payload options like `timeout` or the function to invoke. */ const invokePayloadSchema = zod_v3.z.object({ data: zod_v3.z.record(zod_v3.z.any()).optional(), v: zod_v3.z.string().optional() }); //#endregion exports.STEP_INDEXING_SUFFIX = STEP_INDEXING_SUFFIX; exports.createStepTools = createStepTools; exports.experimentStepRunSymbol = experimentStepRunSymbol; exports.gatewaySymbol = gatewaySymbol; exports.getStepOptions = getStepOptions; exports.group = group; exports.step = step; //# sourceMappingURL=InngestStepTools.cjs.map