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.
373 lines • 15.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.invokePayloadSchema = exports.createStepTools = exports.STEP_INDEXING_SUFFIX = exports.getStepOptions = void 0;
const ai_1 = require("@inngest/ai");
const zod_1 = require("zod");
const consts_js_1 = require("../helpers/consts.js");
const strings_js_1 = require("../helpers/strings.js");
const Temporal = __importStar(require("../helpers/temporal.js"));
const types_js_1 = require("../types.js");
const InngestFunction_js_1 = require("./InngestFunction.js");
const InngestFunctionReference_js_1 = require("./InngestFunctionReference.js");
const getStepOptions = (options) => {
if (typeof options === "string") {
return { id: options };
}
return options;
};
exports.getStepOptions = getStepOptions;
/**
* Suffix used to namespace steps that are automatically indexed.
*/
exports.STEP_INDEXING_SUFFIX = ":";
/**
* Create a new set of step function tools ready to be used in a step function.
* This function should be run and a fresh set of tools provided every time a
* function is run.
*
* An op stack (function state) is passed in as well as some mutable properties
* that the tools can use to submit a new op.
*/
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.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const createTool = (
/**
* A function that returns an ID for this op. This is used to ensure that
* the op stack is correctly filled, submitted, and retrieved with the same
* ID.
*
* It is passed the arguments passed by the user.
*
* Most simple tools will likely only need to define this.
*/
matchOp, opts) => {
return (async (...args) => {
const parsedArgs = args;
return stepHandler({ args: parsedArgs, matchOp, opts });
});
};
/**
* Create a new step run tool that can be used to run a step function using
* `step.run()` as a shim.
*/
const createStepRun = (
/**
* The sub-type of this step tool, exposed via `opts.type` when the op is
* reported.
*/
type) => {
return createTool(({ id, name }, _fn, ...input) => {
const opts = Object.assign(Object.assign({}, (input.length ? { input } : {})), (type ? { type } : {}));
return Object.assign({ id, op: types_js_1.StepOpCode.StepPlanned, name: id, displayName: name !== null && name !== void 0 ? name : id }, (Object.keys(opts).length ? { opts } : {}));
}, {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
fn: (_, fn, ...input) => fn(...input),
});
};
/**
* 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 = {
/**
* Send one or many events to Inngest. Should always be used in place of
* `inngest.send()` to ensure that the event send is successfully retried
* and not sent multiple times due to memoisation.
*
* @example
* ```ts
* await step.sendEvent("emit-user-creation", {
* name: "app/user.created",
* data: { id: 123 },
* });
*
* await step.sendEvent("emit-user-updates", [
* {
* name: "app/user.created",
* data: { id: 123 },
* },
* {
* name: "app/user.feed.created",
* data: { id: 123 },
* },
* ]);
* ```
*
* Returns a promise that will resolve once the event has been sent.
*/
sendEvent: createTool(({ id, name }) => {
return {
id,
op: types_js_1.StepOpCode.StepPlanned,
name: "sendEvent",
displayName: name !== null && name !== void 0 ? name : id,
};
}, {
fn: (idOrOptions, payload) => {
return client["_send"]({
payload,
headers: execution["options"]["headers"],
});
},
}),
/**
* Wait for a particular event to be received before continuing. When the
* event is received, it will be returned.
*
* You can also provide options to control the particular event that is
* received, for example to ensure that a user ID matches between two
* events, or to only wait a maximum amount of time before giving up and
* returning `null` instead of any event data.
*/
waitForEvent: createTool(({ id, name },
/**
* Options to control the event we're waiting for.
*/
opts) => {
const matchOpts = {
timeout: (0, strings_js_1.timeStr)(typeof opts === "string" ? opts : opts.timeout),
};
if (typeof opts !== "string") {
if (opts === null || opts === void 0 ? void 0 : opts.match) {
matchOpts.if = `event.${opts.match} == async.${opts.match}`;
}
else if (opts === null || opts === void 0 ? void 0 : opts.if) {
matchOpts.if = opts.if;
}
}
return {
id,
op: types_js_1.StepOpCode.WaitForEvent,
name: opts.event,
opts: matchOpts,
displayName: name !== null && name !== void 0 ? name : id,
};
}),
/**
* Use this tool to run business logic. Each call to `run` will be retried
* individually, meaning you can compose complex workflows that safely
* retry dependent asynchronous actions.
*
* The function you pass to `run` will be called only when this "step" is to
* be executed and can be synchronous or asynchronous.
*
* In either case, the return value of the function will be the return value
* of the `run` tool, meaning you can return and reason about return data
* for next steps.
*/
run: createStepRun(),
/**
* AI tooling for running AI models and other AI-related tasks.
*/
ai: {
/**
* Use this tool to have Inngest make your AI calls. Useful for agentic workflows.
*
* Input is also tracked for this tool, meaning you can pass input to the
* function and it will be displayed and editable in the UI.
*/
infer: createTool(({ id, name }, options) => {
var _a, _b;
const modelCopy = Object.assign({}, options.model);
// Allow the model to mutate options and body for this call
(_b = (_a = options.model).onCall) === null || _b === void 0 ? void 0 : _b.call(_a, modelCopy, options.body);
return {
id,
op: types_js_1.StepOpCode.AiGateway,
displayName: name !== null && name !== void 0 ? name : id,
opts: {
type: "step.ai.infer",
url: modelCopy.url,
headers: modelCopy.headers,
auth_key: modelCopy.authKey,
format: modelCopy.format,
body: options.body,
},
};
}),
/**
* Use this tool to wrap AI models and other AI-related tasks. Each call
* to `wrap` will be retried individually, meaning you can compose complex
* workflows that safely retry dependent asynchronous actions.
*
* Input is also tracked for this tool, meaning you can pass input to the
* function and it will be displayed and editable in the UI.
*/
wrap: createStepRun("step.ai.wrap"),
/**
* Models for AI inference and other AI-related tasks.
*/
models: Object.assign({}, ai_1.models),
},
/**
* Wait a specified amount of time before continuing.
*
* The time to wait can be specified using a `number` of milliseconds or an
* `ms`-compatible time string like `"1 hour"`, `"30 mins"`, or `"2.5d"`.
*
* {@link https://npm.im/ms}
*
* To wait until a particular date, use `sleepUntil` instead.
*/
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 = (0, strings_js_1.timeStr)(Temporal.isTemporalDuration(time)
? time.total({ unit: "milliseconds" })
: time);
return {
id,
op: types_js_1.StepOpCode.Sleep,
name: msTimeStr,
displayName: name !== null && name !== void 0 ? name : id,
};
}),
/**
* Wait until a particular date before continuing by passing a `Date`.
*
* To wait for a particular amount of time from now, always use `sleep`
* instead.
*/
sleepUntil: createTool(({ id, name }, time) => {
try {
const iso = 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,
op: types_js_1.StepOpCode.Sleep,
name: iso,
displayName: name !== null && name !== void 0 ? name : 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.
*/
// TODO PrettyError
console.warn("Invalid `Date`, date string, `Temporal.Instant`, or `Temporal.ZonedDateTime` passed to sleepUntil;", err);
// TODO PrettyError
throw new Error(`Invalid \`Date\`, date string, \`Temporal.Instant\`, or \`Temporal.ZonedDateTime\` passed to sleepUntil: ${
// eslint-disable-next-line @typescript-eslint/no-explicit-any
time}`);
}
}),
/**
* Invoke a passed Inngest `function` with the given `data`. Returns the
* result of the returned value of the function or `null` if the function
* does not return a value.
*
* A string ID can also be passed to reference functions outside of the
* current app.
*/
invoke: createTool(({ id, name }, invokeOpts) => {
// Create a discriminated union to operate on based on the input types
// available for this tool.
const optsSchema = exports.invokePayloadSchema.extend({
timeout: zod_1.z.union([zod_1.z.number(), zod_1.z.string(), zod_1.z.date()]).optional(),
});
const parsedFnOpts = optsSchema
.extend({
_type: zod_1.z.literal("fullId").optional().default("fullId"),
function: zod_1.z.string().min(1),
})
.or(optsSchema.extend({
_type: zod_1.z.literal("fnInstance").optional().default("fnInstance"),
function: zod_1.z.instanceof(InngestFunction_js_1.InngestFunction),
}))
.or(optsSchema.extend({
_type: zod_1.z.literal("refInstance").optional().default("refInstance"),
function: zod_1.z.instanceof(InngestFunctionReference_js_1.InngestFunctionReference),
}))
.safeParse(invokeOpts);
if (!parsedFnOpts.success) {
throw new Error(`Invalid invocation options passed to invoke; must include either a function or functionId.`);
}
const { _type, function: fn, data, user, v, timeout } = parsedFnOpts.data;
const payload = { data, user, v };
const opts = {
payload,
function_id: "",
timeout: typeof timeout === "undefined" ? undefined : (0, strings_js_1.timeStr)(timeout),
};
switch (_type) {
case "fnInstance":
opts.function_id = fn.id(fn["client"].id);
break;
case "fullId":
console.warn(`${consts_js_1.logPrefix} Invoking function with \`function: string\` is deprecated and will be removed in v4.0.0; use an imported function or \`referenceFunction()\` instead. See https://innge.st/ts-referencing-functions`);
opts.function_id = fn;
break;
case "refInstance":
opts.function_id = [fn.opts.appId || client.id, fn.opts.functionId]
.filter(Boolean)
.join("-");
break;
}
return {
id,
op: types_js_1.StepOpCode.InvokeFunction,
displayName: name !== null && name !== void 0 ? name : id,
opts,
};
}),
};
return tools;
};
exports.createStepTools = createStepTools;
/**
* The event payload portion of the options for `step.invoke()`. This does not
* include non-payload options like `timeout` or the function to invoke.
*/
exports.invokePayloadSchema = zod_1.z.object({
data: zod_1.z.record(zod_1.z.any()).optional(),
user: zod_1.z.record(zod_1.z.any()).optional(),
v: zod_1.z.string().optional(),
});
//# sourceMappingURL=InngestStepTools.js.map