inngest 4.7.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/components/DeferredFunction.cjs +52 -0
  3. package/components/DeferredFunction.cjs.map +1 -1
  4. package/components/DeferredFunction.d.cts +35 -9
  5. package/components/DeferredFunction.d.cts.map +1 -1
  6. package/components/DeferredFunction.d.ts +35 -9
  7. package/components/DeferredFunction.d.ts.map +1 -1
  8. package/components/DeferredFunction.js +53 -1
  9. package/components/DeferredFunction.js.map +1 -1
  10. package/components/Inngest.cjs +24 -1
  11. package/components/Inngest.cjs.map +1 -1
  12. package/components/Inngest.d.cts +23 -3
  13. package/components/Inngest.d.cts.map +1 -1
  14. package/components/Inngest.d.ts +23 -3
  15. package/components/Inngest.d.ts.map +1 -1
  16. package/components/Inngest.js +24 -1
  17. package/components/Inngest.js.map +1 -1
  18. package/components/InngestCommHandler.d.cts +2 -2
  19. package/components/InngestCommHandler.d.ts +2 -2
  20. package/components/InngestFunction.d.cts +2 -2
  21. package/components/InngestFunction.d.ts +2 -2
  22. package/components/InngestGroupTools.cjs +7 -4
  23. package/components/InngestGroupTools.cjs.map +1 -1
  24. package/components/InngestGroupTools.d.cts +5 -24
  25. package/components/InngestGroupTools.d.cts.map +1 -1
  26. package/components/InngestGroupTools.d.ts +5 -24
  27. package/components/InngestGroupTools.d.ts.map +1 -1
  28. package/components/InngestGroupTools.js +7 -4
  29. package/components/InngestGroupTools.js.map +1 -1
  30. package/components/InngestMetadata.cjs +18 -4
  31. package/components/InngestMetadata.cjs.map +1 -1
  32. package/components/InngestMetadata.d.cts +2 -2
  33. package/components/InngestMetadata.d.cts.map +1 -1
  34. package/components/InngestMetadata.d.ts +2 -2
  35. package/components/InngestMetadata.d.ts.map +1 -1
  36. package/components/InngestMetadata.js +18 -5
  37. package/components/InngestMetadata.js.map +1 -1
  38. package/components/InngestScore.cjs +93 -0
  39. package/components/InngestScore.cjs.map +1 -0
  40. package/components/InngestScore.d.cts +89 -0
  41. package/components/InngestScore.d.cts.map +1 -0
  42. package/components/InngestScore.d.ts +89 -0
  43. package/components/InngestScore.d.ts.map +1 -0
  44. package/components/InngestScore.js +88 -0
  45. package/components/InngestScore.js.map +1 -0
  46. package/components/InngestStepTools.cjs +9 -0
  47. package/components/InngestStepTools.cjs.map +1 -1
  48. package/components/InngestStepTools.d.cts +5 -3
  49. package/components/InngestStepTools.d.cts.map +1 -1
  50. package/components/InngestStepTools.d.ts +5 -3
  51. package/components/InngestStepTools.d.ts.map +1 -1
  52. package/components/InngestStepTools.js +9 -0
  53. package/components/InngestStepTools.js.map +1 -1
  54. package/components/ScoreFunction.cjs +37 -0
  55. package/components/ScoreFunction.cjs.map +1 -0
  56. package/components/ScoreFunction.d.cts +24 -0
  57. package/components/ScoreFunction.d.cts.map +1 -0
  58. package/components/ScoreFunction.d.ts +24 -0
  59. package/components/ScoreFunction.d.ts.map +1 -0
  60. package/components/ScoreFunction.js +37 -0
  61. package/components/ScoreFunction.js.map +1 -0
  62. package/components/execution/InngestExecution.d.cts +2 -2
  63. package/components/execution/InngestExecution.d.ts +2 -2
  64. package/components/execution/als.cjs.map +1 -1
  65. package/components/execution/als.d.cts +1 -0
  66. package/components/execution/als.d.cts.map +1 -1
  67. package/components/execution/als.d.ts +1 -0
  68. package/components/execution/als.d.ts.map +1 -1
  69. package/components/execution/als.js.map +1 -1
  70. package/components/execution/engine.cjs +12 -10
  71. package/components/execution/engine.cjs.map +1 -1
  72. package/components/execution/engine.d.cts +1 -1
  73. package/components/execution/engine.d.cts.map +1 -1
  74. package/components/execution/engine.d.ts +1 -1
  75. package/components/execution/engine.d.ts.map +1 -1
  76. package/components/execution/engine.js +13 -11
  77. package/components/execution/engine.js.map +1 -1
  78. package/components/execution/otel/aiExtractor.cjs +90 -125
  79. package/components/execution/otel/aiExtractor.cjs.map +1 -1
  80. package/components/execution/otel/aiExtractor.d.cts +48 -5
  81. package/components/execution/otel/aiExtractor.d.cts.map +1 -1
  82. package/components/execution/otel/aiExtractor.d.ts +48 -5
  83. package/components/execution/otel/aiExtractor.d.ts.map +1 -1
  84. package/components/execution/otel/aiExtractor.js +90 -125
  85. package/components/execution/otel/aiExtractor.js.map +1 -1
  86. package/components/execution/otel/middleware.cjs +3 -1
  87. package/components/execution/otel/middleware.cjs.map +1 -1
  88. package/components/execution/otel/middleware.d.cts +19 -12
  89. package/components/execution/otel/middleware.d.cts.map +1 -1
  90. package/components/execution/otel/middleware.d.ts +19 -12
  91. package/components/execution/otel/middleware.d.ts.map +1 -1
  92. package/components/execution/otel/middleware.js +4 -2
  93. package/components/execution/otel/middleware.js.map +1 -1
  94. package/components/execution/otel/util.cjs +9 -1
  95. package/components/execution/otel/util.cjs.map +1 -1
  96. package/components/execution/otel/util.js +9 -2
  97. package/components/execution/otel/util.js.map +1 -1
  98. package/components/realtime/types.d.cts +3 -3
  99. package/components/realtime/types.d.cts.map +1 -1
  100. package/components/realtime/types.d.ts +3 -3
  101. package/components/realtime/types.d.ts.map +1 -1
  102. package/experimental.cjs +5 -1
  103. package/experimental.d.cts +4 -1
  104. package/experimental.d.ts +4 -1
  105. package/experimental.js +3 -1
  106. package/helpers/consts.cjs +7 -0
  107. package/helpers/consts.cjs.map +1 -1
  108. package/helpers/consts.d.cts.map +1 -1
  109. package/helpers/consts.d.ts.map +1 -1
  110. package/helpers/consts.js +7 -1
  111. package/helpers/consts.js.map +1 -1
  112. package/helpers/errors.cjs +16 -0
  113. package/helpers/errors.cjs.map +1 -1
  114. package/helpers/errors.d.cts +12 -1
  115. package/helpers/errors.d.cts.map +1 -1
  116. package/helpers/errors.d.ts +12 -1
  117. package/helpers/errors.d.ts.map +1 -1
  118. package/helpers/errors.js +16 -1
  119. package/helpers/errors.js.map +1 -1
  120. package/helpers/types.cjs +4 -0
  121. package/helpers/types.cjs.map +1 -1
  122. package/helpers/types.js +4 -1
  123. package/helpers/types.js.map +1 -1
  124. package/index.d.cts +2 -2
  125. package/index.d.ts +2 -2
  126. package/package.json +1 -1
  127. package/types.cjs.map +1 -1
  128. package/types.d.cts +30 -9
  129. package/types.d.cts.map +1 -1
  130. package/types.d.ts +30 -9
  131. package/types.d.ts.map +1 -1
  132. package/types.js.map +1 -1
  133. package/version.cjs +1 -1
  134. package/version.cjs.map +1 -1
  135. package/version.d.cts +1 -1
  136. package/version.d.ts +1 -1
  137. package/version.js +1 -1
  138. package/version.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"InngestGroupTools.js","names":["options: ParallelOptions","nestedCtx: AsyncContext","experiment: GroupExperiment","experimentStepHashedId: string | undefined","selectedVariant: string","currentCtx","selectCtx: AsyncContext","result","result: unknown"],"sources":["../../src/components/InngestGroupTools.ts"],"sourcesContent":["import type { IsNever } from \"../helpers/types.ts\";\nimport type { StepOptionsOrId } from \"../types.ts\";\nimport {\n type AsyncContext,\n getAsyncCtxSync,\n getAsyncLocalStorage,\n isALSFallback,\n} from \"./execution/als.ts\";\nimport { getStepOptions } from \"./InngestStepTools.ts\";\nimport { NonRetriableError } from \"./NonRetriableError.ts\";\n\n/**\n * Options for the `group.parallel()` helper.\n */\nexport interface ParallelOptions {\n /**\n * The parallel mode to apply to all steps created within the callback.\n *\n * - `\"race\"`: Steps will be executed with race semantics, meaning the first\n * step to complete will \"win\" and remaining steps may be cancelled.\n */\n mode?: \"race\";\n}\n\n/**\n * A helper that sets the parallel mode for all steps created within the\n * callback. This allows you to use native `Promise.race()` with cleaner syntax.\n *\n * @example\n * ```ts\n * // Defaults to \"race\" mode\n * const winner = await group.parallel(async () => {\n * return Promise.race([\n * step.run(\"a\", () => \"a\"),\n * step.run(\"b\", () => \"b\"),\n * step.run(\"c\", () => \"c\"),\n * ]);\n * });\n *\n * // Or explicitly specify the mode\n * const winner = await group.parallel({ mode: \"race\" }, async () => {\n * return Promise.race([\n * step.run(\"a\", () => \"a\"),\n * step.run(\"b\", () => \"b\"),\n * ]);\n * });\n * ```\n */\nconst parallel = async <T>(\n optionsOrCallback: ParallelOptions | (() => Promise<T>),\n maybeCallback?: () => Promise<T>,\n): Promise<T> => {\n const options: ParallelOptions =\n typeof optionsOrCallback === \"function\" ? {} : optionsOrCallback;\n const callback =\n typeof optionsOrCallback === \"function\" ? optionsOrCallback : maybeCallback;\n\n if (!callback) {\n throw new Error(\"`group.parallel()` requires a callback function\");\n }\n\n const currentCtx = getAsyncCtxSync();\n\n if (!currentCtx?.execution) {\n throw new Error(\n \"`group.parallel()` must be called within an Inngest function execution\",\n );\n }\n\n const als = await getAsyncLocalStorage();\n\n if (isALSFallback()) {\n throw new Error(\n \"`group.parallel()` requires AsyncLocalStorage support, which is not available in this runtime. \" +\n \"Workaround: Pass `parallelMode` directly to each step:\\n\" +\n ' step.run({ id: \"my-step\", parallelMode: \"race\" }, fn)',\n );\n }\n\n // Create a new context with the parallelMode set\n const nestedCtx: AsyncContext = {\n ...currentCtx,\n execution: {\n ...currentCtx.execution,\n parallelMode: options.mode ?? \"race\",\n },\n };\n\n // Run the callback inside the nested context\n return als.run(nestedCtx, callback);\n};\n\n/**\n * Configuration for how the experiment selects a variant.\n */\nexport interface ExperimentStrategyConfig {\n strategy: string;\n weights?: Record<string, number>;\n nullishBucket?: boolean;\n}\n\n/**\n * A callable selection function that also carries strategy metadata.\n */\nexport interface ExperimentSelectFn {\n (variantNames?: string[]): Promise<string> | string;\n __experimentConfig: ExperimentStrategyConfig;\n}\n\n/**\n * Options for `group.experiment()`.\n */\nexport interface ExperimentOptions<\n TVariants extends Record<string, () => unknown>,\n> {\n /**\n * A map of variant names to callbacks. The selected variant's callback will\n * be executed at the top level so that any `step.*` calls inside it go\n * through normal step discovery.\n */\n variants: TVariants;\n\n /**\n * A selection function that returns the name of the variant to execute.\n * The result is memoized via a step so the same variant is used on retries.\n */\n select: ExperimentSelectFn;\n}\n\n/**\n * Options for `group.experiment()` when `withVariant` is true, which causes\n * the return type to include both the result and the selected variant name.\n */\nexport interface ExperimentOptionsWithVariant<\n TVariants extends Record<string, () => unknown>,\n> extends ExperimentOptions<TVariants> {\n /**\n * When true, the return value includes the variant name alongside the result.\n */\n withVariant: true;\n}\n\n/**\n * Computes the return type of an experiment based on variant callbacks.\n *\n * When `TConstraint` is `never`, the return type is inferred as the union of\n * all variant callback return types. Otherwise `TConstraint` is used directly.\n */\nexport type VariantResult<\n TConstraint,\n TVariants extends Record<string, () => unknown>,\n> = IsNever<TConstraint> extends true\n ? Awaited<ReturnType<TVariants[keyof TVariants]>>\n : TConstraint;\n\n/**\n * Metadata values stored alongside the experiment step for UI rendering.\n */\nexport interface ExperimentMetadataValues {\n name: string;\n variant: string;\n selection_strategy: string;\n available_variants: string[];\n variant_weights?: Record<string, number>;\n}\n\n/**\n * Overloaded interface for `group.experiment()`.\n */\nexport interface GroupExperiment {\n /**\n * Run an A/B experiment that selects and executes a variant. Returns both\n * the result and the selected variant name.\n */\n <TVariants extends Record<string, () => unknown>>(\n idOrOptions: StepOptionsOrId,\n options: ExperimentOptionsWithVariant<TVariants>,\n ): Promise<{\n result: VariantResult<never, TVariants>;\n variant: string;\n }>;\n\n /**\n * Run an A/B experiment that selects and executes a variant. Returns only\n * the variant callback's result.\n */\n <TVariants extends Record<string, () => unknown>>(\n idOrOptions: StepOptionsOrId,\n options: ExperimentOptions<TVariants>,\n ): Promise<VariantResult<never, TVariants>>;\n}\n\n/**\n * Tools for grouping and coordinating steps.\n *\n * @public\n */\nexport interface GroupTools {\n /**\n * Run a callback where all steps automatically receive a `parallelMode`\n * option, removing the need to tag each step individually. Defaults to\n * `\"race\"` mode.\n *\n * @example\n * ```ts\n * // Defaults to \"race\" mode\n * const winner = await group.parallel(async () => {\n * return Promise.race([\n * step.run(\"a\", () => \"a\"),\n * step.run(\"b\", () => \"b\"),\n * step.run(\"c\", () => \"c\"),\n * ]);\n * });\n *\n * // Or explicitly specify the mode\n * const winner = await group.parallel({ mode: \"race\" }, async () => {\n * return Promise.race([\n * step.run(\"a\", () => \"a\"),\n * step.run(\"b\", () => \"b\"),\n * ]);\n * });\n * ```\n */\n parallel: <T>(\n optionsOrCallback: ParallelOptions | (() => Promise<T>),\n maybeCallback?: () => Promise<T>,\n ) => Promise<T>;\n\n /**\n * Run an A/B experiment within a function. Selects a variant via a memoized\n * step, then executes the selected variant's callback at the top level so\n * its `step.*` calls go through normal step discovery.\n *\n * @example\n * ```ts\n * const result = await group.experiment(\"checkout-flow\", {\n * variants: {\n * control: () => step.run(\"control-checkout\", () => oldCheckout()),\n * new_flow: () => step.run(\"new-checkout\", () => newCheckout()),\n * },\n * select: Object.assign(() => \"control\", {\n * __experimentConfig: { strategy: \"weighted\", weights: { control: 80, new_flow: 20 } },\n * }),\n * });\n * ```\n */\n experiment: GroupExperiment;\n}\n\n/**\n * Dependencies injected into `createGroupTools` from the execution engine.\n */\nexport interface GroupToolsDeps {\n /**\n * A `step.run` variant with `opts.type = \"group.experiment\"`, extracted from\n * step tools via the experiment symbol. Undefined when not available.\n */\n // biome-ignore lint/suspicious/noExplicitAny: internal plumbing\n experimentStepRun?: (...args: any[]) => Promise<any>;\n}\n\n/**\n * Create the `group` tools object provided on the function execution context.\n *\n * @public\n */\nexport const createGroupTools = (deps?: GroupToolsDeps): GroupTools => {\n const experiment: GroupExperiment = (async (\n idOrOptions: StepOptionsOrId,\n // biome-ignore lint/suspicious/noExplicitAny: implementation signature for overloaded interface\n options: any,\n ) => {\n if (!deps?.experimentStepRun) {\n throw new Error(\n \"`group.experiment()` requires step tools to be available. \" +\n \"Ensure you are calling this within an Inngest function execution.\",\n );\n }\n\n const { variants, select, withVariant } = options;\n const variantNames = Object.keys(variants);\n\n if (variantNames.length === 0) {\n throw new Error(\n \"`group.experiment()` requires at least one variant to be defined.\",\n );\n }\n\n if (isALSFallback()) {\n throw new Error(\n \"`group.experiment()` requires AsyncLocalStorage support, which is not available in this runtime.\",\n );\n }\n\n const stepOpts = getStepOptions(idOrOptions);\n\n // Use the experiment step run to memoize the variant selection.\n // This creates a StepPlanned opcode with opts.type = \"group.experiment\".\n let experimentStepHashedId: string | undefined;\n\n const selectedVariant: string = await deps.experimentStepRun(\n idOrOptions,\n async () => {\n // Capture the hashed step ID so we can propagate it to variant sub-steps.\n experimentStepHashedId =\n getAsyncCtxSync()?.execution?.executingStep?.id;\n\n const alsInstance = await getAsyncLocalStorage();\n const currentCtx = getAsyncCtxSync()!;\n const selectCtx: AsyncContext = {\n ...currentCtx,\n execution: {\n ...currentCtx.execution!,\n insideExperimentSelect: true,\n },\n };\n const result = await alsInstance.run(selectCtx, () =>\n select(variantNames),\n );\n\n if (!variantNames.includes(result)) {\n throw new NonRetriableError(\n `group.experiment(\"${stepOpts.id}\"): select() returned \"${result}\" ` +\n `which is not a known variant. Available variants: ${variantNames.join(\", \")}`,\n );\n }\n\n // Attach experiment metadata to this step's OutgoingOp.\n const ctx = getAsyncCtxSync();\n const execInstance = ctx?.execution?.instance;\n\n if (execInstance && experimentStepHashedId) {\n execInstance.addMetadata(\n experimentStepHashedId,\n \"inngest.experiment\",\n \"step\",\n \"merge\",\n {\n name: stepOpts.id,\n variant: result,\n selection_strategy: select.__experimentConfig.strategy,\n available_variants: variantNames,\n ...(select.__experimentConfig.weights && {\n variant_weights: select.__experimentConfig.weights,\n }),\n } satisfies ExperimentMetadataValues,\n );\n\n if (select.__experimentConfig.nullishBucket) {\n execInstance.addMetadata(\n experimentStepHashedId,\n \"inngest.warnings\",\n \"step\",\n \"merge\",\n {\n \"sdk.group.experiment.nullishBucket\":\n \"experiment.bucket() received a null/undefined value; \" +\n 'hashing empty string \"\" for variant selection',\n },\n );\n }\n }\n\n return result;\n },\n );\n\n // Look up and execute the selected variant's callback at the top level\n // so its step.* calls go through normal step discovery.\n const variantFn = variants[selectedVariant];\n\n if (!variantFn) {\n throw new Error(\n `group.experiment(\"${stepOpts.id}\"): variant \"${selectedVariant}\" ` +\n `was selected but is not defined. Available variants: ${variantNames.join(\", \")}`,\n );\n }\n\n // Propagate experiment context via ALS so variant sub-steps include\n // experiment fields in their OutgoingOp.opts. The executor reads these\n // fields from opts and emits the step-scoped `inngest.experiment`\n // metadata span itself — the SDK does not need to call addMetadata()\n // for variant steps. See the companion executor change in inngest/inngest\n // for the server-side emission path.\n //\n // Also track whether any step tool is invoked to detect zero-step\n // variants.\n //\n // NOTE: experimentStepHashedId may be undefined on replay because it\n // is captured inside the selection step callback, which doesn't run\n // when memoized. We still set experimentContext (with an empty string\n // for the hashed ID fallback) so that variant sub-steps discovered on\n // replay still carry experiment fields in their opts and the executor\n // can attach metadata to their ClickHouse rows.\n const currentCtx = getAsyncCtxSync();\n const stepTracker = { found: false };\n let result: unknown;\n\n if (currentCtx?.execution && !isALSFallback()) {\n const als = await getAsyncLocalStorage();\n const nestedCtx: AsyncContext = {\n ...currentCtx,\n execution: {\n ...currentCtx.execution,\n experimentContext: {\n experimentStepID: experimentStepHashedId ?? \"\",\n experimentName: stepOpts.id,\n variant: selectedVariant,\n selectionStrategy: select.__experimentConfig.strategy,\n },\n experimentStepTracker: stepTracker,\n },\n };\n result = await als.run(nestedCtx, () => variantFn());\n } else {\n result = await variantFn();\n }\n\n // If the variant returned without invoking any step tools, it will\n // silently re-execute on every replay. Throw a non-retriable error\n // to prevent this.\n if (!stepTracker.found && !isALSFallback()) {\n throw new NonRetriableError(\n `group.experiment(\"${stepOpts.id}\"): variant \"${selectedVariant}\" ` +\n \"did not invoke any step tools. Wrap your variant logic in \" +\n \"step.run() to ensure it is memoized and not re-executed on replay.\",\n );\n }\n\n if (withVariant) {\n return { result, variant: selectedVariant };\n }\n\n return result;\n }) as GroupExperiment;\n\n return { parallel, experiment };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"InngestGroupTools.js","names":["options: ParallelOptions","nestedCtx: AsyncContext","experiment: GroupExperiment","experimentStepHashedId: string | undefined","selectedVariant: string","currentCtx","selectCtx: AsyncContext","result","result: unknown"],"sources":["../../src/components/InngestGroupTools.ts"],"sourcesContent":["import type { IsNever } from \"../helpers/types.ts\";\nimport type { ExperimentRef, StepOptionsOrId } from \"../types.ts\";\nimport {\n type AsyncContext,\n getAsyncCtxSync,\n getAsyncLocalStorage,\n isALSFallback,\n} from \"./execution/als.ts\";\nimport { getStepOptions } from \"./InngestStepTools.ts\";\nimport { NonRetriableError } from \"./NonRetriableError.ts\";\n\n/**\n * Options for the `group.parallel()` helper.\n */\nexport interface ParallelOptions {\n /**\n * The parallel mode to apply to all steps created within the callback.\n *\n * - `\"race\"`: Steps will be executed with race semantics, meaning the first\n * step to complete will \"win\" and remaining steps may be cancelled.\n */\n mode?: \"race\";\n}\n\n/**\n * A helper that sets the parallel mode for all steps created within the\n * callback. This allows you to use native `Promise.race()` with cleaner syntax.\n *\n * @example\n * ```ts\n * // Defaults to \"race\" mode\n * const winner = await group.parallel(async () => {\n * return Promise.race([\n * step.run(\"a\", () => \"a\"),\n * step.run(\"b\", () => \"b\"),\n * step.run(\"c\", () => \"c\"),\n * ]);\n * });\n *\n * // Or explicitly specify the mode\n * const winner = await group.parallel({ mode: \"race\" }, async () => {\n * return Promise.race([\n * step.run(\"a\", () => \"a\"),\n * step.run(\"b\", () => \"b\"),\n * ]);\n * });\n * ```\n */\nconst parallel = async <T>(\n optionsOrCallback: ParallelOptions | (() => Promise<T>),\n maybeCallback?: () => Promise<T>,\n): Promise<T> => {\n const options: ParallelOptions =\n typeof optionsOrCallback === \"function\" ? {} : optionsOrCallback;\n const callback =\n typeof optionsOrCallback === \"function\" ? optionsOrCallback : maybeCallback;\n\n if (!callback) {\n throw new Error(\"`group.parallel()` requires a callback function\");\n }\n\n const currentCtx = getAsyncCtxSync();\n\n if (!currentCtx?.execution) {\n throw new Error(\n \"`group.parallel()` must be called within an Inngest function execution\",\n );\n }\n\n const als = await getAsyncLocalStorage();\n\n if (isALSFallback()) {\n throw new Error(\n \"`group.parallel()` requires AsyncLocalStorage support, which is not available in this runtime. \" +\n \"Workaround: Pass `parallelMode` directly to each step:\\n\" +\n ' step.run({ id: \"my-step\", parallelMode: \"race\" }, fn)',\n );\n }\n\n // Create a new context with the parallelMode set\n const nestedCtx: AsyncContext = {\n ...currentCtx,\n execution: {\n ...currentCtx.execution,\n parallelMode: options.mode ?? \"race\",\n },\n };\n\n // Run the callback inside the nested context\n return als.run(nestedCtx, callback);\n};\n\n/**\n * Configuration for how the experiment selects a variant.\n */\nexport interface ExperimentStrategyConfig {\n strategy: string;\n weights?: Record<string, number>;\n nullishBucket?: boolean;\n}\n\n/**\n * A callable selection function that also carries strategy metadata.\n */\nexport interface ExperimentSelectFn {\n (variantNames?: string[]): Promise<string> | string;\n __experimentConfig: ExperimentStrategyConfig;\n}\n\n/**\n * Options for `group.experiment()`.\n */\nexport interface ExperimentOptions<\n TVariants extends Record<string, () => unknown>,\n> {\n /**\n * A map of variant names to callbacks. The selected variant's callback will\n * be executed at the top level so that any `step.*` calls inside it go\n * through normal step discovery.\n */\n variants: TVariants;\n\n /**\n * A selection function that returns the name of the variant to execute.\n * The result is memoized via a step so the same variant is used on retries.\n */\n select: ExperimentSelectFn;\n}\n\n/**\n * Computes the return type of an experiment based on variant callbacks.\n *\n * When `TConstraint` is `never`, the return type is inferred as the union of\n * all variant callback return types. Otherwise `TConstraint` is used directly.\n */\nexport type VariantResult<\n TConstraint,\n TVariants extends Record<string, () => unknown>,\n> = IsNever<TConstraint> extends true\n ? Awaited<ReturnType<TVariants[keyof TVariants]>>\n : TConstraint;\n\n/**\n * Metadata values stored alongside the experiment step for UI rendering.\n */\nexport interface ExperimentMetadataValues {\n name: string;\n variant: string;\n selection_strategy: string;\n available_variants: string[];\n variant_weights?: Record<string, number>;\n}\n\n/** Run an A/B experiment; returns the result, the selected variant, and a\n * replay-stable handle for scoring the experiment later. */\nexport interface GroupExperiment {\n <TVariants extends Record<string, () => unknown>>(\n idOrOptions: StepOptionsOrId,\n options: ExperimentOptions<TVariants>,\n ): Promise<{\n result: VariantResult<never, TVariants>;\n variant: string;\n experimentRef: ExperimentRef;\n }>;\n}\n\n/**\n * Tools for grouping and coordinating steps.\n *\n * @public\n */\nexport interface GroupTools {\n /**\n * Run a callback where all steps automatically receive a `parallelMode`\n * option, removing the need to tag each step individually. Defaults to\n * `\"race\"` mode.\n *\n * @example\n * ```ts\n * // Defaults to \"race\" mode\n * const winner = await group.parallel(async () => {\n * return Promise.race([\n * step.run(\"a\", () => \"a\"),\n * step.run(\"b\", () => \"b\"),\n * step.run(\"c\", () => \"c\"),\n * ]);\n * });\n *\n * // Or explicitly specify the mode\n * const winner = await group.parallel({ mode: \"race\" }, async () => {\n * return Promise.race([\n * step.run(\"a\", () => \"a\"),\n * step.run(\"b\", () => \"b\"),\n * ]);\n * });\n * ```\n */\n parallel: <T>(\n optionsOrCallback: ParallelOptions | (() => Promise<T>),\n maybeCallback?: () => Promise<T>,\n ) => Promise<T>;\n\n /**\n * Run an A/B experiment within a function. Selects a variant via a memoized\n * step, then executes the selected variant's callback at the top level so\n * its `step.*` calls go through normal step discovery.\n *\n * @example\n * ```ts\n * const result = await group.experiment(\"checkout-flow\", {\n * variants: {\n * control: () => step.run(\"control-checkout\", () => oldCheckout()),\n * new_flow: () => step.run(\"new-checkout\", () => newCheckout()),\n * },\n * select: Object.assign(() => \"control\", {\n * __experimentConfig: { strategy: \"weighted\", weights: { control: 80, new_flow: 20 } },\n * }),\n * });\n * ```\n */\n experiment: GroupExperiment;\n}\n\n/**\n * Dependencies injected into `createGroupTools` from the execution engine.\n */\nexport interface GroupToolsDeps {\n /**\n * A `step.run` variant with `opts.type = \"group.experiment\"`, extracted from\n * step tools via the experiment symbol. Undefined when not available.\n */\n // biome-ignore lint/suspicious/noExplicitAny: internal plumbing\n experimentStepRun?: (...args: any[]) => Promise<any>;\n}\n\n/**\n * Create the `group` tools object provided on the function execution context.\n *\n * @public\n */\nexport const createGroupTools = (deps?: GroupToolsDeps): GroupTools => {\n const experiment: GroupExperiment = (async (\n idOrOptions: StepOptionsOrId,\n // biome-ignore lint/suspicious/noExplicitAny: implementation signature for overloaded interface\n options: any,\n ) => {\n if (!deps?.experimentStepRun) {\n throw new Error(\n \"`group.experiment()` requires step tools to be available. \" +\n \"Ensure you are calling this within an Inngest function execution.\",\n );\n }\n\n const { variants, select } = options;\n const variantNames = Object.keys(variants);\n\n if (variantNames.length === 0) {\n throw new Error(\n \"`group.experiment()` requires at least one variant to be defined.\",\n );\n }\n\n if (isALSFallback()) {\n throw new Error(\n \"`group.experiment()` requires AsyncLocalStorage support, which is not available in this runtime.\",\n );\n }\n\n const stepOpts = getStepOptions(idOrOptions);\n\n // Use the experiment step run to memoize the variant selection.\n // This creates a StepPlanned opcode with opts.type = \"group.experiment\".\n let experimentStepHashedId: string | undefined;\n\n const selectedVariant: string = await deps.experimentStepRun(\n idOrOptions,\n async () => {\n // Capture the hashed step ID so we can propagate it to variant sub-steps.\n experimentStepHashedId =\n getAsyncCtxSync()?.execution?.executingStep?.id;\n\n const alsInstance = await getAsyncLocalStorage();\n const currentCtx = getAsyncCtxSync()!;\n const selectCtx: AsyncContext = {\n ...currentCtx,\n execution: {\n ...currentCtx.execution!,\n insideExperimentSelect: true,\n },\n };\n const result = await alsInstance.run(selectCtx, () =>\n select(variantNames),\n );\n\n if (!variantNames.includes(result)) {\n throw new NonRetriableError(\n `group.experiment(\"${stepOpts.id}\"): select() returned \"${result}\" ` +\n `which is not a known variant. Available variants: ${variantNames.join(\", \")}`,\n );\n }\n\n // Attach experiment metadata to this step's OutgoingOp.\n const ctx = getAsyncCtxSync();\n const execInstance = ctx?.execution?.instance;\n\n if (execInstance && experimentStepHashedId) {\n execInstance.addMetadata(\n experimentStepHashedId,\n \"inngest.experiment\",\n \"step\",\n \"merge\",\n {\n name: stepOpts.id,\n variant: result,\n selection_strategy: select.__experimentConfig.strategy,\n available_variants: variantNames,\n ...(select.__experimentConfig.weights && {\n variant_weights: select.__experimentConfig.weights,\n }),\n } satisfies ExperimentMetadataValues,\n );\n\n if (select.__experimentConfig.nullishBucket) {\n execInstance.addMetadata(\n experimentStepHashedId,\n \"inngest.warnings\",\n \"step\",\n \"merge\",\n {\n \"sdk.group.experiment.nullishBucket\":\n \"experiment.bucket() received a null/undefined value; \" +\n 'hashing empty string \"\" for variant selection',\n },\n );\n }\n }\n\n return result;\n },\n );\n\n // Look up and execute the selected variant's callback at the top level\n // so its step.* calls go through normal step discovery.\n const variantFn = variants[selectedVariant];\n\n if (!variantFn) {\n throw new Error(\n `group.experiment(\"${stepOpts.id}\"): variant \"${selectedVariant}\" ` +\n `was selected but is not defined. Available variants: ${variantNames.join(\", \")}`,\n );\n }\n\n // Propagate experiment context via ALS so variant sub-steps include\n // experiment fields in their OutgoingOp.opts. The executor reads these\n // fields from opts and emits the step-scoped `inngest.experiment`\n // metadata span itself — the SDK does not need to call addMetadata()\n // for variant steps. See the companion executor change in inngest/inngest\n // for the server-side emission path.\n //\n // Also track whether any step tool is invoked to detect zero-step\n // variants.\n //\n // NOTE: experimentStepHashedId may be undefined on replay because it\n // is captured inside the selection step callback, which doesn't run\n // when memoized. We still set experimentContext (with an empty string\n // for the hashed ID fallback) so that variant sub-steps discovered on\n // replay still carry experiment fields in their opts and the executor\n // can attach metadata to their ClickHouse rows.\n const currentCtx = getAsyncCtxSync();\n const stepTracker = { found: false };\n let result: unknown;\n\n if (currentCtx?.execution && !isALSFallback()) {\n const als = await getAsyncLocalStorage();\n const nestedCtx: AsyncContext = {\n ...currentCtx,\n execution: {\n ...currentCtx.execution,\n experimentContext: {\n experimentStepID: experimentStepHashedId ?? \"\",\n experimentName: stepOpts.id,\n variant: selectedVariant,\n selectionStrategy: select.__experimentConfig.strategy,\n },\n experimentStepTracker: stepTracker,\n },\n };\n result = await als.run(nestedCtx, () => variantFn());\n } else {\n result = await variantFn();\n }\n\n // If the variant returned without invoking any step tools, it will\n // silently re-execute on every replay. Throw a non-retriable error\n // to prevent this.\n if (!stepTracker.found && !isALSFallback()) {\n throw new NonRetriableError(\n `group.experiment(\"${stepOpts.id}\"): variant \"${selectedVariant}\" ` +\n \"did not invoke any step tools. Wrap your variant logic in \" +\n \"step.run() to ensure it is memoized and not re-executed on replay.\",\n );\n }\n\n return {\n result,\n variant: selectedVariant,\n experimentRef: { experimentName: stepOpts.id, variant: selectedVariant },\n };\n }) as GroupExperiment;\n\n return { parallel, experiment };\n};\n"],"mappings":"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"}
@@ -64,18 +64,19 @@ function buildTarget(config, ctx) {
64
64
  const stepCtxReason = !ctxExecution ? "no function execution context is available" : !ctxExecution.executingStep ? "you are not inside a step.run() callback" : "you are targeting a different run";
65
65
  if (config.attempt === null && (!isSameRunAsCtx || !ctxExecution?.executingStep)) throw new Error(`attempt() was called without a value, but ${stepCtxReason}`);
66
66
  if (config.stepId === null && (!isSameRunAsCtx || !ctxExecution?.executingStep)) throw new Error(`step() was called without a value, but ${stepCtxReason}`);
67
+ const targetAttempt = config.attempt !== void 0 ? config.attempt ?? ctxAttempt : config.stepId === null ? ctxAttempt : void 0;
67
68
  if (config.spanId !== void 0) return {
68
69
  run_id: targetRunId,
69
70
  step_id: config.stepId ?? ctxStepId,
70
71
  step_index: config.stepIndex,
71
- step_attempt: config.attempt ?? ctxAttempt,
72
+ step_attempt: targetAttempt ?? (config.stepId === void 0 ? ctxAttempt : void 0),
72
73
  span_id: config.spanId
73
74
  };
74
75
  else if (config.stepId !== void 0) return {
75
76
  run_id: targetRunId,
76
77
  step_id: config.stepId ?? ctxStepId,
77
78
  step_index: config.stepIndex,
78
- step_attempt: config.attempt ?? ctxAttempt
79
+ step_attempt: targetAttempt
79
80
  };
80
81
  else if (config.runId !== void 0) return { run_id: targetRunId };
81
82
  else if (ctxStepId && ctxAttempt !== void 0) return {
@@ -112,6 +113,19 @@ function getBatchScope(config) {
112
113
  if (config.runId !== void 0) return "run";
113
114
  return "step";
114
115
  }
116
+ function targetsCurrentStep(config, ctx) {
117
+ const executingStep = ctx?.execution?.executingStep;
118
+ if (!executingStep) return false;
119
+ const targetStepId = config.stepId;
120
+ const currentUserlandStepId = executingStep.userlandId;
121
+ if (targetStepId === void 0) return true;
122
+ if (targetStepId === null) return true;
123
+ return targetStepId === currentUserlandStepId;
124
+ }
125
+ /**
126
+ * Internal metadata write helper shared by metadata and score helpers.
127
+ * @internal
128
+ */
115
129
  async function performOp(client, config, values, kind, op) {
116
130
  const ctx = await require_als.getAsyncCtx();
117
131
  const target = buildTarget(config, ctx);
@@ -119,9 +133,8 @@ async function performOp(client, config, values, kind, op) {
119
133
  const isInsideStep = !!ctx?.execution?.executingStep;
120
134
  if (isInsideRun && !isInsideStep) client[require_Inngest.internalLoggerSymbol].warn("metadata.update() called outside of a step; this metadata may be lost on retries. Wrap the call in step.run() for durable metadata.");
121
135
  const runId = config.runId ?? ctx?.execution?.ctx?.runId;
122
- const stepId = config.stepId ?? ctx?.execution?.executingStep?.id;
123
136
  const attempt = config.attempt ?? ctx?.execution?.ctx?.attempt;
124
- if (runId === ctx?.execution?.ctx?.runId && stepId === ctx?.execution?.executingStep?.id && attempt === ctx?.execution?.ctx?.attempt && !config.spanId) {
137
+ if (runId === ctx?.execution?.ctx?.runId && targetsCurrentStep(config, ctx) && attempt === ctx?.execution?.ctx?.attempt && !config.spanId) {
125
138
  const executingStep = ctx?.execution?.executingStep;
126
139
  const execInstance = ctx?.execution?.instance;
127
140
  const scope = getBatchScope(config);
@@ -169,4 +182,5 @@ const metadataMiddleware = () => {
169
182
  exports.UnscopedMetadataBuilder = UnscopedMetadataBuilder;
170
183
  exports.metadataMiddleware = metadataMiddleware;
171
184
  exports.metadataSymbol = metadataSymbol;
185
+ exports.performOp = performOp;
172
186
  //# sourceMappingURL=InngestMetadata.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"InngestMetadata.cjs","names":["client: Inngest","config: BuilderConfig","getAsyncCtx","internalLoggerSymbol","Middleware"],"sources":["../../src/components/InngestMetadata.ts"],"sourcesContent":["import type { Simplify } from \"../helpers/types.ts\";\nimport type { MetadataTarget } from \"../types.ts\";\nimport { type AsyncContext, getAsyncCtx } from \"./execution/als.ts\";\nimport { type Inngest, internalLoggerSymbol } from \"./Inngest.ts\";\nimport type { ExperimentalStepTools } from \"./InngestStepTools.ts\";\nimport { Middleware } from \"./middleware/middleware.ts\";\n\n/**\n * The level at which to attach the metadata.\n */\nexport type MetadataScope = \"run\" | \"step\" | \"extended_trace\";\n\n/**\n * Metadata of the same kind attached to the same item at the same scope are combined.\n */\nexport type MetadataKind =\n | \"inngest.experiment\"\n | \"inngest.warnings\"\n | \"inngest.ai\"\n | `userland.${string}`;\n\n/**\n * The operation use to combine multiple metadata updates of the same kind.\n */\nexport type MetadataOpcode = \"merge\";\n\n/**\n * A metadata update containing `values` to be merged according to `op`\n * at the configured `scope` for the configured `kind`.\n */\nexport type MetadataUpdate = {\n kind: MetadataKind;\n scope: MetadataScope;\n op: MetadataOpcode;\n values: MetadataValues;\n};\n\nexport type MetadataValues = Record<string, unknown>;\n\ninterface BuilderConfig {\n runId?: string | null;\n stepId?: string | null;\n stepIndex?: number;\n attempt?: number | null;\n spanId?: string;\n}\n\n/**\n * Configures and sends metadata updates.\n *\n * This is used to limit the available methods as target is\n * configured and the specified scope narrows.\n */\nexport type MetadataBuilder<Extras = {}> = Simplify<\n {\n /**\n * Sets the metadata context to a specific (or current if omitted) run.\n */\n run(id?: string): Simplify<Omit<MetadataBuilder<Extras>, \"run\">>;\n\n /**\n * Sets the metadata context to a specific (or current if omitted) step.\n */\n step(\n id?: string,\n index?: number,\n ): Simplify<Omit<MetadataBuilder<Extras>, \"run\" | \"step\">>;\n\n /**\n * Sets the metadata context to a specific (or current if omitted) step attempt.\n */\n attempt(\n index?: number,\n ): Simplify<Omit<MetadataBuilder<Extras>, \"run\" | \"step\" | \"attempt\">>;\n\n /**\n * Sets the metadata context to a specific span.\n */\n span(\n id: string,\n ): Simplify<\n Omit<MetadataBuilder<Extras>, \"run\" | \"step\" | \"attempt\" | \"span\">\n >;\n\n /**\n * Attach metadata to the configured run/step/step attempt/span.\n *\n * By default it will attach metadata to the current run if\n * executed inside the body of `createFunction` or to the\n * current step attempt if executed inside `step.run`.\n */\n update(values: Record<string, unknown>, kind?: string): Promise<void>;\n } & Extras\n>;\n\n/**\n * A wrapper around `MetadataBuilder` to attach metadata as a step.\n */\nexport type MetadataStepTool = MetadataBuilder<{\n /**\n * Allows many `updates` to be sent with the same scope.\n */\n do: (fn: (builder: MetadataBuilder) => Promise<void>) => Promise<void>;\n}>;\n\n/**\n * Configures and sends metadata updates.\n *\n * It sends metadata updates via step opcodes if the metadata is\n * configured to be attached to the current run/step/step attempt\n * and `update` is called inside of `step.run`.\n *\n * Otherwise it sends updates via the Inngest API.\n */\nexport class UnscopedMetadataBuilder implements MetadataBuilder {\n constructor(\n private client: Inngest,\n private config: BuilderConfig = {},\n ) {}\n\n run(id?: string): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n runId: id ?? null,\n });\n }\n\n step(id?: string, index?: number): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n stepId: id ?? null,\n stepIndex: index ?? 0,\n });\n }\n\n attempt(attempt?: number): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n attempt: attempt ?? null,\n });\n }\n\n span(id: string): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n spanId: id,\n });\n }\n\n async update(\n values: Record<string, unknown>,\n kind: string = \"default\",\n ): Promise<void> {\n await performOp(\n this.client,\n this.config,\n values,\n `userland.${kind}`,\n \"merge\",\n );\n }\n\n toJSON() {\n return this.config;\n }\n}\n\n/**\n * Creates a `MetadataTarget` based on the current execution context and the `BuilderConfig` created using\n * `MetadataBuilder`.\n */\nexport function buildTarget(\n config: BuilderConfig,\n ctx?: AsyncContext,\n): MetadataTarget {\n const ctxExecution = ctx?.execution;\n const ctxRunId = ctxExecution?.ctx?.runId;\n const ctxStepId = ctxExecution?.executingStep?.id;\n const ctxAttempt = ctxExecution?.ctx?.attempt;\n const targetRunId = config.runId ?? ctxRunId;\n if (!targetRunId) throw new Error(\"No run context available\");\n\n const isSameRunAsCtx = ctxRunId !== undefined && targetRunId === ctxRunId;\n\n const stepCtxReason = !ctxExecution\n ? \"no function execution context is available\"\n : !ctxExecution.executingStep\n ? \"you are not inside a step.run() callback\"\n : \"you are targeting a different run\";\n\n if (\n config.attempt === null &&\n (!isSameRunAsCtx || !ctxExecution?.executingStep)\n )\n throw new Error(\n `attempt() was called without a value, but ${stepCtxReason}`,\n );\n if (\n config.stepId === null &&\n (!isSameRunAsCtx || !ctxExecution?.executingStep)\n )\n throw new Error(`step() was called without a value, but ${stepCtxReason}`);\n\n if (config.spanId !== undefined) {\n return {\n run_id: targetRunId,\n step_id: config.stepId ?? ctxStepId,\n step_index: config.stepIndex,\n step_attempt: config.attempt ?? ctxAttempt,\n span_id: config.spanId,\n };\n } else if (config.stepId !== undefined) {\n return {\n run_id: targetRunId,\n step_id: config.stepId ?? ctxStepId,\n step_index: config.stepIndex,\n step_attempt: config.attempt ?? ctxAttempt,\n };\n } else if (config.runId !== undefined) {\n return {\n run_id: targetRunId,\n };\n } else if (ctxStepId && ctxAttempt !== undefined) {\n return {\n run_id: targetRunId,\n step_id: ctxStepId,\n step_attempt: ctxAttempt,\n };\n } else {\n return {\n run_id: targetRunId,\n };\n }\n}\n\n/**\n * Creates a metadata array payload for API calls.\n */\nexport function createMetadataPayload(\n kind: string,\n op: MetadataOpcode,\n metadata: Record<string, unknown>,\n) {\n return [\n {\n kind,\n op,\n values: metadata,\n },\n ];\n}\n\n/**\n * Sends metadata update via REST API to a specific target.\n */\nexport async function sendMetadataViaAPI(\n client: Inngest,\n target: MetadataTarget,\n kind: string,\n op: MetadataOpcode,\n metadata: Record<string, unknown>,\n headers?: Record<string, string>,\n): Promise<void> {\n const metadataArray = createMetadataPayload(kind, op, metadata);\n\n await client[\"updateMetadata\"]({\n target,\n metadata: metadataArray,\n headers,\n });\n}\n\nfunction getBatchScope(config: BuilderConfig): MetadataScope {\n if (config.spanId !== undefined) return \"extended_trace\";\n if (config.stepId !== undefined) return \"step\";\n if (config.runId !== undefined) return \"run\";\n\n return \"step\";\n}\n\nasync function performOp(\n client: Inngest,\n config: BuilderConfig,\n values: Record<string, unknown>,\n kind: MetadataKind,\n op: MetadataOpcode,\n): Promise<void> {\n const ctx = await getAsyncCtx();\n const target = buildTarget(config, ctx);\n\n const isInsideRun = !!ctx?.execution;\n const isInsideStep = !!ctx?.execution?.executingStep;\n if (isInsideRun && !isInsideStep) {\n client[internalLoggerSymbol].warn(\n \"metadata.update() called outside of a step; this metadata may be lost on retries. Wrap the call in step.run() for durable metadata.\",\n );\n }\n\n const runId = config.runId ?? ctx?.execution?.ctx?.runId;\n const stepId = config.stepId ?? ctx?.execution?.executingStep?.id;\n // TODO: get step index from ctx?\n const attempt = config.attempt ?? ctx?.execution?.ctx?.attempt;\n\n // We can batch metadata if we're updating the current run\n const canBatch =\n runId === ctx?.execution?.ctx?.runId &&\n stepId === ctx?.execution?.executingStep?.id &&\n attempt === ctx?.execution?.ctx?.attempt &&\n !config.spanId;\n\n if (canBatch) {\n const executingStep = ctx?.execution?.executingStep;\n const execInstance = ctx?.execution?.instance;\n const scope = getBatchScope(config);\n\n if (\n executingStep?.id &&\n execInstance &&\n execInstance.addMetadata(executingStep.id, kind, scope, op, values)\n ) {\n return;\n }\n }\n\n const headers =\n (\n ctx?.execution?.instance as\n | { options?: { headers?: Record<string, string> } }\n | undefined\n )?.options?.headers ?? undefined;\n\n await sendMetadataViaAPI(client, target, kind, op, values, headers);\n}\n\nexport const metadataSymbol = Symbol.for(\"inngest.step.metadata\");\n\n/**\n * Middleware that enables the experimental step.metadata() feature.\n *\n * @example\n * ```ts\n * import { metadataMiddleware } from \"inngest/experimental\";\n *\n * const inngest = new Inngest({\n * id: \"my-app\",\n * middleware: [metadataMiddleware()],\n * });\n * ```\n */\nexport const metadataMiddleware = () => {\n class MetadataMiddleware extends Middleware.BaseMiddleware {\n readonly id = \"inngest:metadata\";\n\n static override onRegister({ client }: Middleware.OnRegisterArgs) {\n client[\"experimentalMetadataEnabled\"] = true;\n }\n\n override transformFunctionInput(\n arg: Middleware.TransformFunctionInputArgs,\n ): Middleware.TransformFunctionInputArgs & {\n ctx: {\n step: {\n /**\n * Create a durable metadata update wrapped in a step\n *\n * @param memoizationId - The step ID used for the step itself, ensuring the\n * metadata update is only performed once even on function retries.\n *\n * @example\n * ```ts\n * // Update metadata for the current run\n * await step.metadata(\"update-status\").update({ status: \"processing\" });\n *\n * // Update metadata for a different run\n * await step.metadata(\"notify-parent\")\n * .run(parentRunId)\n * .update({ childCompleted: true });\n * ```\n */\n metadata: ExperimentalStepTools[typeof metadataSymbol];\n };\n };\n } {\n return {\n ...arg,\n ctx: {\n ...arg.ctx,\n step: {\n ...arg.ctx.step,\n // Access the hidden symbol-keyed metadata tool from step tools\n metadata: (arg.ctx.step as unknown as ExperimentalStepTools)[\n metadataSymbol\n ],\n },\n },\n };\n }\n }\n\n return MetadataMiddleware;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"InngestMetadata.cjs","names":["client: Inngest","config: BuilderConfig","getAsyncCtx","internalLoggerSymbol","Middleware"],"sources":["../../src/components/InngestMetadata.ts"],"sourcesContent":["import type { Simplify } from \"../helpers/types.ts\";\nimport type { MetadataTarget } from \"../types.ts\";\nimport { type AsyncContext, getAsyncCtx } from \"./execution/als.ts\";\nimport { type Inngest, internalLoggerSymbol } from \"./Inngest.ts\";\nimport type { ExperimentalStepTools } from \"./InngestStepTools.ts\";\nimport { Middleware } from \"./middleware/middleware.ts\";\n\n/**\n * The level at which to attach the metadata.\n */\nexport type MetadataScope = \"run\" | \"step\" | \"extended_trace\";\n\n/**\n * Metadata of the same kind attached to the same item at the same scope are combined.\n */\nexport type MetadataKind =\n | \"inngest.experiment\"\n | \"inngest.score\"\n | \"inngest.warnings\"\n | \"inngest.ai\"\n | `userland.${string}`;\n\n/**\n * The operation use to combine multiple metadata updates of the same kind.\n */\nexport type MetadataOpcode = \"merge\";\n\n/**\n * A metadata update containing `values` to be merged according to `op`\n * at the configured `scope` for the configured `kind`.\n */\nexport type MetadataUpdate = {\n kind: MetadataKind;\n scope: MetadataScope;\n op: MetadataOpcode;\n values: MetadataValues;\n};\n\nexport type MetadataValues = Record<string, unknown>;\n\n/**\n * Internal metadata target config shared by metadata and score helpers.\n * @internal\n */\nexport interface BuilderConfig {\n runId?: string | null;\n stepId?: string | null;\n stepIndex?: number;\n attempt?: number | null;\n spanId?: string;\n}\n\n/**\n * Configures and sends metadata updates.\n *\n * This is used to limit the available methods as target is\n * configured and the specified scope narrows.\n */\nexport type MetadataBuilder<Extras = {}> = Simplify<\n {\n /**\n * Sets the metadata context to a specific (or current if omitted) run.\n */\n run(id?: string): Simplify<Omit<MetadataBuilder<Extras>, \"run\">>;\n\n /**\n * Sets the metadata context to a specific (or current if omitted) step.\n */\n step(\n id?: string,\n index?: number,\n ): Simplify<Omit<MetadataBuilder<Extras>, \"run\" | \"step\">>;\n\n /**\n * Sets the metadata context to a specific (or current if omitted) step attempt.\n */\n attempt(\n index?: number,\n ): Simplify<Omit<MetadataBuilder<Extras>, \"run\" | \"step\" | \"attempt\">>;\n\n /**\n * Sets the metadata context to a specific span.\n */\n span(\n id: string,\n ): Simplify<\n Omit<MetadataBuilder<Extras>, \"run\" | \"step\" | \"attempt\" | \"span\">\n >;\n\n /**\n * Attach metadata to the configured run/step/step attempt/span.\n *\n * By default it will attach metadata to the current run if\n * executed inside the body of `createFunction` or to the\n * current step attempt if executed inside `step.run`.\n */\n update(values: Record<string, unknown>, kind?: string): Promise<void>;\n } & Extras\n>;\n\n/**\n * A wrapper around `MetadataBuilder` to attach metadata as a step.\n */\nexport type MetadataStepTool = MetadataBuilder<{\n /**\n * Allows many `updates` to be sent with the same scope.\n */\n do: (fn: (builder: MetadataBuilder) => Promise<void>) => Promise<void>;\n}>;\n\n/**\n * Configures and sends metadata updates.\n *\n * It sends metadata updates via step opcodes if the metadata is\n * configured to be attached to the current run/step/step attempt\n * and `update` is called inside of `step.run`.\n *\n * Otherwise it sends updates via the Inngest API.\n */\nexport class UnscopedMetadataBuilder implements MetadataBuilder {\n constructor(\n private client: Inngest,\n private config: BuilderConfig = {},\n ) {}\n\n run(id?: string): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n runId: id ?? null,\n });\n }\n\n step(id?: string, index?: number): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n stepId: id ?? null,\n stepIndex: index ?? 0,\n });\n }\n\n attempt(attempt?: number): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n attempt: attempt ?? null,\n });\n }\n\n span(id: string): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n spanId: id,\n });\n }\n\n async update(\n values: Record<string, unknown>,\n kind: string = \"default\",\n ): Promise<void> {\n await performOp(\n this.client,\n this.config,\n values,\n `userland.${kind}`,\n \"merge\",\n );\n }\n\n toJSON() {\n return this.config;\n }\n}\n\n/**\n * Creates a `MetadataTarget` based on the current execution context and the `BuilderConfig` created using\n * `MetadataBuilder`.\n */\nexport function buildTarget(\n config: BuilderConfig,\n ctx?: AsyncContext,\n): MetadataTarget {\n const ctxExecution = ctx?.execution;\n const ctxRunId = ctxExecution?.ctx?.runId;\n const ctxStepId = ctxExecution?.executingStep?.id;\n const ctxAttempt = ctxExecution?.ctx?.attempt;\n const targetRunId = config.runId ?? ctxRunId;\n if (!targetRunId) throw new Error(\"No run context available\");\n\n const isSameRunAsCtx = ctxRunId !== undefined && targetRunId === ctxRunId;\n\n const stepCtxReason = !ctxExecution\n ? \"no function execution context is available\"\n : !ctxExecution.executingStep\n ? \"you are not inside a step.run() callback\"\n : \"you are targeting a different run\";\n\n if (\n config.attempt === null &&\n (!isSameRunAsCtx || !ctxExecution?.executingStep)\n )\n throw new Error(\n `attempt() was called without a value, but ${stepCtxReason}`,\n );\n if (\n config.stepId === null &&\n (!isSameRunAsCtx || !ctxExecution?.executingStep)\n )\n throw new Error(`step() was called without a value, but ${stepCtxReason}`);\n\n const targetAttempt =\n config.attempt !== undefined\n ? (config.attempt ?? ctxAttempt)\n : config.stepId === null\n ? ctxAttempt\n : undefined;\n\n if (config.spanId !== undefined) {\n return {\n run_id: targetRunId,\n step_id: config.stepId ?? ctxStepId,\n step_index: config.stepIndex,\n step_attempt:\n targetAttempt ?? (config.stepId === undefined ? ctxAttempt : undefined),\n span_id: config.spanId,\n };\n } else if (config.stepId !== undefined) {\n return {\n run_id: targetRunId,\n step_id: config.stepId ?? ctxStepId,\n step_index: config.stepIndex,\n step_attempt: targetAttempt,\n };\n } else if (config.runId !== undefined) {\n return {\n run_id: targetRunId,\n };\n } else if (ctxStepId && ctxAttempt !== undefined) {\n return {\n run_id: targetRunId,\n step_id: ctxStepId,\n step_attempt: ctxAttempt,\n };\n } else {\n return {\n run_id: targetRunId,\n };\n }\n}\n\n/**\n * Creates a metadata array payload for API calls.\n */\nexport function createMetadataPayload(\n kind: string,\n op: MetadataOpcode,\n metadata: Record<string, unknown>,\n) {\n return [\n {\n kind,\n op,\n values: metadata,\n },\n ];\n}\n\n/**\n * Sends metadata update via REST API to a specific target.\n */\nexport async function sendMetadataViaAPI(\n client: Inngest,\n target: MetadataTarget,\n kind: string,\n op: MetadataOpcode,\n metadata: Record<string, unknown>,\n headers?: Record<string, string>,\n): Promise<void> {\n const metadataArray = createMetadataPayload(kind, op, metadata);\n\n await client[\"updateMetadata\"]({\n target,\n metadata: metadataArray,\n headers,\n });\n}\n\nfunction getBatchScope(config: BuilderConfig): MetadataScope {\n if (config.spanId !== undefined) return \"extended_trace\";\n if (config.stepId !== undefined) return \"step\";\n if (config.runId !== undefined) return \"run\";\n\n return \"step\";\n}\n\nfunction targetsCurrentStep(\n config: BuilderConfig,\n ctx?: AsyncContext,\n): boolean {\n const executingStep = ctx?.execution?.executingStep;\n if (!executingStep) {\n return false;\n }\n\n const targetStepId = config.stepId;\n const currentUserlandStepId = executingStep.userlandId;\n\n if (targetStepId === undefined) {\n return true;\n }\n\n if (targetStepId === null) {\n return true;\n }\n\n return targetStepId === currentUserlandStepId;\n}\n\n/**\n * Internal metadata write helper shared by metadata and score helpers.\n * @internal\n */\nexport async function performOp(\n client: Inngest,\n config: BuilderConfig,\n values: Record<string, unknown>,\n kind: MetadataKind,\n op: MetadataOpcode,\n): Promise<void> {\n const ctx = await getAsyncCtx();\n const target = buildTarget(config, ctx);\n\n const isInsideRun = !!ctx?.execution;\n const isInsideStep = !!ctx?.execution?.executingStep;\n if (isInsideRun && !isInsideStep) {\n client[internalLoggerSymbol].warn(\n \"metadata.update() called outside of a step; this metadata may be lost on retries. Wrap the call in step.run() for durable metadata.\",\n );\n }\n\n const runId = config.runId ?? ctx?.execution?.ctx?.runId;\n const attempt = config.attempt ?? ctx?.execution?.ctx?.attempt;\n\n // We can batch metadata if we're updating the current run\n const canBatch =\n runId === ctx?.execution?.ctx?.runId &&\n targetsCurrentStep(config, ctx) &&\n attempt === ctx?.execution?.ctx?.attempt &&\n !config.spanId;\n\n if (canBatch) {\n const executingStep = ctx?.execution?.executingStep;\n const execInstance = ctx?.execution?.instance;\n const scope = getBatchScope(config);\n\n if (\n executingStep?.id &&\n execInstance &&\n execInstance.addMetadata(executingStep.id, kind, scope, op, values)\n ) {\n return;\n }\n }\n\n const headers =\n (\n ctx?.execution?.instance as\n | { options?: { headers?: Record<string, string> } }\n | undefined\n )?.options?.headers ?? undefined;\n\n await sendMetadataViaAPI(client, target, kind, op, values, headers);\n}\n\nexport const metadataSymbol = Symbol.for(\"inngest.step.metadata\");\n\n/**\n * Middleware that enables the experimental step.metadata() feature.\n *\n * @example\n * ```ts\n * import { metadataMiddleware } from \"inngest/experimental\";\n *\n * const inngest = new Inngest({\n * id: \"my-app\",\n * middleware: [metadataMiddleware()],\n * });\n * ```\n */\nexport const metadataMiddleware = () => {\n class MetadataMiddleware extends Middleware.BaseMiddleware {\n readonly id = \"inngest:metadata\";\n\n static override onRegister({ client }: Middleware.OnRegisterArgs) {\n client[\"experimentalMetadataEnabled\"] = true;\n }\n\n override transformFunctionInput(\n arg: Middleware.TransformFunctionInputArgs,\n ): Middleware.TransformFunctionInputArgs & {\n ctx: {\n step: {\n /**\n * Create a durable metadata update wrapped in a step\n *\n * @param memoizationId - The step ID used for the step itself, ensuring the\n * metadata update is only performed once even on function retries.\n *\n * @example\n * ```ts\n * // Update metadata for the current run\n * await step.metadata(\"update-status\").update({ status: \"processing\" });\n *\n * // Update metadata for a different run\n * await step.metadata(\"notify-parent\")\n * .run(parentRunId)\n * .update({ childCompleted: true });\n * ```\n */\n metadata: ExperimentalStepTools[typeof metadataSymbol];\n };\n };\n } {\n return {\n ...arg,\n ctx: {\n ...arg.ctx,\n step: {\n ...arg.ctx.step,\n // Access the hidden symbol-keyed metadata tool from step tools\n metadata: (arg.ctx.step as unknown as ExperimentalStepTools)[\n metadataSymbol\n ],\n },\n },\n };\n }\n }\n\n return MetadataMiddleware;\n};\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { MaybePromise, Simplify } from "../helpers/types.cjs";
2
- import { Middleware } from "./middleware/middleware.cjs";
3
2
  import { ExperimentalStepTools } from "./InngestStepTools.cjs";
3
+ import { Middleware } from "./middleware/middleware.cjs";
4
4
  import { SendEventBaseOutput } from "../types.cjs";
5
5
  import { Inngest } from "./Inngest.cjs";
6
6
 
@@ -13,7 +13,7 @@ type MetadataScope = "run" | "step" | "extended_trace";
13
13
  /**
14
14
  * Metadata of the same kind attached to the same item at the same scope are combined.
15
15
  */
16
- type MetadataKind = "inngest.experiment" | "inngest.warnings" | "inngest.ai" | `userland.${string}`;
16
+ type MetadataKind = "inngest.experiment" | "inngest.score" | "inngest.warnings" | "inngest.ai" | `userland.${string}`;
17
17
  /**
18
18
  * The operation use to combine multiple metadata updates of the same kind.
19
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"InngestMetadata.d.cts","names":[],"sources":["../../src/components/InngestMetadata.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAUA;AAKA;AASY,KAdA,aAAA,GAcc,KAAA,GAAA,MAAA,GAAA,gBAAA;AAM1B;;;AAES,KAjBG,YAAA,GAiBH,oBAAA,GAAA,kBAAA,GAAA,YAAA,GAAA,YAAA,MAAA,EAAA;;;;AAKG,KAbA,cAAA,GAac,OAAG;AAgB7B;;;;AAK+B,KA5BnB,cAAA,GA4BmB;MAAT,EA3Bd,YA2Bc;OAQe,EAlC5B,aAkC4B;MAjC/B,cAiCe;QAAL,EAhCN,cAgCM;;AAOqB,KApCzB,cAAA,GAAiB,MAoCQ,CAAA,MAAA,EAAA,OAAA,CAAA;;;;;;AAyBrC;AAA4B,KA7ChB,eA6CgB,CAAA,SAAA,CAAA,CAAA,CAAA,GA7Ce,QA6Cf,CAAA;;;;KAAG,CAAA,EAAA,CAAA,EAAA,MAAA,CAAA,EAxCT,QAwCS,CAxCA,IAwCA,CAxCK,eAwCL,CAxCqB,MAwCrB,CAAA,EAAA,KAAA,CAAA,CAAA;EAAe;AA4O9C;AAeA;EAmDC,IAAA,CAAA,EAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EA9UM,QA8UN,CA9Ue,IA8Uf,CA9UoB,eA8UpB,CA9UoC,MA8UpC,CAAA,EAAA,KAAA,GAAA,MAAA,CAAA,CAAA;;;;SAzCM,CAAA,KAAW,CAAA,EAAA,MAAA,CAAA,EA9RX,QA8RW,CA9RF,IA8RE,CA9RG,eA8RH,CA9RmB,MA8RnB,CAAA,EAAA,KAAA,GAAA,MAAA,GAAA,SAAA,CAAA,CAAA;;;;oBAvRX,SACD,KAAK,gBAAgB;;;;;;;;iBAUR,yCAAyC;IACtD;;;;KAMM,gBAAA,GAAmB;;;;qBAIV,oBAAoB,kBAAkB;;cAwO9C;;;;;;;;;;;;;;cAeA;;;;;;;gCASF,UAAA,CAAW,6BACf,UAAA,CAAW;;;;;;;;;;;;;;;;;;;;oBAoBE,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;KA1BN,UAAA,CAAW"}
1
+ {"version":3,"file":"InngestMetadata.d.cts","names":[],"sources":["../../src/components/InngestMetadata.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAUA;AAKA;AAUY,KAfA,aAAA,GAec,KAAA,GAAA,MAAA,GAAA,gBAAA;AAM1B;;;AAES,KAlBG,YAAA,GAkBH,oBAAA,GAAA,eAAA,GAAA,kBAAA,GAAA,YAAA,GAAA,YAAA,MAAA,EAAA;;;;AAKG,KAbA,cAAA,GAac,OAAG;AAoB7B;;;;AAK+B,KAhCnB,cAAA,GAgCmB;MAAT,EA/Bd,YA+Bc;OAQe,EAtC5B,aAsC4B;MArC/B,cAqCe;QAAL,EApCN,cAoCM;;AAOqB,KAxCzB,cAAA,GAAiB,MAwCQ,CAAA,MAAA,EAAA,OAAA,CAAA;;AAyBrC;;;;;AAA+B,KA7CnB,eA6CmB,CAAA,SAAA,CAAA,CAAA,CAAA,GA7CY,QA6CZ,CAAA;EAAe;AA6Q9C;AAeA;EAmDC,GAAA,CAAA,EAAA,CAAA,EAAA,MAAA,CAAA,EAvXqB,QAuXrB,CAvX8B,IAuX9B,CAvXmC,eAuXnC,CAvXmD,MAuXnD,CAAA,EAAA,KAAA,CAAA,CAAA;;;;MAzCM,CAAA,EAAW,CAAX,EAAA,MAAW,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EAtUX,QAsUW,CAtUF,IAsUE,CAtUG,eAsUH,CAtUmB,MAsUnB,CAAA,EAAA,KAAA,GAAA,MAAA,CAAA,CAAA;;;;2BA/TX,SAAS,KAAK,gBAAgB;;;;oBAO9B,SACD,KAAK,gBAAgB;;;;;;;;iBAUR,yCAAyC;IACtD;;;;KAMM,gBAAA,GAAmB;;;;qBAIV,oBAAoB,kBAAkB;;cAyQ9C;;;;;;;;;;;;;;cAeA;;;;;;;gCASF,UAAA,CAAW,6BACf,UAAA,CAAW;;;;;;;;;;;;;;;;;;;;oBAoBE,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;KA1BN,UAAA,CAAW"}
@@ -1,6 +1,6 @@
1
1
  import { MaybePromise, Simplify } from "../helpers/types.js";
2
- import { Middleware } from "./middleware/middleware.js";
3
2
  import { ExperimentalStepTools } from "./InngestStepTools.js";
3
+ import { Middleware } from "./middleware/middleware.js";
4
4
  import { SendEventBaseOutput } from "../types.js";
5
5
  import { Inngest } from "./Inngest.js";
6
6
 
@@ -13,7 +13,7 @@ type MetadataScope = "run" | "step" | "extended_trace";
13
13
  /**
14
14
  * Metadata of the same kind attached to the same item at the same scope are combined.
15
15
  */
16
- type MetadataKind = "inngest.experiment" | "inngest.warnings" | "inngest.ai" | `userland.${string}`;
16
+ type MetadataKind = "inngest.experiment" | "inngest.score" | "inngest.warnings" | "inngest.ai" | `userland.${string}`;
17
17
  /**
18
18
  * The operation use to combine multiple metadata updates of the same kind.
19
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"InngestMetadata.d.ts","names":[],"sources":["../../src/components/InngestMetadata.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAUA;AAKA;AASY,KAdA,aAAA,GAcc,KAAA,GAAA,MAAA,GAAA,gBAAA;AAM1B;;;AAES,KAjBG,YAAA,GAiBH,oBAAA,GAAA,kBAAA,GAAA,YAAA,GAAA,YAAA,MAAA,EAAA;;;;AAKG,KAbA,cAAA,GAac,OAAG;AAgB7B;;;;AAK+B,KA5BnB,cAAA,GA4BmB;MAAT,EA3Bd,YA2Bc;OAQe,EAlC5B,aAkC4B;MAjC/B,cAiCe;QAAL,EAhCN,cAgCM;;AAOqB,KApCzB,cAAA,GAAiB,MAoCQ,CAAA,MAAA,EAAA,OAAA,CAAA;;;;;;AAyBrC;AAA4B,KA7ChB,eA6CgB,CAAA,SAAA,CAAA,CAAA,CAAA,GA7Ce,QA6Cf,CAAA;;;;KAAG,CAAA,EAAA,CAAA,EAAA,MAAA,CAAA,EAxCT,QAwCS,CAxCA,IAwCA,CAxCK,eAwCL,CAxCqB,MAwCrB,CAAA,EAAA,KAAA,CAAA,CAAA;EAAe;AA4O9C;AAeA;EAmDC,IAAA,CAAA,EAAA,CAAA,EAAA,MAAA,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EA9UM,QA8UN,CA9Ue,IA8Uf,CA9UoB,eA8UpB,CA9UoC,MA8UpC,CAAA,EAAA,KAAA,GAAA,MAAA,CAAA,CAAA;;;;SAzCM,CAAA,KAAW,CAAA,EAAA,MAAA,CAAA,EA9RX,QA8RW,CA9RF,IA8RE,CA9RG,eA8RH,CA9RmB,MA8RnB,CAAA,EAAA,KAAA,GAAA,MAAA,GAAA,SAAA,CAAA,CAAA;;;;oBAvRX,SACD,KAAK,gBAAgB;;;;;;;;iBAUR,yCAAyC;IACtD;;;;KAMM,gBAAA,GAAmB;;;;qBAIV,oBAAoB,kBAAkB;;cAwO9C;;;;;;;;;;;;;;cAeA;;;;;;;gCASF,UAAA,CAAW,6BACf,UAAA,CAAW;;;;;;;;;;;;;;;;;;;;oBAoBE,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;KA1BN,UAAA,CAAW"}
1
+ {"version":3,"file":"InngestMetadata.d.ts","names":[],"sources":["../../src/components/InngestMetadata.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAUA;AAKA;AAUY,KAfA,aAAA,GAec,KAAA,GAAA,MAAA,GAAA,gBAAA;AAM1B;;;AAES,KAlBG,YAAA,GAkBH,oBAAA,GAAA,eAAA,GAAA,kBAAA,GAAA,YAAA,GAAA,YAAA,MAAA,EAAA;;;;AAKG,KAbA,cAAA,GAac,OAAG;AAoB7B;;;;AAK+B,KAhCnB,cAAA,GAgCmB;MAAT,EA/Bd,YA+Bc;OAQe,EAtC5B,aAsC4B;MArC/B,cAqCe;QAAL,EApCN,cAoCM;;AAOqB,KAxCzB,cAAA,GAAiB,MAwCQ,CAAA,MAAA,EAAA,OAAA,CAAA;;AAyBrC;;;;;AAA+B,KA7CnB,eA6CmB,CAAA,SAAA,CAAA,CAAA,CAAA,GA7CY,QA6CZ,CAAA;EAAe;AA6Q9C;AAeA;EAmDC,GAAA,CAAA,EAAA,CAAA,EAAA,MAAA,CAAA,EAvXqB,QAuXrB,CAvX8B,IAuX9B,CAvXmC,eAuXnC,CAvXmD,MAuXnD,CAAA,EAAA,KAAA,CAAA,CAAA;;;;MAzCM,CAAA,EAAW,CAAX,EAAA,MAAW,EAAA,KAAA,CAAA,EAAA,MAAA,CAAA,EAtUX,QAsUW,CAtUF,IAsUE,CAtUG,eAsUH,CAtUmB,MAsUnB,CAAA,EAAA,KAAA,GAAA,MAAA,CAAA,CAAA;;;;2BA/TX,SAAS,KAAK,gBAAgB;;;;oBAO9B,SACD,KAAK,gBAAgB;;;;;;;;iBAUR,yCAAyC;IACtD;;;;KAMM,gBAAA,GAAmB;;;;qBAIV,oBAAoB,kBAAkB;;cAyQ9C;;;;;;;;;;;;;;cAeA;;;;;;;gCASF,UAAA,CAAW,6BACf,UAAA,CAAW;;;;;;;;;;;;;;;;;;;;oBAoBE,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;KA1BN,UAAA,CAAW"}
@@ -64,18 +64,19 @@ function buildTarget(config, ctx) {
64
64
  const stepCtxReason = !ctxExecution ? "no function execution context is available" : !ctxExecution.executingStep ? "you are not inside a step.run() callback" : "you are targeting a different run";
65
65
  if (config.attempt === null && (!isSameRunAsCtx || !ctxExecution?.executingStep)) throw new Error(`attempt() was called without a value, but ${stepCtxReason}`);
66
66
  if (config.stepId === null && (!isSameRunAsCtx || !ctxExecution?.executingStep)) throw new Error(`step() was called without a value, but ${stepCtxReason}`);
67
+ const targetAttempt = config.attempt !== void 0 ? config.attempt ?? ctxAttempt : config.stepId === null ? ctxAttempt : void 0;
67
68
  if (config.spanId !== void 0) return {
68
69
  run_id: targetRunId,
69
70
  step_id: config.stepId ?? ctxStepId,
70
71
  step_index: config.stepIndex,
71
- step_attempt: config.attempt ?? ctxAttempt,
72
+ step_attempt: targetAttempt ?? (config.stepId === void 0 ? ctxAttempt : void 0),
72
73
  span_id: config.spanId
73
74
  };
74
75
  else if (config.stepId !== void 0) return {
75
76
  run_id: targetRunId,
76
77
  step_id: config.stepId ?? ctxStepId,
77
78
  step_index: config.stepIndex,
78
- step_attempt: config.attempt ?? ctxAttempt
79
+ step_attempt: targetAttempt
79
80
  };
80
81
  else if (config.runId !== void 0) return { run_id: targetRunId };
81
82
  else if (ctxStepId && ctxAttempt !== void 0) return {
@@ -112,6 +113,19 @@ function getBatchScope(config) {
112
113
  if (config.runId !== void 0) return "run";
113
114
  return "step";
114
115
  }
116
+ function targetsCurrentStep(config, ctx) {
117
+ const executingStep = ctx?.execution?.executingStep;
118
+ if (!executingStep) return false;
119
+ const targetStepId = config.stepId;
120
+ const currentUserlandStepId = executingStep.userlandId;
121
+ if (targetStepId === void 0) return true;
122
+ if (targetStepId === null) return true;
123
+ return targetStepId === currentUserlandStepId;
124
+ }
125
+ /**
126
+ * Internal metadata write helper shared by metadata and score helpers.
127
+ * @internal
128
+ */
115
129
  async function performOp(client, config, values, kind, op) {
116
130
  const ctx = await getAsyncCtx();
117
131
  const target = buildTarget(config, ctx);
@@ -119,9 +133,8 @@ async function performOp(client, config, values, kind, op) {
119
133
  const isInsideStep = !!ctx?.execution?.executingStep;
120
134
  if (isInsideRun && !isInsideStep) client[internalLoggerSymbol].warn("metadata.update() called outside of a step; this metadata may be lost on retries. Wrap the call in step.run() for durable metadata.");
121
135
  const runId = config.runId ?? ctx?.execution?.ctx?.runId;
122
- const stepId = config.stepId ?? ctx?.execution?.executingStep?.id;
123
136
  const attempt = config.attempt ?? ctx?.execution?.ctx?.attempt;
124
- if (runId === ctx?.execution?.ctx?.runId && stepId === ctx?.execution?.executingStep?.id && attempt === ctx?.execution?.ctx?.attempt && !config.spanId) {
137
+ if (runId === ctx?.execution?.ctx?.runId && targetsCurrentStep(config, ctx) && attempt === ctx?.execution?.ctx?.attempt && !config.spanId) {
125
138
  const executingStep = ctx?.execution?.executingStep;
126
139
  const execInstance = ctx?.execution?.instance;
127
140
  const scope = getBatchScope(config);
@@ -166,5 +179,5 @@ const metadataMiddleware = () => {
166
179
  };
167
180
 
168
181
  //#endregion
169
- export { UnscopedMetadataBuilder, metadataMiddleware, metadataSymbol };
182
+ export { UnscopedMetadataBuilder, metadataMiddleware, metadataSymbol, performOp };
170
183
  //# sourceMappingURL=InngestMetadata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"InngestMetadata.js","names":["client: Inngest","config: BuilderConfig"],"sources":["../../src/components/InngestMetadata.ts"],"sourcesContent":["import type { Simplify } from \"../helpers/types.ts\";\nimport type { MetadataTarget } from \"../types.ts\";\nimport { type AsyncContext, getAsyncCtx } from \"./execution/als.ts\";\nimport { type Inngest, internalLoggerSymbol } from \"./Inngest.ts\";\nimport type { ExperimentalStepTools } from \"./InngestStepTools.ts\";\nimport { Middleware } from \"./middleware/middleware.ts\";\n\n/**\n * The level at which to attach the metadata.\n */\nexport type MetadataScope = \"run\" | \"step\" | \"extended_trace\";\n\n/**\n * Metadata of the same kind attached to the same item at the same scope are combined.\n */\nexport type MetadataKind =\n | \"inngest.experiment\"\n | \"inngest.warnings\"\n | \"inngest.ai\"\n | `userland.${string}`;\n\n/**\n * The operation use to combine multiple metadata updates of the same kind.\n */\nexport type MetadataOpcode = \"merge\";\n\n/**\n * A metadata update containing `values` to be merged according to `op`\n * at the configured `scope` for the configured `kind`.\n */\nexport type MetadataUpdate = {\n kind: MetadataKind;\n scope: MetadataScope;\n op: MetadataOpcode;\n values: MetadataValues;\n};\n\nexport type MetadataValues = Record<string, unknown>;\n\ninterface BuilderConfig {\n runId?: string | null;\n stepId?: string | null;\n stepIndex?: number;\n attempt?: number | null;\n spanId?: string;\n}\n\n/**\n * Configures and sends metadata updates.\n *\n * This is used to limit the available methods as target is\n * configured and the specified scope narrows.\n */\nexport type MetadataBuilder<Extras = {}> = Simplify<\n {\n /**\n * Sets the metadata context to a specific (or current if omitted) run.\n */\n run(id?: string): Simplify<Omit<MetadataBuilder<Extras>, \"run\">>;\n\n /**\n * Sets the metadata context to a specific (or current if omitted) step.\n */\n step(\n id?: string,\n index?: number,\n ): Simplify<Omit<MetadataBuilder<Extras>, \"run\" | \"step\">>;\n\n /**\n * Sets the metadata context to a specific (or current if omitted) step attempt.\n */\n attempt(\n index?: number,\n ): Simplify<Omit<MetadataBuilder<Extras>, \"run\" | \"step\" | \"attempt\">>;\n\n /**\n * Sets the metadata context to a specific span.\n */\n span(\n id: string,\n ): Simplify<\n Omit<MetadataBuilder<Extras>, \"run\" | \"step\" | \"attempt\" | \"span\">\n >;\n\n /**\n * Attach metadata to the configured run/step/step attempt/span.\n *\n * By default it will attach metadata to the current run if\n * executed inside the body of `createFunction` or to the\n * current step attempt if executed inside `step.run`.\n */\n update(values: Record<string, unknown>, kind?: string): Promise<void>;\n } & Extras\n>;\n\n/**\n * A wrapper around `MetadataBuilder` to attach metadata as a step.\n */\nexport type MetadataStepTool = MetadataBuilder<{\n /**\n * Allows many `updates` to be sent with the same scope.\n */\n do: (fn: (builder: MetadataBuilder) => Promise<void>) => Promise<void>;\n}>;\n\n/**\n * Configures and sends metadata updates.\n *\n * It sends metadata updates via step opcodes if the metadata is\n * configured to be attached to the current run/step/step attempt\n * and `update` is called inside of `step.run`.\n *\n * Otherwise it sends updates via the Inngest API.\n */\nexport class UnscopedMetadataBuilder implements MetadataBuilder {\n constructor(\n private client: Inngest,\n private config: BuilderConfig = {},\n ) {}\n\n run(id?: string): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n runId: id ?? null,\n });\n }\n\n step(id?: string, index?: number): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n stepId: id ?? null,\n stepIndex: index ?? 0,\n });\n }\n\n attempt(attempt?: number): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n attempt: attempt ?? null,\n });\n }\n\n span(id: string): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n spanId: id,\n });\n }\n\n async update(\n values: Record<string, unknown>,\n kind: string = \"default\",\n ): Promise<void> {\n await performOp(\n this.client,\n this.config,\n values,\n `userland.${kind}`,\n \"merge\",\n );\n }\n\n toJSON() {\n return this.config;\n }\n}\n\n/**\n * Creates a `MetadataTarget` based on the current execution context and the `BuilderConfig` created using\n * `MetadataBuilder`.\n */\nexport function buildTarget(\n config: BuilderConfig,\n ctx?: AsyncContext,\n): MetadataTarget {\n const ctxExecution = ctx?.execution;\n const ctxRunId = ctxExecution?.ctx?.runId;\n const ctxStepId = ctxExecution?.executingStep?.id;\n const ctxAttempt = ctxExecution?.ctx?.attempt;\n const targetRunId = config.runId ?? ctxRunId;\n if (!targetRunId) throw new Error(\"No run context available\");\n\n const isSameRunAsCtx = ctxRunId !== undefined && targetRunId === ctxRunId;\n\n const stepCtxReason = !ctxExecution\n ? \"no function execution context is available\"\n : !ctxExecution.executingStep\n ? \"you are not inside a step.run() callback\"\n : \"you are targeting a different run\";\n\n if (\n config.attempt === null &&\n (!isSameRunAsCtx || !ctxExecution?.executingStep)\n )\n throw new Error(\n `attempt() was called without a value, but ${stepCtxReason}`,\n );\n if (\n config.stepId === null &&\n (!isSameRunAsCtx || !ctxExecution?.executingStep)\n )\n throw new Error(`step() was called without a value, but ${stepCtxReason}`);\n\n if (config.spanId !== undefined) {\n return {\n run_id: targetRunId,\n step_id: config.stepId ?? ctxStepId,\n step_index: config.stepIndex,\n step_attempt: config.attempt ?? ctxAttempt,\n span_id: config.spanId,\n };\n } else if (config.stepId !== undefined) {\n return {\n run_id: targetRunId,\n step_id: config.stepId ?? ctxStepId,\n step_index: config.stepIndex,\n step_attempt: config.attempt ?? ctxAttempt,\n };\n } else if (config.runId !== undefined) {\n return {\n run_id: targetRunId,\n };\n } else if (ctxStepId && ctxAttempt !== undefined) {\n return {\n run_id: targetRunId,\n step_id: ctxStepId,\n step_attempt: ctxAttempt,\n };\n } else {\n return {\n run_id: targetRunId,\n };\n }\n}\n\n/**\n * Creates a metadata array payload for API calls.\n */\nexport function createMetadataPayload(\n kind: string,\n op: MetadataOpcode,\n metadata: Record<string, unknown>,\n) {\n return [\n {\n kind,\n op,\n values: metadata,\n },\n ];\n}\n\n/**\n * Sends metadata update via REST API to a specific target.\n */\nexport async function sendMetadataViaAPI(\n client: Inngest,\n target: MetadataTarget,\n kind: string,\n op: MetadataOpcode,\n metadata: Record<string, unknown>,\n headers?: Record<string, string>,\n): Promise<void> {\n const metadataArray = createMetadataPayload(kind, op, metadata);\n\n await client[\"updateMetadata\"]({\n target,\n metadata: metadataArray,\n headers,\n });\n}\n\nfunction getBatchScope(config: BuilderConfig): MetadataScope {\n if (config.spanId !== undefined) return \"extended_trace\";\n if (config.stepId !== undefined) return \"step\";\n if (config.runId !== undefined) return \"run\";\n\n return \"step\";\n}\n\nasync function performOp(\n client: Inngest,\n config: BuilderConfig,\n values: Record<string, unknown>,\n kind: MetadataKind,\n op: MetadataOpcode,\n): Promise<void> {\n const ctx = await getAsyncCtx();\n const target = buildTarget(config, ctx);\n\n const isInsideRun = !!ctx?.execution;\n const isInsideStep = !!ctx?.execution?.executingStep;\n if (isInsideRun && !isInsideStep) {\n client[internalLoggerSymbol].warn(\n \"metadata.update() called outside of a step; this metadata may be lost on retries. Wrap the call in step.run() for durable metadata.\",\n );\n }\n\n const runId = config.runId ?? ctx?.execution?.ctx?.runId;\n const stepId = config.stepId ?? ctx?.execution?.executingStep?.id;\n // TODO: get step index from ctx?\n const attempt = config.attempt ?? ctx?.execution?.ctx?.attempt;\n\n // We can batch metadata if we're updating the current run\n const canBatch =\n runId === ctx?.execution?.ctx?.runId &&\n stepId === ctx?.execution?.executingStep?.id &&\n attempt === ctx?.execution?.ctx?.attempt &&\n !config.spanId;\n\n if (canBatch) {\n const executingStep = ctx?.execution?.executingStep;\n const execInstance = ctx?.execution?.instance;\n const scope = getBatchScope(config);\n\n if (\n executingStep?.id &&\n execInstance &&\n execInstance.addMetadata(executingStep.id, kind, scope, op, values)\n ) {\n return;\n }\n }\n\n const headers =\n (\n ctx?.execution?.instance as\n | { options?: { headers?: Record<string, string> } }\n | undefined\n )?.options?.headers ?? undefined;\n\n await sendMetadataViaAPI(client, target, kind, op, values, headers);\n}\n\nexport const metadataSymbol = Symbol.for(\"inngest.step.metadata\");\n\n/**\n * Middleware that enables the experimental step.metadata() feature.\n *\n * @example\n * ```ts\n * import { metadataMiddleware } from \"inngest/experimental\";\n *\n * const inngest = new Inngest({\n * id: \"my-app\",\n * middleware: [metadataMiddleware()],\n * });\n * ```\n */\nexport const metadataMiddleware = () => {\n class MetadataMiddleware extends Middleware.BaseMiddleware {\n readonly id = \"inngest:metadata\";\n\n static override onRegister({ client }: Middleware.OnRegisterArgs) {\n client[\"experimentalMetadataEnabled\"] = true;\n }\n\n override transformFunctionInput(\n arg: Middleware.TransformFunctionInputArgs,\n ): Middleware.TransformFunctionInputArgs & {\n ctx: {\n step: {\n /**\n * Create a durable metadata update wrapped in a step\n *\n * @param memoizationId - The step ID used for the step itself, ensuring the\n * metadata update is only performed once even on function retries.\n *\n * @example\n * ```ts\n * // Update metadata for the current run\n * await step.metadata(\"update-status\").update({ status: \"processing\" });\n *\n * // Update metadata for a different run\n * await step.metadata(\"notify-parent\")\n * .run(parentRunId)\n * .update({ childCompleted: true });\n * ```\n */\n metadata: ExperimentalStepTools[typeof metadataSymbol];\n };\n };\n } {\n return {\n ...arg,\n ctx: {\n ...arg.ctx,\n step: {\n ...arg.ctx.step,\n // Access the hidden symbol-keyed metadata tool from step tools\n metadata: (arg.ctx.step as unknown as ExperimentalStepTools)[\n metadataSymbol\n ],\n },\n },\n };\n }\n }\n\n return MetadataMiddleware;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"InngestMetadata.js","names":["client: Inngest","config: BuilderConfig"],"sources":["../../src/components/InngestMetadata.ts"],"sourcesContent":["import type { Simplify } from \"../helpers/types.ts\";\nimport type { MetadataTarget } from \"../types.ts\";\nimport { type AsyncContext, getAsyncCtx } from \"./execution/als.ts\";\nimport { type Inngest, internalLoggerSymbol } from \"./Inngest.ts\";\nimport type { ExperimentalStepTools } from \"./InngestStepTools.ts\";\nimport { Middleware } from \"./middleware/middleware.ts\";\n\n/**\n * The level at which to attach the metadata.\n */\nexport type MetadataScope = \"run\" | \"step\" | \"extended_trace\";\n\n/**\n * Metadata of the same kind attached to the same item at the same scope are combined.\n */\nexport type MetadataKind =\n | \"inngest.experiment\"\n | \"inngest.score\"\n | \"inngest.warnings\"\n | \"inngest.ai\"\n | `userland.${string}`;\n\n/**\n * The operation use to combine multiple metadata updates of the same kind.\n */\nexport type MetadataOpcode = \"merge\";\n\n/**\n * A metadata update containing `values` to be merged according to `op`\n * at the configured `scope` for the configured `kind`.\n */\nexport type MetadataUpdate = {\n kind: MetadataKind;\n scope: MetadataScope;\n op: MetadataOpcode;\n values: MetadataValues;\n};\n\nexport type MetadataValues = Record<string, unknown>;\n\n/**\n * Internal metadata target config shared by metadata and score helpers.\n * @internal\n */\nexport interface BuilderConfig {\n runId?: string | null;\n stepId?: string | null;\n stepIndex?: number;\n attempt?: number | null;\n spanId?: string;\n}\n\n/**\n * Configures and sends metadata updates.\n *\n * This is used to limit the available methods as target is\n * configured and the specified scope narrows.\n */\nexport type MetadataBuilder<Extras = {}> = Simplify<\n {\n /**\n * Sets the metadata context to a specific (or current if omitted) run.\n */\n run(id?: string): Simplify<Omit<MetadataBuilder<Extras>, \"run\">>;\n\n /**\n * Sets the metadata context to a specific (or current if omitted) step.\n */\n step(\n id?: string,\n index?: number,\n ): Simplify<Omit<MetadataBuilder<Extras>, \"run\" | \"step\">>;\n\n /**\n * Sets the metadata context to a specific (or current if omitted) step attempt.\n */\n attempt(\n index?: number,\n ): Simplify<Omit<MetadataBuilder<Extras>, \"run\" | \"step\" | \"attempt\">>;\n\n /**\n * Sets the metadata context to a specific span.\n */\n span(\n id: string,\n ): Simplify<\n Omit<MetadataBuilder<Extras>, \"run\" | \"step\" | \"attempt\" | \"span\">\n >;\n\n /**\n * Attach metadata to the configured run/step/step attempt/span.\n *\n * By default it will attach metadata to the current run if\n * executed inside the body of `createFunction` or to the\n * current step attempt if executed inside `step.run`.\n */\n update(values: Record<string, unknown>, kind?: string): Promise<void>;\n } & Extras\n>;\n\n/**\n * A wrapper around `MetadataBuilder` to attach metadata as a step.\n */\nexport type MetadataStepTool = MetadataBuilder<{\n /**\n * Allows many `updates` to be sent with the same scope.\n */\n do: (fn: (builder: MetadataBuilder) => Promise<void>) => Promise<void>;\n}>;\n\n/**\n * Configures and sends metadata updates.\n *\n * It sends metadata updates via step opcodes if the metadata is\n * configured to be attached to the current run/step/step attempt\n * and `update` is called inside of `step.run`.\n *\n * Otherwise it sends updates via the Inngest API.\n */\nexport class UnscopedMetadataBuilder implements MetadataBuilder {\n constructor(\n private client: Inngest,\n private config: BuilderConfig = {},\n ) {}\n\n run(id?: string): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n runId: id ?? null,\n });\n }\n\n step(id?: string, index?: number): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n stepId: id ?? null,\n stepIndex: index ?? 0,\n });\n }\n\n attempt(attempt?: number): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n attempt: attempt ?? null,\n });\n }\n\n span(id: string): UnscopedMetadataBuilder {\n return new UnscopedMetadataBuilder(this.client, {\n ...this.config,\n spanId: id,\n });\n }\n\n async update(\n values: Record<string, unknown>,\n kind: string = \"default\",\n ): Promise<void> {\n await performOp(\n this.client,\n this.config,\n values,\n `userland.${kind}`,\n \"merge\",\n );\n }\n\n toJSON() {\n return this.config;\n }\n}\n\n/**\n * Creates a `MetadataTarget` based on the current execution context and the `BuilderConfig` created using\n * `MetadataBuilder`.\n */\nexport function buildTarget(\n config: BuilderConfig,\n ctx?: AsyncContext,\n): MetadataTarget {\n const ctxExecution = ctx?.execution;\n const ctxRunId = ctxExecution?.ctx?.runId;\n const ctxStepId = ctxExecution?.executingStep?.id;\n const ctxAttempt = ctxExecution?.ctx?.attempt;\n const targetRunId = config.runId ?? ctxRunId;\n if (!targetRunId) throw new Error(\"No run context available\");\n\n const isSameRunAsCtx = ctxRunId !== undefined && targetRunId === ctxRunId;\n\n const stepCtxReason = !ctxExecution\n ? \"no function execution context is available\"\n : !ctxExecution.executingStep\n ? \"you are not inside a step.run() callback\"\n : \"you are targeting a different run\";\n\n if (\n config.attempt === null &&\n (!isSameRunAsCtx || !ctxExecution?.executingStep)\n )\n throw new Error(\n `attempt() was called without a value, but ${stepCtxReason}`,\n );\n if (\n config.stepId === null &&\n (!isSameRunAsCtx || !ctxExecution?.executingStep)\n )\n throw new Error(`step() was called without a value, but ${stepCtxReason}`);\n\n const targetAttempt =\n config.attempt !== undefined\n ? (config.attempt ?? ctxAttempt)\n : config.stepId === null\n ? ctxAttempt\n : undefined;\n\n if (config.spanId !== undefined) {\n return {\n run_id: targetRunId,\n step_id: config.stepId ?? ctxStepId,\n step_index: config.stepIndex,\n step_attempt:\n targetAttempt ?? (config.stepId === undefined ? ctxAttempt : undefined),\n span_id: config.spanId,\n };\n } else if (config.stepId !== undefined) {\n return {\n run_id: targetRunId,\n step_id: config.stepId ?? ctxStepId,\n step_index: config.stepIndex,\n step_attempt: targetAttempt,\n };\n } else if (config.runId !== undefined) {\n return {\n run_id: targetRunId,\n };\n } else if (ctxStepId && ctxAttempt !== undefined) {\n return {\n run_id: targetRunId,\n step_id: ctxStepId,\n step_attempt: ctxAttempt,\n };\n } else {\n return {\n run_id: targetRunId,\n };\n }\n}\n\n/**\n * Creates a metadata array payload for API calls.\n */\nexport function createMetadataPayload(\n kind: string,\n op: MetadataOpcode,\n metadata: Record<string, unknown>,\n) {\n return [\n {\n kind,\n op,\n values: metadata,\n },\n ];\n}\n\n/**\n * Sends metadata update via REST API to a specific target.\n */\nexport async function sendMetadataViaAPI(\n client: Inngest,\n target: MetadataTarget,\n kind: string,\n op: MetadataOpcode,\n metadata: Record<string, unknown>,\n headers?: Record<string, string>,\n): Promise<void> {\n const metadataArray = createMetadataPayload(kind, op, metadata);\n\n await client[\"updateMetadata\"]({\n target,\n metadata: metadataArray,\n headers,\n });\n}\n\nfunction getBatchScope(config: BuilderConfig): MetadataScope {\n if (config.spanId !== undefined) return \"extended_trace\";\n if (config.stepId !== undefined) return \"step\";\n if (config.runId !== undefined) return \"run\";\n\n return \"step\";\n}\n\nfunction targetsCurrentStep(\n config: BuilderConfig,\n ctx?: AsyncContext,\n): boolean {\n const executingStep = ctx?.execution?.executingStep;\n if (!executingStep) {\n return false;\n }\n\n const targetStepId = config.stepId;\n const currentUserlandStepId = executingStep.userlandId;\n\n if (targetStepId === undefined) {\n return true;\n }\n\n if (targetStepId === null) {\n return true;\n }\n\n return targetStepId === currentUserlandStepId;\n}\n\n/**\n * Internal metadata write helper shared by metadata and score helpers.\n * @internal\n */\nexport async function performOp(\n client: Inngest,\n config: BuilderConfig,\n values: Record<string, unknown>,\n kind: MetadataKind,\n op: MetadataOpcode,\n): Promise<void> {\n const ctx = await getAsyncCtx();\n const target = buildTarget(config, ctx);\n\n const isInsideRun = !!ctx?.execution;\n const isInsideStep = !!ctx?.execution?.executingStep;\n if (isInsideRun && !isInsideStep) {\n client[internalLoggerSymbol].warn(\n \"metadata.update() called outside of a step; this metadata may be lost on retries. Wrap the call in step.run() for durable metadata.\",\n );\n }\n\n const runId = config.runId ?? ctx?.execution?.ctx?.runId;\n const attempt = config.attempt ?? ctx?.execution?.ctx?.attempt;\n\n // We can batch metadata if we're updating the current run\n const canBatch =\n runId === ctx?.execution?.ctx?.runId &&\n targetsCurrentStep(config, ctx) &&\n attempt === ctx?.execution?.ctx?.attempt &&\n !config.spanId;\n\n if (canBatch) {\n const executingStep = ctx?.execution?.executingStep;\n const execInstance = ctx?.execution?.instance;\n const scope = getBatchScope(config);\n\n if (\n executingStep?.id &&\n execInstance &&\n execInstance.addMetadata(executingStep.id, kind, scope, op, values)\n ) {\n return;\n }\n }\n\n const headers =\n (\n ctx?.execution?.instance as\n | { options?: { headers?: Record<string, string> } }\n | undefined\n )?.options?.headers ?? undefined;\n\n await sendMetadataViaAPI(client, target, kind, op, values, headers);\n}\n\nexport const metadataSymbol = Symbol.for(\"inngest.step.metadata\");\n\n/**\n * Middleware that enables the experimental step.metadata() feature.\n *\n * @example\n * ```ts\n * import { metadataMiddleware } from \"inngest/experimental\";\n *\n * const inngest = new Inngest({\n * id: \"my-app\",\n * middleware: [metadataMiddleware()],\n * });\n * ```\n */\nexport const metadataMiddleware = () => {\n class MetadataMiddleware extends Middleware.BaseMiddleware {\n readonly id = \"inngest:metadata\";\n\n static override onRegister({ client }: Middleware.OnRegisterArgs) {\n client[\"experimentalMetadataEnabled\"] = true;\n }\n\n override transformFunctionInput(\n arg: Middleware.TransformFunctionInputArgs,\n ): Middleware.TransformFunctionInputArgs & {\n ctx: {\n step: {\n /**\n * Create a durable metadata update wrapped in a step\n *\n * @param memoizationId - The step ID used for the step itself, ensuring the\n * metadata update is only performed once even on function retries.\n *\n * @example\n * ```ts\n * // Update metadata for the current run\n * await step.metadata(\"update-status\").update({ status: \"processing\" });\n *\n * // Update metadata for a different run\n * await step.metadata(\"notify-parent\")\n * .run(parentRunId)\n * .update({ childCompleted: true });\n * ```\n */\n metadata: ExperimentalStepTools[typeof metadataSymbol];\n };\n };\n } {\n return {\n ...arg,\n ctx: {\n ...arg.ctx,\n step: {\n ...arg.ctx.step,\n // Access the hidden symbol-keyed metadata tool from step tools\n metadata: (arg.ctx.step as unknown as ExperimentalStepTools)[\n metadataSymbol\n ],\n },\n },\n };\n }\n }\n\n return MetadataMiddleware;\n};\n"],"mappings":";;;;;;;;;;;;;;AAuHA,IAAa,0BAAb,MAAa,wBAAmD;CAC9D,YACE,AAAQA,QACR,AAAQC,SAAwB,EAAE,EAClC;EAFQ;EACA;;CAGV,IAAI,IAAsC;AACxC,SAAO,IAAI,wBAAwB,KAAK,QAAQ;GAC9C,GAAG,KAAK;GACR,OAAO,MAAM;GACd,CAAC;;CAGJ,KAAK,IAAa,OAAyC;AACzD,SAAO,IAAI,wBAAwB,KAAK,QAAQ;GAC9C,GAAG,KAAK;GACR,QAAQ,MAAM;GACd,WAAW,SAAS;GACrB,CAAC;;CAGJ,QAAQ,SAA2C;AACjD,SAAO,IAAI,wBAAwB,KAAK,QAAQ;GAC9C,GAAG,KAAK;GACR,SAAS,WAAW;GACrB,CAAC;;CAGJ,KAAK,IAAqC;AACxC,SAAO,IAAI,wBAAwB,KAAK,QAAQ;GAC9C,GAAG,KAAK;GACR,QAAQ;GACT,CAAC;;CAGJ,MAAM,OACJ,QACA,OAAe,WACA;AACf,QAAM,UACJ,KAAK,QACL,KAAK,QACL,QACA,YAAY,QACZ,QACD;;CAGH,SAAS;AACP,SAAO,KAAK;;;;;;;AAQhB,SAAgB,YACd,QACA,KACgB;CAChB,MAAM,eAAe,KAAK;CAC1B,MAAM,WAAW,cAAc,KAAK;CACpC,MAAM,YAAY,cAAc,eAAe;CAC/C,MAAM,aAAa,cAAc,KAAK;CACtC,MAAM,cAAc,OAAO,SAAS;AACpC,KAAI,CAAC,YAAa,OAAM,IAAI,MAAM,2BAA2B;CAE7D,MAAM,iBAAiB,aAAa,UAAa,gBAAgB;CAEjE,MAAM,gBAAgB,CAAC,eACnB,+CACA,CAAC,aAAa,gBACZ,6CACA;AAEN,KACE,OAAO,YAAY,SAClB,CAAC,kBAAkB,CAAC,cAAc,eAEnC,OAAM,IAAI,MACR,6CAA6C,gBAC9C;AACH,KACE,OAAO,WAAW,SACjB,CAAC,kBAAkB,CAAC,cAAc,eAEnC,OAAM,IAAI,MAAM,0CAA0C,gBAAgB;CAE5E,MAAM,gBACJ,OAAO,YAAY,SACd,OAAO,WAAW,aACnB,OAAO,WAAW,OAChB,aACA;AAER,KAAI,OAAO,WAAW,OACpB,QAAO;EACL,QAAQ;EACR,SAAS,OAAO,UAAU;EAC1B,YAAY,OAAO;EACnB,cACE,kBAAkB,OAAO,WAAW,SAAY,aAAa;EAC/D,SAAS,OAAO;EACjB;UACQ,OAAO,WAAW,OAC3B,QAAO;EACL,QAAQ;EACR,SAAS,OAAO,UAAU;EAC1B,YAAY,OAAO;EACnB,cAAc;EACf;UACQ,OAAO,UAAU,OAC1B,QAAO,EACL,QAAQ,aACT;UACQ,aAAa,eAAe,OACrC,QAAO;EACL,QAAQ;EACR,SAAS;EACT,cAAc;EACf;KAED,QAAO,EACL,QAAQ,aACT;;;;;AAOL,SAAgB,sBACd,MACA,IACA,UACA;AACA,QAAO,CACL;EACE;EACA;EACA,QAAQ;EACT,CACF;;;;;AAMH,eAAsB,mBACpB,QACA,QACA,MACA,IACA,UACA,SACe;CACf,MAAM,gBAAgB,sBAAsB,MAAM,IAAI,SAAS;AAE/D,OAAM,OAAO,kBAAkB;EAC7B;EACA,UAAU;EACV;EACD,CAAC;;AAGJ,SAAS,cAAc,QAAsC;AAC3D,KAAI,OAAO,WAAW,OAAW,QAAO;AACxC,KAAI,OAAO,WAAW,OAAW,QAAO;AACxC,KAAI,OAAO,UAAU,OAAW,QAAO;AAEvC,QAAO;;AAGT,SAAS,mBACP,QACA,KACS;CACT,MAAM,gBAAgB,KAAK,WAAW;AACtC,KAAI,CAAC,cACH,QAAO;CAGT,MAAM,eAAe,OAAO;CAC5B,MAAM,wBAAwB,cAAc;AAE5C,KAAI,iBAAiB,OACnB,QAAO;AAGT,KAAI,iBAAiB,KACnB,QAAO;AAGT,QAAO,iBAAiB;;;;;;AAO1B,eAAsB,UACpB,QACA,QACA,QACA,MACA,IACe;CACf,MAAM,MAAM,MAAM,aAAa;CAC/B,MAAM,SAAS,YAAY,QAAQ,IAAI;CAEvC,MAAM,cAAc,CAAC,CAAC,KAAK;CAC3B,MAAM,eAAe,CAAC,CAAC,KAAK,WAAW;AACvC,KAAI,eAAe,CAAC,aAClB,QAAO,sBAAsB,KAC3B,sIACD;CAGH,MAAM,QAAQ,OAAO,SAAS,KAAK,WAAW,KAAK;CACnD,MAAM,UAAU,OAAO,WAAW,KAAK,WAAW,KAAK;AASvD,KALE,UAAU,KAAK,WAAW,KAAK,SAC/B,mBAAmB,QAAQ,IAAI,IAC/B,YAAY,KAAK,WAAW,KAAK,WACjC,CAAC,OAAO,QAEI;EACZ,MAAM,gBAAgB,KAAK,WAAW;EACtC,MAAM,eAAe,KAAK,WAAW;EACrC,MAAM,QAAQ,cAAc,OAAO;AAEnC,MACE,eAAe,MACf,gBACA,aAAa,YAAY,cAAc,IAAI,MAAM,OAAO,IAAI,OAAO,CAEnE;;AAWJ,OAAM,mBAAmB,QAAQ,QAAQ,MAAM,IAAI,SAL/C,KAAK,WAAW,WAGf,SAAS,WAAW,OAE0C;;AAGrE,MAAa,iBAAiB,OAAO,IAAI,wBAAwB;;;;;;;;;;;;;;AAejE,MAAa,2BAA2B;CACtC,MAAM,2BAA2B,WAAW,eAAe;EACzD,AAAS,KAAK;EAEd,OAAgB,WAAW,EAAE,UAAqC;AAChE,UAAO,iCAAiC;;EAG1C,AAAS,uBACP,KAwBA;AACA,UAAO;IACL,GAAG;IACH,KAAK;KACH,GAAG,IAAI;KACP,MAAM;MACJ,GAAG,IAAI,IAAI;MAEX,UAAW,IAAI,IAAI,KACjB;MAEH;KACF;IACF;;;AAIL,QAAO"}
@@ -0,0 +1,93 @@
1
+ const require_types = require('../helpers/types.cjs');
2
+ const require_middleware = require('./middleware/middleware.cjs');
3
+ const require_InngestMetadata = require('./InngestMetadata.cjs');
4
+
5
+ //#region src/components/InngestScore.ts
6
+ const scoreKind = "inngest.score";
7
+ const experimentKind = "inngest.experiment";
8
+ const maxScoreNameByteLength = 128;
9
+ const scoreSymbol = Symbol.for("inngest.step.score");
10
+ function validateIdField({ value, field, required }) {
11
+ if (!required && value === void 0) return;
12
+ if (!(typeof value === "string" && value.trim().length > 0)) throw new Error(`${field} must be a non-empty string`);
13
+ }
14
+ function validateScoreFields(options, requiredTargetIds) {
15
+ if (!require_types.isRecord(options)) throw new Error("score options must be an object");
16
+ for (const field of ["runId", "stepId"]) validateIdField({
17
+ value: options[field],
18
+ field,
19
+ required: requiredTargetIds.includes(field)
20
+ });
21
+ if (typeof options.name !== "string" || options.name.trim().length === 0) throw new Error("score name must be a non-empty string");
22
+ if (/[\x00-\x1f\x7f']/.test(options.name)) throw new Error("score name must not contain control characters or single quotes");
23
+ const nameByteLength = new TextEncoder().encode(options.name).length;
24
+ if (nameByteLength > maxScoreNameByteLength) throw new Error(`score name must be ${maxScoreNameByteLength} bytes or fewer in UTF-8 (got ${nameByteLength})`);
25
+ if (typeof options.value !== "boolean" && !require_types.isFiniteNumber(options.value)) throw new Error("score value must be a finite number or boolean");
26
+ }
27
+ function validateSendScoreOptions(options) {
28
+ validateScoreFields(options, []);
29
+ }
30
+ function validateStepScoreOptions(options) {
31
+ validateScoreFields(options, []);
32
+ }
33
+ async function sendScore(client, options) {
34
+ validateSendScoreOptions(options);
35
+ await require_InngestMetadata.performOp(client, {
36
+ runId: options.runId,
37
+ stepId: options.stepId
38
+ }, { [options.name]: { value: options.value } }, `${scoreKind}`, "merge");
39
+ }
40
+ async function sendStepScore(client, options) {
41
+ validateStepScoreOptions(options);
42
+ await require_InngestMetadata.performOp(client, {
43
+ runId: options.stepId === void 0 ? options.runId ?? null : options.runId,
44
+ stepId: options.stepId
45
+ }, { [options.name]: { value: options.value } }, `${scoreKind}`, "merge");
46
+ }
47
+ function validateExperimentRef(experiment) {
48
+ if (!require_types.isRecord(experiment)) throw new Error("experiment must be an object");
49
+ for (const field of ["experimentName", "variant"]) if (typeof experiment[field] !== "string" || experiment[field].trim().length === 0) throw new Error(`experiment.${field} must be a non-empty string`);
50
+ }
51
+ async function sendScoreExperiment(client, options) {
52
+ validateSendScoreOptions(options);
53
+ validateExperimentRef(options.experiment);
54
+ const target = {
55
+ runId: options.runId,
56
+ stepId: options.stepId
57
+ };
58
+ await require_InngestMetadata.performOp(client, target, {
59
+ experiment_name: options.experiment.experimentName,
60
+ variant: options.experiment.variant
61
+ }, experimentKind, "merge");
62
+ await require_InngestMetadata.performOp(client, target, { [options.name]: { value: options.value } }, scoreKind, "merge");
63
+ }
64
+ const scoreMiddleware = () => {
65
+ class ScoreMiddleware extends require_middleware.Middleware.BaseMiddleware {
66
+ id = "inngest:score";
67
+ static onRegister({ client }) {
68
+ client["experimentalScoreEnabled"] = true;
69
+ }
70
+ transformFunctionInput(arg) {
71
+ return {
72
+ ...arg,
73
+ ctx: {
74
+ ...arg.ctx,
75
+ step: {
76
+ ...arg.ctx.step,
77
+ score: arg.ctx.step[scoreSymbol]
78
+ }
79
+ }
80
+ };
81
+ }
82
+ }
83
+ return ScoreMiddleware;
84
+ };
85
+
86
+ //#endregion
87
+ exports.scoreMiddleware = scoreMiddleware;
88
+ exports.scoreSymbol = scoreSymbol;
89
+ exports.sendScore = sendScore;
90
+ exports.sendScoreExperiment = sendScoreExperiment;
91
+ exports.sendStepScore = sendStepScore;
92
+ exports.validateStepScoreOptions = validateStepScoreOptions;
93
+ //# sourceMappingURL=InngestScore.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InngestScore.cjs","names":["isRecord","isFiniteNumber","performOp","Middleware"],"sources":["../../src/components/InngestScore.ts"],"sourcesContent":["import { isFiniteNumber, isRecord } from \"../helpers/types.ts\";\nimport type { ExperimentRef } from \"../types.ts\";\nimport type { Inngest } from \"./Inngest.ts\";\nimport { performOp } from \"./InngestMetadata.ts\";\nimport type { ExperimentalStepTools } from \"./InngestStepTools.ts\";\nimport { Middleware } from \"./middleware/middleware.ts\";\n\nconst scoreKind = \"inngest.score\" as const;\nconst experimentKind = \"inngest.experiment\" as const;\nconst maxKindByteLength = 128;\nconst maxScoreNameByteLength = maxKindByteLength;\n\ntype ScoreValue = number | boolean;\n\nexport type ScoreOptions = {\n runId?: string;\n stepId?: string;\n name: string;\n value: ScoreValue;\n};\n\nexport type ScoreExperimentOptions = ScoreOptions & {\n experiment: ExperimentRef;\n};\n\n/**\n * The client `score` API. Call it directly to write a live score for a run or\n * step; use `.experiment(...)` to attach a score to a `group.experiment()`\n * variant.\n */\nexport interface ClientScore {\n /**\n * Write a live score for a run or a specific run step. Explicit targets win;\n * otherwise the current run or step is inferred from the execution context.\n * For standalone durable score writes, prefer `step.score()`.\n */\n (options: ScoreOptions): Promise<void>;\n\n /**\n * Attach a score to a previously-selected experiment variant, using the\n * `experiment` ref returned by `group.experiment()`. Writes the score and the\n * experiment attribution together so they co-locate on one row.\n *\n * **Call at the function-body level** (outside any `step.run()` callback), or\n * pass an explicit `runId`, so the write is run-scoped and attaches to the\n * experiment. Calling inside `step.run()` without `runId` produces a\n * step-scoped write the experiment detail backend never surfaces.\n */\n experiment(options: ScoreExperimentOptions): Promise<void>;\n}\n\nexport type ScoreStepTool = (\n memoizationId: string,\n options: ScoreOptions,\n) => Promise<void>;\n\nexport const scoreSymbol = Symbol.for(\"inngest.step.score\");\n\nfunction validateIdField({\n value,\n field,\n required,\n}: {\n value: unknown;\n field: string;\n required: boolean;\n}): void {\n if (!required && value === undefined) {\n return;\n }\n\n const isValidString = typeof value === \"string\" && value.trim().length > 0;\n if (!isValidString) {\n throw new Error(`${field} must be a non-empty string`);\n }\n}\n\nfunction validateScoreFields(\n options: unknown,\n requiredTargetIds: readonly (\"runId\" | \"stepId\")[],\n): asserts options is {\n runId?: unknown;\n stepId?: unknown;\n name: unknown;\n value: unknown;\n} {\n if (!isRecord(options)) {\n throw new Error(\"score options must be an object\");\n }\n\n for (const field of [\"runId\", \"stepId\"] as const) {\n validateIdField({\n value: options[field],\n field,\n required: requiredTargetIds.includes(field),\n });\n }\n\n if (typeof options.name !== \"string\" || options.name.trim().length === 0) {\n throw new Error(\"score name must be a non-empty string\");\n }\n\n // Single quote rejection mirrors the cloud MetricKeyRegex; without it,\n // valid-looking score names like \"it's-broken\" would silently drop in\n // variant aggregation.\n // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — rejecting control chars and single quotes in user-supplied names\n if (/[\\x00-\\x1f\\x7f']/.test(options.name)) {\n throw new Error(\n \"score name must not contain control characters or single quotes\",\n );\n }\n\n const nameByteLength = new TextEncoder().encode(options.name).length;\n if (nameByteLength > maxScoreNameByteLength) {\n throw new Error(\n `score name must be ${maxScoreNameByteLength} bytes or fewer in UTF-8 (got ${nameByteLength})`,\n );\n }\n\n if (typeof options.value !== \"boolean\" && !isFiniteNumber(options.value)) {\n throw new Error(\"score value must be a finite number or boolean\");\n }\n}\n\nfunction validateSendScoreOptions(\n options: unknown,\n): asserts options is ScoreOptions {\n validateScoreFields(options, []);\n}\n\nexport function validateStepScoreOptions(\n options: unknown,\n): asserts options is ScoreOptions {\n validateScoreFields(options, []);\n}\n\nexport async function sendScore(\n client: Inngest,\n options: ScoreOptions,\n): Promise<void> {\n validateSendScoreOptions(options);\n\n await performOp(\n client,\n {\n runId: options.runId,\n stepId: options.stepId,\n },\n { [options.name]: { value: options.value } },\n `${scoreKind}`,\n \"merge\",\n );\n}\n\nexport async function sendStepScore(\n client: Inngest,\n options: ScoreOptions,\n): Promise<void> {\n validateStepScoreOptions(options);\n\n await performOp(\n client,\n {\n // Omitted stepId means run scope and null keeps current-run lookup intact.\n runId:\n options.stepId === undefined ? (options.runId ?? null) : options.runId,\n stepId: options.stepId,\n },\n { [options.name]: { value: options.value } },\n `${scoreKind}`,\n \"merge\",\n );\n}\n\nfunction validateExperimentRef(\n experiment: unknown,\n): asserts experiment is ExperimentRef {\n if (!isRecord(experiment)) {\n throw new Error(\"experiment must be an object\");\n }\n for (const field of [\"experimentName\", \"variant\"] as const) {\n if (\n typeof experiment[field] !== \"string\" ||\n (experiment[field] as string).trim().length === 0\n ) {\n throw new Error(`experiment.${field} must be a non-empty string`);\n }\n }\n}\n\nexport async function sendScoreExperiment(\n client: Inngest,\n options: ScoreExperimentOptions,\n): Promise<void> {\n validateSendScoreOptions(options);\n validateExperimentRef(options.experiment);\n\n const target = { runId: options.runId, stepId: options.stepId };\n\n // Write the experiment attribution first, then the score. These are two\n // non-atomic metadata writes; if the second fails, attribution-without-score\n // is the benign state the system already produces (it's exactly what\n // `group.experiment()` leaves after selecting a variant but before scoring).\n // Writing the score first would instead risk a bare, unattributed score that\n // never surfaces in the experiment view.\n await performOp(\n client,\n target,\n {\n experiment_name: options.experiment.experimentName,\n variant: options.experiment.variant,\n },\n experimentKind,\n \"merge\",\n );\n await performOp(\n client,\n target,\n { [options.name]: { value: options.value } },\n scoreKind,\n \"merge\",\n );\n}\n\nexport const scoreMiddleware = () => {\n class ScoreMiddleware extends Middleware.BaseMiddleware {\n readonly id = \"inngest:score\";\n\n static override onRegister({ client }: Middleware.OnRegisterArgs) {\n client[\"experimentalScoreEnabled\"] = true;\n }\n\n override transformFunctionInput(\n arg: Middleware.TransformFunctionInputArgs,\n ): Middleware.TransformFunctionInputArgs & {\n ctx: {\n step: {\n /**\n * Create a durable score update wrapped in a step.\n * Omit `stepId` to attach the score to the run.\n * Use `inngest.score()` for live score writes inside `step.run()`.\n *\n * @param memoizationId - The durable step ID used to memoize this score write.\n */\n score: ExperimentalStepTools[typeof scoreSymbol];\n };\n };\n } {\n return {\n ...arg,\n ctx: {\n ...arg.ctx,\n step: {\n ...arg.ctx.step,\n score: (arg.ctx.step as unknown as ExperimentalStepTools)[\n scoreSymbol\n ],\n },\n },\n };\n }\n }\n\n return ScoreMiddleware;\n};\n"],"mappings":"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"}