UNPKG

@nocobase/flow-engine

Version:

A standalone flow engine for NocoBase, managing workflows, models, and actions.

310 lines (308 loc) 11.6 kB
/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var params_resolvers_exports = {}; __export(params_resolvers_exports, { enqueueVariablesResolve: () => enqueueVariablesResolve, preprocessExpression: () => preprocessExpression, resolveCreateModelOptions: () => resolveCreateModelOptions, resolveDefaultParams: () => resolveDefaultParams, resolveExpressions: () => resolveExpressions }); module.exports = __toCommonJS(params_resolvers_exports); var import_lodash = __toESM(require("lodash")); var import_client = require("@nocobase/utils/client"); async function resolveDefaultParams(defaultParams, ctx) { if (!defaultParams) { return {}; } if (typeof defaultParams === "function") { try { const result = await defaultParams(ctx); return result || {}; } catch (error) { console.error("Error resolving defaultParams function:", error); return {}; } } return defaultParams; } __name(resolveDefaultParams, "resolveDefaultParams"); async function resolveCreateModelOptions(createModelOptions, ctx, extra) { if (!createModelOptions) { return {}; } if (typeof createModelOptions === "function") { try { const result = await createModelOptions(ctx, extra); return result || {}; } catch (error) { console.error("Error resolving createModelOptions function:", error); return {}; } } return createModelOptions; } __name(resolveCreateModelOptions, "resolveCreateModelOptions"); const VAR_AGGREGATOR = { queue: [], timer: null, inflightByRun: /* @__PURE__ */ new Map() }; const BATCH_FLUSH_DELAY_MS = 10; function sortKeysDeep(input) { if (Array.isArray(input)) return input.map(sortKeysDeep); if (import_lodash.default.isPlainObject(input)) { const entries = Object.entries(input).map(([k, v]) => [k, sortKeysDeep(v)]); const sortedEntries = import_lodash.default.sortBy(entries, ([k]) => k); return Object.fromEntries(sortedEntries); } return input; } __name(sortKeysDeep, "sortKeysDeep"); function stableStringifyOrdered(obj) { try { return JSON.stringify(sortKeysDeep(obj)); } catch { try { return JSON.stringify(obj); } catch { return String(obj); } } } __name(stableStringifyOrdered, "stableStringifyOrdered"); function enqueueVariablesResolve(ctx, payload) { const agg = VAR_AGGREGATOR; const runId = ctx.runId || "GLOBAL"; const dedupKey = stableStringifyOrdered(payload); let runMap = agg.inflightByRun.get(runId); if (!runMap) { runMap = /* @__PURE__ */ new Map(); agg.inflightByRun.set(runId, runMap); } const existing = runMap.get(dedupKey); if (existing) return existing; let resolveFn; let rejectFn; const p = new Promise((resolve, reject) => { resolveFn = resolve; rejectFn = reject; }); runMap.set(dedupKey, p); const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`; const item = { id, ctx, payload, resolve: resolveFn, reject: rejectFn, runId, dedupKey }; agg.queue.push(item); const flush = /* @__PURE__ */ __name(async () => { var _a, _b, _c, _d, _e, _f; const items = agg.queue.splice(0, agg.queue.length); agg.timer = null; if (!items.length) return; const api = (_a = items[0].ctx) == null ? void 0 : _a.api; if (!api) { for (const it of items) { try { it.resolve(it.payload.template); } catch (e) { it.reject(e); } finally { (_b = agg.inflightByRun.get(it.runId)) == null ? void 0 : _b.delete(it.dedupKey); } } return; } try { const batch = items.map((it) => ({ id: it.id, template: it.payload.template, contextParams: it.payload.contextParams || {} })); const res = await api.request({ method: "POST", url: "variables:resolve", data: { values: { batch } } }); const top = res.data ?? res; const root = top.data ?? top; const arr1 = root == null ? void 0 : root.results; const resultsArr = Array.isArray(arr1) ? arr1 : Array.isArray(root) ? root : []; const map = /* @__PURE__ */ new Map(); for (const r of resultsArr) { const k = (r == null ? void 0 : r.id) != null ? String(r.id) : ""; if (k) map.set(k, r == null ? void 0 : r.data); } for (const it of items) { try { const k = String(it.id); const resolved = map.has(k) ? map.get(k) : it.payload.template; it.resolve(resolved); } catch (e) { it.reject(e); } finally { (_c = agg.inflightByRun.get(it.runId)) == null ? void 0 : _c.delete(it.dedupKey); } } } catch (e) { for (const it of items) { try { (_e = (_d = ctx == null ? void 0 : ctx.logger) == null ? void 0 : _d.warn) == null ? void 0 : _e.call(_d, { err: e }, "variables:resolve(batch) failed, fallback"); it.resolve(it.payload.template); } catch (err) { it.reject(err); } finally { (_f = agg.inflightByRun.get(it.runId)) == null ? void 0 : _f.delete(it.dedupKey); } } } }, "flush"); if (!agg.timer) { agg.timer = setTimeout(flush, BATCH_FLUSH_DELAY_MS); } return p; } __name(enqueueVariablesResolve, "enqueueVariablesResolve"); async function resolveExpressions(params, ctx) { const compile = /* @__PURE__ */ __name(async (source) => { if (typeof source === "string" && /\{\{.*?\}\}/.test(source)) { return await compileExpression(source, ctx); } if (Array.isArray(source)) { return Promise.all(source.map(compile)); } if (source && typeof source === "object") { const result = {}; for (const [key, value] of Object.entries(source)) { result[key] = await compile(value); } return result; } return source; }, "compile"); return compile(params); } __name(resolveExpressions, "resolveExpressions"); async function processExpression(expression, ctx) { const processedExpr = await preprocessExpression(expression.trim(), ctx); const result = await ctx.runjs(`return ${processedExpr}`, { t: ctx.t }); return (result == null ? void 0 : result.success) ? result.value : void 0; } __name(processExpression, "processExpression"); async function preprocessExpression(expression, ctx) { const pathRegex = /ctx\.([a-zA-Z_$][a-zA-Z0-9_$]*(?:(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)|(?:\[[^\]]+\]))*)(?!\s*\()/g; const pathMatches = []; const firstLevelKeys = /* @__PURE__ */ new Set(); let match; while ((match = pathRegex.exec(expression)) !== null) { const fullPath = match[0]; const pathAfterCtx = match[1]; const firstKeyMatch = pathAfterCtx.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)/); if (firstKeyMatch) { const firstKey = firstKeyMatch[1]; const restPath = pathAfterCtx.substring(firstKey.length); pathMatches.push({ fullPath, firstKey, restPath // 单层路径时为空字符串,多层路径可能包含数组索引 }); firstLevelKeys.add(firstKey); } } if (pathMatches.length === 0) { return expression; } const recordProxyMap = /* @__PURE__ */ new Map(); for (const firstKey of firstLevelKeys) { try { const rawValue = await ctx[firstKey]; const isRecordProxy = rawValue && rawValue.__isRecordProxy__ === true; recordProxyMap.set(firstKey, isRecordProxy); } catch (error) { recordProxyMap.set(firstKey, false); } } pathMatches.sort((a, b) => b.fullPath.length - a.fullPath.length); let processedExpr = expression; for (const { fullPath, firstKey, restPath } of pathMatches) { const isRecordProxy = recordProxyMap.get(firstKey) || false; let processedPath; if (restPath === "") { processedPath = `await ctx.${firstKey}`; } else { processedPath = isRecordProxy ? `await (await ctx.${firstKey})${restPath}` : `(await ctx.${firstKey})${restPath}`; } const escapedFullPath = fullPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const regex = new RegExp(`\\b${escapedFullPath}\\b`, "g"); processedExpr = processedExpr.replace(regex, processedPath); } return processedExpr; } __name(preprocessExpression, "preprocessExpression"); async function compileExpression(expression, ctx) { const matchDotOnly = /* @__PURE__ */ __name((expr) => { const m = expr.trim().match(/^ctx\.([a-zA-Z_$][a-zA-Z0-9_$]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$]*)*)$/); return m ? m[1] : null; }, "matchDotOnly"); const resolveDotOnlyPath = /* @__PURE__ */ __name(async (dotPath) => { const segs = dotPath.split("."); const first = segs.shift(); if (!first) return void 0; const base = await ctx[first]; if (segs.length === 0) return base; return (0, import_client.getValuesByPath)(base, segs.join(".")); }, "resolveDotOnlyPath"); const singleMatch = expression.match(/^\{\{\s*(.+)\s*\}\}$/); if (singleMatch) { const inner = singleMatch[1]; const dotPath = matchDotOnly(inner); if (dotPath) { return await resolveDotOnlyPath(dotPath); } return await processExpression(inner, ctx); } const matches = [...expression.matchAll(/\{\{\s*(.+?)\s*\}\}/g)]; let result = expression; for (const [fullMatch, innerExpr] of matches) { const dotPath = matchDotOnly(innerExpr); const value = dotPath ? await resolveDotOnlyPath(dotPath) : await processExpression(innerExpr, ctx); if (value !== void 0) { const replacement = typeof value === "object" && value !== null ? JSON.stringify(value) : String(value); result = result.replace(fullMatch, replacement); } } return result; } __name(compileExpression, "compileExpression"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { enqueueVariablesResolve, preprocessExpression, resolveCreateModelOptions, resolveDefaultParams, resolveExpressions });