@stndrds/schema 1.0.0-alpha.79 → 1.0.0-alpha.80

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.
@@ -12,7 +12,7 @@ import {
12
12
  validateDraftOrThrow,
13
13
  validateObject,
14
14
  validateObjectOrThrow
15
- } from "./chunk-5SZ5OISG.mjs";
15
+ } from "./chunk-7VNLLASJ.mjs";
16
16
  import {
17
17
  __require
18
18
  } from "./chunk-Y6FXYEAI.mjs";
@@ -1309,6 +1309,67 @@ function isSystemFlow(flow) {
1309
1309
  return flow.system === true;
1310
1310
  }
1311
1311
 
1312
+ // src/types/workflows/node-type-registry.ts
1313
+ var NodeTypeRegistryImpl = class {
1314
+ constructor() {
1315
+ this.definitions = /* @__PURE__ */ new Map();
1316
+ }
1317
+ register(definition) {
1318
+ this.definitions.set(definition.type, definition);
1319
+ }
1320
+ get(type) {
1321
+ const def = this.definitions.get(type);
1322
+ if (!def) {
1323
+ throw new Error(`[NodeTypeRegistry] Unknown node type: "${type}"`);
1324
+ }
1325
+ return def;
1326
+ }
1327
+ has(type) {
1328
+ return this.definitions.has(type);
1329
+ }
1330
+ getAll() {
1331
+ return Array.from(this.definitions.values());
1332
+ }
1333
+ getActionTypes() {
1334
+ return this.getAll().filter((d) => !d.structural).map((d) => d.type);
1335
+ }
1336
+ };
1337
+ var nodeTypeRegistry = new NodeTypeRegistryImpl();
1338
+ function getNodeOutputs(node) {
1339
+ return nodeTypeRegistry.get(node.type).getOutputs(node);
1340
+ }
1341
+ function setNodeNext(node, targetId) {
1342
+ return nodeTypeRegistry.get(node.type).setNext(node, targetId);
1343
+ }
1344
+ function getNodeSlotIds(node) {
1345
+ return nodeTypeRegistry.get(node.type).getSlotIds(node);
1346
+ }
1347
+ function validateNode(node) {
1348
+ return nodeTypeRegistry.get(node.type).validate(node);
1349
+ }
1350
+ function getFormFieldRefs(node) {
1351
+ const refs = [];
1352
+ if (node.fields) {
1353
+ for (const field of node.fields) {
1354
+ if (field.slotId && field.attribute) {
1355
+ refs.push({ slotId: field.slotId, attribute: field.attribute });
1356
+ }
1357
+ }
1358
+ }
1359
+ if (node.rows) {
1360
+ for (const row of node.rows) {
1361
+ if (isFlowFieldsRow(row)) {
1362
+ for (const field of row.fields) {
1363
+ if (field.slotId && field.attribute) {
1364
+ refs.push({ slotId: field.slotId, attribute: field.attribute });
1365
+ }
1366
+ }
1367
+ }
1368
+ }
1369
+ }
1370
+ return refs;
1371
+ }
1372
+
1312
1373
  // src/types/workflows/nodes.ts
1313
1374
  function isSimpleFormNode(node) {
1314
1375
  return node.fields !== void 0 && node.rows === void 0;
@@ -1325,24 +1386,168 @@ function isFormNode(node) {
1325
1386
  function isConditionNode(node) {
1326
1387
  return node.type === "condition";
1327
1388
  }
1328
- function isDocumentNode(node) {
1329
- return node.type === "document";
1389
+ function isAssignNode(node) {
1390
+ return node.type === "assign";
1330
1391
  }
1331
1392
  function isEndNode(node) {
1332
1393
  return node.type === "end";
1333
1394
  }
1334
- function getNodeOutputs(node) {
1335
- switch (node.type) {
1336
- case "start":
1337
- case "form":
1338
- case "document":
1339
- return node.next ? [node.next] : [];
1340
- case "condition":
1341
- return [node.onTrue, node.onFalse].filter((n) => !!n);
1342
- case "end":
1343
- return [];
1395
+
1396
+ // src/types/workflows/node-types/start.node-type.ts
1397
+ var startNodeType = {
1398
+ type: "start",
1399
+ isLinear: true,
1400
+ structural: true,
1401
+ getOutputs(node) {
1402
+ return node.next ? [node.next] : [];
1403
+ },
1404
+ setNext(node, targetId) {
1405
+ return { ...node, next: targetId };
1406
+ },
1407
+ getSlotIds() {
1408
+ return [];
1409
+ },
1410
+ validate() {
1411
+ return [];
1344
1412
  }
1345
- }
1413
+ };
1414
+ nodeTypeRegistry.register(startNodeType);
1415
+
1416
+ // src/types/workflows/node-types/form.node-type.ts
1417
+ var formNodeType = {
1418
+ type: "form",
1419
+ isLinear: true,
1420
+ structural: false,
1421
+ getOutputs(node) {
1422
+ return node.next ? [node.next] : [];
1423
+ },
1424
+ setNext(node, targetId) {
1425
+ return { ...node, next: targetId };
1426
+ },
1427
+ getSlotIds(node) {
1428
+ const slotIds = /* @__PURE__ */ new Set();
1429
+ if (node.fields) {
1430
+ for (const field of node.fields) {
1431
+ if (field.slotId) {
1432
+ slotIds.add(field.slotId);
1433
+ }
1434
+ }
1435
+ }
1436
+ if (node.rows) {
1437
+ for (const row of node.rows) {
1438
+ if (isFlowFieldsRow(row)) {
1439
+ for (const field of row.fields) {
1440
+ if (field.slotId) {
1441
+ slotIds.add(field.slotId);
1442
+ }
1443
+ }
1444
+ }
1445
+ }
1446
+ }
1447
+ return Array.from(slotIds);
1448
+ },
1449
+ validate(node) {
1450
+ const errors = [];
1451
+ const hasFields = node.fields !== void 0 && node.fields.length > 0;
1452
+ const hasRows = node.rows !== void 0 && node.rows.length > 0;
1453
+ if (hasFields && hasRows) {
1454
+ errors.push("FormNode cannot have both 'fields' and 'rows'");
1455
+ }
1456
+ return errors;
1457
+ }
1458
+ };
1459
+ nodeTypeRegistry.register(formNodeType);
1460
+
1461
+ // src/types/workflows/node-types/condition.node-type.ts
1462
+ var conditionNodeType = {
1463
+ type: "condition",
1464
+ isLinear: false,
1465
+ structural: false,
1466
+ getOutputs(node) {
1467
+ return [node.onTrue, node.onFalse].filter((n) => !!n);
1468
+ },
1469
+ setNext() {
1470
+ throw new Error(
1471
+ "[NodeTypeRegistry] ConditionNode does not support setNext. Use onTrue/onFalse directly."
1472
+ );
1473
+ },
1474
+ getSlotIds() {
1475
+ return [];
1476
+ },
1477
+ validate(node) {
1478
+ const errors = [];
1479
+ if (!node.condition) {
1480
+ errors.push("ConditionNode must have a 'condition'");
1481
+ }
1482
+ return errors;
1483
+ }
1484
+ };
1485
+ nodeTypeRegistry.register(conditionNodeType);
1486
+
1487
+ // src/types/workflows/node-types/assign.node-type.ts
1488
+ var assignNodeType = {
1489
+ type: "assign",
1490
+ isLinear: true,
1491
+ structural: false,
1492
+ getOutputs(node) {
1493
+ return node.next ? [node.next] : [];
1494
+ },
1495
+ setNext(node, targetId) {
1496
+ return { ...node, next: targetId };
1497
+ },
1498
+ getSlotIds(node) {
1499
+ const slotIds = /* @__PURE__ */ new Set();
1500
+ if (node.targetSlotId) {
1501
+ slotIds.add(node.targetSlotId);
1502
+ }
1503
+ for (const assignment of node.assignments ?? []) {
1504
+ const src = assignment.source;
1505
+ if (src.type === "slot-ref") {
1506
+ slotIds.add(src.slotId);
1507
+ } else if (src.type === "expression") {
1508
+ const matches = src.template.matchAll(/\{\{\s*([\w]+)\.\w+/g);
1509
+ for (const match of matches) {
1510
+ slotIds.add(match[1]);
1511
+ }
1512
+ }
1513
+ }
1514
+ return [...slotIds];
1515
+ },
1516
+ validate(node) {
1517
+ const errors = [];
1518
+ if (!node.targetSlotId) {
1519
+ errors.push("AssignNode must have a target slot");
1520
+ }
1521
+ if (!node.assignments?.length) {
1522
+ errors.push("AssignNode must have at least one assignment");
1523
+ }
1524
+ if (!node.next) {
1525
+ errors.push("AssignNode must have a 'next' target");
1526
+ }
1527
+ return errors;
1528
+ }
1529
+ };
1530
+ nodeTypeRegistry.register(assignNodeType);
1531
+
1532
+ // src/types/workflows/node-types/end.node-type.ts
1533
+ var endNodeType = {
1534
+ type: "end",
1535
+ isLinear: false,
1536
+ structural: true,
1537
+ getOutputs() {
1538
+ return [];
1539
+ },
1540
+ setNext() {
1541
+ throw new Error("[NodeTypeRegistry] EndNode does not support setNext.");
1542
+ },
1543
+ getSlotIds() {
1544
+ return [];
1545
+ },
1546
+ validate() {
1547
+ return [];
1548
+ }
1549
+ };
1550
+ nodeTypeRegistry.register(endNodeType);
1346
1551
 
1347
1552
  // src/types/workflows/conditions.ts
1348
1553
  function isConditionRule(item) {
@@ -1532,6 +1737,69 @@ function isInvitationOrGrantEvent(event) {
1532
1737
  return event.type.startsWith("workflow.invitation.") || event.type.startsWith("workflow.grant.");
1533
1738
  }
1534
1739
 
1740
+ // src/types/workflows/zones.ts
1741
+ var ZONE_ORDER = ["collect", "actions"];
1742
+ var ZONE_CONFIG = {
1743
+ collect: { allowedNodeTypes: ["form", "condition"] },
1744
+ actions: { allowedNodeTypes: ["condition", "assign"] }
1745
+ };
1746
+ var TERMINAL_NODE_TYPES = /* @__PURE__ */ new Set(["start", "end"]);
1747
+ var NODE_TYPE_ZONE = {
1748
+ form: "collect",
1749
+ condition: "collect",
1750
+ // default — overridden by assignNodeZones logic
1751
+ assign: "actions"
1752
+ };
1753
+ function assignNodeZones(orderedNodes) {
1754
+ const result = /* @__PURE__ */ new Map();
1755
+ const hasFormAfter = new Array(orderedNodes.length).fill(false);
1756
+ let foundForm = false;
1757
+ for (let i = orderedNodes.length - 1; i >= 0; i--) {
1758
+ hasFormAfter[i] = foundForm;
1759
+ if (orderedNodes[i].type === "form") {
1760
+ foundForm = true;
1761
+ }
1762
+ }
1763
+ for (let i = 0; i < orderedNodes.length; i++) {
1764
+ const node = orderedNodes[i];
1765
+ if (TERMINAL_NODE_TYPES.has(node.type)) {
1766
+ result.set(node.id, null);
1767
+ } else if (node.type === "condition") {
1768
+ result.set(node.id, hasFormAfter[i] ? "collect" : "actions");
1769
+ } else {
1770
+ result.set(node.id, NODE_TYPE_ZONE[node.type] ?? null);
1771
+ }
1772
+ }
1773
+ return result;
1774
+ }
1775
+ function groupNodesByZone(orderedNodes) {
1776
+ const zoneMap = assignNodeZones(orderedNodes);
1777
+ const result = {
1778
+ start: null,
1779
+ zones: {
1780
+ collect: [],
1781
+ actions: []
1782
+ },
1783
+ end: null
1784
+ };
1785
+ for (const node of orderedNodes) {
1786
+ if (node.type === "start") {
1787
+ result.start = node;
1788
+ } else if (node.type === "end") {
1789
+ result.end = node;
1790
+ } else {
1791
+ const zone = zoneMap.get(node.id);
1792
+ if (zone) {
1793
+ result.zones[zone].push(node);
1794
+ }
1795
+ }
1796
+ }
1797
+ return result;
1798
+ }
1799
+ function getZoneAllowedTypes(zone) {
1800
+ return ZONE_CONFIG[zone].allowedNodeTypes;
1801
+ }
1802
+
1535
1803
  // src/types/workflows/validation.ts
1536
1804
  import { z } from "zod";
1537
1805
  var ConditionOperatorSchema = z.enum([
@@ -1634,14 +1902,26 @@ var ConditionNodeSchema = z.object({
1634
1902
  onTrue: z.string().nullish(),
1635
1903
  onFalse: z.string().nullish()
1636
1904
  });
1637
- var DocumentNodeSchema = z.object({
1638
- type: z.literal("document"),
1905
+ var AssignmentSourceSchema = z.discriminatedUnion("type", [
1906
+ z.object({ type: z.literal("expression"), template: z.string().min(1) }),
1907
+ z.object({
1908
+ type: z.literal("slot-ref"),
1909
+ slotId: z.string().min(1),
1910
+ properties: z.record(z.string(), z.unknown()).optional()
1911
+ }),
1912
+ z.object({ type: z.literal("static"), value: z.unknown() })
1913
+ ]);
1914
+ var AssignmentMappingSchema = z.object({
1915
+ targetAttribute: z.string().min(1),
1916
+ source: AssignmentSourceSchema
1917
+ });
1918
+ var AssignNodeSchema = z.object({
1919
+ type: z.literal("assign"),
1639
1920
  id: z.string().min(1),
1640
1921
  label: z.string().min(1),
1641
1922
  description: z.string().nullish(),
1642
- templateId: z.string().min(1, "Template ID is required"),
1643
- outputFormat: z.enum(["pdf", "docx"]),
1644
- filename: z.string().nullish(),
1923
+ targetSlotId: z.string().min(1, "Target slot is required"),
1924
+ assignments: z.array(AssignmentMappingSchema).min(1),
1645
1925
  next: z.string().nullish()
1646
1926
  });
1647
1927
  var EndNodeSchema = z.object({
@@ -1654,7 +1934,7 @@ var WorkflowNodeSchema = z.discriminatedUnion("type", [
1654
1934
  StartNodeSchema,
1655
1935
  FormNodeSchema,
1656
1936
  ConditionNodeSchema,
1657
- DocumentNodeSchema,
1937
+ AssignNodeSchema,
1658
1938
  EndNodeSchema
1659
1939
  ]);
1660
1940
  var SlotModeSchema = z.enum(["create", "select", "optional"]);
@@ -1776,15 +2056,8 @@ var WorkflowDefinitionSchema = z.object({
1776
2056
  ).refine(
1777
2057
  (def) => {
1778
2058
  for (const node of Object.values(def.nodes)) {
1779
- if (node.type === "start" || node.type === "form" || node.type === "document") {
1780
- if (node.next && !(node.next in def.nodes)) {
1781
- return false;
1782
- }
1783
- } else if (node.type === "condition") {
1784
- if (node.onTrue && !(node.onTrue in def.nodes)) {
1785
- return false;
1786
- }
1787
- if (node.onFalse && !(node.onFalse in def.nodes)) {
2059
+ for (const targetId of getNodeOutputs(node)) {
2060
+ if (!(targetId in def.nodes)) {
1788
2061
  return false;
1789
2062
  }
1790
2063
  }
@@ -2101,85 +2374,6 @@ var ConditionExecutor = class {
2101
2374
  }
2102
2375
  };
2103
2376
 
2104
- // src/runtime/executors/document.executor.ts
2105
- var DocumentExecutor = class {
2106
- constructor() {
2107
- this.nodeType = "document";
2108
- }
2109
- execute(node, _context) {
2110
- if (!node.templateId) {
2111
- return error("MISSING_TEMPLATE", "DocumentNode must have a templateId");
2112
- }
2113
- if (!node.next) {
2114
- return error("MISSING_NEXT", "DocumentNode must have a 'next' target");
2115
- }
2116
- const contextUpdates = {
2117
- documents: {
2118
- [node.id]: {
2119
- id: "",
2120
- // Will be filled by consumer after generation
2121
- url: "",
2122
- // Will be filled by consumer after generation
2123
- filename: node.filename,
2124
- metadata: {
2125
- templateId: node.templateId,
2126
- templateVersion: node.templateVersion ?? 1,
2127
- targetSlotIds: node.targetSlotIds ?? [],
2128
- status: "pending"
2129
- }
2130
- }
2131
- }
2132
- };
2133
- return success(node.next, contextUpdates);
2134
- }
2135
- canExecute(_node, _context) {
2136
- return true;
2137
- }
2138
- validate(node) {
2139
- const errors = [];
2140
- if (!node.label) {
2141
- errors.push("DocumentNode must have a 'label' property");
2142
- }
2143
- if (!node.templateId) {
2144
- errors.push("DocumentNode must have a 'templateId' property");
2145
- }
2146
- if (!node.next) {
2147
- errors.push("DocumentNode must have a 'next' property");
2148
- }
2149
- if (node.targetSlotIds && node.targetSlotIds.length > 0) {
2150
- for (const slotId of node.targetSlotIds) {
2151
- if (!slotId || typeof slotId !== "string" || slotId.trim() === "") {
2152
- errors.push("DocumentNode targetSlotIds must contain non-empty string values");
2153
- break;
2154
- }
2155
- }
2156
- }
2157
- return errors;
2158
- }
2159
- /**
2160
- * Validate that targetSlotIds reference existing slots in the workflow definition.
2161
- * This is a context-aware validation that requires the workflow's slot definitions.
2162
- *
2163
- * @param node - The document node to validate
2164
- * @param workflowSlots - All slots defined in the workflow
2165
- * @returns Array of validation error messages
2166
- */
2167
- validateSlotReferences(node, workflowSlots) {
2168
- const errors = [];
2169
- if (node.targetSlotIds && node.targetSlotIds.length > 0) {
2170
- const slotIdSet = workflowSlots.reduce((set, s) => set.add(s.id), /* @__PURE__ */ new Set());
2171
- for (const slotId of node.targetSlotIds) {
2172
- if (!slotIdSet.has(slotId)) {
2173
- errors.push(
2174
- `DocumentNode "${node.id}" references unknown slot "${slotId}" in targetSlotIds`
2175
- );
2176
- }
2177
- }
2178
- }
2179
- return errors;
2180
- }
2181
- };
2182
-
2183
2377
  // src/runtime/executors/end.executor.ts
2184
2378
  var EndExecutor = class {
2185
2379
  constructor() {
@@ -2203,6 +2397,13 @@ var FormExecutor = class {
2203
2397
  }
2204
2398
  execute(node, context) {
2205
2399
  const { input } = context;
2400
+ const hasContent = (node.fields?.length ?? 0) > 0 || (node.rows?.length ?? 0) > 0;
2401
+ if (!hasContent) {
2402
+ if (!node.next) {
2403
+ return error("MISSING_NEXT", `FormNode "${node.id}" has no 'next' target defined`);
2404
+ }
2405
+ return success(node.next);
2406
+ }
2206
2407
  if (isEmpty(input)) {
2207
2408
  const requiredParticipationId = node.participantId ?? void 0;
2208
2409
  return wait(`Waiting for form submission: ${node.label}`, {
@@ -2210,10 +2411,7 @@ var FormExecutor = class {
2210
2411
  });
2211
2412
  }
2212
2413
  const formInput = input;
2213
- const slotIds = this.extractSlotIds(node);
2214
- if (slotIds.size === 0) {
2215
- return error("MISSING_FIELDS", "FormNode must have fields or rows with slot references");
2216
- }
2414
+ const slotIds = new Set(getNodeSlotIds(node));
2217
2415
  const contextUpdates = {
2218
2416
  forms: {
2219
2417
  [node.id]: formInput
@@ -2260,39 +2458,11 @@ var FormExecutor = class {
2260
2458
  }
2261
2459
  const hasFields = node.fields !== void 0 && node.fields.length > 0;
2262
2460
  const hasRows = node.rows !== void 0 && node.rows.length > 0;
2263
- if (!(hasFields || hasRows)) {
2264
- errors.push("FormNode must have either 'fields' (simple mode) or 'rows' (advanced mode)");
2265
- }
2266
2461
  if (hasFields && hasRows) {
2267
2462
  errors.push("FormNode cannot have both 'fields' and 'rows'");
2268
2463
  }
2269
2464
  return errors;
2270
2465
  }
2271
- /**
2272
- * Extract all slot IDs referenced in the form
2273
- */
2274
- extractSlotIds(node) {
2275
- const slotIds = /* @__PURE__ */ new Set();
2276
- if (node.fields) {
2277
- for (const field of node.fields) {
2278
- if (field.slotId) {
2279
- slotIds.add(field.slotId);
2280
- }
2281
- }
2282
- }
2283
- if (node.rows) {
2284
- for (const row of node.rows) {
2285
- if (isFlowFieldsRow(row)) {
2286
- for (const field of row.fields) {
2287
- if (field.slotId) {
2288
- slotIds.add(field.slotId);
2289
- }
2290
- }
2291
- }
2292
- }
2293
- }
2294
- return slotIds;
2295
- }
2296
2466
  /**
2297
2467
  * Validate required fields based on slot mode.
2298
2468
  *
@@ -2308,7 +2478,7 @@ var FormExecutor = class {
2308
2478
  */
2309
2479
  validateRequiredFields(node, input, slots, objects) {
2310
2480
  const errors = [];
2311
- const fieldRefs = this.collectFieldRefs(node);
2481
+ const fieldRefs = getFormFieldRefs(node);
2312
2482
  for (const fieldRef of fieldRefs) {
2313
2483
  const slot = slots.find((s) => s.id === fieldRef.slotId);
2314
2484
  if (!slot) {
@@ -2329,7 +2499,7 @@ var FormExecutor = class {
2329
2499
  if (!attribute?.required) continue;
2330
2500
  const slotInput = input[fieldRef.slotId];
2331
2501
  const value = slotInput?.[fieldRef.attribute];
2332
- if (value === void 0 || value === null || value === "") {
2502
+ if (value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0) {
2333
2503
  errors.push(
2334
2504
  `Field "${attribute.label ?? fieldRef.attribute}" is required for ${slot.label}`
2335
2505
  );
@@ -2337,423 +2507,787 @@ var FormExecutor = class {
2337
2507
  }
2338
2508
  return errors;
2339
2509
  }
2340
- /**
2341
- * Collect all field references from a FormNode
2342
- */
2343
- collectFieldRefs(node) {
2344
- const refs = [];
2345
- if (node.fields) {
2346
- for (const field of node.fields) {
2347
- if (field.slotId && field.attribute) {
2348
- refs.push({ slotId: field.slotId, attribute: field.attribute });
2349
- }
2350
- }
2351
- }
2352
- if (node.rows) {
2353
- for (const row of node.rows) {
2354
- if (isFlowFieldsRow(row)) {
2355
- for (const field of row.fields) {
2356
- if (field.slotId && field.attribute) {
2357
- refs.push({ slotId: field.slotId, attribute: field.attribute });
2358
- }
2359
- }
2360
- }
2361
- }
2362
- }
2363
- return refs;
2364
- }
2365
2510
  };
2366
2511
 
2367
- // src/runtime/executors/start.executor.ts
2368
- var StartExecutor = class {
2369
- constructor() {
2370
- this.nodeType = "start";
2371
- }
2372
- execute(node, _context) {
2373
- if (!node.next) {
2374
- return error("MISSING_NEXT", `StartNode "${node.id}" has no 'next' target defined`);
2375
- }
2376
- return success(node.next);
2512
+ // src/format.ts
2513
+ import { getCountryDisplayNameByIso3 } from "@stndrds/constants";
2514
+ var EMPTY_VALUE_PLACEHOLDER = "\u2014";
2515
+ function formatText(value) {
2516
+ return String(value);
2517
+ }
2518
+ function formatCheckbox(value) {
2519
+ return value ? "Yes" : "No";
2520
+ }
2521
+ function formatNumber(value, attribute) {
2522
+ if (typeof value !== "number") return String(value);
2523
+ const decimals = attribute.decimals;
2524
+ if (attribute.unit === "integer") {
2525
+ return value.toLocaleString(void 0, {
2526
+ minimumFractionDigits: 0,
2527
+ maximumFractionDigits: 0
2528
+ });
2377
2529
  }
2378
- canExecute(_node, _context) {
2379
- return true;
2530
+ if (attribute.unit === "percentage") {
2531
+ return (value / 100).toLocaleString(void 0, {
2532
+ style: "percent",
2533
+ minimumFractionDigits: decimals,
2534
+ maximumFractionDigits: decimals
2535
+ });
2380
2536
  }
2381
- validate(node) {
2382
- const errors = [];
2383
- if (!node.next) {
2384
- errors.push("StartNode must have a 'next' property");
2385
- }
2386
- return errors;
2387
- }
2388
- };
2389
-
2390
- // src/runtime/executors/index.ts
2391
- function createDefaultExecutorRegistry() {
2392
- const registry2 = new ExecutorRegistry();
2393
- registry2.register(new StartExecutor());
2394
- registry2.register(new FormExecutor());
2395
- registry2.register(new ConditionExecutor());
2396
- registry2.register(new DocumentExecutor());
2397
- registry2.register(new EndExecutor());
2398
- return registry2;
2399
- }
2400
- var defaultRegistry = null;
2401
- function getDefaultExecutorRegistry() {
2402
- if (!defaultRegistry) {
2403
- defaultRegistry = createDefaultExecutorRegistry();
2404
- }
2405
- return defaultRegistry;
2537
+ return value.toLocaleString(void 0, {
2538
+ minimumFractionDigits: decimals,
2539
+ maximumFractionDigits: decimals
2540
+ });
2406
2541
  }
2407
-
2408
- // src/runtime/formula/evaluator.ts
2409
- import { Parser } from "expr-eval";
2410
- function createFormulaParser() {
2411
- const parser = new Parser();
2412
- parser.functions.IF = (condition, thenValue, elseValue) => condition ? thenValue : elseValue;
2413
- parser.functions.AND = (...args) => args.every(Boolean);
2414
- parser.functions.OR = (...args) => args.some(Boolean);
2415
- parser.functions.NOT = (value) => !value;
2416
- parser.functions.EMPTY = (value) => value === null || value === void 0 || value === "";
2417
- parser.functions.COALESCE = (...args) => args.find((a) => a != null) ?? null;
2418
- parser.functions.DEFAULT = (value, defaultValue) => value == null || value === "" ? defaultValue : value;
2419
- parser.functions.CONCAT = (...args) => args.filter((a) => a != null).map(String).join("");
2420
- parser.functions.UPPER = (value) => String(value ?? "").toUpperCase();
2421
- parser.functions.LOWER = (value) => String(value ?? "").toLowerCase();
2422
- parser.functions.TRIM = (value) => String(value ?? "").trim();
2423
- parser.functions.LENGTH = (value) => String(value ?? "").length;
2424
- parser.functions.LEFT = (value, count) => String(value ?? "").slice(0, count);
2425
- parser.functions.RIGHT = (value, count) => String(value ?? "").slice(-count);
2426
- parser.functions.REPLACE = (value, search, replacement) => String(value ?? "").split(String(search)).join(replacement);
2427
- parser.functions.CONTAINS = (value, search) => String(value ?? "").toLowerCase().includes(String(search).toLowerCase());
2428
- parser.functions.ROUND = (value, decimals = 0) => {
2429
- if (typeof value !== "number" || Number.isNaN(value)) return null;
2430
- const factor = 10 ** decimals;
2431
- return Math.round(value * factor) / factor;
2432
- };
2433
- parser.functions.FLOOR = (value) => {
2434
- if (typeof value !== "number" || Number.isNaN(value)) return null;
2435
- return Math.floor(value);
2436
- };
2437
- parser.functions.CEIL = (value) => {
2438
- if (typeof value !== "number" || Number.isNaN(value)) return null;
2439
- return Math.ceil(value);
2440
- };
2441
- parser.functions.ABS = (value) => {
2442
- if (typeof value !== "number" || Number.isNaN(value)) return null;
2443
- return Math.abs(value);
2444
- };
2445
- parser.functions.MIN = (...args) => {
2446
- const nums = args.filter((n) => typeof n === "number" && !Number.isNaN(n));
2447
- return nums.length > 0 ? Math.min(...nums) : null;
2448
- };
2449
- parser.functions.MAX = (...args) => {
2450
- const nums = args.filter((n) => typeof n === "number" && !Number.isNaN(n));
2451
- return nums.length > 0 ? Math.max(...nums) : null;
2452
- };
2453
- parser.functions.POW = (base, exponent) => {
2454
- if (typeof base !== "number" || typeof exponent !== "number") return null;
2455
- return base ** exponent;
2456
- };
2457
- parser.functions.MOD = (a, b) => {
2458
- if (typeof a !== "number" || typeof b !== "number" || b === 0) return null;
2459
- return a % b;
2460
- };
2461
- parser.functions.NOW = () => (/* @__PURE__ */ new Date()).toISOString();
2462
- parser.functions.TODAY = () => (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2463
- parser.functions.YEAR = (value) => {
2464
- const date2 = parseDate(value);
2465
- return date2 ? date2.getFullYear() : null;
2466
- };
2467
- parser.functions.MONTH = (value) => {
2468
- const date2 = parseDate(value);
2469
- return date2 ? date2.getMonth() + 1 : null;
2470
- };
2471
- parser.functions.DAY = (value) => {
2472
- const date2 = parseDate(value);
2473
- return date2 ? date2.getDate() : null;
2474
- };
2475
- parser.functions.DATE_DIFF = (date1, date2, unit = "days") => {
2476
- const d1 = parseDate(date1);
2477
- const d2 = parseDate(date2);
2478
- if (d1 === null || d2 === null) return null;
2479
- const diffMs = d1.getTime() - d2.getTime();
2480
- const MS_PER_DAY = 1e3 * 60 * 60 * 24;
2481
- const conversions = {
2482
- years: MS_PER_DAY * 365,
2483
- months: MS_PER_DAY * 30,
2484
- weeks: MS_PER_DAY * 7,
2485
- days: MS_PER_DAY,
2486
- hours: 1e3 * 60 * 60,
2487
- minutes: 1e3 * 60
2488
- };
2489
- const divisor = conversions[unit] ?? MS_PER_DAY;
2490
- return diffMs / divisor;
2491
- };
2492
- return parser;
2542
+ function formatCurrency(value, _attribute) {
2543
+ if (typeof value !== "object" || value === null) return String(value);
2544
+ const currency2 = value;
2545
+ if (!("value" in currency2 && "code" in currency2)) return String(value);
2546
+ const formattedValue = currency2.value.toLocaleString(void 0, {
2547
+ minimumFractionDigits: 2,
2548
+ maximumFractionDigits: 2
2549
+ });
2550
+ return `${formattedValue} ${currency2.code}`;
2493
2551
  }
2494
- function parseDate(value) {
2495
- if (!value) return null;
2496
- if (value instanceof Date) return value;
2497
- if (typeof value === "string" || typeof value === "number") {
2552
+ function formatDate(value) {
2553
+ if (value instanceof Date) {
2554
+ return value.toISOString().split("T")[0];
2555
+ }
2556
+ if (typeof value === "string") {
2498
2557
  const date2 = new Date(value);
2499
- return Number.isNaN(date2.getTime()) ? null : date2;
2558
+ if (!Number.isNaN(date2.getTime())) {
2559
+ return date2.toISOString().split("T")[0];
2560
+ }
2500
2561
  }
2501
- return null;
2562
+ return String(value);
2502
2563
  }
2503
- var formulaParser = createFormulaParser();
2504
- function evaluateFormula(expression, values) {
2505
- try {
2506
- const parsed = formulaParser.parse(expression);
2507
- return parsed.evaluate(values);
2508
- } catch {
2509
- return null;
2564
+ function formatPhone(value) {
2565
+ if (typeof value !== "object" || value === null) return String(value);
2566
+ const phone2 = value;
2567
+ if (!("phoneNumber" in phone2)) return String(value);
2568
+ if (!phone2.phoneNumber) return "";
2569
+ if (!phone2.countryCode) return phone2.phoneNumber;
2570
+ return formatPhoneForDisplay(phone2);
2571
+ }
2572
+ function formatLocation(value, attribute) {
2573
+ if (typeof value !== "object" || value === null) return String(value);
2574
+ const loc = value;
2575
+ const granularity = attribute.granularity ?? "full";
2576
+ const parts = [];
2577
+ switch (granularity) {
2578
+ case "country":
2579
+ if (loc.country) parts.push(getCountryDisplayNameByIso3(loc.country));
2580
+ break;
2581
+ case "state":
2582
+ if (loc.state) parts.push(loc.state);
2583
+ if (loc.country) parts.push(getCountryDisplayNameByIso3(loc.country));
2584
+ break;
2585
+ case "city":
2586
+ if (loc.city) parts.push(loc.city);
2587
+ if (loc.state) parts.push(loc.state);
2588
+ if (loc.country) parts.push(getCountryDisplayNameByIso3(loc.country));
2589
+ break;
2590
+ case "coordinates":
2591
+ if (loc.latitude !== void 0 && loc.longitude !== void 0) {
2592
+ parts.push(`${loc.latitude}, ${loc.longitude}`);
2593
+ }
2594
+ break;
2595
+ case "address":
2596
+ if (loc.address) parts.push(loc.address);
2597
+ if (loc.city) parts.push(loc.city);
2598
+ if (loc.state) parts.push(loc.state);
2599
+ if (loc.country) parts.push(getCountryDisplayNameByIso3(loc.country));
2600
+ break;
2601
+ default:
2602
+ if (loc.address) parts.push(loc.address);
2603
+ if (loc.city) parts.push(loc.city);
2604
+ if (loc.state) parts.push(loc.state);
2605
+ if (loc.postalCode) parts.push(loc.postalCode);
2606
+ if (loc.country) parts.push(getCountryDisplayNameByIso3(loc.country));
2607
+ break;
2510
2608
  }
2609
+ return parts.join(", ") || EMPTY_VALUE_PLACEHOLDER;
2511
2610
  }
2512
- function evaluateFormulaWithResult(expression, values) {
2513
- try {
2514
- const parsed = formulaParser.parse(expression);
2515
- const value = parsed.evaluate(values);
2516
- return { value };
2517
- } catch (error2) {
2518
- return {
2519
- value: null,
2520
- error: getErrorMessage(error2)
2521
- };
2611
+ function formatSelect(value, attribute) {
2612
+ if (typeof value !== "string") return String(value);
2613
+ const option = attribute.options?.find((o) => o.value === value);
2614
+ return option?.label ?? String(value);
2615
+ }
2616
+ function formatMultiselect(value, attribute) {
2617
+ if (!Array.isArray(value)) return String(value);
2618
+ if (attribute.options) {
2619
+ const labels = value.map((v) => attribute.options.find((o) => o.value === v)?.label).filter(Boolean);
2620
+ return labels.join(", ");
2522
2621
  }
2622
+ return value.join(", ");
2523
2623
  }
2524
- function formatFormulaResult(value, returnType, decimals) {
2525
- if (value === null || value === void 0) {
2526
- return null;
2624
+ function formatRating(value, attribute) {
2625
+ if (typeof value !== "number") return String(value);
2626
+ const max = attribute.max ?? 5;
2627
+ return `${value}/${max}`;
2628
+ }
2629
+ function formatAttributeValue(value, attribute) {
2630
+ if (value === null || value === void 0 || value === "") {
2631
+ return EMPTY_VALUE_PLACEHOLDER;
2527
2632
  }
2528
- switch (returnType) {
2633
+ switch (attribute.type) {
2634
+ case "text":
2635
+ case "textarea":
2636
+ return formatText(value);
2637
+ case "checkbox":
2638
+ return formatCheckbox(value);
2529
2639
  case "number":
2530
- return formatNumberResult(value, decimals);
2531
- case "boolean":
2532
- return Boolean(value);
2640
+ return formatNumber(value, attribute);
2641
+ case "currency":
2642
+ return formatCurrency(value, attribute);
2533
2643
  case "date":
2534
- return formatDateResult(value);
2535
- case "text":
2644
+ return formatDate(value);
2645
+ case "phone":
2646
+ return formatPhone(value);
2647
+ case "location":
2648
+ return formatLocation(value, attribute);
2649
+ case "select":
2650
+ case "status":
2651
+ return formatSelect(value, attribute);
2652
+ case "multiselect":
2653
+ return formatMultiselect(value, attribute);
2654
+ case "rating":
2655
+ return formatRating(value, attribute);
2656
+ // Unsupported types - return value as-is or placeholder
2657
+ case "file":
2658
+ case "user":
2659
+ case "relation":
2660
+ if (Array.isArray(value)) {
2661
+ return value.join(", ");
2662
+ }
2663
+ return String(value);
2664
+ default: {
2665
+ if (Array.isArray(value)) {
2666
+ return value.join(", ");
2667
+ }
2536
2668
  return String(value);
2669
+ }
2537
2670
  }
2538
2671
  }
2539
- function formatNumberResult(value, decimals) {
2540
- const num = typeof value === "number" ? value : Number(value);
2541
- if (Number.isNaN(num)) return null;
2542
- return decimals !== void 0 ? Number(num.toFixed(decimals)) : num;
2543
- }
2544
- function formatDateResult(value) {
2545
- if (value instanceof Date) return value.toISOString();
2546
- if (typeof value === "string") return value;
2547
- return null;
2548
- }
2549
- function evaluateFormulaAttribute(attr, values) {
2550
- const raw = evaluateFormula(attr.expression, values);
2551
- return formatFormulaResult(raw, attr.returnType, attr.decimals);
2672
+
2673
+ // src/runtime/template.ts
2674
+ var simplePipes = {
2675
+ /** Convert to uppercase */
2676
+ UPPER: (v) => String(v).toUpperCase(),
2677
+ /** Convert to lowercase */
2678
+ LOWER: (v) => String(v).toLowerCase(),
2679
+ /** Capitalize first letter of each word */
2680
+ capitalize: (v) => String(v).replace(/\b\w/g, (c) => c.toUpperCase()),
2681
+ /** Trim whitespace from both ends */
2682
+ trim: (v) => String(v).trim()
2683
+ };
2684
+ var pipesWithArgs = {
2685
+ /** Add prefix only if value is non-empty */
2686
+ prefix: (v, pre = "") => v ? `${pre}${v}` : "",
2687
+ /** Add suffix only if value is non-empty */
2688
+ suffix: (v, suf = "") => v ? `${v}${suf}` : "",
2689
+ /** Wrap value with prefix and suffix only if non-empty */
2690
+ wrap: (v, pre = "", suf = "") => v ? `${pre}${v}${suf}` : "",
2691
+ /** Show default value if empty */
2692
+ default: (v, def = "") => v || def
2693
+ };
2694
+ function getValue(obj, path) {
2695
+ return path.split(".").reduce((acc, key) => {
2696
+ if (acc == null || typeof acc !== "object") return void 0;
2697
+ return acc[key];
2698
+ }, obj);
2552
2699
  }
2553
- function validateFormulaExpression(expression) {
2554
- try {
2555
- formulaParser.parse(expression);
2556
- return { valid: true };
2557
- } catch (error2) {
2558
- return {
2559
- valid: false,
2560
- error: getErrorMessage(error2)
2561
- };
2700
+ var DEFAULT_LABEL_FALLBACK = "(Untitled)";
2701
+ function parsePipeExpression(pipeExpr) {
2702
+ const match = pipeExpr.match(/^(\w+)(?::(.*))?$/);
2703
+ if (!match) return { name: pipeExpr, args: [] };
2704
+ const name = match[1];
2705
+ const argsStr = match[2];
2706
+ if (!argsStr) return { name, args: [] };
2707
+ const args = [];
2708
+ const argRegex = /["']([^"']*?)["']/g;
2709
+ let argMatch;
2710
+ while ((argMatch = argRegex.exec(argsStr)) !== null) {
2711
+ args.push(argMatch[1]);
2562
2712
  }
2713
+ return { name, args };
2563
2714
  }
2564
- function extractFormulaVariables(expression) {
2565
- try {
2566
- const parsed = formulaParser.parse(expression);
2567
- return parsed.variables();
2568
- } catch {
2569
- return [];
2715
+ function renderLabelExpression(template, values, fallback = DEFAULT_LABEL_FALLBACK) {
2716
+ const result = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, expr) => {
2717
+ const orParts = expr.split("||").map((s) => s.trim());
2718
+ const lastPart = orParts[orParts.length - 1];
2719
+ const pipeSplit = lastPart.split("|").map((s) => s.trim());
2720
+ orParts[orParts.length - 1] = pipeSplit[0];
2721
+ const pipes = pipeSplit.slice(1).filter(Boolean);
2722
+ const alternatives = orParts.filter(Boolean);
2723
+ let value = "";
2724
+ for (const alt of alternatives) {
2725
+ const v = getValue(values, alt);
2726
+ if (v != null && v !== "") {
2727
+ value = v;
2728
+ break;
2729
+ }
2730
+ }
2731
+ const isEmpty3 = value == null || value === "";
2732
+ if (isEmpty3 && pipes.length === 0) return "";
2733
+ for (const pipeExpr of pipes) {
2734
+ const { name: pipeName, args } = parsePipeExpression(pipeExpr);
2735
+ const simpleFn = simplePipes[pipeName];
2736
+ if (simpleFn) {
2737
+ if (value != null && value !== "") {
2738
+ value = simpleFn(String(value));
2739
+ }
2740
+ } else {
2741
+ const argFn = pipesWithArgs[pipeName];
2742
+ if (argFn) {
2743
+ value = argFn(String(value ?? ""), ...args);
2744
+ }
2745
+ }
2746
+ }
2747
+ return String(value ?? "");
2748
+ }).trim();
2749
+ return result || fallback;
2750
+ }
2751
+ function isLabelExpression(value) {
2752
+ return /\{\{\s*\S+.*\}\}/.test(value);
2753
+ }
2754
+ function extractAttributeNames(template) {
2755
+ const names = [];
2756
+ const regex = /\{\{\s*([^}]+)\s*\}\}/g;
2757
+ let match;
2758
+ while ((match = regex.exec(template)) !== null) {
2759
+ const expr = match[1].trim();
2760
+ const orParts = expr.split("||").map((s) => s.trim());
2761
+ const lastPart = orParts[orParts.length - 1];
2762
+ orParts[orParts.length - 1] = lastPart.split("|")[0].trim();
2763
+ for (const part of orParts) {
2764
+ if (!part) continue;
2765
+ const rootName = part.split(".")[0];
2766
+ if (rootName && !names.includes(rootName)) {
2767
+ names.push(rootName);
2768
+ }
2769
+ }
2570
2770
  }
2771
+ return names;
2571
2772
  }
2572
- var RELATION_REF_PATTERN = /\b([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
2573
- function extractRelationReferences(expression) {
2574
- const regex = new RegExp(RELATION_REF_PATTERN.source, "g");
2575
- const matches = expression.matchAll(regex);
2576
- return [...matches].map((m) => m[0]);
2773
+ function hasOptions(attr) {
2774
+ return "options" in attr && Array.isArray(attr.options) && attr.options.length > 0;
2577
2775
  }
2578
- function extractRelationNames(expression) {
2579
- const refs = extractRelationReferences(expression);
2580
- const names = refs.map((ref) => ref.split(".")[0]);
2581
- return [...new Set(names)];
2776
+ var FORMATTABLE_TYPES = /* @__PURE__ */ new Set([
2777
+ "currency",
2778
+ "location",
2779
+ "phone",
2780
+ "date",
2781
+ "rating",
2782
+ "select",
2783
+ "status",
2784
+ "multiselect",
2785
+ "number"
2786
+ ]);
2787
+ function enrichValuesForDisplay(values, attributes) {
2788
+ const enriched = { ...values };
2789
+ for (const attr of attributes) {
2790
+ const value = values[attr.name];
2791
+ if (value == null) continue;
2792
+ if (!FORMATTABLE_TYPES.has(attr.type)) continue;
2793
+ const isSelectLike = attr.type === "select" || attr.type === "status" || attr.type === "multiselect";
2794
+ if (isSelectLike && !hasOptions(attr)) continue;
2795
+ if (attr.type === "multiselect" && Array.isArray(value) && value.length === 0) continue;
2796
+ const formatted = formatAttributeValue(value, attr);
2797
+ if (formatted && formatted !== EMPTY_VALUE_PLACEHOLDER) {
2798
+ enriched[attr.name] = formatted;
2799
+ }
2800
+ }
2801
+ return enriched;
2582
2802
  }
2583
- function hasRelationReferences(expression) {
2584
- const regex = new RegExp(RELATION_REF_PATTERN.source);
2585
- return regex.test(expression);
2803
+ var enrichValuesWithSelectLabels = enrichValuesForDisplay;
2804
+ function extractRelationIds(val) {
2805
+ if (typeof val === "string") return [val];
2806
+ if (Array.isArray(val) && typeof val[0] === "string") return [val[0]];
2807
+ return [];
2586
2808
  }
2587
- function flattenRelationsForEval(resolvedRelations) {
2588
- const result = {};
2589
- for (const [relationName, values] of Object.entries(resolvedRelations)) {
2590
- result[relationName] = { ...values };
2809
+ async function computeLabelWithRelations(template, values, attributes, resolveRelationIds) {
2810
+ let enrichedValues = enrichValuesForDisplay(values, attributes);
2811
+ const attrNames = extractAttributeNames(template);
2812
+ const relationAttrs = attributes.filter(
2813
+ (attr) => attr.type === "relation" && attrNames.includes(attr.name)
2814
+ );
2815
+ if (relationAttrs.length === 0) {
2816
+ return renderLabelExpression(template, enrichedValues);
2591
2817
  }
2592
- return result;
2818
+ const allIds = [];
2819
+ for (const attr of relationAttrs) {
2820
+ const ids = extractRelationIds(values[attr.name]);
2821
+ allIds.push(...ids);
2822
+ }
2823
+ if (allIds.length === 0) {
2824
+ return renderLabelExpression(template, enrichedValues);
2825
+ }
2826
+ const resolvedMap = await resolveRelationIds(allIds);
2827
+ enrichedValues = { ...enrichedValues };
2828
+ for (const attr of relationAttrs) {
2829
+ const ids = extractRelationIds(values[attr.name]);
2830
+ if (ids.length > 0 && resolvedMap.has(ids[0])) {
2831
+ enrichedValues[attr.name] = resolvedMap.get(ids[0]);
2832
+ }
2833
+ }
2834
+ return renderLabelExpression(template, enrichedValues);
2593
2835
  }
2594
- async function evaluateFormulaWithRelations(expression, record, schema, resolver) {
2595
- const relationNames = extractRelationNames(expression);
2596
- if (relationNames.length === 0) {
2597
- return evaluateFormula(expression, record.values);
2836
+
2837
+ // src/runtime/executors/assign.executor.ts
2838
+ function buildSlotRef(src) {
2839
+ const hasProps = src.properties && Object.keys(src.properties).length > 0;
2840
+ if (hasProps) {
2841
+ return { id: `$slot:${src.slotId}`, props: src.properties };
2598
2842
  }
2599
- const resolvedRelations = await resolver.resolveRelationValues(record, schema, relationNames);
2600
- const flattenedRelations = flattenRelationsForEval(resolvedRelations);
2601
- const allValues = {
2602
- ...record.values,
2603
- ...flattenedRelations
2604
- };
2605
- return evaluateFormula(expression, allValues);
2843
+ return `$slot:${src.slotId}`;
2606
2844
  }
2607
- async function evaluateFormulaAttributeWithRelations(attr, record, schema, resolver) {
2608
- const raw = await evaluateFormulaWithRelations(attr.expression, record, schema, resolver);
2609
- return formatFormulaResult(raw, attr.returnType, attr.decimals);
2845
+ function buildSlotValuesMap(slots) {
2846
+ const values = {};
2847
+ for (const [slotId, slotData] of Object.entries(slots)) {
2848
+ if (slotData && typeof slotData === "object") {
2849
+ values[slotId] = { ...slotData };
2850
+ }
2851
+ }
2852
+ return values;
2610
2853
  }
2611
-
2612
- // src/runtime/formula/path-parser.ts
2613
- var InvalidPathError = class extends Error {
2614
- constructor(path, segment, reason) {
2615
- super(`Invalid path "${path}" at "${segment}": ${reason}`);
2616
- this.path = path;
2617
- this.segment = segment;
2618
- this.reason = reason;
2619
- this.name = "InvalidPathError";
2854
+ var AssignExecutor = class {
2855
+ constructor() {
2856
+ this.nodeType = "assign";
2620
2857
  }
2621
- };
2622
- var MaxDepthExceededError = class extends Error {
2623
- constructor(path, maxDepth) {
2624
- super(`Path "${path}" exceeds maximum depth of ${maxDepth}`);
2625
- this.path = path;
2626
- this.maxDepth = maxDepth;
2627
- this.name = "MaxDepthExceededError";
2858
+ execute(node, ctx) {
2859
+ if (!node.next) {
2860
+ return error("MISSING_NEXT", "AssignNode must have a 'next' target");
2861
+ }
2862
+ if (!node.targetSlotId) {
2863
+ return error("MISSING_TARGET_SLOT", "AssignNode must have a target slot");
2864
+ }
2865
+ const existing = ctx.executionContext.slots[node.targetSlotId] ?? {};
2866
+ const targetSlot = { ...existing };
2867
+ const slotValues = buildSlotValuesMap(ctx.executionContext.slots);
2868
+ for (const assignment of node.assignments) {
2869
+ targetSlot[assignment.targetAttribute] = resolveSource(assignment.source, slotValues);
2870
+ }
2871
+ const contextUpdates = {
2872
+ slots: { [node.targetSlotId]: targetSlot }
2873
+ };
2874
+ return success(node.next, contextUpdates);
2875
+ }
2876
+ validate(node) {
2877
+ const errors = [];
2878
+ if (!node.targetSlotId) {
2879
+ errors.push("AssignNode must have a target slot");
2880
+ }
2881
+ if (!node.assignments?.length) {
2882
+ errors.push("AssignNode must have at least one assignment");
2883
+ }
2884
+ if (!node.next) {
2885
+ errors.push("AssignNode must have a 'next' target");
2886
+ }
2887
+ return errors;
2628
2888
  }
2629
2889
  };
2630
- async function parsePath(path, startSchema, getSchema, maxDepth = 5) {
2631
- const segments = path.split(".");
2632
- if (segments.length === 0 || segments.length === 1 && segments[0] === "") {
2633
- throw new InvalidPathError(path, path, "Path cannot be empty");
2890
+ function resolveSource(source, slotValues) {
2891
+ switch (source.type) {
2892
+ case "expression":
2893
+ return renderLabelExpression(source.template, slotValues, "");
2894
+ case "slot-ref":
2895
+ return buildSlotRef(source);
2896
+ case "static":
2897
+ return source.value;
2634
2898
  }
2635
- if (segments.length > maxDepth) {
2636
- throw new MaxDepthExceededError(path, maxDepth);
2899
+ }
2900
+
2901
+ // src/runtime/executors/start.executor.ts
2902
+ var StartExecutor = class {
2903
+ constructor() {
2904
+ this.nodeType = "start";
2637
2905
  }
2638
- const result = [];
2639
- let currentSchema = startSchema;
2640
- for (let i = 0; i < segments.length; i++) {
2641
- const segmentName = segments[i];
2642
- const isLastSegment = i === segments.length - 1;
2643
- const attr = currentSchema.attributes.find((a) => a.name === segmentName);
2644
- if (!attr) {
2645
- throw new InvalidPathError(
2646
- path,
2647
- segmentName,
2648
- `Attribute "${segmentName}" not found in object "${currentSchema.name}"`
2649
- );
2650
- }
2651
- if (attr.type === "relation") {
2652
- const relationAttr = attr;
2653
- const targetObject = relationAttr.targets[0]?.object;
2654
- if (!targetObject) {
2655
- throw new InvalidPathError(path, segmentName, "Relation has no target object");
2656
- }
2657
- result.push({
2658
- name: segmentName,
2659
- type: "relation",
2660
- cardinality: relationAttr.cardinality,
2661
- targetObject
2662
- });
2663
- if (!isLastSegment) {
2664
- const nextSchema = await getSchema(targetObject);
2665
- if (!nextSchema) {
2666
- throw new InvalidPathError(
2667
- path,
2668
- segmentName,
2669
- `Target object "${targetObject}" schema not found`
2670
- );
2671
- }
2672
- currentSchema = nextSchema;
2673
- }
2674
- } else {
2675
- if (!isLastSegment) {
2676
- throw new InvalidPathError(
2677
- path,
2678
- segmentName,
2679
- `"${segmentName}" is not a relation but has segments after it`
2680
- );
2681
- }
2682
- result.push({
2683
- name: segmentName,
2684
- type: "attribute"
2685
- });
2906
+ execute(node, _context) {
2907
+ if (!node.next) {
2908
+ return error("MISSING_NEXT", `StartNode "${node.id}" has no 'next' target defined`);
2686
2909
  }
2910
+ return success(node.next);
2687
2911
  }
2688
- return result;
2689
- }
2690
- async function validatePath(path, startSchema, getSchema, maxDepth = 5) {
2691
- try {
2692
- await parsePath(path, startSchema, getSchema, maxDepth);
2912
+ canExecute(_node, _context) {
2693
2913
  return true;
2694
- } catch {
2695
- return false;
2696
2914
  }
2915
+ validate(node) {
2916
+ const errors = [];
2917
+ if (!node.next) {
2918
+ errors.push("StartNode must have a 'next' property");
2919
+ }
2920
+ return errors;
2921
+ }
2922
+ };
2923
+
2924
+ // src/runtime/executors/index.ts
2925
+ function createDefaultExecutorRegistry() {
2926
+ const registry2 = new ExecutorRegistry();
2927
+ registry2.register(new StartExecutor());
2928
+ registry2.register(new FormExecutor());
2929
+ registry2.register(new ConditionExecutor());
2930
+ registry2.register(new AssignExecutor());
2931
+ registry2.register(new EndExecutor());
2932
+ return registry2;
2697
2933
  }
2698
- function pathHasManyCardinality(segments) {
2699
- return segments.some((s) => s.type === "relation" && s.cardinality === "many");
2700
- }
2701
- function getPathDepth(segments) {
2702
- return segments.filter((s) => s.type === "relation").length;
2703
- }
2704
- function getTargetAttributeName(path) {
2705
- const parts = path.split(".");
2706
- return parts[parts.length - 1];
2707
- }
2708
- function getRelationPath(path) {
2709
- const parts = path.split(".");
2710
- if (parts.length <= 1) return null;
2711
- return parts.slice(0, -1).join(".");
2934
+ var defaultRegistry = null;
2935
+ function getDefaultExecutorRegistry() {
2936
+ if (!defaultRegistry) {
2937
+ defaultRegistry = createDefaultExecutorRegistry();
2938
+ }
2939
+ return defaultRegistry;
2712
2940
  }
2713
2941
 
2714
- // src/runtime/formula/path-traversal.ts
2715
- async function traversePath(record, path, startSchemaName, adapter, getSchema, options) {
2716
- const maxDepth = options?.maxDepth ?? 5;
2717
- const startSchema = await getSchema(startSchemaName);
2718
- if (!startSchema) {
2719
- return { values: [], recordCounts: [0] };
2720
- }
2721
- const segments = await parsePath(path, startSchema, getSchema, maxDepth);
2722
- if (segments.length > maxDepth) {
2723
- throw new MaxDepthExceededError(path, maxDepth);
2942
+ // src/runtime/formula/evaluator.ts
2943
+ import { Parser } from "expr-eval";
2944
+ function createFormulaParser() {
2945
+ const parser = new Parser();
2946
+ parser.functions.IF = (condition, thenValue, elseValue) => condition ? thenValue : elseValue;
2947
+ parser.functions.AND = (...args) => args.every(Boolean);
2948
+ parser.functions.OR = (...args) => args.some(Boolean);
2949
+ parser.functions.NOT = (value) => !value;
2950
+ parser.functions.EMPTY = (value) => value === null || value === void 0 || value === "";
2951
+ parser.functions.COALESCE = (...args) => args.find((a) => a != null) ?? null;
2952
+ parser.functions.DEFAULT = (value, defaultValue) => value == null || value === "" ? defaultValue : value;
2953
+ parser.functions.CONCAT = (...args) => args.filter((a) => a != null).map(String).join("");
2954
+ parser.functions.UPPER = (value) => String(value ?? "").toUpperCase();
2955
+ parser.functions.LOWER = (value) => String(value ?? "").toLowerCase();
2956
+ parser.functions.TRIM = (value) => String(value ?? "").trim();
2957
+ parser.functions.LENGTH = (value) => String(value ?? "").length;
2958
+ parser.functions.LEFT = (value, count) => String(value ?? "").slice(0, count);
2959
+ parser.functions.RIGHT = (value, count) => String(value ?? "").slice(-count);
2960
+ parser.functions.REPLACE = (value, search, replacement) => String(value ?? "").split(String(search)).join(replacement);
2961
+ parser.functions.CONTAINS = (value, search) => String(value ?? "").toLowerCase().includes(String(search).toLowerCase());
2962
+ parser.functions.ROUND = (value, decimals = 0) => {
2963
+ if (typeof value !== "number" || Number.isNaN(value)) return null;
2964
+ const factor = 10 ** decimals;
2965
+ return Math.round(value * factor) / factor;
2966
+ };
2967
+ parser.functions.FLOOR = (value) => {
2968
+ if (typeof value !== "number" || Number.isNaN(value)) return null;
2969
+ return Math.floor(value);
2970
+ };
2971
+ parser.functions.CEIL = (value) => {
2972
+ if (typeof value !== "number" || Number.isNaN(value)) return null;
2973
+ return Math.ceil(value);
2974
+ };
2975
+ parser.functions.ABS = (value) => {
2976
+ if (typeof value !== "number" || Number.isNaN(value)) return null;
2977
+ return Math.abs(value);
2978
+ };
2979
+ parser.functions.MIN = (...args) => {
2980
+ const nums = args.filter((n) => typeof n === "number" && !Number.isNaN(n));
2981
+ return nums.length > 0 ? Math.min(...nums) : null;
2982
+ };
2983
+ parser.functions.MAX = (...args) => {
2984
+ const nums = args.filter((n) => typeof n === "number" && !Number.isNaN(n));
2985
+ return nums.length > 0 ? Math.max(...nums) : null;
2986
+ };
2987
+ parser.functions.POW = (base, exponent) => {
2988
+ if (typeof base !== "number" || typeof exponent !== "number") return null;
2989
+ return base ** exponent;
2990
+ };
2991
+ parser.functions.MOD = (a, b) => {
2992
+ if (typeof a !== "number" || typeof b !== "number" || b === 0) return null;
2993
+ return a % b;
2994
+ };
2995
+ parser.functions.NOW = () => (/* @__PURE__ */ new Date()).toISOString();
2996
+ parser.functions.TODAY = () => (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2997
+ parser.functions.YEAR = (value) => {
2998
+ const date2 = parseDate(value);
2999
+ return date2 ? date2.getFullYear() : null;
3000
+ };
3001
+ parser.functions.MONTH = (value) => {
3002
+ const date2 = parseDate(value);
3003
+ return date2 ? date2.getMonth() + 1 : null;
3004
+ };
3005
+ parser.functions.DAY = (value) => {
3006
+ const date2 = parseDate(value);
3007
+ return date2 ? date2.getDate() : null;
3008
+ };
3009
+ parser.functions.DATE_DIFF = (date1, date2, unit = "days") => {
3010
+ const d1 = parseDate(date1);
3011
+ const d2 = parseDate(date2);
3012
+ if (d1 === null || d2 === null) return null;
3013
+ const diffMs = d1.getTime() - d2.getTime();
3014
+ const MS_PER_DAY = 1e3 * 60 * 60 * 24;
3015
+ const conversions = {
3016
+ years: MS_PER_DAY * 365,
3017
+ months: MS_PER_DAY * 30,
3018
+ weeks: MS_PER_DAY * 7,
3019
+ days: MS_PER_DAY,
3020
+ hours: 1e3 * 60 * 60,
3021
+ minutes: 1e3 * 60
3022
+ };
3023
+ const divisor = conversions[unit] ?? MS_PER_DAY;
3024
+ return diffMs / divisor;
3025
+ };
3026
+ return parser;
3027
+ }
3028
+ function parseDate(value) {
3029
+ if (!value) return null;
3030
+ if (value instanceof Date) return value;
3031
+ if (typeof value === "string" || typeof value === "number") {
3032
+ const date2 = new Date(value);
3033
+ return Number.isNaN(date2.getTime()) ? null : date2;
2724
3034
  }
2725
- return await executeTraversal([record], segments, adapter, 0);
3035
+ return null;
2726
3036
  }
2727
- async function executeTraversal(records, segments, adapter, depth) {
2728
- const recordCounts = [records.length];
2729
- if (depth >= segments.length || records.length === 0) {
2730
- return { values: [], recordCounts };
3037
+ var formulaParser = createFormulaParser();
3038
+ function evaluateFormula(expression, values) {
3039
+ try {
3040
+ const parsed = formulaParser.parse(expression);
3041
+ return parsed.evaluate(values);
3042
+ } catch {
3043
+ return null;
2731
3044
  }
2732
- const segment = segments[depth];
2733
- const isLastSegment = depth === segments.length - 1;
2734
- if (segment.type === "attribute") {
2735
- const values = records.map((r) => r.values[segment.name]).filter((v) => v !== void 0);
2736
- return { values, recordCounts };
3045
+ }
3046
+ function evaluateFormulaWithResult(expression, values) {
3047
+ try {
3048
+ const parsed = formulaParser.parse(expression);
3049
+ const value = parsed.evaluate(values);
3050
+ return { value };
3051
+ } catch (error2) {
3052
+ return {
3053
+ value: null,
3054
+ error: getErrorMessage(error2)
3055
+ };
2737
3056
  }
2738
- const relatedIds = collectRelatedIds(records, segment.name);
2739
- if (relatedIds.length === 0) {
2740
- return { values: [], recordCounts };
3057
+ }
3058
+ function formatFormulaResult(value, returnType, decimals) {
3059
+ if (value === null || value === void 0) {
3060
+ return null;
2741
3061
  }
2742
- const relatedRecords = await adapter.objectRecords.findByIds(relatedIds);
2743
- recordCounts.push(relatedRecords.length);
2744
- if (isLastSegment) {
2745
- return { values: relatedIds, recordCounts };
3062
+ switch (returnType) {
3063
+ case "number":
3064
+ return formatNumberResult(value, decimals);
3065
+ case "boolean":
3066
+ return Boolean(value);
3067
+ case "date":
3068
+ return formatDateResult(value);
3069
+ case "text":
3070
+ return String(value);
2746
3071
  }
2747
- const nestedResult = await executeTraversal(relatedRecords, segments, adapter, depth + 1);
2748
- return {
2749
- values: nestedResult.values,
2750
- recordCounts: [...recordCounts, ...nestedResult.recordCounts.slice(1)]
2751
- };
2752
3072
  }
2753
- function collectRelatedIds(records, relationName) {
2754
- const ids = [];
2755
- for (const record of records) {
2756
- const value = record.values[relationName];
3073
+ function formatNumberResult(value, decimals) {
3074
+ const num = typeof value === "number" ? value : Number(value);
3075
+ if (Number.isNaN(num)) return null;
3076
+ return decimals !== void 0 ? Number(num.toFixed(decimals)) : num;
3077
+ }
3078
+ function formatDateResult(value) {
3079
+ if (value instanceof Date) return value.toISOString();
3080
+ if (typeof value === "string") return value;
3081
+ return null;
3082
+ }
3083
+ function evaluateFormulaAttribute(attr, values) {
3084
+ const raw = evaluateFormula(attr.expression, values);
3085
+ return formatFormulaResult(raw, attr.returnType, attr.decimals);
3086
+ }
3087
+ function validateFormulaExpression(expression) {
3088
+ try {
3089
+ formulaParser.parse(expression);
3090
+ return { valid: true };
3091
+ } catch (error2) {
3092
+ return {
3093
+ valid: false,
3094
+ error: getErrorMessage(error2)
3095
+ };
3096
+ }
3097
+ }
3098
+ function extractFormulaVariables(expression) {
3099
+ try {
3100
+ const parsed = formulaParser.parse(expression);
3101
+ return parsed.variables();
3102
+ } catch {
3103
+ return [];
3104
+ }
3105
+ }
3106
+ var RELATION_REF_PATTERN = /\b([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
3107
+ function extractRelationReferences(expression) {
3108
+ const regex = new RegExp(RELATION_REF_PATTERN.source, "g");
3109
+ const matches = expression.matchAll(regex);
3110
+ return [...matches].map((m) => m[0]);
3111
+ }
3112
+ function extractRelationNames(expression) {
3113
+ const refs = extractRelationReferences(expression);
3114
+ const names = refs.map((ref) => ref.split(".")[0]);
3115
+ return [...new Set(names)];
3116
+ }
3117
+ function hasRelationReferences(expression) {
3118
+ const regex = new RegExp(RELATION_REF_PATTERN.source);
3119
+ return regex.test(expression);
3120
+ }
3121
+ function flattenRelationsForEval(resolvedRelations) {
3122
+ const result = {};
3123
+ for (const [relationName, values] of Object.entries(resolvedRelations)) {
3124
+ result[relationName] = { ...values };
3125
+ }
3126
+ return result;
3127
+ }
3128
+ async function evaluateFormulaWithRelations(expression, record, schema, resolver) {
3129
+ const relationNames = extractRelationNames(expression);
3130
+ if (relationNames.length === 0) {
3131
+ return evaluateFormula(expression, record.values);
3132
+ }
3133
+ const resolvedRelations = await resolver.resolveRelationValues(record, schema, relationNames);
3134
+ const flattenedRelations = flattenRelationsForEval(resolvedRelations);
3135
+ const allValues = {
3136
+ ...record.values,
3137
+ ...flattenedRelations
3138
+ };
3139
+ return evaluateFormula(expression, allValues);
3140
+ }
3141
+ async function evaluateFormulaAttributeWithRelations(attr, record, schema, resolver) {
3142
+ const raw = await evaluateFormulaWithRelations(attr.expression, record, schema, resolver);
3143
+ return formatFormulaResult(raw, attr.returnType, attr.decimals);
3144
+ }
3145
+
3146
+ // src/runtime/formula/path-parser.ts
3147
+ var InvalidPathError = class extends Error {
3148
+ constructor(path, segment, reason) {
3149
+ super(`Invalid path "${path}" at "${segment}": ${reason}`);
3150
+ this.path = path;
3151
+ this.segment = segment;
3152
+ this.reason = reason;
3153
+ this.name = "InvalidPathError";
3154
+ }
3155
+ };
3156
+ var MaxDepthExceededError = class extends Error {
3157
+ constructor(path, maxDepth) {
3158
+ super(`Path "${path}" exceeds maximum depth of ${maxDepth}`);
3159
+ this.path = path;
3160
+ this.maxDepth = maxDepth;
3161
+ this.name = "MaxDepthExceededError";
3162
+ }
3163
+ };
3164
+ async function parsePath(path, startSchema, getSchema, maxDepth = 5) {
3165
+ const segments = path.split(".");
3166
+ if (segments.length === 0 || segments.length === 1 && segments[0] === "") {
3167
+ throw new InvalidPathError(path, path, "Path cannot be empty");
3168
+ }
3169
+ if (segments.length > maxDepth) {
3170
+ throw new MaxDepthExceededError(path, maxDepth);
3171
+ }
3172
+ const result = [];
3173
+ let currentSchema = startSchema;
3174
+ for (let i = 0; i < segments.length; i++) {
3175
+ const segmentName = segments[i];
3176
+ const isLastSegment = i === segments.length - 1;
3177
+ const attr = currentSchema.attributes.find((a) => a.name === segmentName);
3178
+ if (!attr) {
3179
+ throw new InvalidPathError(
3180
+ path,
3181
+ segmentName,
3182
+ `Attribute "${segmentName}" not found in object "${currentSchema.name}"`
3183
+ );
3184
+ }
3185
+ if (attr.type === "relation") {
3186
+ const relationAttr = attr;
3187
+ const targetObject = relationAttr.targets[0]?.object;
3188
+ if (!targetObject) {
3189
+ throw new InvalidPathError(path, segmentName, "Relation has no target object");
3190
+ }
3191
+ result.push({
3192
+ name: segmentName,
3193
+ type: "relation",
3194
+ cardinality: relationAttr.cardinality,
3195
+ targetObject
3196
+ });
3197
+ if (!isLastSegment) {
3198
+ const nextSchema = await getSchema(targetObject);
3199
+ if (!nextSchema) {
3200
+ throw new InvalidPathError(
3201
+ path,
3202
+ segmentName,
3203
+ `Target object "${targetObject}" schema not found`
3204
+ );
3205
+ }
3206
+ currentSchema = nextSchema;
3207
+ }
3208
+ } else {
3209
+ if (!isLastSegment) {
3210
+ throw new InvalidPathError(
3211
+ path,
3212
+ segmentName,
3213
+ `"${segmentName}" is not a relation but has segments after it`
3214
+ );
3215
+ }
3216
+ result.push({
3217
+ name: segmentName,
3218
+ type: "attribute"
3219
+ });
3220
+ }
3221
+ }
3222
+ return result;
3223
+ }
3224
+ async function validatePath(path, startSchema, getSchema, maxDepth = 5) {
3225
+ try {
3226
+ await parsePath(path, startSchema, getSchema, maxDepth);
3227
+ return true;
3228
+ } catch {
3229
+ return false;
3230
+ }
3231
+ }
3232
+ function pathHasManyCardinality(segments) {
3233
+ return segments.some((s) => s.type === "relation" && s.cardinality === "many");
3234
+ }
3235
+ function getPathDepth(segments) {
3236
+ return segments.filter((s) => s.type === "relation").length;
3237
+ }
3238
+ function getTargetAttributeName(path) {
3239
+ const parts = path.split(".");
3240
+ return parts[parts.length - 1];
3241
+ }
3242
+ function getRelationPath(path) {
3243
+ const parts = path.split(".");
3244
+ if (parts.length <= 1) return null;
3245
+ return parts.slice(0, -1).join(".");
3246
+ }
3247
+
3248
+ // src/runtime/formula/path-traversal.ts
3249
+ async function traversePath(record, path, startSchemaName, adapter, getSchema, options) {
3250
+ const maxDepth = options?.maxDepth ?? 5;
3251
+ const startSchema = await getSchema(startSchemaName);
3252
+ if (!startSchema) {
3253
+ return { values: [], recordCounts: [0] };
3254
+ }
3255
+ const segments = await parsePath(path, startSchema, getSchema, maxDepth);
3256
+ if (segments.length > maxDepth) {
3257
+ throw new MaxDepthExceededError(path, maxDepth);
3258
+ }
3259
+ return await executeTraversal([record], segments, adapter, 0);
3260
+ }
3261
+ async function executeTraversal(records, segments, adapter, depth) {
3262
+ const recordCounts = [records.length];
3263
+ if (depth >= segments.length || records.length === 0) {
3264
+ return { values: [], recordCounts };
3265
+ }
3266
+ const segment = segments[depth];
3267
+ const isLastSegment = depth === segments.length - 1;
3268
+ if (segment.type === "attribute") {
3269
+ const values = records.map((r) => r.values[segment.name]).filter((v) => v !== void 0);
3270
+ return { values, recordCounts };
3271
+ }
3272
+ const relatedIds = collectRelatedIds(records, segment.name);
3273
+ if (relatedIds.length === 0) {
3274
+ return { values: [], recordCounts };
3275
+ }
3276
+ const relatedRecords = await adapter.objectRecords.findByIds(relatedIds);
3277
+ recordCounts.push(relatedRecords.length);
3278
+ if (isLastSegment) {
3279
+ return { values: relatedIds, recordCounts };
3280
+ }
3281
+ const nestedResult = await executeTraversal(relatedRecords, segments, adapter, depth + 1);
3282
+ return {
3283
+ values: nestedResult.values,
3284
+ recordCounts: [...recordCounts, ...nestedResult.recordCounts.slice(1)]
3285
+ };
3286
+ }
3287
+ function collectRelatedIds(records, relationName) {
3288
+ const ids = [];
3289
+ for (const record of records) {
3290
+ const value = record.values[relationName];
2757
3291
  if (typeof value === "string" && value.length > 0) {
2758
3292
  ids.push(value);
2759
3293
  } else if (Array.isArray(value)) {
@@ -3600,331 +4134,7 @@ var ConcurrentModificationError = class extends SchemaError {
3600
4134
  { recordId }
3601
4135
  );
3602
4136
  }
3603
- };
3604
-
3605
- // src/format.ts
3606
- var EMPTY_VALUE_PLACEHOLDER = "\u2014";
3607
- function formatText(value) {
3608
- return String(value);
3609
- }
3610
- function formatCheckbox(value) {
3611
- return value ? "Yes" : "No";
3612
- }
3613
- function formatNumber(value, attribute) {
3614
- if (typeof value !== "number") return String(value);
3615
- const decimals = attribute.decimals;
3616
- if (attribute.unit === "integer") {
3617
- return value.toLocaleString(void 0, {
3618
- minimumFractionDigits: 0,
3619
- maximumFractionDigits: 0
3620
- });
3621
- }
3622
- if (attribute.unit === "percentage") {
3623
- return (value / 100).toLocaleString(void 0, {
3624
- style: "percent",
3625
- minimumFractionDigits: decimals,
3626
- maximumFractionDigits: decimals
3627
- });
3628
- }
3629
- return value.toLocaleString(void 0, {
3630
- minimumFractionDigits: decimals,
3631
- maximumFractionDigits: decimals
3632
- });
3633
- }
3634
- function formatCurrency(value, _attribute) {
3635
- if (typeof value !== "object" || value === null) return String(value);
3636
- const currency2 = value;
3637
- if (!("value" in currency2 && "code" in currency2)) return String(value);
3638
- const formattedValue = currency2.value.toLocaleString(void 0, {
3639
- minimumFractionDigits: 2,
3640
- maximumFractionDigits: 2
3641
- });
3642
- return `${formattedValue} ${currency2.code}`;
3643
- }
3644
- function formatDate(value) {
3645
- if (value instanceof Date) {
3646
- return value.toISOString().split("T")[0];
3647
- }
3648
- if (typeof value === "string") {
3649
- const date2 = new Date(value);
3650
- if (!Number.isNaN(date2.getTime())) {
3651
- return date2.toISOString().split("T")[0];
3652
- }
3653
- }
3654
- return String(value);
3655
- }
3656
- function formatPhone(value) {
3657
- if (typeof value !== "object" || value === null) return String(value);
3658
- const phone2 = value;
3659
- if (!("phoneNumber" in phone2)) return String(value);
3660
- if (!phone2.phoneNumber) return "";
3661
- if (!phone2.countryCode) return phone2.phoneNumber;
3662
- return formatPhoneForDisplay(phone2);
3663
- }
3664
- function formatLocation(value, attribute) {
3665
- if (typeof value !== "object" || value === null) return String(value);
3666
- const loc = value;
3667
- const granularity = attribute.granularity ?? "full";
3668
- const parts = [];
3669
- switch (granularity) {
3670
- case "country":
3671
- if (loc.country) parts.push(loc.country);
3672
- break;
3673
- case "state":
3674
- if (loc.state) parts.push(loc.state);
3675
- if (loc.country) parts.push(loc.country);
3676
- break;
3677
- case "city":
3678
- if (loc.city) parts.push(loc.city);
3679
- if (loc.state) parts.push(loc.state);
3680
- if (loc.country) parts.push(loc.country);
3681
- break;
3682
- case "coordinates":
3683
- if (loc.latitude !== void 0 && loc.longitude !== void 0) {
3684
- parts.push(`${loc.latitude}, ${loc.longitude}`);
3685
- }
3686
- break;
3687
- case "address":
3688
- if (loc.address) parts.push(loc.address);
3689
- if (loc.city) parts.push(loc.city);
3690
- if (loc.state) parts.push(loc.state);
3691
- if (loc.country) parts.push(loc.country);
3692
- break;
3693
- default:
3694
- if (loc.address) parts.push(loc.address);
3695
- if (loc.city) parts.push(loc.city);
3696
- if (loc.state) parts.push(loc.state);
3697
- if (loc.postalCode) parts.push(loc.postalCode);
3698
- if (loc.country) parts.push(loc.country);
3699
- break;
3700
- }
3701
- return parts.join(", ") || EMPTY_VALUE_PLACEHOLDER;
3702
- }
3703
- function formatSelect(value, attribute) {
3704
- if (typeof value !== "string") return String(value);
3705
- const option = attribute.options?.find((o) => o.value === value);
3706
- return option?.label ?? String(value);
3707
- }
3708
- function formatMultiselect(value, attribute) {
3709
- if (!Array.isArray(value)) return String(value);
3710
- if (attribute.options) {
3711
- const labels = value.map((v) => attribute.options.find((o) => o.value === v)?.label).filter(Boolean);
3712
- return labels.join(", ");
3713
- }
3714
- return value.join(", ");
3715
- }
3716
- function formatRating(value, attribute) {
3717
- if (typeof value !== "number") return String(value);
3718
- const max = attribute.max ?? 5;
3719
- return `${value}/${max}`;
3720
- }
3721
- function formatAttributeValue(value, attribute) {
3722
- if (value === null || value === void 0 || value === "") {
3723
- return EMPTY_VALUE_PLACEHOLDER;
3724
- }
3725
- switch (attribute.type) {
3726
- case "text":
3727
- case "textarea":
3728
- return formatText(value);
3729
- case "checkbox":
3730
- return formatCheckbox(value);
3731
- case "number":
3732
- return formatNumber(value, attribute);
3733
- case "currency":
3734
- return formatCurrency(value, attribute);
3735
- case "date":
3736
- return formatDate(value);
3737
- case "phone":
3738
- return formatPhone(value);
3739
- case "location":
3740
- return formatLocation(value, attribute);
3741
- case "select":
3742
- case "status":
3743
- return formatSelect(value, attribute);
3744
- case "multiselect":
3745
- return formatMultiselect(value, attribute);
3746
- case "rating":
3747
- return formatRating(value, attribute);
3748
- // Unsupported types - return value as-is or placeholder
3749
- case "file":
3750
- case "user":
3751
- case "relation":
3752
- if (Array.isArray(value)) {
3753
- return value.join(", ");
3754
- }
3755
- return String(value);
3756
- default: {
3757
- if (Array.isArray(value)) {
3758
- return value.join(", ");
3759
- }
3760
- return String(value);
3761
- }
3762
- }
3763
- }
3764
-
3765
- // src/runtime/template.ts
3766
- var simplePipes = {
3767
- /** Convert to uppercase */
3768
- UPPER: (v) => String(v).toUpperCase(),
3769
- /** Convert to lowercase */
3770
- LOWER: (v) => String(v).toLowerCase(),
3771
- /** Capitalize first letter of each word */
3772
- capitalize: (v) => String(v).replace(/\b\w/g, (c) => c.toUpperCase()),
3773
- /** Trim whitespace from both ends */
3774
- trim: (v) => String(v).trim()
3775
- };
3776
- var pipesWithArgs = {
3777
- /** Add prefix only if value is non-empty */
3778
- prefix: (v, pre = "") => v ? `${pre}${v}` : "",
3779
- /** Add suffix only if value is non-empty */
3780
- suffix: (v, suf = "") => v ? `${v}${suf}` : "",
3781
- /** Wrap value with prefix and suffix only if non-empty */
3782
- wrap: (v, pre = "", suf = "") => v ? `${pre}${v}${suf}` : "",
3783
- /** Show default value if empty */
3784
- default: (v, def = "") => v || def
3785
- };
3786
- function getValue(obj, path) {
3787
- return path.split(".").reduce((acc, key) => {
3788
- if (acc == null || typeof acc !== "object") return void 0;
3789
- return acc[key];
3790
- }, obj);
3791
- }
3792
- var DEFAULT_LABEL_FALLBACK = "(Untitled)";
3793
- function parsePipeExpression(pipeExpr) {
3794
- const match = pipeExpr.match(/^(\w+)(?::(.*))?$/);
3795
- if (!match) return { name: pipeExpr, args: [] };
3796
- const name = match[1];
3797
- const argsStr = match[2];
3798
- if (!argsStr) return { name, args: [] };
3799
- const args = [];
3800
- const argRegex = /["']([^"']*?)["']/g;
3801
- let argMatch;
3802
- while ((argMatch = argRegex.exec(argsStr)) !== null) {
3803
- args.push(argMatch[1]);
3804
- }
3805
- return { name, args };
3806
- }
3807
- function renderLabelExpression(template, values, fallback = DEFAULT_LABEL_FALLBACK) {
3808
- const result = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, expr) => {
3809
- const orParts = expr.split("||").map((s) => s.trim());
3810
- const lastPart = orParts[orParts.length - 1];
3811
- const pipeSplit = lastPart.split("|").map((s) => s.trim());
3812
- orParts[orParts.length - 1] = pipeSplit[0];
3813
- const pipes = pipeSplit.slice(1).filter(Boolean);
3814
- const alternatives = orParts.filter(Boolean);
3815
- let value = "";
3816
- for (const alt of alternatives) {
3817
- const v = getValue(values, alt);
3818
- if (v != null && v !== "") {
3819
- value = v;
3820
- break;
3821
- }
3822
- }
3823
- const isEmpty3 = value == null || value === "";
3824
- if (isEmpty3 && pipes.length === 0) return "";
3825
- for (const pipeExpr of pipes) {
3826
- const { name: pipeName, args } = parsePipeExpression(pipeExpr);
3827
- const simpleFn = simplePipes[pipeName];
3828
- if (simpleFn) {
3829
- if (value != null && value !== "") {
3830
- value = simpleFn(String(value));
3831
- }
3832
- } else {
3833
- const argFn = pipesWithArgs[pipeName];
3834
- if (argFn) {
3835
- value = argFn(String(value ?? ""), ...args);
3836
- }
3837
- }
3838
- }
3839
- return String(value ?? "");
3840
- }).trim();
3841
- return result || fallback;
3842
- }
3843
- function isLabelExpression(value) {
3844
- return /\{\{\s*\S+.*\}\}/.test(value);
3845
- }
3846
- function extractAttributeNames(template) {
3847
- const names = [];
3848
- const regex = /\{\{\s*([^}]+)\s*\}\}/g;
3849
- let match;
3850
- while ((match = regex.exec(template)) !== null) {
3851
- const expr = match[1].trim();
3852
- const orParts = expr.split("||").map((s) => s.trim());
3853
- const lastPart = orParts[orParts.length - 1];
3854
- orParts[orParts.length - 1] = lastPart.split("|")[0].trim();
3855
- for (const part of orParts) {
3856
- if (!part) continue;
3857
- const rootName = part.split(".")[0];
3858
- if (rootName && !names.includes(rootName)) {
3859
- names.push(rootName);
3860
- }
3861
- }
3862
- }
3863
- return names;
3864
- }
3865
- function hasOptions(attr) {
3866
- return "options" in attr && Array.isArray(attr.options) && attr.options.length > 0;
3867
- }
3868
- var FORMATTABLE_TYPES = /* @__PURE__ */ new Set([
3869
- "currency",
3870
- "location",
3871
- "phone",
3872
- "date",
3873
- "rating",
3874
- "select",
3875
- "status",
3876
- "multiselect",
3877
- "number"
3878
- ]);
3879
- function enrichValuesForDisplay(values, attributes) {
3880
- const enriched = { ...values };
3881
- for (const attr of attributes) {
3882
- const value = values[attr.name];
3883
- if (value == null) continue;
3884
- if (!FORMATTABLE_TYPES.has(attr.type)) continue;
3885
- const isSelectLike = attr.type === "select" || attr.type === "status" || attr.type === "multiselect";
3886
- if (isSelectLike && !hasOptions(attr)) continue;
3887
- if (attr.type === "multiselect" && Array.isArray(value) && value.length === 0) continue;
3888
- const formatted = formatAttributeValue(value, attr);
3889
- if (formatted && formatted !== EMPTY_VALUE_PLACEHOLDER) {
3890
- enriched[attr.name] = formatted;
3891
- }
3892
- }
3893
- return enriched;
3894
- }
3895
- var enrichValuesWithSelectLabels = enrichValuesForDisplay;
3896
- function extractRelationIds(val) {
3897
- if (typeof val === "string") return [val];
3898
- if (Array.isArray(val) && typeof val[0] === "string") return [val[0]];
3899
- return [];
3900
- }
3901
- async function computeLabelWithRelations(template, values, attributes, resolveRelationIds) {
3902
- let enrichedValues = enrichValuesForDisplay(values, attributes);
3903
- const attrNames = extractAttributeNames(template);
3904
- const relationAttrs = attributes.filter(
3905
- (attr) => attr.type === "relation" && attrNames.includes(attr.name)
3906
- );
3907
- if (relationAttrs.length === 0) {
3908
- return renderLabelExpression(template, enrichedValues);
3909
- }
3910
- const allIds = [];
3911
- for (const attr of relationAttrs) {
3912
- const ids = extractRelationIds(values[attr.name]);
3913
- allIds.push(...ids);
3914
- }
3915
- if (allIds.length === 0) {
3916
- return renderLabelExpression(template, enrichedValues);
3917
- }
3918
- const resolvedMap = await resolveRelationIds(allIds);
3919
- enrichedValues = { ...enrichedValues };
3920
- for (const attr of relationAttrs) {
3921
- const ids = extractRelationIds(values[attr.name]);
3922
- if (ids.length > 0 && resolvedMap.has(ids[0])) {
3923
- enrichedValues[attr.name] = resolvedMap.get(ids[0]);
3924
- }
3925
- }
3926
- return renderLabelExpression(template, enrichedValues);
3927
- }
4137
+ };
3928
4138
 
3929
4139
  // src/runtime/mock/mock-object-records.ts
3930
4140
  function createMockObjectRecordsRepository(stores) {
@@ -6621,22 +6831,6 @@ var DocumentAttributeBuilder = class extends BaseAttributeBuilder {
6621
6831
  constructor(name, label) {
6622
6832
  super("document", name, label);
6623
6833
  }
6624
- /**
6625
- * Set a single required template.
6626
- * Only documents using this template can be attached.
6627
- */
6628
- template(templateId) {
6629
- this.attr.templateId = templateId;
6630
- return this;
6631
- }
6632
- /**
6633
- * Set multiple allowed templates.
6634
- * User can choose which template to use when uploading.
6635
- */
6636
- templates(templateIds) {
6637
- this.attr.allowedTemplates = templateIds;
6638
- return this;
6639
- }
6640
6834
  /**
6641
6835
  * Allow multiple documents.
6642
6836
  * Value becomes string[] instead of string.
@@ -8150,6 +8344,74 @@ var WorkflowConditionBuilder = class {
8150
8344
  };
8151
8345
  }
8152
8346
  };
8347
+ var WorkflowAssignBuilder = class {
8348
+ /** @internal */
8349
+ constructor(workflowBuilder, nodeId, label, targetSlotId) {
8350
+ this.assignments = [];
8351
+ this.workflowBuilder = workflowBuilder;
8352
+ this.nodeId = nodeId;
8353
+ this.label = label;
8354
+ this.targetSlotId = targetSlotId;
8355
+ }
8356
+ /**
8357
+ * Set an attribute using an expression template.
8358
+ * Uses mustache syntax: `{{ slotId.attribute }}`.
8359
+ */
8360
+ expression(targetAttribute, template) {
8361
+ this.assignments.push({
8362
+ targetAttribute,
8363
+ source: { type: "expression", template }
8364
+ });
8365
+ return this;
8366
+ }
8367
+ /**
8368
+ * Link a relation attribute to a slot's record.
8369
+ * Optionally include qualified relation properties.
8370
+ */
8371
+ slotRef(targetAttribute, slotId, properties) {
8372
+ this.assignments.push({
8373
+ targetAttribute,
8374
+ source: { type: "slot-ref", slotId, properties }
8375
+ });
8376
+ return this;
8377
+ }
8378
+ /**
8379
+ * Set a static value on an attribute
8380
+ */
8381
+ setValue(targetAttribute, value) {
8382
+ this.assignments.push({
8383
+ targetAttribute,
8384
+ source: { type: "static", value }
8385
+ });
8386
+ return this;
8387
+ }
8388
+ /**
8389
+ * Set the description for this assign node
8390
+ */
8391
+ describe(description) {
8392
+ this.nodeDescription = description;
8393
+ return this;
8394
+ }
8395
+ /**
8396
+ * Set the next node and complete the assign definition
8397
+ */
8398
+ next(nodeId) {
8399
+ if (this.assignments.length === 0) {
8400
+ throw new Error(
8401
+ `[WorkflowBuilder] Assign "${this.nodeId}" must have at least one assignment. Use .expression(), .slotRef() or .setValue() first.`
8402
+ );
8403
+ }
8404
+ return this.workflowBuilder._addNode({
8405
+ type: "assign",
8406
+ id: this.nodeId,
8407
+ label: this.label,
8408
+ description: this.nodeDescription,
8409
+ targetSlotId: this.targetSlotId,
8410
+ assignments: this.assignments,
8411
+ next: nodeId
8412
+ });
8413
+ }
8414
+ };
8153
8415
  var WorkflowEndBuilder = class {
8154
8416
  /** @internal */
8155
8417
  constructor(workflowBuilder, nodeId) {
@@ -8315,6 +8577,12 @@ var WorkflowBuilder = class {
8315
8577
  condition(id, label) {
8316
8578
  return new WorkflowConditionBuilder(this, id, label);
8317
8579
  }
8580
+ /**
8581
+ * Define an assign node (set values on a target slot)
8582
+ */
8583
+ assign(id, label, targetSlotId) {
8584
+ return new WorkflowAssignBuilder(this, id, label, targetSlotId);
8585
+ }
8318
8586
  /**
8319
8587
  * Define an end node
8320
8588
  */
@@ -8392,21 +8660,10 @@ var WorkflowBuilder = class {
8392
8660
  validateNodeReferences() {
8393
8661
  const nodeIds = new Set(Object.keys(this.data.nodes ?? {}));
8394
8662
  for (const node of Object.values(this.data.nodes ?? {})) {
8395
- if (node.type === "start" || node.type === "form") {
8396
- if (node.next && !nodeIds.has(node.next)) {
8397
- throw new Error(
8398
- `[WorkflowBuilder] Node "${node.id}" references unknown node "${node.next}".`
8399
- );
8400
- }
8401
- } else if (node.type === "condition") {
8402
- if (node.onTrue && !nodeIds.has(node.onTrue)) {
8403
- throw new Error(
8404
- `[WorkflowBuilder] Condition "${node.id}" references unknown node "${node.onTrue}" for onTrue.`
8405
- );
8406
- }
8407
- if (node.onFalse && !nodeIds.has(node.onFalse)) {
8663
+ for (const targetId of getNodeOutputs(node)) {
8664
+ if (!nodeIds.has(targetId)) {
8408
8665
  throw new Error(
8409
- `[WorkflowBuilder] Condition "${node.id}" references unknown node "${node.onFalse}" for onFalse.`
8666
+ `[WorkflowBuilder] Node "${node.id}" references unknown node "${targetId}".`
8410
8667
  );
8411
8668
  }
8412
8669
  }
@@ -8415,28 +8672,11 @@ var WorkflowBuilder = class {
8415
8672
  validateSlotReferences() {
8416
8673
  const slotIds = this.data.slots?.reduce((set, s) => set.add(s.id), /* @__PURE__ */ new Set()) ?? /* @__PURE__ */ new Set();
8417
8674
  for (const node of Object.values(this.data.nodes ?? {})) {
8418
- if (node.type === "form") {
8419
- const referencedSlots = /* @__PURE__ */ new Set();
8420
- if (node.fields) {
8421
- for (const field of node.fields) {
8422
- referencedSlots.add(field.slotId);
8423
- }
8424
- }
8425
- if (node.rows) {
8426
- for (const row of node.rows) {
8427
- if (isFlowFieldsRow(row)) {
8428
- for (const field of row.fields) {
8429
- referencedSlots.add(field.slotId);
8430
- }
8431
- }
8432
- }
8433
- }
8434
- for (const slotId of referencedSlots) {
8435
- if (!slotIds.has(slotId)) {
8436
- throw new Error(
8437
- `[WorkflowBuilder] Form "${node.id}" references unknown slot "${slotId}".`
8438
- );
8439
- }
8675
+ for (const slotId of getNodeSlotIds(node)) {
8676
+ if (!slotIds.has(slotId)) {
8677
+ throw new Error(
8678
+ `[WorkflowBuilder] Node "${node.id}" references unknown slot "${slotId}".`
8679
+ );
8440
8680
  }
8441
8681
  }
8442
8682
  }
@@ -8445,24 +8685,12 @@ var WorkflowBuilder = class {
8445
8685
  const nodes = this.data.nodes ?? {};
8446
8686
  const visited = /* @__PURE__ */ new Set();
8447
8687
  const recursionStack = /* @__PURE__ */ new Set();
8448
- const getNextNodes = (nodeId) => {
8449
- const node = nodes[nodeId];
8450
- if (!node) return [];
8451
- switch (node.type) {
8452
- case "start":
8453
- case "form":
8454
- case "document":
8455
- return node.next ? [node.next] : [];
8456
- case "condition":
8457
- return [node.onTrue, node.onFalse].filter((n) => !!n);
8458
- case "end":
8459
- return [];
8460
- }
8461
- };
8462
8688
  const hasCycle = (nodeId) => {
8463
8689
  visited.add(nodeId);
8464
8690
  recursionStack.add(nodeId);
8465
- for (const nextId of getNextNodes(nodeId)) {
8691
+ const node = nodes[nodeId];
8692
+ if (!node) return false;
8693
+ for (const nextId of getNodeOutputs(node)) {
8466
8694
  if (!visited.has(nextId)) {
8467
8695
  if (hasCycle(nextId)) return true;
8468
8696
  } else if (recursionStack.has(nextId)) {
@@ -8479,7 +8707,9 @@ var WorkflowBuilder = class {
8479
8707
  const visit = (nodeId) => {
8480
8708
  if (reachable.has(nodeId)) return;
8481
8709
  reachable.add(nodeId);
8482
- for (const nextId of getNextNodes(nodeId)) {
8710
+ const node = nodes[nodeId];
8711
+ if (!node) return;
8712
+ for (const nextId of getNodeOutputs(node)) {
8483
8713
  visit(nextId);
8484
8714
  }
8485
8715
  };
@@ -8588,7 +8818,6 @@ var SYSTEM_ATTRIBUTES = {
8588
8818
  multiple: true,
8589
8819
  icon: "paperclip",
8590
8820
  description: "Free-form document attachments"
8591
- // No templateId = all templates allowed
8592
8821
  }
8593
8822
  };
8594
8823
  function getSystemAttributeList() {
@@ -10849,14 +11078,20 @@ var RelationPropertiesService = class extends BaseService {
10849
11078
  if (value === null || value === void 0) {
10850
11079
  continue;
10851
11080
  }
10852
- if (attr.cardinality === "many" && Array.isArray(value)) {
10853
- normalized[attr.name] = value.map((item) => {
10854
- if (typeof item === "string") return item;
10855
- if (typeof item === "object" && item !== null && "id" in item) {
10856
- return item.id;
10857
- }
10858
- return item;
10859
- });
11081
+ if (attr.cardinality === "many") {
11082
+ if (Array.isArray(value)) {
11083
+ normalized[attr.name] = value.map((item) => {
11084
+ if (typeof item === "string") return item;
11085
+ if (typeof item === "object" && item !== null && "id" in item) {
11086
+ return item.id;
11087
+ }
11088
+ return item;
11089
+ });
11090
+ } else if (typeof value === "object" && value !== null && "id" in value) {
11091
+ normalized[attr.name] = [value.id];
11092
+ } else if (typeof value === "string") {
11093
+ normalized[attr.name] = [value];
11094
+ }
10860
11095
  } else if (typeof value === "object" && value !== null && "id" in value) {
10861
11096
  normalized[attr.name] = value.id;
10862
11097
  }
@@ -13021,500 +13256,54 @@ var RollupScheduler = class {
13021
13256
  const timeout = setTimeout(async () => {
13022
13257
  await this.executeRecalculation(parentId, parentObjectId);
13023
13258
  this.pending.delete(key);
13024
- }, this.debounceMs);
13025
- this.pending.set(key, { parentId, parentObjectId, timeout });
13026
- if (this.pending.size >= this.maxPending) {
13027
- this.flush();
13028
- }
13029
- }
13030
- /**
13031
- * Execute all pending recalculations immediately
13032
- */
13033
- async flush() {
13034
- const entries = Array.from(this.pending.entries());
13035
- for (const [, entry] of entries) {
13036
- clearTimeout(entry.timeout);
13037
- }
13038
- this.pending.clear();
13039
- const allIds = entries.map(([, entry]) => entry.parentId);
13040
- const records = await this.adapter.objectRecords.findByIds(allIds);
13041
- for (const record of records) {
13042
- const schema = await this.getSchemaById(record.objectId);
13043
- if (schema) {
13044
- await this.rollupService.recalculateAndUpdate(record, schema);
13045
- }
13046
- }
13047
- }
13048
- /**
13049
- * Execute a single recalculation
13050
- */
13051
- async executeRecalculation(parentId, parentObjectId) {
13052
- const record = await this.adapter.objectRecords.findById(parentId);
13053
- if (!record) return;
13054
- const schema = await this.getSchemaById(parentObjectId);
13055
- if (!schema) return;
13056
- await this.rollupService.recalculateAndUpdate(record, schema);
13057
- }
13058
- /**
13059
- * Get number of pending recalculations
13060
- */
13061
- get pendingCount() {
13062
- return this.pending.size;
13063
- }
13064
- /**
13065
- * Clear all pending recalculations without executing them
13066
- */
13067
- clear() {
13068
- for (const [, entry] of this.pending) {
13069
- clearTimeout(entry.timeout);
13070
- }
13071
- this.pending.clear();
13072
- }
13073
- };
13074
-
13075
- // src/runtime/services/document/document-renderer.service.ts
13076
- import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
13077
- var DocumentRenderError = class extends Error {
13078
- constructor(message, templateId, cause) {
13079
- super(message);
13080
- this.templateId = templateId;
13081
- this.cause = cause;
13082
- this.name = "DocumentRenderError";
13083
- }
13084
- };
13085
- var StorageDownloadNotSupportedError = class extends Error {
13086
- constructor() {
13087
- super("Storage adapter does not support download. Required for document rendering.");
13088
- this.name = "StorageDownloadNotSupportedError";
13089
- }
13090
- };
13091
- var DocumentRendererService = class {
13092
- constructor(storageAdapter, options) {
13093
- this.storageAdapter = storageAdapter;
13094
- this.options = options;
13095
- this.schemaCache = /* @__PURE__ */ new Map();
13096
- }
13097
- /**
13098
- * Render a document from a template and context.
13099
- *
13100
- * @param input - Template, context, and optional filename
13101
- * @returns Generated PDF buffer with metadata
13102
- * @throws DocumentRenderError if rendering fails
13103
- * @throws StorageDownloadNotSupportedError if storage doesn't support download
13104
- */
13105
- async render(input) {
13106
- const { template, context, workflow: workflow2, filename } = input;
13107
- if (template.source.type !== "pdf") {
13108
- throw new DocumentRenderError("Only PDF templates are supported for rendering", template.id);
13109
- }
13110
- if (!this.storageAdapter.download) {
13111
- throw new StorageDownloadNotSupportedError();
13112
- }
13113
- try {
13114
- const templateBytes = await this.downloadTemplate(template.source.fileId);
13115
- const pdfDoc = await PDFDocument.load(templateBytes);
13116
- const pages = pdfDoc.getPages();
13117
- const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
13118
- const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
13119
- const resolvedValues = await this.resolveAllFieldValues(
13120
- template.source.fields,
13121
- context,
13122
- workflow2
13123
- );
13124
- for (const field of template.source.fields) {
13125
- this.drawField(pages, field, resolvedValues.get(field.id) ?? "", font, fontBold);
13126
- }
13127
- const outputBytes = await pdfDoc.save();
13128
- const resolvedFilename = this.interpolateFilename(filename, context, template);
13129
- return {
13130
- buffer: Buffer.from(outputBytes),
13131
- filename: resolvedFilename,
13132
- mimeType: "application/pdf",
13133
- pageCount: pages.length
13134
- };
13135
- } catch (error2) {
13136
- if (error2 instanceof DocumentRenderError || error2 instanceof StorageDownloadNotSupportedError) {
13137
- throw error2;
13138
- }
13139
- throw new DocumentRenderError(
13140
- `Failed to render document: ${getErrorMessage(error2)}`,
13141
- template.id,
13142
- error2
13143
- );
13144
- }
13145
- }
13146
- // ============================================================================
13147
- // PRIVATE METHODS
13148
- // ============================================================================
13149
- /**
13150
- * Download the template PDF from storage
13151
- */
13152
- async downloadTemplate(fileId) {
13153
- if (!this.storageAdapter.download) {
13154
- throw new StorageDownloadNotSupportedError();
13155
- }
13156
- let storagePath = fileId;
13157
- if (this.options?.filesRepository) {
13158
- const file2 = await this.options.filesRepository.findById(fileId);
13159
- if (!file2) {
13160
- throw new Error(`Template file not found: ${fileId}`);
13161
- }
13162
- storagePath = file2.storagePath;
13163
- }
13164
- return await this.storageAdapter.download(storagePath);
13165
- }
13166
- /**
13167
- * Resolve all field values, including relations and formatted attributes
13168
- */
13169
- async resolveAllFieldValues(fields, context, workflow2) {
13170
- const resolved = /* @__PURE__ */ new Map();
13171
- const relationBatch = [];
13172
- for (const field of fields) {
13173
- const rawValue = getContextValue(context, field.contextPath);
13174
- const attrInfo = await this.getAttributeInfo(field.contextPath, workflow2);
13175
- if (attrInfo?.attribute) {
13176
- if (attrInfo.attribute.type === "relation" && rawValue && this.options?.relationService) {
13177
- const ids = Array.isArray(rawValue) ? rawValue : [rawValue];
13178
- const stringIds = ids.filter((id) => typeof id === "string");
13179
- if (stringIds.length > 0) {
13180
- relationBatch.push({
13181
- fieldId: field.id,
13182
- attributeId: attrInfo.attribute.id ?? field.id,
13183
- ids: stringIds
13184
- });
13185
- continue;
13186
- }
13187
- }
13188
- const formatted = formatAttributeValue(rawValue, attrInfo.attribute);
13189
- resolved.set(
13190
- field.id,
13191
- formatted === EMPTY_VALUE_PLACEHOLDER ? field.fallback ?? "" : formatted
13192
- );
13193
- } else {
13194
- resolved.set(field.id, this.formatValueSimple(rawValue, field.fallback));
13195
- }
13196
- }
13197
- if (relationBatch.length > 0 && this.options?.relationService) {
13198
- try {
13199
- const batchResult = await this.options.relationService.resolveIdsBatch(
13200
- relationBatch.map((r) => ({ attributeId: r.attributeId, ids: r.ids }))
13201
- );
13202
- for (const { fieldId, attributeId } of relationBatch) {
13203
- const options = batchResult[attributeId] ?? [];
13204
- const labels = options.map((o) => o.label);
13205
- const field = fields.find((f) => f.id === fieldId);
13206
- resolved.set(fieldId, labels.join(", ") || field?.fallback || "");
13207
- }
13208
- } catch {
13209
- for (const { fieldId, ids } of relationBatch) {
13210
- const field = fields.find((f) => f.id === fieldId);
13211
- resolved.set(fieldId, ids.join(", ") || field?.fallback || "");
13212
- }
13213
- }
13214
- }
13215
- return resolved;
13216
- }
13217
- /**
13218
- * Get attribute info from contextPath
13219
- * Parses paths like "slots.client.firstName" to find the attribute definition
13220
- */
13221
- async getAttributeInfo(contextPath, workflow2) {
13222
- const schemaService = this.options?.schemaService;
13223
- if (!schemaService) {
13224
- return null;
13225
- }
13226
- if (!workflow2) {
13227
- return null;
13228
- }
13229
- const parts = contextPath.split(".");
13230
- if (parts.length < 3 || parts[0] !== "slots") {
13231
- return null;
13232
- }
13233
- const slotId = parts[1];
13234
- const attributeName = parts[2];
13235
- const slot = workflow2.slots?.find((s) => s.id === slotId);
13236
- if (!slot) {
13237
- return null;
13238
- }
13239
- let schema = this.schemaCache.get(slot.objectName);
13240
- if (!schema) {
13241
- try {
13242
- schema = await schemaService.getObjectSchemaByName(slot.objectName);
13243
- this.schemaCache.set(slot.objectName, schema);
13244
- } catch {
13245
- return null;
13246
- }
13247
- }
13248
- if (!schema) {
13249
- return null;
13250
- }
13251
- const attribute = schema.attributes.find((a) => a.name === attributeName);
13252
- if (!attribute) {
13253
- return null;
13254
- }
13255
- return { attribute, objectName: slot.objectName };
13256
- }
13257
- /**
13258
- * Draw a single field on the PDF
13259
- */
13260
- drawField(pages, field, value, font, fontBold) {
13261
- const page = pages[field.page];
13262
- if (!page) {
13263
- return;
13264
- }
13265
- if (!value) {
13266
- return;
13267
- }
13268
- const selectedFont = field.fontWeight === "bold" ? fontBold : font;
13269
- const fontSize = field.fontSize ?? 11;
13270
- const { height: pageHeight } = page.getSize();
13271
- let x = field.x;
13272
- if (field.align === "center" || field.align === "right") {
13273
- const textWidth = selectedFont.widthOfTextAtSize(value, fontSize);
13274
- if (field.align === "center") {
13275
- x = field.x + (field.width - textWidth) / 2;
13276
- } else {
13277
- x = field.x + field.width - textWidth;
13278
- }
13279
- }
13280
- const y = pageHeight - field.y - fontSize;
13281
- page.drawText(value, {
13282
- x,
13283
- y,
13284
- size: fontSize,
13285
- font: selectedFont,
13286
- color: rgb(0, 0, 0)
13287
- });
13288
- }
13289
- /**
13290
- * Simple value formatting (fallback when no attribute definition available)
13291
- */
13292
- formatValueSimple(value, fallback) {
13293
- if (value === null || value === void 0) {
13294
- return fallback ?? "";
13295
- }
13296
- if (value instanceof Date) {
13297
- return value.toLocaleDateString();
13298
- }
13299
- if (typeof value === "number") {
13300
- return String(value);
13301
- }
13302
- if (typeof value === "boolean") {
13303
- return value ? "Yes" : "No";
13304
- }
13305
- if (Array.isArray(value)) {
13306
- return value.map((v) => this.formatValueSimple(v)).join(", ");
13307
- }
13308
- if (typeof value === "object") {
13309
- const obj = value;
13310
- if (typeof obj.label === "string") return obj.label;
13311
- if (typeof obj.name === "string") return obj.name;
13312
- return JSON.stringify(value);
13313
- }
13314
- return String(value);
13315
- }
13316
- /**
13317
- * Interpolate filename with context values
13318
- *
13319
- * Supports {{path}} syntax for variable interpolation.
13320
- *
13321
- * @example
13322
- * ```typescript
13323
- * interpolateFilename("contract-{{slots.client.name}}.pdf", context, template)
13324
- * // => "contract-John Doe.pdf"
13325
- * ```
13326
- */
13327
- interpolateFilename(template, context, docTemplate) {
13328
- if (!template) {
13329
- const timestamp = Date.now();
13330
- const baseName = docTemplate.name || "document";
13331
- return `${baseName}-${timestamp}.pdf`;
13332
- }
13333
- const interpolated = template.replace(/\{\{([^}]+)\}\}/g, (_, path) => {
13334
- const value = getContextValue(context, path.trim());
13335
- if (value === null || value === void 0) {
13336
- return "";
13337
- }
13338
- return String(value).replace(/[<>:"/\\|?*]/g, "_").trim();
13339
- });
13340
- if (!interpolated.toLowerCase().endsWith(".pdf")) {
13341
- return `${interpolated}.pdf`;
13342
- }
13343
- return interpolated;
13344
- }
13345
- };
13346
-
13347
- // src/runtime/services/workflow/document-processing.hook.ts
13348
- var DocumentProcessingHook = class extends BaseService {
13349
- constructor(adapter, storageAdapter, options) {
13350
- super(adapter);
13351
- this.adapter = adapter;
13352
- this.storageAdapter = storageAdapter;
13353
- this.options = options;
13354
- this.renderer = new DocumentRendererService(storageAdapter, {
13355
- schemaService: options.schemaService,
13356
- relationService: options.relationService,
13357
- filesRepository: adapter.files
13358
- });
13359
- }
13360
- /**
13361
- * Process all pending document requests in the context.
13362
- *
13363
- * @param context - Current workflow execution context
13364
- * @param workflow - Workflow definition (for slot/object info)
13365
- * @param userId - User ID for audit/permissions
13366
- * @returns Updated context with processed documents
13367
- */
13368
- async process(context, workflow2, userId) {
13369
- const pendingNodeIds = this.findPendingDocuments(context);
13370
- if (pendingNodeIds.length === 0) {
13371
- return context;
13372
- }
13373
- const updatedDocuments = { ...context.documents };
13374
- for (const nodeId of pendingNodeIds) {
13375
- const doc = context.documents[nodeId];
13376
- const metadata = doc.metadata;
13377
- if (!metadata) {
13378
- continue;
13379
- }
13380
- try {
13381
- updatedDocuments[nodeId] = {
13382
- ...doc,
13383
- metadata: { ...metadata, status: "processing" }
13384
- };
13385
- const template = await this.options.documentGenerationService.getByIdOrThrow(
13386
- metadata.templateId
13387
- );
13388
- const renderResult = await this.renderer.render({
13389
- template,
13390
- context,
13391
- workflow: workflow2,
13392
- filename: doc.filename
13393
- });
13394
- const uploadResult = await this.uploadGeneratedDocument(renderResult, template, userId);
13395
- const attachedDocumentIds = await this.attachToRecords(
13396
- renderResult,
13397
- metadata.targetSlotIds,
13398
- context,
13399
- workflow2,
13400
- userId
13401
- );
13402
- updatedDocuments[nodeId] = {
13403
- id: uploadResult.fileId,
13404
- url: uploadResult.url,
13405
- filename: renderResult.filename,
13406
- mimeType: renderResult.mimeType,
13407
- size: renderResult.buffer.length,
13408
- attachedDocumentIds,
13409
- metadata: { ...metadata, status: "completed" }
13410
- };
13411
- } catch (error2) {
13412
- const errorMessage = getErrorMessage(error2);
13413
- updatedDocuments[nodeId] = {
13414
- ...doc,
13415
- metadata: { ...metadata, status: "failed", error: errorMessage }
13416
- };
13417
- }
13259
+ }, this.debounceMs);
13260
+ this.pending.set(key, { parentId, parentObjectId, timeout });
13261
+ if (this.pending.size >= this.maxPending) {
13262
+ this.flush();
13418
13263
  }
13419
- return {
13420
- ...context,
13421
- documents: updatedDocuments
13422
- };
13423
13264
  }
13424
- // ============================================================================
13425
- // PRIVATE METHODS
13426
- // ============================================================================
13427
13265
  /**
13428
- * Find node IDs with pending document requests
13266
+ * Execute all pending recalculations immediately
13429
13267
  */
13430
- findPendingDocuments(context) {
13431
- const pendingIds = [];
13432
- for (const [nodeId, doc] of Object.entries(context.documents)) {
13433
- const metadata = doc.metadata;
13434
- if (metadata?.status === "pending") {
13435
- pendingIds.push(nodeId);
13268
+ async flush() {
13269
+ const entries = Array.from(this.pending.entries());
13270
+ for (const [, entry] of entries) {
13271
+ clearTimeout(entry.timeout);
13272
+ }
13273
+ this.pending.clear();
13274
+ const allIds = entries.map(([, entry]) => entry.parentId);
13275
+ const records = await this.adapter.objectRecords.findByIds(allIds);
13276
+ for (const record of records) {
13277
+ const schema = await this.getSchemaById(record.objectId);
13278
+ if (schema) {
13279
+ await this.rollupService.recalculateAndUpdate(record, schema);
13436
13280
  }
13437
13281
  }
13438
- return pendingIds;
13439
13282
  }
13440
13283
  /**
13441
- * Upload the generated PDF to storage
13284
+ * Execute a single recalculation
13442
13285
  */
13443
- async uploadGeneratedDocument(renderResult, template, userId) {
13444
- const uploadResult = await this.storageAdapter.upload({
13445
- content: renderResult.buffer,
13446
- fileName: renderResult.filename,
13447
- mimeType: renderResult.mimeType,
13448
- size: renderResult.buffer.length,
13449
- tenantId: this.tenantId,
13450
- folderPath: `generated-documents/${template.name}`
13451
- });
13452
- let fileId = `file-${Date.now()}`;
13453
- if (this.adapter.files) {
13454
- const fileRecord = await this.adapter.files.create({
13455
- name: renderResult.filename,
13456
- originalName: renderResult.filename,
13457
- mimeType: renderResult.mimeType,
13458
- size: renderResult.buffer.length,
13459
- storageProvider: uploadResult.storageProvider,
13460
- storagePath: uploadResult.storagePath,
13461
- storageBucket: uploadResult.storageBucket,
13462
- url: uploadResult.url,
13463
- uploadedBy: userId,
13464
- visibility: "private"
13465
- });
13466
- fileId = fileRecord.id;
13467
- }
13468
- return {
13469
- fileId,
13470
- url: uploadResult.url,
13471
- storagePath: uploadResult.storagePath
13472
- };
13286
+ async executeRecalculation(parentId, parentObjectId) {
13287
+ const record = await this.adapter.objectRecords.findById(parentId);
13288
+ if (!record) return;
13289
+ const schema = await this.getSchemaById(parentObjectId);
13290
+ if (!schema) return;
13291
+ await this.rollupService.recalculateAndUpdate(record, schema);
13473
13292
  }
13474
13293
  /**
13475
- * Attach generated document to target records
13294
+ * Get number of pending recalculations
13476
13295
  */
13477
- async attachToRecords(renderResult, targetSlotIds, context, workflow2, userId) {
13478
- const attachedDocumentIds = [];
13479
- const { documentService, recordService } = this.options;
13480
- if (!(documentService && recordService)) {
13481
- return attachedDocumentIds;
13482
- }
13483
- for (const slotId of targetSlotIds) {
13484
- try {
13485
- const recordId = context.createdRecordIds?.[slotId];
13486
- if (!recordId) {
13487
- continue;
13488
- }
13489
- const slotDef = workflow2.slots?.find((s) => s.id === slotId);
13490
- const objectName = slotDef?.objectName;
13491
- if (!objectName) {
13492
- continue;
13493
- }
13494
- const result = await documentService.createRecordDocument({
13495
- objectName,
13496
- recordId,
13497
- fileContent: renderResult.buffer,
13498
- fileName: renderResult.filename,
13499
- mimeType: renderResult.mimeType,
13500
- fileSize: renderResult.buffer.length,
13501
- uploadedBy: userId,
13502
- title: renderResult.filename
13503
- });
13504
- attachedDocumentIds.push(result.document.id);
13505
- const record = await recordService.getRecord(recordId);
13506
- if (record) {
13507
- const attachments = record.values?.attachments ?? [];
13508
- await recordService.updateRecord(
13509
- recordId,
13510
- { attachments: [...attachments, result.document.id] },
13511
- { partial: true }
13512
- );
13513
- }
13514
- } catch {
13515
- }
13296
+ get pendingCount() {
13297
+ return this.pending.size;
13298
+ }
13299
+ /**
13300
+ * Clear all pending recalculations without executing them
13301
+ */
13302
+ clear() {
13303
+ for (const [, entry] of this.pending) {
13304
+ clearTimeout(entry.timeout);
13516
13305
  }
13517
- return attachedDocumentIds;
13306
+ this.pending.clear();
13518
13307
  }
13519
13308
  };
13520
13309
 
@@ -13825,7 +13614,6 @@ var WorkflowInstanceService = class extends BaseService {
13825
13614
  this.executorRegistry = options?.executorRegistry ?? getDefaultExecutorRegistry();
13826
13615
  this.schemaService = options?.schemaService;
13827
13616
  this.recordService = options?.recordService;
13828
- this.documentProcessingHook = options?.documentProcessingHook;
13829
13617
  }
13830
13618
  /**
13831
13619
  * Start a new workflow instance
@@ -14171,14 +13959,7 @@ var WorkflowInstanceService = class extends BaseService {
14171
13959
  }
14172
13960
  case "complete": {
14173
13961
  try {
14174
- let updatedContext = await this.persistSlots(current);
14175
- if (this.documentProcessingHook) {
14176
- updatedContext = await this.documentProcessingHook.process(
14177
- updatedContext,
14178
- current.workflowSnapshot,
14179
- current.startedBy
14180
- );
14181
- }
13962
+ const updatedContext = await this.persistSlots(current);
14182
13963
  current = {
14183
13964
  ...current,
14184
13965
  context: updatedContext,
@@ -15530,471 +15311,10 @@ var UserProfileService = class extends BaseService {
15530
15311
  }
15531
15312
  };
15532
15313
 
15533
- // src/runtime/services/document/document-generation.service.ts
15534
- var DocumentGenerationTemplateNotFoundError = class extends Error {
15535
- constructor(templateId) {
15536
- super(`Document generation template not found: ${templateId}`);
15537
- this.templateId = templateId;
15538
- this.name = "DocumentGenerationTemplateNotFoundError";
15539
- }
15540
- };
15541
- var DocumentGenerationNotConfiguredError = class extends Error {
15542
- constructor() {
15543
- super(
15544
- "DocumentGenerationTemplatesRepository not available. Enable document generation in adapter."
15545
- );
15546
- this.name = "DocumentGenerationNotConfiguredError";
15547
- }
15548
- };
15549
- var DocumentGenerationService = class extends BaseService {
15550
- /**
15551
- * Get the document generation templates repository.
15552
- * @throws DocumentGenerationNotConfiguredError if repository not available
15553
- */
15554
- get repo() {
15555
- const repo = this.adapter.documentGenerationTemplates;
15556
- if (!repo) {
15557
- throw new DocumentGenerationNotConfiguredError();
15558
- }
15559
- return repo;
15560
- }
15561
- // ============================================================================
15562
- // READ OPERATIONS
15563
- // ============================================================================
15564
- /**
15565
- * Get a template by ID.
15566
- *
15567
- * @param id - Template ID
15568
- * @returns Template or null if not found
15569
- */
15570
- async getById(id) {
15571
- return await this.repo.findById(id);
15572
- }
15573
- /**
15574
- * Get a template by ID, throwing if not found.
15575
- *
15576
- * @param id - Template ID
15577
- * @returns Template
15578
- * @throws DocumentGenerationTemplateNotFoundError if not found
15579
- */
15580
- async getByIdOrThrow(id) {
15581
- const template = await this.repo.findById(id);
15582
- if (!template) {
15583
- throw new DocumentGenerationTemplateNotFoundError(id);
15584
- }
15585
- return template;
15586
- }
15587
- /**
15588
- * Get a template by name.
15589
- *
15590
- * @param name - Template name (unique within tenant)
15591
- * @returns Template or null if not found
15592
- */
15593
- async getByName(name) {
15594
- return await this.repo.findByName(name);
15595
- }
15596
- /**
15597
- * List templates for the current tenant.
15598
- *
15599
- * @param options - List options (sourceType filter, pagination)
15600
- * @returns Array of templates
15601
- */
15602
- async list(options) {
15603
- return await this.repo.list(options);
15604
- }
15605
- // ============================================================================
15606
- // WRITE OPERATIONS
15607
- // ============================================================================
15608
- /**
15609
- * Create a new document generation template.
15610
- *
15611
- * @param input - Template data
15612
- * @returns Created template
15613
- */
15614
- async create(input) {
15615
- return await this.repo.create(input);
15616
- }
15617
- /**
15618
- * Update a template.
15619
- *
15620
- * @param id - Template ID
15621
- * @param input - Fields to update
15622
- * @returns Updated template
15623
- */
15624
- async update(id, input) {
15625
- return await this.repo.update(id, input);
15626
- }
15627
- /**
15628
- * Delete a template.
15629
- *
15630
- * @param id - Template ID
15631
- */
15632
- async delete(id) {
15633
- await this.repo.delete(id);
15634
- }
15635
- };
15636
-
15637
- // src/templates/index.ts
15638
- var SYSTEM_TEMPLATE_IDS = {
15639
- FRENCH_ID_CARD: "00000000-0000-0000-0001-000000000001",
15640
- PASSPORT: "00000000-0000-0000-0001-000000000002",
15641
- DRIVING_LICENSE: "00000000-0000-0000-0001-000000000003",
15642
- PROOF_OF_ADDRESS: "00000000-0000-0000-0001-000000000004",
15643
- SIGNABLE_CONTRACT: "00000000-0000-0000-0001-000000000005",
15644
- GENERIC_DOCUMENT: "00000000-0000-0000-0001-000000000006"
15645
- };
15646
- var FRENCH_ID_CARD = {
15647
- id: SYSTEM_TEMPLATE_IDS.FRENCH_ID_CARD,
15648
- tenantId: null,
15649
- name: "french_id_card",
15650
- label: "Carte d'identit\xE9 fran\xE7aise",
15651
- description: "Carte nationale d'identit\xE9 fran\xE7aise (recto/verso)",
15652
- icon: "credit-card",
15653
- system: true,
15654
- slots: [
15655
- {
15656
- name: "front",
15657
- label: "Recto",
15658
- description: "Face avant de la carte d'identit\xE9",
15659
- required: true,
15660
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
15661
- maxSize: 10 * 1024 * 1024,
15662
- // 10MB
15663
- order: 1
15664
- },
15665
- {
15666
- name: "back",
15667
- label: "Verso",
15668
- description: "Face arri\xE8re de la carte d'identit\xE9",
15669
- required: true,
15670
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
15671
- maxSize: 10 * 1024 * 1024,
15672
- order: 2
15673
- }
15674
- ],
15675
- allowAdditionalFiles: false,
15676
- autoProcessing: {
15677
- ocr: { enabled: true },
15678
- identityVerification: { enabled: true, documentType: "national_id" }
15679
- },
15680
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15681
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15682
- };
15683
- var PASSPORT = {
15684
- id: SYSTEM_TEMPLATE_IDS.PASSPORT,
15685
- tenantId: null,
15686
- name: "passport",
15687
- label: "Passeport",
15688
- description: "Passeport international",
15689
- icon: "badge",
15690
- system: true,
15691
- slots: [
15692
- {
15693
- name: "data_page",
15694
- label: "Page de donn\xE9es",
15695
- description: "Page avec photo et MRZ",
15696
- required: true,
15697
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
15698
- maxSize: 10 * 1024 * 1024,
15699
- order: 1
15700
- }
15701
- ],
15702
- allowAdditionalFiles: true,
15703
- autoProcessing: {
15704
- ocr: { enabled: true },
15705
- identityVerification: { enabled: true, documentType: "passport" }
15706
- },
15707
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15708
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15709
- };
15710
- var DRIVING_LICENSE = {
15711
- id: SYSTEM_TEMPLATE_IDS.DRIVING_LICENSE,
15712
- tenantId: null,
15713
- name: "driving_license",
15714
- label: "Permis de conduire",
15715
- description: "Permis de conduire",
15716
- icon: "credit-card-check",
15717
- system: true,
15718
- slots: [
15719
- {
15720
- name: "front",
15721
- label: "Recto",
15722
- required: true,
15723
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
15724
- maxSize: 10 * 1024 * 1024,
15725
- order: 1
15726
- },
15727
- {
15728
- name: "back",
15729
- label: "Verso",
15730
- required: false,
15731
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
15732
- maxSize: 10 * 1024 * 1024,
15733
- order: 2
15734
- }
15735
- ],
15736
- allowAdditionalFiles: false,
15737
- autoProcessing: {
15738
- ocr: { enabled: true },
15739
- identityVerification: { enabled: true, documentType: "driving_license" }
15740
- },
15741
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15742
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15743
- };
15744
- var PROOF_OF_ADDRESS = {
15745
- id: SYSTEM_TEMPLATE_IDS.PROOF_OF_ADDRESS,
15746
- tenantId: null,
15747
- name: "proof_of_address",
15748
- label: "Justificatif de domicile",
15749
- description: "Facture, relev\xE9 bancaire ou attestation de moins de 3 mois",
15750
- icon: "home",
15751
- system: true,
15752
- slots: [
15753
- {
15754
- name: "document",
15755
- label: "Document",
15756
- required: true,
15757
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
15758
- maxSize: 10 * 1024 * 1024,
15759
- order: 1
15760
- }
15761
- ],
15762
- allowAdditionalFiles: false,
15763
- autoProcessing: {
15764
- ocr: { enabled: true }
15765
- },
15766
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15767
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15768
- };
15769
- var SIGNABLE_CONTRACT = {
15770
- id: SYSTEM_TEMPLATE_IDS.SIGNABLE_CONTRACT,
15771
- tenantId: null,
15772
- name: "signable_contract",
15773
- label: "Contrat \xE0 signer",
15774
- description: "Document PDF n\xE9cessitant une signature \xE9lectronique",
15775
- icon: "file-text",
15776
- system: true,
15777
- slots: [
15778
- {
15779
- name: "contract",
15780
- label: "Contrat",
15781
- required: true,
15782
- allowedMimeTypes: ["application/pdf"],
15783
- maxSize: 50 * 1024 * 1024,
15784
- // 50MB for contracts
15785
- order: 1
15786
- }
15787
- ],
15788
- allowAdditionalFiles: true,
15789
- autoProcessing: {
15790
- signature: { enabled: true }
15791
- },
15792
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15793
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15794
- };
15795
- var GENERIC_DOCUMENT = {
15796
- id: SYSTEM_TEMPLATE_IDS.GENERIC_DOCUMENT,
15797
- tenantId: null,
15798
- name: "generic_document",
15799
- label: "Document",
15800
- description: "Document g\xE9n\xE9rique",
15801
- icon: "file",
15802
- system: true,
15803
- slots: [
15804
- {
15805
- name: "file",
15806
- label: "Fichier",
15807
- required: true,
15808
- order: 1
15809
- }
15810
- ],
15811
- allowAdditionalFiles: true,
15812
- autoProcessing: {},
15813
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15814
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15815
- };
15816
- var SYSTEM_TEMPLATES = [
15817
- FRENCH_ID_CARD,
15818
- PASSPORT,
15819
- DRIVING_LICENSE,
15820
- PROOF_OF_ADDRESS,
15821
- SIGNABLE_CONTRACT,
15822
- GENERIC_DOCUMENT
15823
- ];
15824
- function getSystemTemplate(name) {
15825
- return SYSTEM_TEMPLATES.find((t) => t.name === name);
15826
- }
15827
- function isSystemTemplate(name) {
15828
- return SYSTEM_TEMPLATES.some((t) => t.name === name);
15829
- }
15830
-
15831
- // src/runtime/services/document/document-template.service.ts
15832
- var DocumentTemplateService = class extends BaseService {
15833
- constructor(adapter) {
15834
- super(adapter);
15835
- }
15836
- // ============================================================================
15837
- // READ
15838
- // ============================================================================
15839
- /**
15840
- * Get a template by ID.
15841
- * Checks custom templates first, then system templates.
15842
- */
15843
- async getTemplate(templateId) {
15844
- const systemTemplate = SYSTEM_TEMPLATES.find((t) => t.id === templateId);
15845
- if (systemTemplate) {
15846
- return systemTemplate;
15847
- }
15848
- if (!this.adapter.documentTemplates) {
15849
- return null;
15850
- }
15851
- return await this.adapter.documentTemplates.findById(templateId);
15852
- }
15853
- /**
15854
- * Get a template by name.
15855
- * Checks custom templates first (tenant-specific), then system templates.
15856
- */
15857
- async getTemplateByName(name) {
15858
- if (this.adapter.documentTemplates) {
15859
- const customTemplate = await this.adapter.documentTemplates.findByName(name);
15860
- if (customTemplate) {
15861
- return customTemplate;
15862
- }
15863
- }
15864
- return getSystemTemplate(name) ?? null;
15865
- }
15866
- /**
15867
- * Get multiple templates by names.
15868
- */
15869
- async getTemplatesByNames(names) {
15870
- const results = [];
15871
- const missingNames = [];
15872
- for (const name of names) {
15873
- const systemTemplate = getSystemTemplate(name);
15874
- if (systemTemplate) {
15875
- results.push(systemTemplate);
15876
- } else {
15877
- missingNames.push(name);
15878
- }
15879
- }
15880
- if (missingNames.length > 0 && this.adapter.documentTemplates) {
15881
- const customTemplates = await this.adapter.documentTemplates.findByNames(missingNames);
15882
- results.push(...customTemplates);
15883
- }
15884
- return results;
15885
- }
15886
- /**
15887
- * Get a template or throw if not found.
15888
- */
15889
- async getTemplateOrThrow(templateId) {
15890
- const template = await this.getTemplate(templateId);
15891
- if (!template) {
15892
- throw new Error(`Document template with id "${templateId}" not found`);
15893
- }
15894
- return template;
15895
- }
15896
- /**
15897
- * Get a template by name or throw if not found.
15898
- */
15899
- async getTemplateByNameOrThrow(name) {
15900
- const template = await this.getTemplateByName(name);
15901
- if (!template) {
15902
- throw new Error(`Document template "${name}" not found`);
15903
- }
15904
- return template;
15905
- }
15906
- // ============================================================================
15907
- // LIST
15908
- // ============================================================================
15909
- /**
15910
- * List all available templates.
15911
- * Includes both system templates and tenant-specific templates.
15912
- */
15913
- async listTemplates(options) {
15914
- if (options?.systemOnly) {
15915
- return SYSTEM_TEMPLATES;
15916
- }
15917
- const templates = [...SYSTEM_TEMPLATES];
15918
- if (this.adapter.documentTemplates) {
15919
- const customTemplates = await this.adapter.documentTemplates.list(options);
15920
- templates.push(...customTemplates);
15921
- }
15922
- return templates;
15923
- }
15924
- /**
15925
- * Get only system templates.
15926
- */
15927
- getSystemTemplates() {
15928
- return SYSTEM_TEMPLATES;
15929
- }
15930
- // ============================================================================
15931
- // CREATE
15932
- // ============================================================================
15933
- /**
15934
- * Create a custom template.
15935
- * System templates cannot be created via this method.
15936
- */
15937
- async createTemplate(data) {
15938
- if (!this.adapter.documentTemplates) {
15939
- throw new Error("Document templates repository is not configured");
15940
- }
15941
- const existingSystem = getSystemTemplate(data.name);
15942
- if (existingSystem) {
15943
- throw new Error(`Template name "${data.name}" is reserved for a system template`);
15944
- }
15945
- const existing = await this.adapter.documentTemplates.findByName(data.name);
15946
- if (existing) {
15947
- throw new Error(`Template with name "${data.name}" already exists`);
15948
- }
15949
- return await this.adapter.documentTemplates.create(data);
15950
- }
15951
- // ============================================================================
15952
- // UPDATE
15953
- // ============================================================================
15954
- /**
15955
- * Update a custom template.
15956
- * System templates cannot be updated.
15957
- */
15958
- async updateTemplate(templateId, data) {
15959
- if (!this.adapter.documentTemplates) {
15960
- throw new Error("Document templates repository is not configured");
15961
- }
15962
- const existing = await this.getTemplate(templateId);
15963
- if (!existing) {
15964
- throw new Error(`Document template with id "${templateId}" not found`);
15965
- }
15966
- if (existing.system) {
15967
- throw new Error("System templates cannot be modified");
15968
- }
15969
- return await this.adapter.documentTemplates.update(templateId, data);
15970
- }
15971
- // ============================================================================
15972
- // DELETE
15973
- // ============================================================================
15974
- /**
15975
- * Delete a custom template.
15976
- * System templates cannot be deleted.
15977
- */
15978
- async deleteTemplate(templateId) {
15979
- if (!this.adapter.documentTemplates) {
15980
- throw new Error("Document templates repository is not configured");
15981
- }
15982
- const existing = await this.getTemplate(templateId);
15983
- if (!existing) {
15984
- throw new Error(`Document template with id "${templateId}" not found`);
15985
- }
15986
- if (existing.system) {
15987
- throw new Error("System templates cannot be deleted");
15988
- }
15989
- await this.adapter.documentTemplates.delete(templateId);
15990
- }
15991
- };
15992
-
15993
15314
  // src/runtime/services/document/document.service.ts
15994
15315
  var DocumentService = class extends BaseService {
15995
15316
  constructor(adapter, options) {
15996
15317
  super(adapter);
15997
- this.templateService = options?.templateService ?? new DocumentTemplateService(adapter);
15998
15318
  this.fileService = options?.fileService ?? null;
15999
15319
  }
16000
15320
  // ============================================================================
@@ -16010,22 +15330,8 @@ var DocumentService = class extends BaseService {
16010
15330
  if (!this.adapter.documents) {
16011
15331
  throw new Error("Documents repository is not configured");
16012
15332
  }
16013
- const template = await this.templateService.getTemplate(data.templateId);
16014
- if (!template) {
16015
- throw new Error(`Template with id "${data.templateId}" not found`);
16016
- }
16017
15333
  return await this.adapter.documents.create(data);
16018
15334
  }
16019
- /**
16020
- * Create a document with a template name instead of ID.
16021
- */
16022
- async createDocumentByTemplateName(templateName, data) {
16023
- const template = await this.templateService.getTemplateByNameOrThrow(templateName);
16024
- return await this.createDocument({
16025
- ...data,
16026
- templateId: template.id
16027
- });
16028
- }
16029
15335
  // ============================================================================
16030
15336
  // READ
16031
15337
  // ============================================================================
@@ -16057,13 +15363,6 @@ var DocumentService = class extends BaseService {
16057
15363
  }
16058
15364
  return await this.adapter.documents.findByIds(documentIds);
16059
15365
  }
16060
- /**
16061
- * Get the template for a document.
16062
- */
16063
- async getDocumentTemplate(documentId) {
16064
- const document2 = await this.getDocumentOrThrow(documentId);
16065
- return await this.templateService.getTemplateOrThrow(document2.templateId);
16066
- }
16067
15366
  // ============================================================================
16068
15367
  // LIST
16069
15368
  // ============================================================================
@@ -16154,27 +15453,19 @@ var DocumentService = class extends BaseService {
16154
15453
  if (!this.adapter.documentSlots) {
16155
15454
  throw new Error("Document slots repository is not configured");
16156
15455
  }
16157
- const document2 = await this.getDocumentOrThrow(documentId);
16158
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
16159
- const slotDef = template.slots.find((s) => s.name === data.slotName);
16160
- const isAdditional = !slotDef;
16161
- if (isAdditional && !template.allowAdditionalFiles) {
16162
- throw new Error(
16163
- `Slot "${data.slotName}" is not defined in template and additional files are not allowed`
16164
- );
16165
- }
15456
+ await this.getDocumentOrThrow(documentId);
16166
15457
  const existingSlot = await this.adapter.documentSlots.findByDocumentAndSlot(
16167
15458
  documentId,
16168
15459
  data.slotName
16169
15460
  );
16170
- if (existingSlot && !isAdditional) {
15461
+ if (existingSlot) {
16171
15462
  await this.adapter.documentSlots.delete(existingSlot.id);
16172
15463
  }
16173
15464
  const slot = await this.adapter.documentSlots.create({
16174
15465
  documentId,
16175
15466
  slotName: data.slotName,
16176
15467
  fileId: data.fileId,
16177
- isAdditional
15468
+ isAdditional: data.isAdditional ?? false
16178
15469
  });
16179
15470
  await this.recalculateStatus(documentId);
16180
15471
  return slot;
@@ -16208,13 +15499,9 @@ var DocumentService = class extends BaseService {
16208
15499
  * - failed: At least one job failed
16209
15500
  */
16210
15501
  async recalculateStatus(documentId) {
16211
- const document2 = await this.getDocumentOrThrow(documentId);
16212
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
15502
+ await this.getDocumentOrThrow(documentId);
16213
15503
  const slots = await this.getSlots(documentId);
16214
- const requiredSlots = template.slots.filter((s) => s.required);
16215
- const filledSlotNames = slots.reduce((set, s) => set.add(s.slotName), /* @__PURE__ */ new Set());
16216
- const allRequiredFilled = requiredSlots.every((s) => filledSlotNames.has(s.name));
16217
- if (!allRequiredFilled) {
15504
+ if (slots.length === 0) {
16218
15505
  return await this.updateStatus(documentId, "draft");
16219
15506
  }
16220
15507
  if (this.adapter.documentJobs) {
@@ -16249,13 +15536,12 @@ var DocumentService = class extends BaseService {
16249
15536
  return document2?.status !== "draft";
16250
15537
  }
16251
15538
  /**
16252
- * Get document with its template and slots.
15539
+ * Get document with its slots.
16253
15540
  */
16254
15541
  async getDocumentWithDetails(documentId) {
16255
15542
  const document2 = await this.getDocumentOrThrow(documentId);
16256
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
16257
15543
  const slots = await this.getSlots(documentId);
16258
- return { document: document2, template, slots };
15544
+ return { document: document2, slots };
16259
15545
  }
16260
15546
  // ============================================================================
16261
15547
  // RECORD DOCUMENTS
@@ -16318,7 +15604,7 @@ var DocumentService = class extends BaseService {
16318
15604
  *
16319
15605
  * This method:
16320
15606
  * 1. Uploads the file
16321
- * 2. Creates a document with the specified template
15607
+ * 2. Creates a document
16322
15608
  * 3. Adds the file to the document slot
16323
15609
  *
16324
15610
  * Note: The caller is responsible for updating record.values with the document ID.
@@ -16332,17 +15618,7 @@ var DocumentService = class extends BaseService {
16332
15618
  if (!this.adapter.documents) {
16333
15619
  throw new Error("Documents repository is not configured");
16334
15620
  }
16335
- const {
16336
- objectName,
16337
- recordId,
16338
- fileContent,
16339
- fileName,
16340
- mimeType,
16341
- fileSize,
16342
- uploadedBy,
16343
- title,
16344
- templateId = SYSTEM_TEMPLATE_IDS.GENERIC_DOCUMENT
16345
- } = input;
15621
+ const { objectName, recordId, fileContent, fileName, mimeType, fileSize, uploadedBy, title } = input;
16346
15622
  const uploadedFile = await this.fileService.uploadFile({
16347
15623
  content: fileContent,
16348
15624
  fileName,
@@ -16353,7 +15629,6 @@ var DocumentService = class extends BaseService {
16353
15629
  uploadedBy
16354
15630
  });
16355
15631
  const document2 = await this.createDocument({
16356
- templateId,
16357
15632
  title: title ?? fileName
16358
15633
  });
16359
15634
  const slot = await this.addSlot(document2.id, {
@@ -16373,10 +15648,7 @@ var DocumentProcessingService = class extends BaseService {
16373
15648
  constructor(adapter, config) {
16374
15649
  super(adapter);
16375
15650
  this.config = config;
16376
- this.templateService = new DocumentTemplateService(adapter);
16377
- this.documentService = new DocumentService(adapter, {
16378
- templateService: this.templateService
16379
- });
15651
+ this.documentService = new DocumentService(adapter);
16380
15652
  }
16381
15653
  // ============================================================================
16382
15654
  // OCR PROCESSING
@@ -16659,18 +15931,11 @@ var DocumentProcessingService = class extends BaseService {
16659
15931
  if (!this.adapter.documentJobs) {
16660
15932
  throw new Error("Document jobs repository is not configured");
16661
15933
  }
16662
- const document2 = await this.documentService.getDocumentOrThrow(documentId);
16663
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
16664
- if (!template.autoProcessing?.identityVerification?.enabled) {
16665
- throw new Error("Identity verification is not enabled for this document type");
16666
- }
15934
+ await this.documentService.getDocumentOrThrow(documentId);
16667
15935
  const job = await this.adapter.documentJobs.create({
16668
15936
  documentId,
16669
15937
  type: "identity_verification",
16670
- provider: this.config.identityAdapter.name,
16671
- input: {
16672
- documentType: template.autoProcessing.identityVerification.documentType
16673
- }
15938
+ provider: this.config.identityAdapter.name
16674
15939
  });
16675
15940
  return job;
16676
15941
  }
@@ -16743,17 +16008,16 @@ var DocumentProcessingService = class extends BaseService {
16743
16008
  * Called after all required slots are uploaded.
16744
16009
  */
16745
16010
  async triggerAutoProcessing(documentId) {
16746
- const document2 = await this.documentService.getDocumentOrThrow(documentId);
16747
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
16011
+ await this.documentService.getDocumentOrThrow(documentId);
16748
16012
  const slots = await this.documentService.getSlots(documentId);
16749
16013
  const jobs = [];
16750
- if (template.autoProcessing?.ocr?.enabled && this.config.ocrAdapter) {
16014
+ if (this.config.ocrAdapter) {
16751
16015
  for (const slot of slots) {
16752
16016
  const job = await this.processOcr(documentId, slot.slotName);
16753
16017
  jobs.push(job);
16754
16018
  }
16755
16019
  }
16756
- if (template.autoProcessing?.identityVerification?.enabled && this.config.identityAdapter) {
16020
+ if (this.config.identityAdapter) {
16757
16021
  const job = await this.verifyIdentity(documentId);
16758
16022
  jobs.push(job);
16759
16023
  }
@@ -16839,6 +16103,18 @@ var DocumentProcessingService = class extends BaseService {
16839
16103
  };
16840
16104
 
16841
16105
  // src/runtime/services/file.service.ts
16106
+ function decodeFileName(name) {
16107
+ try {
16108
+ const bytes = Buffer.from(name, "latin1");
16109
+ const decoded = bytes.toString("utf-8");
16110
+ if (Buffer.from(decoded, "utf-8").equals(bytes)) {
16111
+ return decoded;
16112
+ }
16113
+ return name;
16114
+ } catch {
16115
+ return name;
16116
+ }
16117
+ }
16842
16118
  var FileService = class extends BaseService {
16843
16119
  constructor(adapter, options) {
16844
16120
  super(adapter);
@@ -16888,17 +16164,18 @@ var FileService = class extends BaseService {
16888
16164
  }
16889
16165
  input.folderPath = sanitized.replace(/^\/+/, "").replace(/\/+/g, "/");
16890
16166
  }
16167
+ const fileName = decodeFileName(input.fileName);
16891
16168
  const uploadResult = await this.adapter.storage.upload({
16892
16169
  content: input.content,
16893
- fileName: input.fileName,
16170
+ fileName,
16894
16171
  mimeType: input.mimeType,
16895
16172
  size: input.size,
16896
16173
  tenantId: this.tenantId,
16897
16174
  folderPath: input.folderPath
16898
16175
  });
16899
16176
  const file2 = await this.adapter.files.create({
16900
- name: input.fileName,
16901
- originalName: input.fileName,
16177
+ name: fileName,
16178
+ originalName: fileName,
16902
16179
  mimeType: input.mimeType,
16903
16180
  size: input.size,
16904
16181
  storageProvider: uploadResult.storageProvider,
@@ -18835,14 +18112,19 @@ export {
18835
18112
  getSystemAttributeList,
18836
18113
  isSystemAttribute,
18837
18114
  isSystemAttributeObject,
18115
+ nodeTypeRegistry,
18116
+ getNodeOutputs,
18117
+ setNodeNext,
18118
+ getNodeSlotIds,
18119
+ validateNode,
18120
+ getFormFieldRefs,
18838
18121
  isSimpleFormNode,
18839
18122
  isAdvancedFormNode,
18840
18123
  isStartNode,
18841
18124
  isFormNode,
18842
18125
  isConditionNode,
18843
- isDocumentNode,
18126
+ isAssignNode,
18844
18127
  isEndNode,
18845
- getNodeOutputs,
18846
18128
  isConditionRule,
18847
18129
  isConditionGroup,
18848
18130
  eq,
@@ -18875,6 +18157,11 @@ export {
18875
18157
  isInstanceEvent,
18876
18158
  isNodeEvent,
18877
18159
  isInvitationOrGrantEvent,
18160
+ ZONE_ORDER,
18161
+ ZONE_CONFIG,
18162
+ assignNodeZones,
18163
+ groupNodesByZone,
18164
+ getZoneAllowedTypes,
18878
18165
  ConditionOperatorSchema,
18879
18166
  ConditionRuleSchema,
18880
18167
  ConditionGroupSchema,
@@ -18884,7 +18171,9 @@ export {
18884
18171
  FlowRowSchema,
18885
18172
  FormNodeSchema,
18886
18173
  ConditionNodeSchema,
18887
- DocumentNodeSchema,
18174
+ AssignmentSourceSchema,
18175
+ AssignmentMappingSchema,
18176
+ AssignNodeSchema,
18888
18177
  EndNodeSchema,
18889
18178
  WorkflowNodeSchema,
18890
18179
  SlotModeSchema,
@@ -18981,21 +18270,12 @@ export {
18981
18270
  WorkflowFormBuilder,
18982
18271
  WorkflowSimpleFormBuilder,
18983
18272
  WorkflowConditionBuilder,
18273
+ WorkflowAssignBuilder,
18984
18274
  WorkflowEndBuilder,
18985
18275
  WorkflowStartBuilder,
18986
18276
  WorkflowBuilder,
18987
18277
  workflow,
18988
18278
  registry,
18989
- SYSTEM_TEMPLATE_IDS,
18990
- FRENCH_ID_CARD,
18991
- PASSPORT,
18992
- DRIVING_LICENSE,
18993
- PROOF_OF_ADDRESS,
18994
- SIGNABLE_CONTRACT,
18995
- GENERIC_DOCUMENT,
18996
- SYSTEM_TEMPLATES,
18997
- getSystemTemplate,
18998
- isSystemTemplate,
18999
18279
  WorkflowJwtService,
19000
18280
  hashOptions,
19001
18281
  cacheKeys,
@@ -19041,9 +18321,9 @@ export {
19041
18321
  complete,
19042
18322
  error,
19043
18323
  ConditionExecutor,
19044
- DocumentExecutor,
19045
18324
  EndExecutor,
19046
18325
  FormExecutor,
18326
+ AssignExecutor,
19047
18327
  StartExecutor,
19048
18328
  createDefaultExecutorRegistry,
19049
18329
  getDefaultExecutorRegistry,
@@ -19106,10 +18386,6 @@ export {
19106
18386
  RecordService,
19107
18387
  FormulaResolverService,
19108
18388
  RollupScheduler,
19109
- DocumentRenderError,
19110
- StorageDownloadNotSupportedError,
19111
- DocumentRendererService,
19112
- DocumentProcessingHook,
19113
18389
  GrantNotFoundError,
19114
18390
  GrantExpiredError,
19115
18391
  GrantRevokedError,
@@ -19124,10 +18400,6 @@ export {
19124
18400
  WorkflowRelationService,
19125
18401
  WorkflowService,
19126
18402
  UserProfileService,
19127
- DocumentGenerationTemplateNotFoundError,
19128
- DocumentGenerationNotConfiguredError,
19129
- DocumentGenerationService,
19130
- DocumentTemplateService,
19131
18403
  DocumentService,
19132
18404
  DocumentProcessingService,
19133
18405
  FileService,