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.
123 lines (121 loc) • 5.63 kB
JavaScript
const require_NonRetriableError = require('./NonRetriableError.cjs');
const require_als = require('./execution/als.cjs');
const require_InngestStepTools = require('./InngestStepTools.cjs');
//#region src/components/InngestGroupTools.ts
/**
* A helper that sets the parallel mode for all steps created within the
* callback. This allows you to use native `Promise.race()` with cleaner syntax.
*
* @example
* ```ts
* // Defaults to "race" mode
* const winner = await group.parallel(async () => {
* return Promise.race([
* step.run("a", () => "a"),
* step.run("b", () => "b"),
* step.run("c", () => "c"),
* ]);
* });
*
* // Or explicitly specify the mode
* const winner = await group.parallel({ mode: "race" }, async () => {
* return Promise.race([
* step.run("a", () => "a"),
* step.run("b", () => "b"),
* ]);
* });
* ```
*/
const parallel = async (optionsOrCallback, maybeCallback) => {
const options = typeof optionsOrCallback === "function" ? {} : optionsOrCallback;
const callback = typeof optionsOrCallback === "function" ? optionsOrCallback : maybeCallback;
if (!callback) throw new Error("`group.parallel()` requires a callback function");
const currentCtx = require_als.getAsyncCtxSync();
if (!currentCtx?.execution) throw new Error("`group.parallel()` must be called within an Inngest function execution");
const als = await require_als.getAsyncLocalStorage();
if (require_als.isALSFallback()) throw new Error("`group.parallel()` requires AsyncLocalStorage support, which is not available in this runtime. Workaround: Pass `parallelMode` directly to each step:\n step.run({ id: \"my-step\", parallelMode: \"race\" }, fn)");
const nestedCtx = {
...currentCtx,
execution: {
...currentCtx.execution,
parallelMode: options.mode ?? "race"
}
};
return als.run(nestedCtx, callback);
};
/**
* Create the `group` tools object provided on the function execution context.
*
* @public
*/
const createGroupTools = (deps) => {
const experiment = (async (idOrOptions, options) => {
if (!deps?.experimentStepRun) throw new Error("`group.experiment()` requires step tools to be available. Ensure you are calling this within an Inngest function execution.");
const { variants, select, withVariant } = options;
const variantNames = Object.keys(variants);
if (variantNames.length === 0) throw new Error("`group.experiment()` requires at least one variant to be defined.");
if (require_als.isALSFallback()) throw new Error("`group.experiment()` requires AsyncLocalStorage support, which is not available in this runtime.");
const stepOpts = require_InngestStepTools.getStepOptions(idOrOptions);
let experimentStepHashedId;
const selectedVariant = await deps.experimentStepRun(idOrOptions, async () => {
experimentStepHashedId = require_als.getAsyncCtxSync()?.execution?.executingStep?.id;
const alsInstance = await require_als.getAsyncLocalStorage();
const currentCtx$1 = require_als.getAsyncCtxSync();
const selectCtx = {
...currentCtx$1,
execution: {
...currentCtx$1.execution,
insideExperimentSelect: true
}
};
const result$1 = await alsInstance.run(selectCtx, () => select(variantNames));
if (!variantNames.includes(result$1)) throw new require_NonRetriableError.NonRetriableError(`group.experiment("${stepOpts.id}"): select() returned "${result$1}" which is not a known variant. Available variants: ${variantNames.join(", ")}`);
const execInstance = require_als.getAsyncCtxSync()?.execution?.instance;
if (execInstance && experimentStepHashedId) {
execInstance.addMetadata(experimentStepHashedId, "inngest.experiment", "step", "merge", {
experiment_name: stepOpts.id,
variant_selected: result$1,
selection_strategy: select.__experimentConfig.strategy,
available_variants: variantNames,
...select.__experimentConfig.weights && { variant_weights: select.__experimentConfig.weights }
});
if (select.__experimentConfig.nullishBucket) execInstance.addMetadata(experimentStepHashedId, "inngest.warnings", "step", "merge", { message: "experiment.bucket() received a null/undefined value; hashing empty string \"\" for variant selection" });
}
return result$1;
});
const variantFn = variants[selectedVariant];
if (!variantFn) throw new Error(`group.experiment("${stepOpts.id}"): variant "${selectedVariant}" was selected but is not defined. Available variants: ${variantNames.join(", ")}`);
const currentCtx = require_als.getAsyncCtxSync();
const stepTracker = { found: false };
let result;
if (currentCtx?.execution && !require_als.isALSFallback()) {
const als = await require_als.getAsyncLocalStorage();
const nestedCtx = {
...currentCtx,
execution: {
...currentCtx.execution,
...experimentStepHashedId && { experimentContext: {
experimentStepID: experimentStepHashedId,
experimentName: stepOpts.id,
variant: selectedVariant
} },
experimentStepTracker: stepTracker
}
};
result = await als.run(nestedCtx, () => variantFn());
} else result = await variantFn();
if (!stepTracker.found && !require_als.isALSFallback()) throw new require_NonRetriableError.NonRetriableError(`group.experiment("${stepOpts.id}"): variant "${selectedVariant}" did not invoke any step tools. Wrap your variant logic in step.run() to ensure it is memoized and not re-executed on replay.`);
if (withVariant) return {
result,
variant: selectedVariant
};
return result;
});
return {
parallel,
experiment
};
};
//#endregion
exports.createGroupTools = createGroupTools;
//# sourceMappingURL=InngestGroupTools.cjs.map