@stndrds/schema 1.0.0-alpha.78 → 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";
@@ -1292,6 +1292,84 @@ function createQueryBuilder(recordService, adapter, objectName, options) {
1292
1292
  return new QueryBuilder(recordService, adapter, objectName, initialState);
1293
1293
  }
1294
1294
 
1295
+ // src/types/flows.ts
1296
+ function isFlowFieldsRow(row) {
1297
+ return !row.type || row.type === "fields";
1298
+ }
1299
+ function isLayoutRow(row) {
1300
+ return !!row.type && row.type !== "fields";
1301
+ }
1302
+ function isFlowDefinition(obj) {
1303
+ return typeof obj === "object" && obj !== null && "slots" in obj && "pages" in obj && "relations" in obj && "status" in obj;
1304
+ }
1305
+ function isFlowPublished(flow) {
1306
+ return flow.status === "published";
1307
+ }
1308
+ function isSystemFlow(flow) {
1309
+ return flow.system === true;
1310
+ }
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
+
1295
1373
  // src/types/workflows/nodes.ts
1296
1374
  function isSimpleFormNode(node) {
1297
1375
  return node.fields !== void 0 && node.rows === void 0;
@@ -1308,24 +1386,168 @@ function isFormNode(node) {
1308
1386
  function isConditionNode(node) {
1309
1387
  return node.type === "condition";
1310
1388
  }
1311
- function isDocumentNode(node) {
1312
- return node.type === "document";
1389
+ function isAssignNode(node) {
1390
+ return node.type === "assign";
1313
1391
  }
1314
1392
  function isEndNode(node) {
1315
1393
  return node.type === "end";
1316
1394
  }
1317
- function getNodeOutputs(node) {
1318
- switch (node.type) {
1319
- case "start":
1320
- case "form":
1321
- case "document":
1322
- return node.next ? [node.next] : [];
1323
- case "condition":
1324
- return [node.onTrue, node.onFalse].filter((n) => !!n);
1325
- case "end":
1326
- 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 [];
1327
1412
  }
1328
- }
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);
1329
1551
 
1330
1552
  // src/types/workflows/conditions.ts
1331
1553
  function isConditionRule(item) {
@@ -1459,6 +1681,11 @@ function setContextValue(context, path, value) {
1459
1681
  current[parts[parts.length - 1]] = value;
1460
1682
  }
1461
1683
 
1684
+ // src/types/workflows/form-context.ts
1685
+ function isFormFieldsRow(row) {
1686
+ return !row.type || row.type === "fields";
1687
+ }
1688
+
1462
1689
  // src/types/workflows/theme.ts
1463
1690
  var DEFAULT_THEME = {
1464
1691
  borderRadius: 8,
@@ -1510,6 +1737,69 @@ function isInvitationOrGrantEvent(event) {
1510
1737
  return event.type.startsWith("workflow.invitation.") || event.type.startsWith("workflow.grant.");
1511
1738
  }
1512
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
+
1513
1803
  // src/types/workflows/validation.ts
1514
1804
  import { z } from "zod";
1515
1805
  var ConditionOperatorSchema = z.enum([
@@ -1551,14 +1841,40 @@ var FlowRowFieldSchema = z.object({
1551
1841
  id: z.string().min(1),
1552
1842
  slotId: z.string().min(1),
1553
1843
  attribute: z.string().min(1),
1554
- label: z.string().optional(),
1844
+ label: z.string().max(200).optional(),
1845
+ tooltip: z.string().max(1e3).optional(),
1555
1846
  required: z.boolean().optional()
1556
1847
  });
1557
- var FlowRowSchema = z.object({
1848
+ var FlowFieldsRowSchema = z.object({
1558
1849
  id: z.string().min(1),
1559
1850
  order: z.number(),
1851
+ type: z.literal("fields").optional(),
1560
1852
  fields: z.array(FlowRowFieldSchema)
1561
1853
  });
1854
+ var FlowHeadingRowSchema = z.object({
1855
+ id: z.string().min(1),
1856
+ order: z.number(),
1857
+ type: z.literal("heading"),
1858
+ content: z.string().min(1).max(200),
1859
+ level: z.union([z.literal(1), z.literal(2), z.literal(3)]).optional()
1860
+ });
1861
+ var FlowSeparatorRowSchema = z.object({
1862
+ id: z.string().min(1),
1863
+ order: z.number(),
1864
+ type: z.literal("separator")
1865
+ });
1866
+ var FlowTextRowSchema = z.object({
1867
+ id: z.string().min(1),
1868
+ order: z.number(),
1869
+ type: z.literal("text"),
1870
+ content: z.string().min(1).max(5e3)
1871
+ });
1872
+ var FlowRowSchema = z.union([
1873
+ FlowHeadingRowSchema,
1874
+ FlowSeparatorRowSchema,
1875
+ FlowTextRowSchema,
1876
+ FlowFieldsRowSchema
1877
+ ]);
1562
1878
  var FormNodeSchema = z.object({
1563
1879
  type: z.literal("form"),
1564
1880
  id: z.string().min(1),
@@ -1586,14 +1902,26 @@ var ConditionNodeSchema = z.object({
1586
1902
  onTrue: z.string().nullish(),
1587
1903
  onFalse: z.string().nullish()
1588
1904
  });
1589
- var DocumentNodeSchema = z.object({
1590
- 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"),
1591
1920
  id: z.string().min(1),
1592
1921
  label: z.string().min(1),
1593
1922
  description: z.string().nullish(),
1594
- templateId: z.string().min(1, "Template ID is required"),
1595
- outputFormat: z.enum(["pdf", "docx"]),
1596
- filename: z.string().nullish(),
1923
+ targetSlotId: z.string().min(1, "Target slot is required"),
1924
+ assignments: z.array(AssignmentMappingSchema).min(1),
1597
1925
  next: z.string().nullish()
1598
1926
  });
1599
1927
  var EndNodeSchema = z.object({
@@ -1606,7 +1934,7 @@ var WorkflowNodeSchema = z.discriminatedUnion("type", [
1606
1934
  StartNodeSchema,
1607
1935
  FormNodeSchema,
1608
1936
  ConditionNodeSchema,
1609
- DocumentNodeSchema,
1937
+ AssignNodeSchema,
1610
1938
  EndNodeSchema
1611
1939
  ]);
1612
1940
  var SlotModeSchema = z.enum(["create", "select", "optional"]);
@@ -1728,15 +2056,8 @@ var WorkflowDefinitionSchema = z.object({
1728
2056
  ).refine(
1729
2057
  (def) => {
1730
2058
  for (const node of Object.values(def.nodes)) {
1731
- if (node.type === "start" || node.type === "form" || node.type === "document") {
1732
- if (node.next && !(node.next in def.nodes)) {
1733
- return false;
1734
- }
1735
- } else if (node.type === "condition") {
1736
- if (node.onTrue && !(node.onTrue in def.nodes)) {
1737
- return false;
1738
- }
1739
- if (node.onFalse && !(node.onFalse in def.nodes)) {
2059
+ for (const targetId of getNodeOutputs(node)) {
2060
+ if (!(targetId in def.nodes)) {
1740
2061
  return false;
1741
2062
  }
1742
2063
  }
@@ -2053,85 +2374,6 @@ var ConditionExecutor = class {
2053
2374
  }
2054
2375
  };
2055
2376
 
2056
- // src/runtime/executors/document.executor.ts
2057
- var DocumentExecutor = class {
2058
- constructor() {
2059
- this.nodeType = "document";
2060
- }
2061
- execute(node, _context) {
2062
- if (!node.templateId) {
2063
- return error("MISSING_TEMPLATE", "DocumentNode must have a templateId");
2064
- }
2065
- if (!node.next) {
2066
- return error("MISSING_NEXT", "DocumentNode must have a 'next' target");
2067
- }
2068
- const contextUpdates = {
2069
- documents: {
2070
- [node.id]: {
2071
- id: "",
2072
- // Will be filled by consumer after generation
2073
- url: "",
2074
- // Will be filled by consumer after generation
2075
- filename: node.filename,
2076
- metadata: {
2077
- templateId: node.templateId,
2078
- templateVersion: node.templateVersion ?? 1,
2079
- targetSlotIds: node.targetSlotIds ?? [],
2080
- status: "pending"
2081
- }
2082
- }
2083
- }
2084
- };
2085
- return success(node.next, contextUpdates);
2086
- }
2087
- canExecute(_node, _context) {
2088
- return true;
2089
- }
2090
- validate(node) {
2091
- const errors = [];
2092
- if (!node.label) {
2093
- errors.push("DocumentNode must have a 'label' property");
2094
- }
2095
- if (!node.templateId) {
2096
- errors.push("DocumentNode must have a 'templateId' property");
2097
- }
2098
- if (!node.next) {
2099
- errors.push("DocumentNode must have a 'next' property");
2100
- }
2101
- if (node.targetSlotIds && node.targetSlotIds.length > 0) {
2102
- for (const slotId of node.targetSlotIds) {
2103
- if (!slotId || typeof slotId !== "string" || slotId.trim() === "") {
2104
- errors.push("DocumentNode targetSlotIds must contain non-empty string values");
2105
- break;
2106
- }
2107
- }
2108
- }
2109
- return errors;
2110
- }
2111
- /**
2112
- * Validate that targetSlotIds reference existing slots in the workflow definition.
2113
- * This is a context-aware validation that requires the workflow's slot definitions.
2114
- *
2115
- * @param node - The document node to validate
2116
- * @param workflowSlots - All slots defined in the workflow
2117
- * @returns Array of validation error messages
2118
- */
2119
- validateSlotReferences(node, workflowSlots) {
2120
- const errors = [];
2121
- if (node.targetSlotIds && node.targetSlotIds.length > 0) {
2122
- const slotIdSet = workflowSlots.reduce((set, s) => set.add(s.id), /* @__PURE__ */ new Set());
2123
- for (const slotId of node.targetSlotIds) {
2124
- if (!slotIdSet.has(slotId)) {
2125
- errors.push(
2126
- `DocumentNode "${node.id}" references unknown slot "${slotId}" in targetSlotIds`
2127
- );
2128
- }
2129
- }
2130
- }
2131
- return errors;
2132
- }
2133
- };
2134
-
2135
2377
  // src/runtime/executors/end.executor.ts
2136
2378
  var EndExecutor = class {
2137
2379
  constructor() {
@@ -2155,6 +2397,13 @@ var FormExecutor = class {
2155
2397
  }
2156
2398
  execute(node, context) {
2157
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
+ }
2158
2407
  if (isEmpty(input)) {
2159
2408
  const requiredParticipationId = node.participantId ?? void 0;
2160
2409
  return wait(`Waiting for form submission: ${node.label}`, {
@@ -2162,10 +2411,7 @@ var FormExecutor = class {
2162
2411
  });
2163
2412
  }
2164
2413
  const formInput = input;
2165
- const slotIds = this.extractSlotIds(node);
2166
- if (slotIds.size === 0) {
2167
- return error("MISSING_FIELDS", "FormNode must have fields or rows with slot references");
2168
- }
2414
+ const slotIds = new Set(getNodeSlotIds(node));
2169
2415
  const contextUpdates = {
2170
2416
  forms: {
2171
2417
  [node.id]: formInput
@@ -2212,37 +2458,11 @@ var FormExecutor = class {
2212
2458
  }
2213
2459
  const hasFields = node.fields !== void 0 && node.fields.length > 0;
2214
2460
  const hasRows = node.rows !== void 0 && node.rows.length > 0;
2215
- if (!(hasFields || hasRows)) {
2216
- errors.push("FormNode must have either 'fields' (simple mode) or 'rows' (advanced mode)");
2217
- }
2218
2461
  if (hasFields && hasRows) {
2219
2462
  errors.push("FormNode cannot have both 'fields' and 'rows'");
2220
2463
  }
2221
2464
  return errors;
2222
2465
  }
2223
- /**
2224
- * Extract all slot IDs referenced in the form
2225
- */
2226
- extractSlotIds(node) {
2227
- const slotIds = /* @__PURE__ */ new Set();
2228
- if (node.fields) {
2229
- for (const field of node.fields) {
2230
- if (field.slotId) {
2231
- slotIds.add(field.slotId);
2232
- }
2233
- }
2234
- }
2235
- if (node.rows) {
2236
- for (const row of node.rows) {
2237
- for (const field of row.fields) {
2238
- if (field.slotId) {
2239
- slotIds.add(field.slotId);
2240
- }
2241
- }
2242
- }
2243
- }
2244
- return slotIds;
2245
- }
2246
2466
  /**
2247
2467
  * Validate required fields based on slot mode.
2248
2468
  *
@@ -2258,7 +2478,7 @@ var FormExecutor = class {
2258
2478
  */
2259
2479
  validateRequiredFields(node, input, slots, objects) {
2260
2480
  const errors = [];
2261
- const fieldRefs = this.collectFieldRefs(node);
2481
+ const fieldRefs = getFormFieldRefs(node);
2262
2482
  for (const fieldRef of fieldRefs) {
2263
2483
  const slot = slots.find((s) => s.id === fieldRef.slotId);
2264
2484
  if (!slot) {
@@ -2279,7 +2499,7 @@ var FormExecutor = class {
2279
2499
  if (!attribute?.required) continue;
2280
2500
  const slotInput = input[fieldRef.slotId];
2281
2501
  const value = slotInput?.[fieldRef.attribute];
2282
- if (value === void 0 || value === null || value === "") {
2502
+ if (value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0) {
2283
2503
  errors.push(
2284
2504
  `Field "${attribute.label ?? fieldRef.attribute}" is required for ${slot.label}`
2285
2505
  );
@@ -2287,41 +2507,407 @@ var FormExecutor = class {
2287
2507
  }
2288
2508
  return errors;
2289
2509
  }
2290
- /**
2291
- * Collect all field references from a FormNode
2292
- */
2293
- collectFieldRefs(node) {
2294
- const refs = [];
2295
- if (node.fields) {
2296
- for (const field of node.fields) {
2297
- if (field.slotId && field.attribute) {
2298
- refs.push({ slotId: field.slotId, attribute: field.attribute });
2299
- }
2300
- }
2301
- }
2302
- if (node.rows) {
2303
- for (const row of node.rows) {
2304
- for (const field of row.fields) {
2305
- if (field.slotId && field.attribute) {
2306
- refs.push({ slotId: field.slotId, attribute: field.attribute });
2307
- }
2308
- }
2309
- }
2310
- }
2311
- return refs;
2312
- }
2313
2510
  };
2314
2511
 
2315
- // src/runtime/executors/start.executor.ts
2316
- var StartExecutor = class {
2317
- constructor() {
2318
- this.nodeType = "start";
2319
- }
2320
- execute(node, _context) {
2321
- if (!node.next) {
2322
- return error("MISSING_NEXT", `StartNode "${node.id}" has no 'next' target defined`);
2323
- }
2324
- 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
+ });
2529
+ }
2530
+ if (attribute.unit === "percentage") {
2531
+ return (value / 100).toLocaleString(void 0, {
2532
+ style: "percent",
2533
+ minimumFractionDigits: decimals,
2534
+ maximumFractionDigits: decimals
2535
+ });
2536
+ }
2537
+ return value.toLocaleString(void 0, {
2538
+ minimumFractionDigits: decimals,
2539
+ maximumFractionDigits: decimals
2540
+ });
2541
+ }
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}`;
2551
+ }
2552
+ function formatDate(value) {
2553
+ if (value instanceof Date) {
2554
+ return value.toISOString().split("T")[0];
2555
+ }
2556
+ if (typeof value === "string") {
2557
+ const date2 = new Date(value);
2558
+ if (!Number.isNaN(date2.getTime())) {
2559
+ return date2.toISOString().split("T")[0];
2560
+ }
2561
+ }
2562
+ return String(value);
2563
+ }
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;
2608
+ }
2609
+ return parts.join(", ") || EMPTY_VALUE_PLACEHOLDER;
2610
+ }
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(", ");
2621
+ }
2622
+ return value.join(", ");
2623
+ }
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;
2632
+ }
2633
+ switch (attribute.type) {
2634
+ case "text":
2635
+ case "textarea":
2636
+ return formatText(value);
2637
+ case "checkbox":
2638
+ return formatCheckbox(value);
2639
+ case "number":
2640
+ return formatNumber(value, attribute);
2641
+ case "currency":
2642
+ return formatCurrency(value, attribute);
2643
+ case "date":
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
+ }
2668
+ return String(value);
2669
+ }
2670
+ }
2671
+ }
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);
2699
+ }
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]);
2712
+ }
2713
+ return { name, args };
2714
+ }
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
+ }
2770
+ }
2771
+ return names;
2772
+ }
2773
+ function hasOptions(attr) {
2774
+ return "options" in attr && Array.isArray(attr.options) && attr.options.length > 0;
2775
+ }
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;
2802
+ }
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 [];
2808
+ }
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);
2817
+ }
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);
2835
+ }
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 };
2842
+ }
2843
+ return `$slot:${src.slotId}`;
2844
+ }
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;
2853
+ }
2854
+ var AssignExecutor = class {
2855
+ constructor() {
2856
+ this.nodeType = "assign";
2857
+ }
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;
2888
+ }
2889
+ };
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;
2898
+ }
2899
+ }
2900
+
2901
+ // src/runtime/executors/start.executor.ts
2902
+ var StartExecutor = class {
2903
+ constructor() {
2904
+ this.nodeType = "start";
2905
+ }
2906
+ execute(node, _context) {
2907
+ if (!node.next) {
2908
+ return error("MISSING_NEXT", `StartNode "${node.id}" has no 'next' target defined`);
2909
+ }
2910
+ return success(node.next);
2325
2911
  }
2326
2912
  canExecute(_node, _context) {
2327
2913
  return true;
@@ -2341,7 +2927,7 @@ function createDefaultExecutorRegistry() {
2341
2927
  registry2.register(new StartExecutor());
2342
2928
  registry2.register(new FormExecutor());
2343
2929
  registry2.register(new ConditionExecutor());
2344
- registry2.register(new DocumentExecutor());
2930
+ registry2.register(new AssignExecutor());
2345
2931
  registry2.register(new EndExecutor());
2346
2932
  return registry2;
2347
2933
  }
@@ -3367,512 +3953,188 @@ var SchemaErrorCode = {
3367
3953
  // Duplicates
3368
3954
  DUPLICATE_OBJECT: "SCHEMA_DUPLICATE_OBJECT",
3369
3955
  DUPLICATE_ATTRIBUTE: "SCHEMA_DUPLICATE_ATTRIBUTE",
3370
- // Concurrency
3371
- CONFLICT: "SCHEMA_CONFLICT"
3372
- };
3373
- var SchemaError = class extends Error {
3374
- constructor(message, code = SchemaErrorCode.UNKNOWN, details) {
3375
- super(message);
3376
- this.name = "SchemaError";
3377
- this.code = code;
3378
- this.details = details;
3379
- Object.setPrototypeOf(this, new.target.prototype);
3380
- }
3381
- toJSON() {
3382
- return {
3383
- name: this.name,
3384
- code: this.code,
3385
- message: this.message,
3386
- details: this.details
3387
- };
3388
- }
3389
- };
3390
- var NotFoundError = class extends SchemaError {
3391
- constructor(resourceType, resourceId, code = SchemaErrorCode.RECORD_NOT_FOUND) {
3392
- super(`${resourceType} with id "${resourceId}" not found`, code, {
3393
- resourceType,
3394
- resourceId
3395
- });
3396
- this.name = "NotFoundError";
3397
- this.resourceType = resourceType;
3398
- this.resourceId = resourceId;
3399
- }
3400
- };
3401
- var ObjectNotFoundError = class extends NotFoundError {
3402
- constructor(objectId) {
3403
- super("Object", objectId, SchemaErrorCode.OBJECT_NOT_FOUND);
3404
- this.name = "ObjectNotFoundError";
3405
- }
3406
- };
3407
- var AttributeNotFoundError = class extends NotFoundError {
3408
- constructor(attributeId) {
3409
- super("Attribute", attributeId, SchemaErrorCode.ATTRIBUTE_NOT_FOUND);
3410
- this.name = "AttributeNotFoundError";
3411
- }
3412
- };
3413
- var RecordNotFoundError = class extends NotFoundError {
3414
- constructor(recordId) {
3415
- super("Record", recordId, SchemaErrorCode.RECORD_NOT_FOUND);
3416
- this.name = "RecordNotFoundError";
3417
- }
3418
- };
3419
- var UserProfileNotFoundError = class extends NotFoundError {
3420
- constructor(identifier) {
3421
- super("UserProfile", identifier, SchemaErrorCode.USER_PROFILE_NOT_FOUND);
3422
- this.name = "UserProfileNotFoundError";
3423
- }
3424
- };
3425
- var FileNotFoundError = class extends NotFoundError {
3426
- constructor(fileId) {
3427
- super("File", fileId, SchemaErrorCode.FILE_NOT_FOUND);
3428
- this.name = "FileNotFoundError";
3429
- }
3430
- };
3431
- var ValidationError = class _ValidationError extends SchemaError {
3432
- constructor(message, errors) {
3433
- super(message, SchemaErrorCode.VALIDATION_FAILED, { errors });
3434
- this.name = "ValidationError";
3435
- this.errors = errors;
3436
- }
3437
- /**
3438
- * Create a validation error from Zod-style errors
3439
- */
3440
- static fromZodErrors(errors) {
3441
- const details = errors.map((err) => ({
3442
- path: err.path.map(String),
3443
- message: err.message
3444
- }));
3445
- const message = `Validation failed: ${details.map((d) => `${d.path.join(".")}: ${d.message}`).join(", ")}`;
3446
- return new _ValidationError(message, details);
3447
- }
3448
- };
3449
- var ProtectedResourceError = class extends SchemaError {
3450
- constructor(resourceType, resourceName, operation) {
3451
- const code = resourceType === "object" ? SchemaErrorCode.PROTECTED_OBJECT : resourceType === "view" ? SchemaErrorCode.PROTECTED_VIEW : SchemaErrorCode.PROTECTED_ATTRIBUTE;
3452
- super(`Cannot ${operation} system ${resourceType} "${resourceName}"`, code, {
3453
- resourceType,
3454
- resourceName,
3455
- operation
3456
- });
3457
- this.name = "ProtectedResourceError";
3458
- this.resourceType = resourceType;
3459
- this.resourceName = resourceName;
3460
- this.operation = operation;
3461
- }
3462
- };
3463
- var SyncError = class extends SchemaError {
3464
- constructor(objectName, message, cause) {
3465
- super(`Failed to sync object "${objectName}": ${message}`, SchemaErrorCode.SYNC_FAILED, {
3466
- objectName,
3467
- cause: cause?.message
3468
- });
3469
- this.name = "SyncError";
3470
- this.objectName = objectName;
3471
- this.cause = cause;
3472
- }
3473
- };
3474
- var NotSystemObjectError = class extends SchemaError {
3475
- constructor(objectName) {
3476
- super(
3477
- `Object "${objectName}" is not marked as system. Native objects must have system=true.`,
3478
- SchemaErrorCode.NOT_SYSTEM_OBJECT,
3479
- { objectName }
3480
- );
3481
- this.name = "NotSystemObjectError";
3482
- this.objectName = objectName;
3483
- }
3956
+ // Concurrency
3957
+ CONFLICT: "SCHEMA_CONFLICT"
3484
3958
  };
3485
- var DuplicateError = class extends SchemaError {
3486
- constructor(resourceType, resourceName) {
3487
- const code = resourceType === "object" ? SchemaErrorCode.DUPLICATE_OBJECT : SchemaErrorCode.DUPLICATE_ATTRIBUTE;
3488
- super(
3489
- `${resourceType === "object" ? "Object" : "Attribute"} "${resourceName}" already exists`,
3490
- code,
3491
- { resourceType, resourceName }
3492
- );
3493
- this.name = "DuplicateError";
3494
- this.resourceType = resourceType;
3495
- this.resourceName = resourceName;
3959
+ var SchemaError = class extends Error {
3960
+ constructor(message, code = SchemaErrorCode.UNKNOWN, details) {
3961
+ super(message);
3962
+ this.name = "SchemaError";
3963
+ this.code = code;
3964
+ this.details = details;
3965
+ Object.setPrototypeOf(this, new.target.prototype);
3966
+ }
3967
+ toJSON() {
3968
+ return {
3969
+ name: this.name,
3970
+ code: this.code,
3971
+ message: this.message,
3972
+ details: this.details
3973
+ };
3496
3974
  }
3497
3975
  };
3498
- function isSchemaError(error2) {
3499
- return error2 instanceof SchemaError;
3500
- }
3501
- function isNotFoundError(error2) {
3502
- return error2 instanceof NotFoundError;
3503
- }
3504
- function isValidationError(error2) {
3505
- return error2 instanceof ValidationError;
3506
- }
3507
- function isProtectedResourceError(error2) {
3508
- return error2 instanceof ProtectedResourceError;
3509
- }
3510
- var ForbiddenError = class extends SchemaError {
3511
- constructor(objectName, action, userId) {
3512
- super(`No ${action} permission on object "${objectName}"`, SchemaErrorCode.FORBIDDEN, {
3513
- objectName,
3514
- action,
3515
- userId
3976
+ var NotFoundError = class extends SchemaError {
3977
+ constructor(resourceType, resourceId, code = SchemaErrorCode.RECORD_NOT_FOUND) {
3978
+ super(`${resourceType} with id "${resourceId}" not found`, code, {
3979
+ resourceType,
3980
+ resourceId
3516
3981
  });
3517
- this.name = "ForbiddenError";
3518
- this.objectName = objectName;
3519
- this.action = action;
3520
- this.userId = userId;
3982
+ this.name = "NotFoundError";
3983
+ this.resourceType = resourceType;
3984
+ this.resourceId = resourceId;
3521
3985
  }
3522
3986
  };
3523
- var ProtectedRoleError = class extends SchemaError {
3524
- constructor(roleName, operation) {
3525
- super(`Cannot ${operation} system role "${roleName}"`, SchemaErrorCode.PROTECTED_ROLE, {
3526
- roleName,
3527
- operation
3528
- });
3529
- this.name = "ProtectedRoleError";
3530
- this.roleName = roleName;
3531
- this.operation = operation;
3987
+ var ObjectNotFoundError = class extends NotFoundError {
3988
+ constructor(objectId) {
3989
+ super("Object", objectId, SchemaErrorCode.OBJECT_NOT_FOUND);
3990
+ this.name = "ObjectNotFoundError";
3532
3991
  }
3533
3992
  };
3534
- var RoleNotFoundError = class extends NotFoundError {
3535
- constructor(roleId) {
3536
- super("Role", roleId, SchemaErrorCode.ROLE_NOT_FOUND);
3537
- this.name = "RoleNotFoundError";
3993
+ var AttributeNotFoundError = class extends NotFoundError {
3994
+ constructor(attributeId) {
3995
+ super("Attribute", attributeId, SchemaErrorCode.ATTRIBUTE_NOT_FOUND);
3996
+ this.name = "AttributeNotFoundError";
3538
3997
  }
3539
3998
  };
3540
- function isForbiddenError(error2) {
3541
- return error2 instanceof ForbiddenError;
3542
- }
3543
- var ConcurrentModificationError = class extends SchemaError {
3999
+ var RecordNotFoundError = class extends NotFoundError {
3544
4000
  constructor(recordId) {
3545
- super(
3546
- `Record ${recordId} was modified by another request. Please refresh and try again.`,
3547
- SchemaErrorCode.CONFLICT,
3548
- { recordId }
3549
- );
4001
+ super("Record", recordId, SchemaErrorCode.RECORD_NOT_FOUND);
4002
+ this.name = "RecordNotFoundError";
3550
4003
  }
3551
4004
  };
3552
-
3553
- // src/format.ts
3554
- var EMPTY_VALUE_PLACEHOLDER = "\u2014";
3555
- function formatText(value) {
3556
- return String(value);
3557
- }
3558
- function formatCheckbox(value) {
3559
- return value ? "Yes" : "No";
3560
- }
3561
- function formatNumber(value, attribute) {
3562
- if (typeof value !== "number") return String(value);
3563
- const decimals = attribute.decimals;
3564
- if (attribute.unit === "integer") {
3565
- return value.toLocaleString(void 0, {
3566
- minimumFractionDigits: 0,
3567
- maximumFractionDigits: 0
3568
- });
3569
- }
3570
- if (attribute.unit === "percentage") {
3571
- return (value / 100).toLocaleString(void 0, {
3572
- style: "percent",
3573
- minimumFractionDigits: decimals,
3574
- maximumFractionDigits: decimals
3575
- });
3576
- }
3577
- return value.toLocaleString(void 0, {
3578
- minimumFractionDigits: decimals,
3579
- maximumFractionDigits: decimals
3580
- });
3581
- }
3582
- function formatCurrency(value, _attribute) {
3583
- if (typeof value !== "object" || value === null) return String(value);
3584
- const currency2 = value;
3585
- if (!("value" in currency2 && "code" in currency2)) return String(value);
3586
- const formattedValue = currency2.value.toLocaleString(void 0, {
3587
- minimumFractionDigits: 2,
3588
- maximumFractionDigits: 2
3589
- });
3590
- return `${formattedValue} ${currency2.code}`;
3591
- }
3592
- function formatDate(value) {
3593
- if (value instanceof Date) {
3594
- return value.toISOString().split("T")[0];
3595
- }
3596
- if (typeof value === "string") {
3597
- const date2 = new Date(value);
3598
- if (!Number.isNaN(date2.getTime())) {
3599
- return date2.toISOString().split("T")[0];
3600
- }
3601
- }
3602
- return String(value);
3603
- }
3604
- function formatPhone(value) {
3605
- if (typeof value !== "object" || value === null) return String(value);
3606
- const phone2 = value;
3607
- if (!("phoneNumber" in phone2)) return String(value);
3608
- if (!phone2.phoneNumber) return "";
3609
- if (!phone2.countryCode) return phone2.phoneNumber;
3610
- return formatPhoneForDisplay(phone2);
3611
- }
3612
- function formatLocation(value, attribute) {
3613
- if (typeof value !== "object" || value === null) return String(value);
3614
- const loc = value;
3615
- const granularity = attribute.granularity ?? "full";
3616
- const parts = [];
3617
- switch (granularity) {
3618
- case "country":
3619
- if (loc.country) parts.push(loc.country);
3620
- break;
3621
- case "state":
3622
- if (loc.state) parts.push(loc.state);
3623
- if (loc.country) parts.push(loc.country);
3624
- break;
3625
- case "city":
3626
- if (loc.city) parts.push(loc.city);
3627
- if (loc.state) parts.push(loc.state);
3628
- if (loc.country) parts.push(loc.country);
3629
- break;
3630
- case "coordinates":
3631
- if (loc.latitude !== void 0 && loc.longitude !== void 0) {
3632
- parts.push(`${loc.latitude}, ${loc.longitude}`);
3633
- }
3634
- break;
3635
- case "address":
3636
- if (loc.address) parts.push(loc.address);
3637
- if (loc.city) parts.push(loc.city);
3638
- if (loc.state) parts.push(loc.state);
3639
- if (loc.country) parts.push(loc.country);
3640
- break;
3641
- default:
3642
- if (loc.address) parts.push(loc.address);
3643
- if (loc.city) parts.push(loc.city);
3644
- if (loc.state) parts.push(loc.state);
3645
- if (loc.postalCode) parts.push(loc.postalCode);
3646
- if (loc.country) parts.push(loc.country);
3647
- break;
4005
+ var UserProfileNotFoundError = class extends NotFoundError {
4006
+ constructor(identifier) {
4007
+ super("UserProfile", identifier, SchemaErrorCode.USER_PROFILE_NOT_FOUND);
4008
+ this.name = "UserProfileNotFoundError";
3648
4009
  }
3649
- return parts.join(", ") || EMPTY_VALUE_PLACEHOLDER;
3650
- }
3651
- function formatSelect(value, attribute) {
3652
- if (typeof value !== "string") return String(value);
3653
- const option = attribute.options?.find((o) => o.value === value);
3654
- return option?.label ?? String(value);
3655
- }
3656
- function formatMultiselect(value, attribute) {
3657
- if (!Array.isArray(value)) return String(value);
3658
- if (attribute.options) {
3659
- const labels = value.map((v) => attribute.options.find((o) => o.value === v)?.label).filter(Boolean);
3660
- return labels.join(", ");
4010
+ };
4011
+ var FileNotFoundError = class extends NotFoundError {
4012
+ constructor(fileId) {
4013
+ super("File", fileId, SchemaErrorCode.FILE_NOT_FOUND);
4014
+ this.name = "FileNotFoundError";
3661
4015
  }
3662
- return value.join(", ");
3663
- }
3664
- function formatRating(value, attribute) {
3665
- if (typeof value !== "number") return String(value);
3666
- const max = attribute.max ?? 5;
3667
- return `${value}/${max}`;
3668
- }
3669
- function formatAttributeValue(value, attribute) {
3670
- if (value === null || value === void 0 || value === "") {
3671
- return EMPTY_VALUE_PLACEHOLDER;
4016
+ };
4017
+ var ValidationError = class _ValidationError extends SchemaError {
4018
+ constructor(message, errors) {
4019
+ super(message, SchemaErrorCode.VALIDATION_FAILED, { errors });
4020
+ this.name = "ValidationError";
4021
+ this.errors = errors;
3672
4022
  }
3673
- switch (attribute.type) {
3674
- case "text":
3675
- case "textarea":
3676
- return formatText(value);
3677
- case "checkbox":
3678
- return formatCheckbox(value);
3679
- case "number":
3680
- return formatNumber(value, attribute);
3681
- case "currency":
3682
- return formatCurrency(value, attribute);
3683
- case "date":
3684
- return formatDate(value);
3685
- case "phone":
3686
- return formatPhone(value);
3687
- case "location":
3688
- return formatLocation(value, attribute);
3689
- case "select":
3690
- case "status":
3691
- return formatSelect(value, attribute);
3692
- case "multiselect":
3693
- return formatMultiselect(value, attribute);
3694
- case "rating":
3695
- return formatRating(value, attribute);
3696
- // Unsupported types - return value as-is or placeholder
3697
- case "file":
3698
- case "user":
3699
- case "relation":
3700
- if (Array.isArray(value)) {
3701
- return value.join(", ");
3702
- }
3703
- return String(value);
3704
- default: {
3705
- if (Array.isArray(value)) {
3706
- return value.join(", ");
3707
- }
3708
- return String(value);
3709
- }
4023
+ /**
4024
+ * Create a validation error from Zod-style errors
4025
+ */
4026
+ static fromZodErrors(errors) {
4027
+ const details = errors.map((err) => ({
4028
+ path: err.path.map(String),
4029
+ message: err.message
4030
+ }));
4031
+ const message = `Validation failed: ${details.map((d) => `${d.path.join(".")}: ${d.message}`).join(", ")}`;
4032
+ return new _ValidationError(message, details);
3710
4033
  }
3711
- }
3712
-
3713
- // src/runtime/template.ts
3714
- var simplePipes = {
3715
- /** Convert to uppercase */
3716
- UPPER: (v) => String(v).toUpperCase(),
3717
- /** Convert to lowercase */
3718
- LOWER: (v) => String(v).toLowerCase(),
3719
- /** Capitalize first letter of each word */
3720
- capitalize: (v) => String(v).replace(/\b\w/g, (c) => c.toUpperCase()),
3721
- /** Trim whitespace from both ends */
3722
- trim: (v) => String(v).trim()
3723
4034
  };
3724
- var pipesWithArgs = {
3725
- /** Add prefix only if value is non-empty */
3726
- prefix: (v, pre = "") => v ? `${pre}${v}` : "",
3727
- /** Add suffix only if value is non-empty */
3728
- suffix: (v, suf = "") => v ? `${v}${suf}` : "",
3729
- /** Wrap value with prefix and suffix only if non-empty */
3730
- wrap: (v, pre = "", suf = "") => v ? `${pre}${v}${suf}` : "",
3731
- /** Show default value if empty */
3732
- default: (v, def = "") => v || def
4035
+ var ProtectedResourceError = class extends SchemaError {
4036
+ constructor(resourceType, resourceName, operation) {
4037
+ const code = resourceType === "object" ? SchemaErrorCode.PROTECTED_OBJECT : resourceType === "view" ? SchemaErrorCode.PROTECTED_VIEW : SchemaErrorCode.PROTECTED_ATTRIBUTE;
4038
+ super(`Cannot ${operation} system ${resourceType} "${resourceName}"`, code, {
4039
+ resourceType,
4040
+ resourceName,
4041
+ operation
4042
+ });
4043
+ this.name = "ProtectedResourceError";
4044
+ this.resourceType = resourceType;
4045
+ this.resourceName = resourceName;
4046
+ this.operation = operation;
4047
+ }
3733
4048
  };
3734
- function getValue(obj, path) {
3735
- return path.split(".").reduce((acc, key) => {
3736
- if (acc == null || typeof acc !== "object") return void 0;
3737
- return acc[key];
3738
- }, obj);
3739
- }
3740
- var DEFAULT_LABEL_FALLBACK = "(Untitled)";
3741
- function parsePipeExpression(pipeExpr) {
3742
- const match = pipeExpr.match(/^(\w+)(?::(.*))?$/);
3743
- if (!match) return { name: pipeExpr, args: [] };
3744
- const name = match[1];
3745
- const argsStr = match[2];
3746
- if (!argsStr) return { name, args: [] };
3747
- const args = [];
3748
- const argRegex = /["']([^"']*?)["']/g;
3749
- let argMatch;
3750
- while ((argMatch = argRegex.exec(argsStr)) !== null) {
3751
- args.push(argMatch[1]);
4049
+ var SyncError = class extends SchemaError {
4050
+ constructor(objectName, message, cause) {
4051
+ super(`Failed to sync object "${objectName}": ${message}`, SchemaErrorCode.SYNC_FAILED, {
4052
+ objectName,
4053
+ cause: cause?.message
4054
+ });
4055
+ this.name = "SyncError";
4056
+ this.objectName = objectName;
4057
+ this.cause = cause;
3752
4058
  }
3753
- return { name, args };
3754
- }
3755
- function renderLabelExpression(template, values, fallback = DEFAULT_LABEL_FALLBACK) {
3756
- const result = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, expr) => {
3757
- const orParts = expr.split("||").map((s) => s.trim());
3758
- const lastPart = orParts[orParts.length - 1];
3759
- const pipeSplit = lastPart.split("|").map((s) => s.trim());
3760
- orParts[orParts.length - 1] = pipeSplit[0];
3761
- const pipes = pipeSplit.slice(1).filter(Boolean);
3762
- const alternatives = orParts.filter(Boolean);
3763
- let value = "";
3764
- for (const alt of alternatives) {
3765
- const v = getValue(values, alt);
3766
- if (v != null && v !== "") {
3767
- value = v;
3768
- break;
3769
- }
3770
- }
3771
- const isEmpty3 = value == null || value === "";
3772
- if (isEmpty3 && pipes.length === 0) return "";
3773
- for (const pipeExpr of pipes) {
3774
- const { name: pipeName, args } = parsePipeExpression(pipeExpr);
3775
- const simpleFn = simplePipes[pipeName];
3776
- if (simpleFn) {
3777
- if (value != null && value !== "") {
3778
- value = simpleFn(String(value));
3779
- }
3780
- } else {
3781
- const argFn = pipesWithArgs[pipeName];
3782
- if (argFn) {
3783
- value = argFn(String(value ?? ""), ...args);
3784
- }
3785
- }
3786
- }
3787
- return String(value ?? "");
3788
- }).trim();
3789
- return result || fallback;
3790
- }
3791
- function isLabelExpression(value) {
3792
- return /\{\{\s*\S+.*\}\}/.test(value);
3793
- }
3794
- function extractAttributeNames(template) {
3795
- const names = [];
3796
- const regex = /\{\{\s*([^}]+)\s*\}\}/g;
3797
- let match;
3798
- while ((match = regex.exec(template)) !== null) {
3799
- const expr = match[1].trim();
3800
- const orParts = expr.split("||").map((s) => s.trim());
3801
- const lastPart = orParts[orParts.length - 1];
3802
- orParts[orParts.length - 1] = lastPart.split("|")[0].trim();
3803
- for (const part of orParts) {
3804
- if (!part) continue;
3805
- const rootName = part.split(".")[0];
3806
- if (rootName && !names.includes(rootName)) {
3807
- names.push(rootName);
3808
- }
3809
- }
4059
+ };
4060
+ var NotSystemObjectError = class extends SchemaError {
4061
+ constructor(objectName) {
4062
+ super(
4063
+ `Object "${objectName}" is not marked as system. Native objects must have system=true.`,
4064
+ SchemaErrorCode.NOT_SYSTEM_OBJECT,
4065
+ { objectName }
4066
+ );
4067
+ this.name = "NotSystemObjectError";
4068
+ this.objectName = objectName;
3810
4069
  }
3811
- return names;
4070
+ };
4071
+ var DuplicateError = class extends SchemaError {
4072
+ constructor(resourceType, resourceName) {
4073
+ const code = resourceType === "object" ? SchemaErrorCode.DUPLICATE_OBJECT : SchemaErrorCode.DUPLICATE_ATTRIBUTE;
4074
+ super(
4075
+ `${resourceType === "object" ? "Object" : "Attribute"} "${resourceName}" already exists`,
4076
+ code,
4077
+ { resourceType, resourceName }
4078
+ );
4079
+ this.name = "DuplicateError";
4080
+ this.resourceType = resourceType;
4081
+ this.resourceName = resourceName;
4082
+ }
4083
+ };
4084
+ function isSchemaError(error2) {
4085
+ return error2 instanceof SchemaError;
3812
4086
  }
3813
- function hasOptions(attr) {
3814
- return "options" in attr && Array.isArray(attr.options) && attr.options.length > 0;
4087
+ function isNotFoundError(error2) {
4088
+ return error2 instanceof NotFoundError;
3815
4089
  }
3816
- var FORMATTABLE_TYPES = /* @__PURE__ */ new Set([
3817
- "currency",
3818
- "location",
3819
- "phone",
3820
- "date",
3821
- "rating",
3822
- "select",
3823
- "status",
3824
- "multiselect",
3825
- "number"
3826
- ]);
3827
- function enrichValuesForDisplay(values, attributes) {
3828
- const enriched = { ...values };
3829
- for (const attr of attributes) {
3830
- const value = values[attr.name];
3831
- if (value == null) continue;
3832
- if (!FORMATTABLE_TYPES.has(attr.type)) continue;
3833
- const isSelectLike = attr.type === "select" || attr.type === "status" || attr.type === "multiselect";
3834
- if (isSelectLike && !hasOptions(attr)) continue;
3835
- if (attr.type === "multiselect" && Array.isArray(value) && value.length === 0) continue;
3836
- const formatted = formatAttributeValue(value, attr);
3837
- if (formatted && formatted !== EMPTY_VALUE_PLACEHOLDER) {
3838
- enriched[attr.name] = formatted;
3839
- }
3840
- }
3841
- return enriched;
4090
+ function isValidationError(error2) {
4091
+ return error2 instanceof ValidationError;
3842
4092
  }
3843
- var enrichValuesWithSelectLabels = enrichValuesForDisplay;
3844
- function extractRelationIds(val) {
3845
- if (typeof val === "string") return [val];
3846
- if (Array.isArray(val) && typeof val[0] === "string") return [val[0]];
3847
- return [];
4093
+ function isProtectedResourceError(error2) {
4094
+ return error2 instanceof ProtectedResourceError;
3848
4095
  }
3849
- async function computeLabelWithRelations(template, values, attributes, resolveRelationIds) {
3850
- let enrichedValues = enrichValuesForDisplay(values, attributes);
3851
- const attrNames = extractAttributeNames(template);
3852
- const relationAttrs = attributes.filter(
3853
- (attr) => attr.type === "relation" && attrNames.includes(attr.name)
3854
- );
3855
- if (relationAttrs.length === 0) {
3856
- return renderLabelExpression(template, enrichedValues);
3857
- }
3858
- const allIds = [];
3859
- for (const attr of relationAttrs) {
3860
- const ids = extractRelationIds(values[attr.name]);
3861
- allIds.push(...ids);
4096
+ var ForbiddenError = class extends SchemaError {
4097
+ constructor(objectName, action, userId) {
4098
+ super(`No ${action} permission on object "${objectName}"`, SchemaErrorCode.FORBIDDEN, {
4099
+ objectName,
4100
+ action,
4101
+ userId
4102
+ });
4103
+ this.name = "ForbiddenError";
4104
+ this.objectName = objectName;
4105
+ this.action = action;
4106
+ this.userId = userId;
3862
4107
  }
3863
- if (allIds.length === 0) {
3864
- return renderLabelExpression(template, enrichedValues);
4108
+ };
4109
+ var ProtectedRoleError = class extends SchemaError {
4110
+ constructor(roleName, operation) {
4111
+ super(`Cannot ${operation} system role "${roleName}"`, SchemaErrorCode.PROTECTED_ROLE, {
4112
+ roleName,
4113
+ operation
4114
+ });
4115
+ this.name = "ProtectedRoleError";
4116
+ this.roleName = roleName;
4117
+ this.operation = operation;
3865
4118
  }
3866
- const resolvedMap = await resolveRelationIds(allIds);
3867
- enrichedValues = { ...enrichedValues };
3868
- for (const attr of relationAttrs) {
3869
- const ids = extractRelationIds(values[attr.name]);
3870
- if (ids.length > 0 && resolvedMap.has(ids[0])) {
3871
- enrichedValues[attr.name] = resolvedMap.get(ids[0]);
3872
- }
4119
+ };
4120
+ var RoleNotFoundError = class extends NotFoundError {
4121
+ constructor(roleId) {
4122
+ super("Role", roleId, SchemaErrorCode.ROLE_NOT_FOUND);
4123
+ this.name = "RoleNotFoundError";
3873
4124
  }
3874
- return renderLabelExpression(template, enrichedValues);
4125
+ };
4126
+ function isForbiddenError(error2) {
4127
+ return error2 instanceof ForbiddenError;
3875
4128
  }
4129
+ var ConcurrentModificationError = class extends SchemaError {
4130
+ constructor(recordId) {
4131
+ super(
4132
+ `Record ${recordId} was modified by another request. Please refresh and try again.`,
4133
+ SchemaErrorCode.CONFLICT,
4134
+ { recordId }
4135
+ );
4136
+ }
4137
+ };
3876
4138
 
3877
4139
  // src/runtime/mock/mock-object-records.ts
3878
4140
  function createMockObjectRecordsRepository(stores) {
@@ -4398,9 +4660,12 @@ function createMockUserProfilesRepository(stores) {
4398
4660
  if (!existing) {
4399
4661
  return Promise.reject(new Error(`UserProfile ${id} not found`));
4400
4662
  }
4663
+ const sanitized = Object.fromEntries(
4664
+ Object.entries(data).map(([k, v]) => [k, v === null ? void 0 : v])
4665
+ );
4401
4666
  const updated = {
4402
4667
  ...existing,
4403
- ...data,
4668
+ ...sanitized,
4404
4669
  updatedAt: /* @__PURE__ */ new Date()
4405
4670
  };
4406
4671
  stores.userProfiles.set(id, updated);
@@ -5631,7 +5896,10 @@ var NON_SORTABLE_TYPES = /* @__PURE__ */ new Set([
5631
5896
  "richtext",
5632
5897
  "file",
5633
5898
  "document",
5634
- "location"
5899
+ "location",
5900
+ "user",
5901
+ "multiselect",
5902
+ "relation"
5635
5903
  ]);
5636
5904
  function isAttributeSortable(attr) {
5637
5905
  return !NON_SORTABLE_TYPES.has(attr.type);
@@ -6563,22 +6831,6 @@ var DocumentAttributeBuilder = class extends BaseAttributeBuilder {
6563
6831
  constructor(name, label) {
6564
6832
  super("document", name, label);
6565
6833
  }
6566
- /**
6567
- * Set a single required template.
6568
- * Only documents using this template can be attached.
6569
- */
6570
- template(templateId) {
6571
- this.attr.templateId = templateId;
6572
- return this;
6573
- }
6574
- /**
6575
- * Set multiple allowed templates.
6576
- * User can choose which template to use when uploading.
6577
- */
6578
- templates(templateIds) {
6579
- this.attr.allowedTemplates = templateIds;
6580
- return this;
6581
- }
6582
6834
  /**
6583
6835
  * Allow multiple documents.
6584
6836
  * Value becomes string[] instead of string.
@@ -8092,6 +8344,74 @@ var WorkflowConditionBuilder = class {
8092
8344
  };
8093
8345
  }
8094
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
+ };
8095
8415
  var WorkflowEndBuilder = class {
8096
8416
  /** @internal */
8097
8417
  constructor(workflowBuilder, nodeId) {
@@ -8257,6 +8577,12 @@ var WorkflowBuilder = class {
8257
8577
  condition(id, label) {
8258
8578
  return new WorkflowConditionBuilder(this, id, label);
8259
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
+ }
8260
8586
  /**
8261
8587
  * Define an end node
8262
8588
  */
@@ -8334,21 +8660,10 @@ var WorkflowBuilder = class {
8334
8660
  validateNodeReferences() {
8335
8661
  const nodeIds = new Set(Object.keys(this.data.nodes ?? {}));
8336
8662
  for (const node of Object.values(this.data.nodes ?? {})) {
8337
- if (node.type === "start" || node.type === "form") {
8338
- if (node.next && !nodeIds.has(node.next)) {
8339
- throw new Error(
8340
- `[WorkflowBuilder] Node "${node.id}" references unknown node "${node.next}".`
8341
- );
8342
- }
8343
- } else if (node.type === "condition") {
8344
- if (node.onTrue && !nodeIds.has(node.onTrue)) {
8663
+ for (const targetId of getNodeOutputs(node)) {
8664
+ if (!nodeIds.has(targetId)) {
8345
8665
  throw new Error(
8346
- `[WorkflowBuilder] Condition "${node.id}" references unknown node "${node.onTrue}" for onTrue.`
8347
- );
8348
- }
8349
- if (node.onFalse && !nodeIds.has(node.onFalse)) {
8350
- throw new Error(
8351
- `[WorkflowBuilder] Condition "${node.id}" references unknown node "${node.onFalse}" for onFalse.`
8666
+ `[WorkflowBuilder] Node "${node.id}" references unknown node "${targetId}".`
8352
8667
  );
8353
8668
  }
8354
8669
  }
@@ -8357,26 +8672,11 @@ var WorkflowBuilder = class {
8357
8672
  validateSlotReferences() {
8358
8673
  const slotIds = this.data.slots?.reduce((set, s) => set.add(s.id), /* @__PURE__ */ new Set()) ?? /* @__PURE__ */ new Set();
8359
8674
  for (const node of Object.values(this.data.nodes ?? {})) {
8360
- if (node.type === "form") {
8361
- const referencedSlots = /* @__PURE__ */ new Set();
8362
- if (node.fields) {
8363
- for (const field of node.fields) {
8364
- referencedSlots.add(field.slotId);
8365
- }
8366
- }
8367
- if (node.rows) {
8368
- for (const row of node.rows) {
8369
- for (const field of row.fields) {
8370
- referencedSlots.add(field.slotId);
8371
- }
8372
- }
8373
- }
8374
- for (const slotId of referencedSlots) {
8375
- if (!slotIds.has(slotId)) {
8376
- throw new Error(
8377
- `[WorkflowBuilder] Form "${node.id}" references unknown slot "${slotId}".`
8378
- );
8379
- }
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
+ );
8380
8680
  }
8381
8681
  }
8382
8682
  }
@@ -8385,24 +8685,12 @@ var WorkflowBuilder = class {
8385
8685
  const nodes = this.data.nodes ?? {};
8386
8686
  const visited = /* @__PURE__ */ new Set();
8387
8687
  const recursionStack = /* @__PURE__ */ new Set();
8388
- const getNextNodes = (nodeId) => {
8389
- const node = nodes[nodeId];
8390
- if (!node) return [];
8391
- switch (node.type) {
8392
- case "start":
8393
- case "form":
8394
- case "document":
8395
- return node.next ? [node.next] : [];
8396
- case "condition":
8397
- return [node.onTrue, node.onFalse].filter((n) => !!n);
8398
- case "end":
8399
- return [];
8400
- }
8401
- };
8402
8688
  const hasCycle = (nodeId) => {
8403
8689
  visited.add(nodeId);
8404
8690
  recursionStack.add(nodeId);
8405
- for (const nextId of getNextNodes(nodeId)) {
8691
+ const node = nodes[nodeId];
8692
+ if (!node) return false;
8693
+ for (const nextId of getNodeOutputs(node)) {
8406
8694
  if (!visited.has(nextId)) {
8407
8695
  if (hasCycle(nextId)) return true;
8408
8696
  } else if (recursionStack.has(nextId)) {
@@ -8419,7 +8707,9 @@ var WorkflowBuilder = class {
8419
8707
  const visit = (nodeId) => {
8420
8708
  if (reachable.has(nodeId)) return;
8421
8709
  reachable.add(nodeId);
8422
- for (const nextId of getNextNodes(nodeId)) {
8710
+ const node = nodes[nodeId];
8711
+ if (!node) return;
8712
+ for (const nextId of getNodeOutputs(node)) {
8423
8713
  visit(nextId);
8424
8714
  }
8425
8715
  };
@@ -8528,7 +8818,6 @@ var SYSTEM_ATTRIBUTES = {
8528
8818
  multiple: true,
8529
8819
  icon: "paperclip",
8530
8820
  description: "Free-form document attachments"
8531
- // No templateId = all templates allowed
8532
8821
  }
8533
8822
  };
8534
8823
  function getSystemAttributeList() {
@@ -10789,14 +11078,20 @@ var RelationPropertiesService = class extends BaseService {
10789
11078
  if (value === null || value === void 0) {
10790
11079
  continue;
10791
11080
  }
10792
- if (attr.cardinality === "many" && Array.isArray(value)) {
10793
- normalized[attr.name] = value.map((item) => {
10794
- if (typeof item === "string") return item;
10795
- if (typeof item === "object" && item !== null && "id" in item) {
10796
- return item.id;
10797
- }
10798
- return item;
10799
- });
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
+ }
10800
11095
  } else if (typeof value === "object" && value !== null && "id" in value) {
10801
11096
  normalized[attr.name] = value.id;
10802
11097
  }
@@ -12961,500 +13256,54 @@ var RollupScheduler = class {
12961
13256
  const timeout = setTimeout(async () => {
12962
13257
  await this.executeRecalculation(parentId, parentObjectId);
12963
13258
  this.pending.delete(key);
12964
- }, this.debounceMs);
12965
- this.pending.set(key, { parentId, parentObjectId, timeout });
12966
- if (this.pending.size >= this.maxPending) {
12967
- this.flush();
12968
- }
12969
- }
12970
- /**
12971
- * Execute all pending recalculations immediately
12972
- */
12973
- async flush() {
12974
- const entries = Array.from(this.pending.entries());
12975
- for (const [, entry] of entries) {
12976
- clearTimeout(entry.timeout);
12977
- }
12978
- this.pending.clear();
12979
- const allIds = entries.map(([, entry]) => entry.parentId);
12980
- const records = await this.adapter.objectRecords.findByIds(allIds);
12981
- for (const record of records) {
12982
- const schema = await this.getSchemaById(record.objectId);
12983
- if (schema) {
12984
- await this.rollupService.recalculateAndUpdate(record, schema);
12985
- }
12986
- }
12987
- }
12988
- /**
12989
- * Execute a single recalculation
12990
- */
12991
- async executeRecalculation(parentId, parentObjectId) {
12992
- const record = await this.adapter.objectRecords.findById(parentId);
12993
- if (!record) return;
12994
- const schema = await this.getSchemaById(parentObjectId);
12995
- if (!schema) return;
12996
- await this.rollupService.recalculateAndUpdate(record, schema);
12997
- }
12998
- /**
12999
- * Get number of pending recalculations
13000
- */
13001
- get pendingCount() {
13002
- return this.pending.size;
13003
- }
13004
- /**
13005
- * Clear all pending recalculations without executing them
13006
- */
13007
- clear() {
13008
- for (const [, entry] of this.pending) {
13009
- clearTimeout(entry.timeout);
13010
- }
13011
- this.pending.clear();
13012
- }
13013
- };
13014
-
13015
- // src/runtime/services/document/document-renderer.service.ts
13016
- import { PDFDocument, StandardFonts, rgb } from "pdf-lib";
13017
- var DocumentRenderError = class extends Error {
13018
- constructor(message, templateId, cause) {
13019
- super(message);
13020
- this.templateId = templateId;
13021
- this.cause = cause;
13022
- this.name = "DocumentRenderError";
13023
- }
13024
- };
13025
- var StorageDownloadNotSupportedError = class extends Error {
13026
- constructor() {
13027
- super("Storage adapter does not support download. Required for document rendering.");
13028
- this.name = "StorageDownloadNotSupportedError";
13029
- }
13030
- };
13031
- var DocumentRendererService = class {
13032
- constructor(storageAdapter, options) {
13033
- this.storageAdapter = storageAdapter;
13034
- this.options = options;
13035
- this.schemaCache = /* @__PURE__ */ new Map();
13036
- }
13037
- /**
13038
- * Render a document from a template and context.
13039
- *
13040
- * @param input - Template, context, and optional filename
13041
- * @returns Generated PDF buffer with metadata
13042
- * @throws DocumentRenderError if rendering fails
13043
- * @throws StorageDownloadNotSupportedError if storage doesn't support download
13044
- */
13045
- async render(input) {
13046
- const { template, context, workflow: workflow2, filename } = input;
13047
- if (template.source.type !== "pdf") {
13048
- throw new DocumentRenderError("Only PDF templates are supported for rendering", template.id);
13049
- }
13050
- if (!this.storageAdapter.download) {
13051
- throw new StorageDownloadNotSupportedError();
13052
- }
13053
- try {
13054
- const templateBytes = await this.downloadTemplate(template.source.fileId);
13055
- const pdfDoc = await PDFDocument.load(templateBytes);
13056
- const pages = pdfDoc.getPages();
13057
- const font = await pdfDoc.embedFont(StandardFonts.Helvetica);
13058
- const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
13059
- const resolvedValues = await this.resolveAllFieldValues(
13060
- template.source.fields,
13061
- context,
13062
- workflow2
13063
- );
13064
- for (const field of template.source.fields) {
13065
- this.drawField(pages, field, resolvedValues.get(field.id) ?? "", font, fontBold);
13066
- }
13067
- const outputBytes = await pdfDoc.save();
13068
- const resolvedFilename = this.interpolateFilename(filename, context, template);
13069
- return {
13070
- buffer: Buffer.from(outputBytes),
13071
- filename: resolvedFilename,
13072
- mimeType: "application/pdf",
13073
- pageCount: pages.length
13074
- };
13075
- } catch (error2) {
13076
- if (error2 instanceof DocumentRenderError || error2 instanceof StorageDownloadNotSupportedError) {
13077
- throw error2;
13078
- }
13079
- throw new DocumentRenderError(
13080
- `Failed to render document: ${getErrorMessage(error2)}`,
13081
- template.id,
13082
- error2
13083
- );
13084
- }
13085
- }
13086
- // ============================================================================
13087
- // PRIVATE METHODS
13088
- // ============================================================================
13089
- /**
13090
- * Download the template PDF from storage
13091
- */
13092
- async downloadTemplate(fileId) {
13093
- if (!this.storageAdapter.download) {
13094
- throw new StorageDownloadNotSupportedError();
13095
- }
13096
- let storagePath = fileId;
13097
- if (this.options?.filesRepository) {
13098
- const file2 = await this.options.filesRepository.findById(fileId);
13099
- if (!file2) {
13100
- throw new Error(`Template file not found: ${fileId}`);
13101
- }
13102
- storagePath = file2.storagePath;
13103
- }
13104
- return await this.storageAdapter.download(storagePath);
13105
- }
13106
- /**
13107
- * Resolve all field values, including relations and formatted attributes
13108
- */
13109
- async resolveAllFieldValues(fields, context, workflow2) {
13110
- const resolved = /* @__PURE__ */ new Map();
13111
- const relationBatch = [];
13112
- for (const field of fields) {
13113
- const rawValue = getContextValue(context, field.contextPath);
13114
- const attrInfo = await this.getAttributeInfo(field.contextPath, workflow2);
13115
- if (attrInfo?.attribute) {
13116
- if (attrInfo.attribute.type === "relation" && rawValue && this.options?.relationService) {
13117
- const ids = Array.isArray(rawValue) ? rawValue : [rawValue];
13118
- const stringIds = ids.filter((id) => typeof id === "string");
13119
- if (stringIds.length > 0) {
13120
- relationBatch.push({
13121
- fieldId: field.id,
13122
- attributeId: attrInfo.attribute.id ?? field.id,
13123
- ids: stringIds
13124
- });
13125
- continue;
13126
- }
13127
- }
13128
- const formatted = formatAttributeValue(rawValue, attrInfo.attribute);
13129
- resolved.set(
13130
- field.id,
13131
- formatted === EMPTY_VALUE_PLACEHOLDER ? field.fallback ?? "" : formatted
13132
- );
13133
- } else {
13134
- resolved.set(field.id, this.formatValueSimple(rawValue, field.fallback));
13135
- }
13136
- }
13137
- if (relationBatch.length > 0 && this.options?.relationService) {
13138
- try {
13139
- const batchResult = await this.options.relationService.resolveIdsBatch(
13140
- relationBatch.map((r) => ({ attributeId: r.attributeId, ids: r.ids }))
13141
- );
13142
- for (const { fieldId, attributeId } of relationBatch) {
13143
- const options = batchResult[attributeId] ?? [];
13144
- const labels = options.map((o) => o.label);
13145
- const field = fields.find((f) => f.id === fieldId);
13146
- resolved.set(fieldId, labels.join(", ") || field?.fallback || "");
13147
- }
13148
- } catch {
13149
- for (const { fieldId, ids } of relationBatch) {
13150
- const field = fields.find((f) => f.id === fieldId);
13151
- resolved.set(fieldId, ids.join(", ") || field?.fallback || "");
13152
- }
13153
- }
13154
- }
13155
- return resolved;
13156
- }
13157
- /**
13158
- * Get attribute info from contextPath
13159
- * Parses paths like "slots.client.firstName" to find the attribute definition
13160
- */
13161
- async getAttributeInfo(contextPath, workflow2) {
13162
- const schemaService = this.options?.schemaService;
13163
- if (!schemaService) {
13164
- return null;
13165
- }
13166
- if (!workflow2) {
13167
- return null;
13168
- }
13169
- const parts = contextPath.split(".");
13170
- if (parts.length < 3 || parts[0] !== "slots") {
13171
- return null;
13172
- }
13173
- const slotId = parts[1];
13174
- const attributeName = parts[2];
13175
- const slot = workflow2.slots?.find((s) => s.id === slotId);
13176
- if (!slot) {
13177
- return null;
13178
- }
13179
- let schema = this.schemaCache.get(slot.objectName);
13180
- if (!schema) {
13181
- try {
13182
- schema = await schemaService.getObjectSchemaByName(slot.objectName);
13183
- this.schemaCache.set(slot.objectName, schema);
13184
- } catch {
13185
- return null;
13186
- }
13187
- }
13188
- if (!schema) {
13189
- return null;
13190
- }
13191
- const attribute = schema.attributes.find((a) => a.name === attributeName);
13192
- if (!attribute) {
13193
- return null;
13194
- }
13195
- return { attribute, objectName: slot.objectName };
13196
- }
13197
- /**
13198
- * Draw a single field on the PDF
13199
- */
13200
- drawField(pages, field, value, font, fontBold) {
13201
- const page = pages[field.page];
13202
- if (!page) {
13203
- return;
13204
- }
13205
- if (!value) {
13206
- return;
13207
- }
13208
- const selectedFont = field.fontWeight === "bold" ? fontBold : font;
13209
- const fontSize = field.fontSize ?? 11;
13210
- const { height: pageHeight } = page.getSize();
13211
- let x = field.x;
13212
- if (field.align === "center" || field.align === "right") {
13213
- const textWidth = selectedFont.widthOfTextAtSize(value, fontSize);
13214
- if (field.align === "center") {
13215
- x = field.x + (field.width - textWidth) / 2;
13216
- } else {
13217
- x = field.x + field.width - textWidth;
13218
- }
13219
- }
13220
- const y = pageHeight - field.y - fontSize;
13221
- page.drawText(value, {
13222
- x,
13223
- y,
13224
- size: fontSize,
13225
- font: selectedFont,
13226
- color: rgb(0, 0, 0)
13227
- });
13228
- }
13229
- /**
13230
- * Simple value formatting (fallback when no attribute definition available)
13231
- */
13232
- formatValueSimple(value, fallback) {
13233
- if (value === null || value === void 0) {
13234
- return fallback ?? "";
13235
- }
13236
- if (value instanceof Date) {
13237
- return value.toLocaleDateString();
13238
- }
13239
- if (typeof value === "number") {
13240
- return String(value);
13241
- }
13242
- if (typeof value === "boolean") {
13243
- return value ? "Yes" : "No";
13244
- }
13245
- if (Array.isArray(value)) {
13246
- return value.map((v) => this.formatValueSimple(v)).join(", ");
13247
- }
13248
- if (typeof value === "object") {
13249
- const obj = value;
13250
- if (typeof obj.label === "string") return obj.label;
13251
- if (typeof obj.name === "string") return obj.name;
13252
- return JSON.stringify(value);
13253
- }
13254
- return String(value);
13255
- }
13256
- /**
13257
- * Interpolate filename with context values
13258
- *
13259
- * Supports {{path}} syntax for variable interpolation.
13260
- *
13261
- * @example
13262
- * ```typescript
13263
- * interpolateFilename("contract-{{slots.client.name}}.pdf", context, template)
13264
- * // => "contract-John Doe.pdf"
13265
- * ```
13266
- */
13267
- interpolateFilename(template, context, docTemplate) {
13268
- if (!template) {
13269
- const timestamp = Date.now();
13270
- const baseName = docTemplate.name || "document";
13271
- return `${baseName}-${timestamp}.pdf`;
13272
- }
13273
- const interpolated = template.replace(/\{\{([^}]+)\}\}/g, (_, path) => {
13274
- const value = getContextValue(context, path.trim());
13275
- if (value === null || value === void 0) {
13276
- return "";
13277
- }
13278
- return String(value).replace(/[<>:"/\\|?*]/g, "_").trim();
13279
- });
13280
- if (!interpolated.toLowerCase().endsWith(".pdf")) {
13281
- return `${interpolated}.pdf`;
13282
- }
13283
- return interpolated;
13284
- }
13285
- };
13286
-
13287
- // src/runtime/services/workflow/document-processing.hook.ts
13288
- var DocumentProcessingHook = class extends BaseService {
13289
- constructor(adapter, storageAdapter, options) {
13290
- super(adapter);
13291
- this.adapter = adapter;
13292
- this.storageAdapter = storageAdapter;
13293
- this.options = options;
13294
- this.renderer = new DocumentRendererService(storageAdapter, {
13295
- schemaService: options.schemaService,
13296
- relationService: options.relationService,
13297
- filesRepository: adapter.files
13298
- });
13299
- }
13300
- /**
13301
- * Process all pending document requests in the context.
13302
- *
13303
- * @param context - Current workflow execution context
13304
- * @param workflow - Workflow definition (for slot/object info)
13305
- * @param userId - User ID for audit/permissions
13306
- * @returns Updated context with processed documents
13307
- */
13308
- async process(context, workflow2, userId) {
13309
- const pendingNodeIds = this.findPendingDocuments(context);
13310
- if (pendingNodeIds.length === 0) {
13311
- return context;
13312
- }
13313
- const updatedDocuments = { ...context.documents };
13314
- for (const nodeId of pendingNodeIds) {
13315
- const doc = context.documents[nodeId];
13316
- const metadata = doc.metadata;
13317
- if (!metadata) {
13318
- continue;
13319
- }
13320
- try {
13321
- updatedDocuments[nodeId] = {
13322
- ...doc,
13323
- metadata: { ...metadata, status: "processing" }
13324
- };
13325
- const template = await this.options.documentGenerationService.getByIdOrThrow(
13326
- metadata.templateId
13327
- );
13328
- const renderResult = await this.renderer.render({
13329
- template,
13330
- context,
13331
- workflow: workflow2,
13332
- filename: doc.filename
13333
- });
13334
- const uploadResult = await this.uploadGeneratedDocument(renderResult, template, userId);
13335
- const attachedDocumentIds = await this.attachToRecords(
13336
- renderResult,
13337
- metadata.targetSlotIds,
13338
- context,
13339
- workflow2,
13340
- userId
13341
- );
13342
- updatedDocuments[nodeId] = {
13343
- id: uploadResult.fileId,
13344
- url: uploadResult.url,
13345
- filename: renderResult.filename,
13346
- mimeType: renderResult.mimeType,
13347
- size: renderResult.buffer.length,
13348
- attachedDocumentIds,
13349
- metadata: { ...metadata, status: "completed" }
13350
- };
13351
- } catch (error2) {
13352
- const errorMessage = getErrorMessage(error2);
13353
- updatedDocuments[nodeId] = {
13354
- ...doc,
13355
- metadata: { ...metadata, status: "failed", error: errorMessage }
13356
- };
13357
- }
13259
+ }, this.debounceMs);
13260
+ this.pending.set(key, { parentId, parentObjectId, timeout });
13261
+ if (this.pending.size >= this.maxPending) {
13262
+ this.flush();
13358
13263
  }
13359
- return {
13360
- ...context,
13361
- documents: updatedDocuments
13362
- };
13363
13264
  }
13364
- // ============================================================================
13365
- // PRIVATE METHODS
13366
- // ============================================================================
13367
13265
  /**
13368
- * Find node IDs with pending document requests
13266
+ * Execute all pending recalculations immediately
13369
13267
  */
13370
- findPendingDocuments(context) {
13371
- const pendingIds = [];
13372
- for (const [nodeId, doc] of Object.entries(context.documents)) {
13373
- const metadata = doc.metadata;
13374
- if (metadata?.status === "pending") {
13375
- 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);
13376
13280
  }
13377
13281
  }
13378
- return pendingIds;
13379
13282
  }
13380
13283
  /**
13381
- * Upload the generated PDF to storage
13284
+ * Execute a single recalculation
13382
13285
  */
13383
- async uploadGeneratedDocument(renderResult, template, userId) {
13384
- const uploadResult = await this.storageAdapter.upload({
13385
- content: renderResult.buffer,
13386
- fileName: renderResult.filename,
13387
- mimeType: renderResult.mimeType,
13388
- size: renderResult.buffer.length,
13389
- tenantId: this.tenantId,
13390
- folderPath: `generated-documents/${template.name}`
13391
- });
13392
- let fileId = `file-${Date.now()}`;
13393
- if (this.adapter.files) {
13394
- const fileRecord = await this.adapter.files.create({
13395
- name: renderResult.filename,
13396
- originalName: renderResult.filename,
13397
- mimeType: renderResult.mimeType,
13398
- size: renderResult.buffer.length,
13399
- storageProvider: uploadResult.storageProvider,
13400
- storagePath: uploadResult.storagePath,
13401
- storageBucket: uploadResult.storageBucket,
13402
- url: uploadResult.url,
13403
- uploadedBy: userId,
13404
- visibility: "private"
13405
- });
13406
- fileId = fileRecord.id;
13407
- }
13408
- return {
13409
- fileId,
13410
- url: uploadResult.url,
13411
- storagePath: uploadResult.storagePath
13412
- };
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);
13413
13292
  }
13414
13293
  /**
13415
- * Attach generated document to target records
13294
+ * Get number of pending recalculations
13416
13295
  */
13417
- async attachToRecords(renderResult, targetSlotIds, context, workflow2, userId) {
13418
- const attachedDocumentIds = [];
13419
- const { documentService, recordService } = this.options;
13420
- if (!(documentService && recordService)) {
13421
- return attachedDocumentIds;
13422
- }
13423
- for (const slotId of targetSlotIds) {
13424
- try {
13425
- const recordId = context.createdRecordIds?.[slotId];
13426
- if (!recordId) {
13427
- continue;
13428
- }
13429
- const slotDef = workflow2.slots?.find((s) => s.id === slotId);
13430
- const objectName = slotDef?.objectName;
13431
- if (!objectName) {
13432
- continue;
13433
- }
13434
- const result = await documentService.createRecordDocument({
13435
- objectName,
13436
- recordId,
13437
- fileContent: renderResult.buffer,
13438
- fileName: renderResult.filename,
13439
- mimeType: renderResult.mimeType,
13440
- fileSize: renderResult.buffer.length,
13441
- uploadedBy: userId,
13442
- title: renderResult.filename
13443
- });
13444
- attachedDocumentIds.push(result.document.id);
13445
- const record = await recordService.getRecord(recordId);
13446
- if (record) {
13447
- const attachments = record.values?.attachments ?? [];
13448
- await recordService.updateRecord(
13449
- recordId,
13450
- { attachments: [...attachments, result.document.id] },
13451
- { partial: true }
13452
- );
13453
- }
13454
- } catch {
13455
- }
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);
13456
13305
  }
13457
- return attachedDocumentIds;
13306
+ this.pending.clear();
13458
13307
  }
13459
13308
  };
13460
13309
 
@@ -13765,7 +13614,6 @@ var WorkflowInstanceService = class extends BaseService {
13765
13614
  this.executorRegistry = options?.executorRegistry ?? getDefaultExecutorRegistry();
13766
13615
  this.schemaService = options?.schemaService;
13767
13616
  this.recordService = options?.recordService;
13768
- this.documentProcessingHook = options?.documentProcessingHook;
13769
13617
  }
13770
13618
  /**
13771
13619
  * Start a new workflow instance
@@ -14111,14 +13959,7 @@ var WorkflowInstanceService = class extends BaseService {
14111
13959
  }
14112
13960
  case "complete": {
14113
13961
  try {
14114
- let updatedContext = await this.persistSlots(current);
14115
- if (this.documentProcessingHook) {
14116
- updatedContext = await this.documentProcessingHook.process(
14117
- updatedContext,
14118
- current.workflowSnapshot,
14119
- current.startedBy
14120
- );
14121
- }
13962
+ const updatedContext = await this.persistSlots(current);
14122
13963
  current = {
14123
13964
  ...current,
14124
13965
  context: updatedContext,
@@ -15470,471 +15311,10 @@ var UserProfileService = class extends BaseService {
15470
15311
  }
15471
15312
  };
15472
15313
 
15473
- // src/runtime/services/document/document-generation.service.ts
15474
- var DocumentGenerationTemplateNotFoundError = class extends Error {
15475
- constructor(templateId) {
15476
- super(`Document generation template not found: ${templateId}`);
15477
- this.templateId = templateId;
15478
- this.name = "DocumentGenerationTemplateNotFoundError";
15479
- }
15480
- };
15481
- var DocumentGenerationNotConfiguredError = class extends Error {
15482
- constructor() {
15483
- super(
15484
- "DocumentGenerationTemplatesRepository not available. Enable document generation in adapter."
15485
- );
15486
- this.name = "DocumentGenerationNotConfiguredError";
15487
- }
15488
- };
15489
- var DocumentGenerationService = class extends BaseService {
15490
- /**
15491
- * Get the document generation templates repository.
15492
- * @throws DocumentGenerationNotConfiguredError if repository not available
15493
- */
15494
- get repo() {
15495
- const repo = this.adapter.documentGenerationTemplates;
15496
- if (!repo) {
15497
- throw new DocumentGenerationNotConfiguredError();
15498
- }
15499
- return repo;
15500
- }
15501
- // ============================================================================
15502
- // READ OPERATIONS
15503
- // ============================================================================
15504
- /**
15505
- * Get a template by ID.
15506
- *
15507
- * @param id - Template ID
15508
- * @returns Template or null if not found
15509
- */
15510
- async getById(id) {
15511
- return await this.repo.findById(id);
15512
- }
15513
- /**
15514
- * Get a template by ID, throwing if not found.
15515
- *
15516
- * @param id - Template ID
15517
- * @returns Template
15518
- * @throws DocumentGenerationTemplateNotFoundError if not found
15519
- */
15520
- async getByIdOrThrow(id) {
15521
- const template = await this.repo.findById(id);
15522
- if (!template) {
15523
- throw new DocumentGenerationTemplateNotFoundError(id);
15524
- }
15525
- return template;
15526
- }
15527
- /**
15528
- * Get a template by name.
15529
- *
15530
- * @param name - Template name (unique within tenant)
15531
- * @returns Template or null if not found
15532
- */
15533
- async getByName(name) {
15534
- return await this.repo.findByName(name);
15535
- }
15536
- /**
15537
- * List templates for the current tenant.
15538
- *
15539
- * @param options - List options (sourceType filter, pagination)
15540
- * @returns Array of templates
15541
- */
15542
- async list(options) {
15543
- return await this.repo.list(options);
15544
- }
15545
- // ============================================================================
15546
- // WRITE OPERATIONS
15547
- // ============================================================================
15548
- /**
15549
- * Create a new document generation template.
15550
- *
15551
- * @param input - Template data
15552
- * @returns Created template
15553
- */
15554
- async create(input) {
15555
- return await this.repo.create(input);
15556
- }
15557
- /**
15558
- * Update a template.
15559
- *
15560
- * @param id - Template ID
15561
- * @param input - Fields to update
15562
- * @returns Updated template
15563
- */
15564
- async update(id, input) {
15565
- return await this.repo.update(id, input);
15566
- }
15567
- /**
15568
- * Delete a template.
15569
- *
15570
- * @param id - Template ID
15571
- */
15572
- async delete(id) {
15573
- await this.repo.delete(id);
15574
- }
15575
- };
15576
-
15577
- // src/templates/index.ts
15578
- var SYSTEM_TEMPLATE_IDS = {
15579
- FRENCH_ID_CARD: "00000000-0000-0000-0001-000000000001",
15580
- PASSPORT: "00000000-0000-0000-0001-000000000002",
15581
- DRIVING_LICENSE: "00000000-0000-0000-0001-000000000003",
15582
- PROOF_OF_ADDRESS: "00000000-0000-0000-0001-000000000004",
15583
- SIGNABLE_CONTRACT: "00000000-0000-0000-0001-000000000005",
15584
- GENERIC_DOCUMENT: "00000000-0000-0000-0001-000000000006"
15585
- };
15586
- var FRENCH_ID_CARD = {
15587
- id: SYSTEM_TEMPLATE_IDS.FRENCH_ID_CARD,
15588
- tenantId: null,
15589
- name: "french_id_card",
15590
- label: "Carte d'identit\xE9 fran\xE7aise",
15591
- description: "Carte nationale d'identit\xE9 fran\xE7aise (recto/verso)",
15592
- icon: "credit-card",
15593
- system: true,
15594
- slots: [
15595
- {
15596
- name: "front",
15597
- label: "Recto",
15598
- description: "Face avant de la carte d'identit\xE9",
15599
- required: true,
15600
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
15601
- maxSize: 10 * 1024 * 1024,
15602
- // 10MB
15603
- order: 1
15604
- },
15605
- {
15606
- name: "back",
15607
- label: "Verso",
15608
- description: "Face arri\xE8re de la carte d'identit\xE9",
15609
- required: true,
15610
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
15611
- maxSize: 10 * 1024 * 1024,
15612
- order: 2
15613
- }
15614
- ],
15615
- allowAdditionalFiles: false,
15616
- autoProcessing: {
15617
- ocr: { enabled: true },
15618
- identityVerification: { enabled: true, documentType: "national_id" }
15619
- },
15620
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15621
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15622
- };
15623
- var PASSPORT = {
15624
- id: SYSTEM_TEMPLATE_IDS.PASSPORT,
15625
- tenantId: null,
15626
- name: "passport",
15627
- label: "Passeport",
15628
- description: "Passeport international",
15629
- icon: "badge",
15630
- system: true,
15631
- slots: [
15632
- {
15633
- name: "data_page",
15634
- label: "Page de donn\xE9es",
15635
- description: "Page avec photo et MRZ",
15636
- required: true,
15637
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
15638
- maxSize: 10 * 1024 * 1024,
15639
- order: 1
15640
- }
15641
- ],
15642
- allowAdditionalFiles: true,
15643
- autoProcessing: {
15644
- ocr: { enabled: true },
15645
- identityVerification: { enabled: true, documentType: "passport" }
15646
- },
15647
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15648
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15649
- };
15650
- var DRIVING_LICENSE = {
15651
- id: SYSTEM_TEMPLATE_IDS.DRIVING_LICENSE,
15652
- tenantId: null,
15653
- name: "driving_license",
15654
- label: "Permis de conduire",
15655
- description: "Permis de conduire",
15656
- icon: "credit-card-check",
15657
- system: true,
15658
- slots: [
15659
- {
15660
- name: "front",
15661
- label: "Recto",
15662
- required: true,
15663
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
15664
- maxSize: 10 * 1024 * 1024,
15665
- order: 1
15666
- },
15667
- {
15668
- name: "back",
15669
- label: "Verso",
15670
- required: false,
15671
- allowedMimeTypes: ["image/jpeg", "image/png", "image/webp"],
15672
- maxSize: 10 * 1024 * 1024,
15673
- order: 2
15674
- }
15675
- ],
15676
- allowAdditionalFiles: false,
15677
- autoProcessing: {
15678
- ocr: { enabled: true },
15679
- identityVerification: { enabled: true, documentType: "driving_license" }
15680
- },
15681
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15682
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15683
- };
15684
- var PROOF_OF_ADDRESS = {
15685
- id: SYSTEM_TEMPLATE_IDS.PROOF_OF_ADDRESS,
15686
- tenantId: null,
15687
- name: "proof_of_address",
15688
- label: "Justificatif de domicile",
15689
- description: "Facture, relev\xE9 bancaire ou attestation de moins de 3 mois",
15690
- icon: "home",
15691
- system: true,
15692
- slots: [
15693
- {
15694
- name: "document",
15695
- label: "Document",
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: false,
15703
- autoProcessing: {
15704
- ocr: { enabled: true }
15705
- },
15706
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15707
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15708
- };
15709
- var SIGNABLE_CONTRACT = {
15710
- id: SYSTEM_TEMPLATE_IDS.SIGNABLE_CONTRACT,
15711
- tenantId: null,
15712
- name: "signable_contract",
15713
- label: "Contrat \xE0 signer",
15714
- description: "Document PDF n\xE9cessitant une signature \xE9lectronique",
15715
- icon: "file-text",
15716
- system: true,
15717
- slots: [
15718
- {
15719
- name: "contract",
15720
- label: "Contrat",
15721
- required: true,
15722
- allowedMimeTypes: ["application/pdf"],
15723
- maxSize: 50 * 1024 * 1024,
15724
- // 50MB for contracts
15725
- order: 1
15726
- }
15727
- ],
15728
- allowAdditionalFiles: true,
15729
- autoProcessing: {
15730
- signature: { enabled: true }
15731
- },
15732
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15733
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15734
- };
15735
- var GENERIC_DOCUMENT = {
15736
- id: SYSTEM_TEMPLATE_IDS.GENERIC_DOCUMENT,
15737
- tenantId: null,
15738
- name: "generic_document",
15739
- label: "Document",
15740
- description: "Document g\xE9n\xE9rique",
15741
- icon: "file",
15742
- system: true,
15743
- slots: [
15744
- {
15745
- name: "file",
15746
- label: "Fichier",
15747
- required: true,
15748
- order: 1
15749
- }
15750
- ],
15751
- allowAdditionalFiles: true,
15752
- autoProcessing: {},
15753
- createdAt: /* @__PURE__ */ new Date("2024-01-01"),
15754
- updatedAt: /* @__PURE__ */ new Date("2024-01-01")
15755
- };
15756
- var SYSTEM_TEMPLATES = [
15757
- FRENCH_ID_CARD,
15758
- PASSPORT,
15759
- DRIVING_LICENSE,
15760
- PROOF_OF_ADDRESS,
15761
- SIGNABLE_CONTRACT,
15762
- GENERIC_DOCUMENT
15763
- ];
15764
- function getSystemTemplate(name) {
15765
- return SYSTEM_TEMPLATES.find((t) => t.name === name);
15766
- }
15767
- function isSystemTemplate(name) {
15768
- return SYSTEM_TEMPLATES.some((t) => t.name === name);
15769
- }
15770
-
15771
- // src/runtime/services/document/document-template.service.ts
15772
- var DocumentTemplateService = class extends BaseService {
15773
- constructor(adapter) {
15774
- super(adapter);
15775
- }
15776
- // ============================================================================
15777
- // READ
15778
- // ============================================================================
15779
- /**
15780
- * Get a template by ID.
15781
- * Checks custom templates first, then system templates.
15782
- */
15783
- async getTemplate(templateId) {
15784
- const systemTemplate = SYSTEM_TEMPLATES.find((t) => t.id === templateId);
15785
- if (systemTemplate) {
15786
- return systemTemplate;
15787
- }
15788
- if (!this.adapter.documentTemplates) {
15789
- return null;
15790
- }
15791
- return await this.adapter.documentTemplates.findById(templateId);
15792
- }
15793
- /**
15794
- * Get a template by name.
15795
- * Checks custom templates first (tenant-specific), then system templates.
15796
- */
15797
- async getTemplateByName(name) {
15798
- if (this.adapter.documentTemplates) {
15799
- const customTemplate = await this.adapter.documentTemplates.findByName(name);
15800
- if (customTemplate) {
15801
- return customTemplate;
15802
- }
15803
- }
15804
- return getSystemTemplate(name) ?? null;
15805
- }
15806
- /**
15807
- * Get multiple templates by names.
15808
- */
15809
- async getTemplatesByNames(names) {
15810
- const results = [];
15811
- const missingNames = [];
15812
- for (const name of names) {
15813
- const systemTemplate = getSystemTemplate(name);
15814
- if (systemTemplate) {
15815
- results.push(systemTemplate);
15816
- } else {
15817
- missingNames.push(name);
15818
- }
15819
- }
15820
- if (missingNames.length > 0 && this.adapter.documentTemplates) {
15821
- const customTemplates = await this.adapter.documentTemplates.findByNames(missingNames);
15822
- results.push(...customTemplates);
15823
- }
15824
- return results;
15825
- }
15826
- /**
15827
- * Get a template or throw if not found.
15828
- */
15829
- async getTemplateOrThrow(templateId) {
15830
- const template = await this.getTemplate(templateId);
15831
- if (!template) {
15832
- throw new Error(`Document template with id "${templateId}" not found`);
15833
- }
15834
- return template;
15835
- }
15836
- /**
15837
- * Get a template by name or throw if not found.
15838
- */
15839
- async getTemplateByNameOrThrow(name) {
15840
- const template = await this.getTemplateByName(name);
15841
- if (!template) {
15842
- throw new Error(`Document template "${name}" not found`);
15843
- }
15844
- return template;
15845
- }
15846
- // ============================================================================
15847
- // LIST
15848
- // ============================================================================
15849
- /**
15850
- * List all available templates.
15851
- * Includes both system templates and tenant-specific templates.
15852
- */
15853
- async listTemplates(options) {
15854
- if (options?.systemOnly) {
15855
- return SYSTEM_TEMPLATES;
15856
- }
15857
- const templates = [...SYSTEM_TEMPLATES];
15858
- if (this.adapter.documentTemplates) {
15859
- const customTemplates = await this.adapter.documentTemplates.list(options);
15860
- templates.push(...customTemplates);
15861
- }
15862
- return templates;
15863
- }
15864
- /**
15865
- * Get only system templates.
15866
- */
15867
- getSystemTemplates() {
15868
- return SYSTEM_TEMPLATES;
15869
- }
15870
- // ============================================================================
15871
- // CREATE
15872
- // ============================================================================
15873
- /**
15874
- * Create a custom template.
15875
- * System templates cannot be created via this method.
15876
- */
15877
- async createTemplate(data) {
15878
- if (!this.adapter.documentTemplates) {
15879
- throw new Error("Document templates repository is not configured");
15880
- }
15881
- const existingSystem = getSystemTemplate(data.name);
15882
- if (existingSystem) {
15883
- throw new Error(`Template name "${data.name}" is reserved for a system template`);
15884
- }
15885
- const existing = await this.adapter.documentTemplates.findByName(data.name);
15886
- if (existing) {
15887
- throw new Error(`Template with name "${data.name}" already exists`);
15888
- }
15889
- return await this.adapter.documentTemplates.create(data);
15890
- }
15891
- // ============================================================================
15892
- // UPDATE
15893
- // ============================================================================
15894
- /**
15895
- * Update a custom template.
15896
- * System templates cannot be updated.
15897
- */
15898
- async updateTemplate(templateId, data) {
15899
- if (!this.adapter.documentTemplates) {
15900
- throw new Error("Document templates repository is not configured");
15901
- }
15902
- const existing = await this.getTemplate(templateId);
15903
- if (!existing) {
15904
- throw new Error(`Document template with id "${templateId}" not found`);
15905
- }
15906
- if (existing.system) {
15907
- throw new Error("System templates cannot be modified");
15908
- }
15909
- return await this.adapter.documentTemplates.update(templateId, data);
15910
- }
15911
- // ============================================================================
15912
- // DELETE
15913
- // ============================================================================
15914
- /**
15915
- * Delete a custom template.
15916
- * System templates cannot be deleted.
15917
- */
15918
- async deleteTemplate(templateId) {
15919
- if (!this.adapter.documentTemplates) {
15920
- throw new Error("Document templates repository is not configured");
15921
- }
15922
- const existing = await this.getTemplate(templateId);
15923
- if (!existing) {
15924
- throw new Error(`Document template with id "${templateId}" not found`);
15925
- }
15926
- if (existing.system) {
15927
- throw new Error("System templates cannot be deleted");
15928
- }
15929
- await this.adapter.documentTemplates.delete(templateId);
15930
- }
15931
- };
15932
-
15933
15314
  // src/runtime/services/document/document.service.ts
15934
15315
  var DocumentService = class extends BaseService {
15935
15316
  constructor(adapter, options) {
15936
15317
  super(adapter);
15937
- this.templateService = options?.templateService ?? new DocumentTemplateService(adapter);
15938
15318
  this.fileService = options?.fileService ?? null;
15939
15319
  }
15940
15320
  // ============================================================================
@@ -15950,22 +15330,8 @@ var DocumentService = class extends BaseService {
15950
15330
  if (!this.adapter.documents) {
15951
15331
  throw new Error("Documents repository is not configured");
15952
15332
  }
15953
- const template = await this.templateService.getTemplate(data.templateId);
15954
- if (!template) {
15955
- throw new Error(`Template with id "${data.templateId}" not found`);
15956
- }
15957
15333
  return await this.adapter.documents.create(data);
15958
15334
  }
15959
- /**
15960
- * Create a document with a template name instead of ID.
15961
- */
15962
- async createDocumentByTemplateName(templateName, data) {
15963
- const template = await this.templateService.getTemplateByNameOrThrow(templateName);
15964
- return await this.createDocument({
15965
- ...data,
15966
- templateId: template.id
15967
- });
15968
- }
15969
15335
  // ============================================================================
15970
15336
  // READ
15971
15337
  // ============================================================================
@@ -15997,13 +15363,6 @@ var DocumentService = class extends BaseService {
15997
15363
  }
15998
15364
  return await this.adapter.documents.findByIds(documentIds);
15999
15365
  }
16000
- /**
16001
- * Get the template for a document.
16002
- */
16003
- async getDocumentTemplate(documentId) {
16004
- const document2 = await this.getDocumentOrThrow(documentId);
16005
- return await this.templateService.getTemplateOrThrow(document2.templateId);
16006
- }
16007
15366
  // ============================================================================
16008
15367
  // LIST
16009
15368
  // ============================================================================
@@ -16094,27 +15453,19 @@ var DocumentService = class extends BaseService {
16094
15453
  if (!this.adapter.documentSlots) {
16095
15454
  throw new Error("Document slots repository is not configured");
16096
15455
  }
16097
- const document2 = await this.getDocumentOrThrow(documentId);
16098
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
16099
- const slotDef = template.slots.find((s) => s.name === data.slotName);
16100
- const isAdditional = !slotDef;
16101
- if (isAdditional && !template.allowAdditionalFiles) {
16102
- throw new Error(
16103
- `Slot "${data.slotName}" is not defined in template and additional files are not allowed`
16104
- );
16105
- }
15456
+ await this.getDocumentOrThrow(documentId);
16106
15457
  const existingSlot = await this.adapter.documentSlots.findByDocumentAndSlot(
16107
15458
  documentId,
16108
15459
  data.slotName
16109
15460
  );
16110
- if (existingSlot && !isAdditional) {
15461
+ if (existingSlot) {
16111
15462
  await this.adapter.documentSlots.delete(existingSlot.id);
16112
15463
  }
16113
15464
  const slot = await this.adapter.documentSlots.create({
16114
15465
  documentId,
16115
15466
  slotName: data.slotName,
16116
15467
  fileId: data.fileId,
16117
- isAdditional
15468
+ isAdditional: data.isAdditional ?? false
16118
15469
  });
16119
15470
  await this.recalculateStatus(documentId);
16120
15471
  return slot;
@@ -16148,13 +15499,9 @@ var DocumentService = class extends BaseService {
16148
15499
  * - failed: At least one job failed
16149
15500
  */
16150
15501
  async recalculateStatus(documentId) {
16151
- const document2 = await this.getDocumentOrThrow(documentId);
16152
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
15502
+ await this.getDocumentOrThrow(documentId);
16153
15503
  const slots = await this.getSlots(documentId);
16154
- const requiredSlots = template.slots.filter((s) => s.required);
16155
- const filledSlotNames = slots.reduce((set, s) => set.add(s.slotName), /* @__PURE__ */ new Set());
16156
- const allRequiredFilled = requiredSlots.every((s) => filledSlotNames.has(s.name));
16157
- if (!allRequiredFilled) {
15504
+ if (slots.length === 0) {
16158
15505
  return await this.updateStatus(documentId, "draft");
16159
15506
  }
16160
15507
  if (this.adapter.documentJobs) {
@@ -16189,13 +15536,12 @@ var DocumentService = class extends BaseService {
16189
15536
  return document2?.status !== "draft";
16190
15537
  }
16191
15538
  /**
16192
- * Get document with its template and slots.
15539
+ * Get document with its slots.
16193
15540
  */
16194
15541
  async getDocumentWithDetails(documentId) {
16195
15542
  const document2 = await this.getDocumentOrThrow(documentId);
16196
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
16197
15543
  const slots = await this.getSlots(documentId);
16198
- return { document: document2, template, slots };
15544
+ return { document: document2, slots };
16199
15545
  }
16200
15546
  // ============================================================================
16201
15547
  // RECORD DOCUMENTS
@@ -16258,7 +15604,7 @@ var DocumentService = class extends BaseService {
16258
15604
  *
16259
15605
  * This method:
16260
15606
  * 1. Uploads the file
16261
- * 2. Creates a document with the specified template
15607
+ * 2. Creates a document
16262
15608
  * 3. Adds the file to the document slot
16263
15609
  *
16264
15610
  * Note: The caller is responsible for updating record.values with the document ID.
@@ -16272,17 +15618,7 @@ var DocumentService = class extends BaseService {
16272
15618
  if (!this.adapter.documents) {
16273
15619
  throw new Error("Documents repository is not configured");
16274
15620
  }
16275
- const {
16276
- objectName,
16277
- recordId,
16278
- fileContent,
16279
- fileName,
16280
- mimeType,
16281
- fileSize,
16282
- uploadedBy,
16283
- title,
16284
- templateId = SYSTEM_TEMPLATE_IDS.GENERIC_DOCUMENT
16285
- } = input;
15621
+ const { objectName, recordId, fileContent, fileName, mimeType, fileSize, uploadedBy, title } = input;
16286
15622
  const uploadedFile = await this.fileService.uploadFile({
16287
15623
  content: fileContent,
16288
15624
  fileName,
@@ -16293,7 +15629,6 @@ var DocumentService = class extends BaseService {
16293
15629
  uploadedBy
16294
15630
  });
16295
15631
  const document2 = await this.createDocument({
16296
- templateId,
16297
15632
  title: title ?? fileName
16298
15633
  });
16299
15634
  const slot = await this.addSlot(document2.id, {
@@ -16313,10 +15648,7 @@ var DocumentProcessingService = class extends BaseService {
16313
15648
  constructor(adapter, config) {
16314
15649
  super(adapter);
16315
15650
  this.config = config;
16316
- this.templateService = new DocumentTemplateService(adapter);
16317
- this.documentService = new DocumentService(adapter, {
16318
- templateService: this.templateService
16319
- });
15651
+ this.documentService = new DocumentService(adapter);
16320
15652
  }
16321
15653
  // ============================================================================
16322
15654
  // OCR PROCESSING
@@ -16599,18 +15931,11 @@ var DocumentProcessingService = class extends BaseService {
16599
15931
  if (!this.adapter.documentJobs) {
16600
15932
  throw new Error("Document jobs repository is not configured");
16601
15933
  }
16602
- const document2 = await this.documentService.getDocumentOrThrow(documentId);
16603
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
16604
- if (!template.autoProcessing?.identityVerification?.enabled) {
16605
- throw new Error("Identity verification is not enabled for this document type");
16606
- }
15934
+ await this.documentService.getDocumentOrThrow(documentId);
16607
15935
  const job = await this.adapter.documentJobs.create({
16608
15936
  documentId,
16609
15937
  type: "identity_verification",
16610
- provider: this.config.identityAdapter.name,
16611
- input: {
16612
- documentType: template.autoProcessing.identityVerification.documentType
16613
- }
15938
+ provider: this.config.identityAdapter.name
16614
15939
  });
16615
15940
  return job;
16616
15941
  }
@@ -16683,17 +16008,16 @@ var DocumentProcessingService = class extends BaseService {
16683
16008
  * Called after all required slots are uploaded.
16684
16009
  */
16685
16010
  async triggerAutoProcessing(documentId) {
16686
- const document2 = await this.documentService.getDocumentOrThrow(documentId);
16687
- const template = await this.templateService.getTemplateOrThrow(document2.templateId);
16011
+ await this.documentService.getDocumentOrThrow(documentId);
16688
16012
  const slots = await this.documentService.getSlots(documentId);
16689
16013
  const jobs = [];
16690
- if (template.autoProcessing?.ocr?.enabled && this.config.ocrAdapter) {
16014
+ if (this.config.ocrAdapter) {
16691
16015
  for (const slot of slots) {
16692
16016
  const job = await this.processOcr(documentId, slot.slotName);
16693
16017
  jobs.push(job);
16694
16018
  }
16695
16019
  }
16696
- if (template.autoProcessing?.identityVerification?.enabled && this.config.identityAdapter) {
16020
+ if (this.config.identityAdapter) {
16697
16021
  const job = await this.verifyIdentity(documentId);
16698
16022
  jobs.push(job);
16699
16023
  }
@@ -16779,6 +16103,18 @@ var DocumentProcessingService = class extends BaseService {
16779
16103
  };
16780
16104
 
16781
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
+ }
16782
16118
  var FileService = class extends BaseService {
16783
16119
  constructor(adapter, options) {
16784
16120
  super(adapter);
@@ -16828,17 +16164,18 @@ var FileService = class extends BaseService {
16828
16164
  }
16829
16165
  input.folderPath = sanitized.replace(/^\/+/, "").replace(/\/+/g, "/");
16830
16166
  }
16167
+ const fileName = decodeFileName(input.fileName);
16831
16168
  const uploadResult = await this.adapter.storage.upload({
16832
16169
  content: input.content,
16833
- fileName: input.fileName,
16170
+ fileName,
16834
16171
  mimeType: input.mimeType,
16835
16172
  size: input.size,
16836
16173
  tenantId: this.tenantId,
16837
16174
  folderPath: input.folderPath
16838
16175
  });
16839
16176
  const file2 = await this.adapter.files.create({
16840
- name: input.fileName,
16841
- originalName: input.fileName,
16177
+ name: fileName,
16178
+ originalName: fileName,
16842
16179
  mimeType: input.mimeType,
16843
16180
  size: input.size,
16844
16181
  storageProvider: uploadResult.storageProvider,
@@ -18761,6 +18098,11 @@ export {
18761
18098
  AttributeInUseError,
18762
18099
  ObjectReferencedError,
18763
18100
  getErrorMessage,
18101
+ isFlowFieldsRow,
18102
+ isLayoutRow,
18103
+ isFlowDefinition,
18104
+ isFlowPublished,
18105
+ isSystemFlow,
18764
18106
  NoopGeocodingAdapter,
18765
18107
  SYSTEM_FIELD_NAMES,
18766
18108
  RESERVED_ATTRIBUTE_NAMES,
@@ -18770,14 +18112,19 @@ export {
18770
18112
  getSystemAttributeList,
18771
18113
  isSystemAttribute,
18772
18114
  isSystemAttributeObject,
18115
+ nodeTypeRegistry,
18116
+ getNodeOutputs,
18117
+ setNodeNext,
18118
+ getNodeSlotIds,
18119
+ validateNode,
18120
+ getFormFieldRefs,
18773
18121
  isSimpleFormNode,
18774
18122
  isAdvancedFormNode,
18775
18123
  isStartNode,
18776
18124
  isFormNode,
18777
18125
  isConditionNode,
18778
- isDocumentNode,
18126
+ isAssignNode,
18779
18127
  isEndNode,
18780
- getNodeOutputs,
18781
18128
  isConditionRule,
18782
18129
  isConditionGroup,
18783
18130
  eq,
@@ -18803,12 +18150,18 @@ export {
18803
18150
  createEmptyContext,
18804
18151
  getContextValue,
18805
18152
  setContextValue,
18153
+ isFormFieldsRow,
18806
18154
  DEFAULT_THEME,
18807
18155
  mergeWithDefaults,
18808
18156
  generateCssVariables,
18809
18157
  isInstanceEvent,
18810
18158
  isNodeEvent,
18811
18159
  isInvitationOrGrantEvent,
18160
+ ZONE_ORDER,
18161
+ ZONE_CONFIG,
18162
+ assignNodeZones,
18163
+ groupNodesByZone,
18164
+ getZoneAllowedTypes,
18812
18165
  ConditionOperatorSchema,
18813
18166
  ConditionRuleSchema,
18814
18167
  ConditionGroupSchema,
@@ -18818,7 +18171,9 @@ export {
18818
18171
  FlowRowSchema,
18819
18172
  FormNodeSchema,
18820
18173
  ConditionNodeSchema,
18821
- DocumentNodeSchema,
18174
+ AssignmentSourceSchema,
18175
+ AssignmentMappingSchema,
18176
+ AssignNodeSchema,
18822
18177
  EndNodeSchema,
18823
18178
  WorkflowNodeSchema,
18824
18179
  SlotModeSchema,
@@ -18915,21 +18270,12 @@ export {
18915
18270
  WorkflowFormBuilder,
18916
18271
  WorkflowSimpleFormBuilder,
18917
18272
  WorkflowConditionBuilder,
18273
+ WorkflowAssignBuilder,
18918
18274
  WorkflowEndBuilder,
18919
18275
  WorkflowStartBuilder,
18920
18276
  WorkflowBuilder,
18921
18277
  workflow,
18922
18278
  registry,
18923
- SYSTEM_TEMPLATE_IDS,
18924
- FRENCH_ID_CARD,
18925
- PASSPORT,
18926
- DRIVING_LICENSE,
18927
- PROOF_OF_ADDRESS,
18928
- SIGNABLE_CONTRACT,
18929
- GENERIC_DOCUMENT,
18930
- SYSTEM_TEMPLATES,
18931
- getSystemTemplate,
18932
- isSystemTemplate,
18933
18279
  WorkflowJwtService,
18934
18280
  hashOptions,
18935
18281
  cacheKeys,
@@ -18975,9 +18321,9 @@ export {
18975
18321
  complete,
18976
18322
  error,
18977
18323
  ConditionExecutor,
18978
- DocumentExecutor,
18979
18324
  EndExecutor,
18980
18325
  FormExecutor,
18326
+ AssignExecutor,
18981
18327
  StartExecutor,
18982
18328
  createDefaultExecutorRegistry,
18983
18329
  getDefaultExecutorRegistry,
@@ -19040,10 +18386,6 @@ export {
19040
18386
  RecordService,
19041
18387
  FormulaResolverService,
19042
18388
  RollupScheduler,
19043
- DocumentRenderError,
19044
- StorageDownloadNotSupportedError,
19045
- DocumentRendererService,
19046
- DocumentProcessingHook,
19047
18389
  GrantNotFoundError,
19048
18390
  GrantExpiredError,
19049
18391
  GrantRevokedError,
@@ -19058,10 +18400,6 @@ export {
19058
18400
  WorkflowRelationService,
19059
18401
  WorkflowService,
19060
18402
  UserProfileService,
19061
- DocumentGenerationTemplateNotFoundError,
19062
- DocumentGenerationNotConfiguredError,
19063
- DocumentGenerationService,
19064
- DocumentTemplateService,
19065
18403
  DocumentService,
19066
18404
  DocumentProcessingService,
19067
18405
  FileService,