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