import type { NodeExecutionContext } from "./runtimeTypes"; import type { Item, Items, NodeActivationId, NodeId, RunDataSnapshot, RunId, WorkflowId } from "./workflowTypes"; export type ItemExprResolvedContext = Readonly<{ runId: RunId; workflowId: WorkflowId; nodeId: NodeId; activationId: NodeActivationId; data: RunDataSnapshot; }>; export type ItemExprContext = ItemExprResolvedContext; export type ItemExprArgs = Readonly<{ item: Item; itemIndex: number; items: Items; ctx: ItemExprContext; }>; export type ItemExprCallback = (args: ItemExprArgs) => T | Promise; export type ItemExpr = Readonly<{ readonly __codemationItemExpr: "codemation.itemExpr"; readonly fn: ItemExprCallback; }>; export function itemExpr(fn: ItemExprCallback): ItemExpr { return { __codemationItemExpr: "codemation.itemExpr", fn }; } export function isItemExpr(value: unknown): value is ItemExpr { if (typeof value !== "object" || value === null) { return false; } const v = value as Record; if (v["__codemationItemExpr"] === "codemation.itemExpr" && typeof v["fn"] === "function") { return true; } for (const sym of Object.getOwnPropertySymbols(v)) { if (sym.description === "codemation.itemExpr" && v[sym] === true) { return true; } } const keys = Object.keys(v); if (keys.length === 1 && keys[0] === "fn" && typeof (v as { fn?: unknown }).fn === "function") { return true; } return false; } function containsItemExprInUnknown(value: unknown, seen: WeakSet = new WeakSet()): boolean { if (isItemExpr(value)) { return true; } if (value === null || typeof value !== "object") { return false; } if (seen.has(value as object)) { return false; } seen.add(value as object); if (Array.isArray(value)) { return value.some((entry) => containsItemExprInUnknown(entry, seen)); } for (const entry of Object.values(value as Record)) { if (containsItemExprInUnknown(entry, seen)) { return true; } } return false; } export async function resolveItemExprsInUnknown( value: unknown, args: ItemExprArgs, seen: WeakSet = new WeakSet(), ): Promise { if (isItemExpr(value)) { return await Promise.resolve(value.fn(args)); } if (value === null || typeof value !== "object") { return value; } if (seen.has(value as object)) { return value; } seen.add(value as object); if (Array.isArray(value)) { const out: unknown[] = []; for (let i = 0; i < value.length; i++) { out.push(await resolveItemExprsInUnknown(value[i], args, seen)); } return out; } const rec = value as Record; const entries = Object.entries(rec); const proto = Object.getPrototypeOf(value); if (proto !== Object.prototype && proto !== null && entries.length === 0) { return value; } const out = Object.create(proto) as Record; for (const [k, v] of entries) { out[k] = await resolveItemExprsInUnknown(v, args, seen); } return out; } export async function resolveItemExprsForExecution( config: unknown, nodeCtx: NodeExecutionContext, item: Item, itemIndex: number, items: Items, ): Promise { const exprArgs: ItemExprArgs = { item, itemIndex, items, ctx: { runId: nodeCtx.runId, workflowId: nodeCtx.workflowId, nodeId: nodeCtx.nodeId, activationId: nodeCtx.activationId, data: nodeCtx.data, }, }; if (!containsItemExprInUnknown(config)) { return undefined; } return await resolveItemExprsInUnknown(config, exprArgs); }