@stndrds/schema 0.1.0-alpha.15 → 0.1.0-alpha.17

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.
Files changed (38) hide show
  1. package/dist/chunk-4D23X5XN.mjs +7257 -0
  2. package/dist/chunk-6RPWHMTH.mjs +7101 -0
  3. package/dist/chunk-BTVPMGYW.mjs +6327 -0
  4. package/dist/chunk-C7HZ55GJ.mjs +6882 -0
  5. package/dist/chunk-CMO3JEJL.mjs +7259 -0
  6. package/dist/chunk-JDUTFH3J.mjs +6940 -0
  7. package/dist/chunk-JWRGX3UW.mjs +7265 -0
  8. package/dist/chunk-OUSLILLE.mjs +7259 -0
  9. package/dist/chunk-PMJZPJDP.mjs +7265 -0
  10. package/dist/chunk-QAGN53HE.mjs +6327 -0
  11. package/dist/chunk-UUOAVXAI.mjs +6327 -0
  12. package/dist/chunk-VYZ3BOSB.mjs +6946 -0
  13. package/dist/chunk-XAX75NSP.mjs +6321 -0
  14. package/dist/chunk-YEV46K3M.mjs +7259 -0
  15. package/dist/chunk-ZG3PT3RL.mjs +6944 -0
  16. package/dist/index.d.mts +268 -4
  17. package/dist/index.d.ts +268 -4
  18. package/dist/index.js +1136 -168
  19. package/dist/index.mjs +35 -1
  20. package/dist/runtime--rp-TG-h.d.mts +5181 -0
  21. package/dist/runtime--rp-TG-h.d.ts +5181 -0
  22. package/dist/runtime-BPYJWZfC.d.mts +5130 -0
  23. package/dist/runtime-BPYJWZfC.d.ts +5130 -0
  24. package/dist/runtime-DRc_OhnM.d.mts +5142 -0
  25. package/dist/runtime-DRc_OhnM.d.ts +5142 -0
  26. package/dist/runtime-D_S3m9jf.d.mts +5142 -0
  27. package/dist/runtime-D_S3m9jf.d.ts +5142 -0
  28. package/dist/runtime-Db3rRxbJ.d.mts +4821 -0
  29. package/dist/runtime-Db3rRxbJ.d.ts +4821 -0
  30. package/dist/runtime-DlCiIIfr.d.mts +5130 -0
  31. package/dist/runtime-DlCiIIfr.d.ts +5130 -0
  32. package/dist/runtime-WqYxjhjJ.d.mts +5125 -0
  33. package/dist/runtime-WqYxjhjJ.d.ts +5125 -0
  34. package/dist/runtime.d.mts +1 -1
  35. package/dist/runtime.d.ts +1 -1
  36. package/dist/runtime.js +855 -222
  37. package/dist/runtime.mjs +5 -1
  38. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -129,8 +129,13 @@ __export(index_exports, {
129
129
  DEFAULT_ROLE_LABELS: () => DEFAULT_ROLE_LABELS,
130
130
  DEFAULT_ROLE_PERMISSIONS: () => DEFAULT_ROLE_PERMISSIONS,
131
131
  DuplicateError: () => DuplicateError,
132
+ EMPTY_VALUE_PLACEHOLDER: () => EMPTY_VALUE_PLACEHOLDER,
132
133
  FileNotFoundError: () => FileNotFoundError,
133
134
  FileService: () => FileService,
135
+ FlowBuilder: () => FlowBuilder,
136
+ FlowPageBuilder: () => FlowPageBuilder,
137
+ FlowRowBuilder: () => FlowRowBuilder,
138
+ FlowService: () => FlowService,
134
139
  ForbiddenError: () => ForbiddenError,
135
140
  GeocodingService: () => GeocodingService,
136
141
  GlobalSearchService: () => GlobalSearchService,
@@ -195,9 +200,12 @@ __export(index_exports, {
195
200
  currencyConfigSchema: () => currencyConfigSchema,
196
201
  date: () => date,
197
202
  dateConfigSchema: () => dateConfigSchema,
203
+ enrichValuesWithSelectLabels: () => enrichValuesWithSelectLabels,
198
204
  extractAttributeNames: () => extractAttributeNames,
199
205
  file: () => file,
200
206
  fileConfigSchema: () => fileConfigSchema,
207
+ flow: () => flow,
208
+ formatAttributeValue: () => formatAttributeValue,
201
209
  generateId: () => generateId,
202
210
  generatePrefixedId: () => generatePrefixedId,
203
211
  getAttributeConfigSchema: () => getAttributeConfigSchema,
@@ -205,9 +213,12 @@ __export(index_exports, {
205
213
  getSyncPreview: () => getSyncPreview,
206
214
  getViewSyncPreview: () => getViewSyncPreview,
207
215
  group: () => group,
216
+ isActivityTab: () => isActivityTab,
208
217
  isAdvancedFilterState: () => isAdvancedFilterState,
209
218
  isCustomTab: () => isCustomTab,
210
219
  isDefaultRole: () => isDefaultRole,
220
+ isFlowDefinition: () => isFlowDefinition,
221
+ isFlowPublished: () => isFlowPublished,
211
222
  isForbiddenError: () => isForbiddenError,
212
223
  isFormTab: () => isFormTab,
213
224
  isLabelExpression: () => isLabelExpression,
@@ -216,6 +227,7 @@ __export(index_exports, {
216
227
  isProtectedResourceError: () => isProtectedResourceError,
217
228
  isRecordComplete: () => isRecordComplete,
218
229
  isSchemaError: () => isSchemaError,
230
+ isSystemFlow: () => isSystemFlow,
219
231
  isTableTab: () => isTableTab,
220
232
  isValidationError: () => isValidationError,
221
233
  location: () => location,
@@ -392,6 +404,17 @@ function isNoValueOperator(operator) {
392
404
  return NO_VALUE_OPERATORS.includes(operator);
393
405
  }
394
406
 
407
+ // src/types/flows.ts
408
+ function isFlowDefinition(obj) {
409
+ return typeof obj === "object" && obj !== null && "slots" in obj && "pages" in obj && "relations" in obj && "status" in obj;
410
+ }
411
+ function isFlowPublished(flow2) {
412
+ return flow2.status === "published";
413
+ }
414
+ function isSystemFlow(flow2) {
415
+ return flow2.system === true;
416
+ }
417
+
395
418
  // src/types/geocoding.ts
396
419
  var NoopGeocodingAdapter = class {
397
420
  async autocomplete() {
@@ -415,6 +438,9 @@ function isTableTab(tab) {
415
438
  function isCustomTab(tab) {
416
439
  return tab.type === "custom";
417
440
  }
441
+ function isActivityTab(tab) {
442
+ return tab.type === "activity";
443
+ }
418
444
 
419
445
  // src/utils.ts
420
446
  function generateId() {
@@ -431,6 +457,168 @@ function generatePrefixedId(prefix) {
431
457
  return `${prefix}_${generateId()}`;
432
458
  }
433
459
 
460
+ // src/format.ts
461
+ var import_constants = require("@stndrds/constants");
462
+ var EMPTY_VALUE_PLACEHOLDER = "\u2014";
463
+ function formatText(value) {
464
+ return String(value);
465
+ }
466
+ function formatCheckbox(value) {
467
+ return value ? "Yes" : "No";
468
+ }
469
+ function formatNumber(value, attribute) {
470
+ if (typeof value !== "number") return String(value);
471
+ const decimals = attribute.decimals;
472
+ return value.toLocaleString(void 0, {
473
+ minimumFractionDigits: decimals,
474
+ maximumFractionDigits: decimals
475
+ });
476
+ }
477
+ function formatCurrency(value, _attribute) {
478
+ if (typeof value !== "object" || value === null) return String(value);
479
+ const currency2 = value;
480
+ if (!("value" in currency2 && "code" in currency2)) return String(value);
481
+ const formattedValue = currency2.value.toLocaleString(void 0, {
482
+ minimumFractionDigits: 2,
483
+ maximumFractionDigits: 2
484
+ });
485
+ return `${formattedValue} ${currency2.code}`;
486
+ }
487
+ function formatDate(value) {
488
+ if (value instanceof Date) {
489
+ return value.toISOString().split("T")[0];
490
+ }
491
+ if (typeof value === "string") {
492
+ const date2 = new Date(value);
493
+ if (!Number.isNaN(date2.getTime())) {
494
+ return date2.toISOString().split("T")[0];
495
+ }
496
+ }
497
+ return String(value);
498
+ }
499
+ function formatTimestamp(value) {
500
+ if (typeof value === "number") {
501
+ return new Date(value).toISOString();
502
+ }
503
+ if (value instanceof Date) {
504
+ return value.toISOString();
505
+ }
506
+ return String(value);
507
+ }
508
+ function formatPhone(value) {
509
+ if (typeof value !== "object" || value === null) return String(value);
510
+ const phone2 = value;
511
+ if (!("phoneNumber" in phone2)) return String(value);
512
+ if (phone2.countryCode) {
513
+ const country = (0, import_constants.getCountryByIso3)(phone2.countryCode);
514
+ const dial = country?.phoneCode ?? "";
515
+ return `${dial} ${phone2.phoneNumber}`.trim();
516
+ }
517
+ return phone2.phoneNumber;
518
+ }
519
+ function formatLocation(value, attribute) {
520
+ if (typeof value !== "object" || value === null) return String(value);
521
+ const loc = value;
522
+ const granularity = attribute.granularity ?? "full";
523
+ const parts = [];
524
+ switch (granularity) {
525
+ case "country":
526
+ if (loc.country) parts.push(loc.country);
527
+ break;
528
+ case "state":
529
+ if (loc.state) parts.push(loc.state);
530
+ if (loc.country) parts.push(loc.country);
531
+ break;
532
+ case "city":
533
+ if (loc.city) parts.push(loc.city);
534
+ if (loc.state) parts.push(loc.state);
535
+ if (loc.country) parts.push(loc.country);
536
+ break;
537
+ case "coordinates":
538
+ if (loc.latitude !== void 0 && loc.longitude !== void 0) {
539
+ parts.push(`${loc.latitude}, ${loc.longitude}`);
540
+ }
541
+ break;
542
+ case "address":
543
+ if (loc.address) parts.push(loc.address);
544
+ if (loc.city) parts.push(loc.city);
545
+ if (loc.state) parts.push(loc.state);
546
+ if (loc.country) parts.push(loc.country);
547
+ break;
548
+ default:
549
+ if (loc.address) parts.push(loc.address);
550
+ if (loc.city) parts.push(loc.city);
551
+ if (loc.state) parts.push(loc.state);
552
+ if (loc.postalCode) parts.push(loc.postalCode);
553
+ if (loc.country) parts.push(loc.country);
554
+ break;
555
+ }
556
+ return parts.join(", ") || EMPTY_VALUE_PLACEHOLDER;
557
+ }
558
+ function formatSelect(value, attribute) {
559
+ if (typeof value !== "string") return String(value);
560
+ const option = attribute.options?.find((o) => o.value === value);
561
+ return option?.label ?? String(value);
562
+ }
563
+ function formatMultiselect(value, attribute) {
564
+ if (!Array.isArray(value)) return String(value);
565
+ if (attribute.options) {
566
+ const labels = value.map((v) => attribute.options.find((o) => o.value === v)?.label).filter(Boolean);
567
+ return labels.join(", ");
568
+ }
569
+ return value.join(", ");
570
+ }
571
+ function formatRating(value, attribute) {
572
+ if (typeof value !== "number") return String(value);
573
+ const max = attribute.max ?? 5;
574
+ return `${value}/${max}`;
575
+ }
576
+ function formatAttributeValue(value, attribute) {
577
+ if (value === null || value === void 0 || value === "") {
578
+ return EMPTY_VALUE_PLACEHOLDER;
579
+ }
580
+ switch (attribute.type) {
581
+ case "text":
582
+ case "textarea":
583
+ return formatText(value);
584
+ case "checkbox":
585
+ return formatCheckbox(value);
586
+ case "number":
587
+ return formatNumber(value, attribute);
588
+ case "currency":
589
+ return formatCurrency(value, attribute);
590
+ case "date":
591
+ return formatDate(value);
592
+ case "timestamp":
593
+ return formatTimestamp(value);
594
+ case "phone":
595
+ return formatPhone(value);
596
+ case "location":
597
+ return formatLocation(value, attribute);
598
+ case "select":
599
+ case "status":
600
+ return formatSelect(value, attribute);
601
+ case "multiselect":
602
+ return formatMultiselect(value, attribute);
603
+ case "rating":
604
+ return formatRating(value, attribute);
605
+ // Unsupported types - return value as-is or placeholder
606
+ case "file":
607
+ case "user":
608
+ case "relation":
609
+ if (Array.isArray(value)) {
610
+ return value.join(", ");
611
+ }
612
+ return String(value);
613
+ default: {
614
+ if (Array.isArray(value)) {
615
+ return value.join(", ");
616
+ }
617
+ return String(value);
618
+ }
619
+ }
620
+ }
621
+
434
622
  // src/constants/index.ts
435
623
  init_default_roles();
436
624
 
@@ -634,8 +822,8 @@ function isForbiddenError(error) {
634
822
  }
635
823
 
636
824
  // src/builders/attribute-validators.ts
637
- var import_constants = require("@stndrds/constants");
638
- var VALID_ICONS = new Set(import_constants.ICONS);
825
+ var import_constants2 = require("@stndrds/constants");
826
+ var VALID_ICONS = new Set(import_constants2.ICONS);
639
827
  var ATTRIBUTE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
640
828
  var MAX_NAME_LENGTH = 63;
641
829
  var MAX_LABEL_LENGTH = 128;
@@ -1284,8 +1472,361 @@ function rating(config) {
1284
1472
  return new RatingAttributeBuilder(config.name, config.label);
1285
1473
  }
1286
1474
 
1475
+ // src/builders/flow-builder.ts
1476
+ var import_zod = require("zod");
1477
+ var FlowRowBuilder = class {
1478
+ /** @internal */
1479
+ constructor(pageBuilder, id, order) {
1480
+ this.pageBuilder = pageBuilder;
1481
+ this.rowData = {
1482
+ id,
1483
+ order,
1484
+ fields: []
1485
+ };
1486
+ }
1487
+ /**
1488
+ * Add a field to the current row
1489
+ *
1490
+ * @param slotId - Reference to a slot defined with .slot()
1491
+ * @param attribute - Attribute name on the object
1492
+ * @param options - Optional field configuration
1493
+ *
1494
+ * @example
1495
+ * ```typescript
1496
+ * .row("row-1")
1497
+ * .field("mr", "firstName")
1498
+ * .field("mr", "lastName")
1499
+ * ```
1500
+ */
1501
+ field(slotId, attribute, options) {
1502
+ const field = {
1503
+ id: `${this.rowData.id}-${slotId}-${attribute}`,
1504
+ slotId,
1505
+ attribute,
1506
+ label: options?.label,
1507
+ required: options?.required
1508
+ };
1509
+ this.rowData.fields.push(field);
1510
+ return this;
1511
+ }
1512
+ /**
1513
+ * Start a new row on the same page
1514
+ */
1515
+ row(id) {
1516
+ return this.pageBuilder._finalizeRow(this.rowData).row(id);
1517
+ }
1518
+ /**
1519
+ * Start a new page
1520
+ */
1521
+ page(id, label, description) {
1522
+ return this.pageBuilder._finalizeRow(this.rowData)._getFlow().page(id, label, description);
1523
+ }
1524
+ /**
1525
+ * Build the final flow definition
1526
+ */
1527
+ build() {
1528
+ return this.pageBuilder._finalizeRow(this.rowData)._getFlow().build();
1529
+ }
1530
+ };
1531
+ var FlowPageBuilder = class {
1532
+ /** @internal */
1533
+ constructor(flow2, id, label, description) {
1534
+ this.rowOrder = 0;
1535
+ this.flow = flow2;
1536
+ this.pageData = {
1537
+ id,
1538
+ label,
1539
+ description,
1540
+ order: 0,
1541
+ // Will be set when finalized
1542
+ rows: []
1543
+ };
1544
+ }
1545
+ /**
1546
+ * Add a row to the current page
1547
+ *
1548
+ * @param id - Unique row identifier
1549
+ *
1550
+ * @example
1551
+ * ```typescript
1552
+ * .page("identity", "Identité")
1553
+ * .row("name-row")
1554
+ * .field("mr", "firstName")
1555
+ * .field("mr", "lastName")
1556
+ * .row("email-row")
1557
+ * .field("mr", "email")
1558
+ * ```
1559
+ */
1560
+ row(id) {
1561
+ this.rowOrder++;
1562
+ return new FlowRowBuilder(this, id, this.rowOrder);
1563
+ }
1564
+ /**
1565
+ * Start a new page
1566
+ */
1567
+ page(id, label, description) {
1568
+ return this.flow._finalizePage(this.pageData).page(id, label, description);
1569
+ }
1570
+ /**
1571
+ * Build the final flow definition
1572
+ */
1573
+ build() {
1574
+ return this.flow._finalizePage(this.pageData).build();
1575
+ }
1576
+ /** @internal */
1577
+ _finalizeRow(row) {
1578
+ if (row.id && row.fields.length > 0) {
1579
+ this.pageData.rows?.push(row);
1580
+ }
1581
+ return this;
1582
+ }
1583
+ /** @internal */
1584
+ _getFlow() {
1585
+ return this.flow._finalizePage(this.pageData);
1586
+ }
1587
+ /** @internal */
1588
+ _getPageData() {
1589
+ return this.pageData;
1590
+ }
1591
+ };
1592
+ var FlowBuilder = class {
1593
+ constructor(name, label) {
1594
+ this.data = {
1595
+ slots: [],
1596
+ pages: [],
1597
+ relations: [],
1598
+ status: "published",
1599
+ // System flows are always published
1600
+ version: 1,
1601
+ system: true
1602
+ };
1603
+ this.validateName(name);
1604
+ this.data.name = name;
1605
+ this.data.label = label;
1606
+ }
1607
+ /**
1608
+ * Set flow description
1609
+ */
1610
+ description(value) {
1611
+ this.data.description = value;
1612
+ return this;
1613
+ }
1614
+ /**
1615
+ * Set flow icon
1616
+ */
1617
+ icon(value) {
1618
+ this.data.icon = value;
1619
+ return this;
1620
+ }
1621
+ /**
1622
+ * Set extensible metadata
1623
+ */
1624
+ metadata(value) {
1625
+ this.data.metadata = value;
1626
+ return this;
1627
+ }
1628
+ /**
1629
+ * Add a slot (object to create in the flow)
1630
+ *
1631
+ * @param id - Unique slot identifier (used to reference in fields and relations)
1632
+ * @param objectName - Name of the object to create
1633
+ * @param label - Display label for the slot
1634
+ * @param options - Optional slot configuration
1635
+ *
1636
+ * @example
1637
+ * ```typescript
1638
+ * .slot("mr", "contacts", "Monsieur", { color: "blue", icon: "user" })
1639
+ * .slot("company", "companies", "Entreprise", { color: "green" })
1640
+ * ```
1641
+ */
1642
+ slot(id, objectName, label, options) {
1643
+ if (this.data.slots?.some((s) => s.id === id)) {
1644
+ throw new Error(`[FlowBuilder] Duplicate slot id: "${id}"`);
1645
+ }
1646
+ this.data.slots?.push({
1647
+ id,
1648
+ objectName,
1649
+ label,
1650
+ color: options?.color,
1651
+ icon: options?.icon
1652
+ });
1653
+ return this;
1654
+ }
1655
+ /**
1656
+ * Define a relation between two slots
1657
+ * The source slot will be linked to the target slot via the specified attribute
1658
+ *
1659
+ * @param sourceSlotId - Slot that has the relation attribute
1660
+ * @param sourceAttribute - Relation attribute name on the source
1661
+ * @param targetSlotId - Target slot to link to
1662
+ *
1663
+ * @example
1664
+ * ```typescript
1665
+ * // mr.company → company (the contact's company field will be set to the created company)
1666
+ * .relation("mr", "company", "company")
1667
+ * ```
1668
+ */
1669
+ relation(sourceSlotId, sourceAttribute, targetSlotId) {
1670
+ this.data.relations?.push({
1671
+ id: `${sourceSlotId}-${sourceAttribute}-${targetSlotId}`,
1672
+ sourceSlotId,
1673
+ sourceAttribute,
1674
+ targetSlotId
1675
+ });
1676
+ return this;
1677
+ }
1678
+ /**
1679
+ * Start a new page/step in the flow
1680
+ *
1681
+ * @param id - Unique page identifier
1682
+ * @param label - Display label
1683
+ * @param description - Optional description
1684
+ *
1685
+ * @example
1686
+ * ```typescript
1687
+ * .page("identity", "Identité", "Informations personnelles")
1688
+ * .row("name")
1689
+ * .field("mr", "firstName")
1690
+ * .field("mr", "lastName")
1691
+ * ```
1692
+ */
1693
+ page(id, label, description) {
1694
+ return new FlowPageBuilder(this, id, label, description);
1695
+ }
1696
+ /**
1697
+ * Build and validate the flow definition
1698
+ *
1699
+ * @throws Error if validation fails
1700
+ */
1701
+ build() {
1702
+ this.validateRequiredData();
1703
+ const slotIds = new Set(this.data.slots?.map((s) => s.id) ?? []);
1704
+ this.validateFieldReferences(slotIds);
1705
+ this.validateRelationReferences(slotIds);
1706
+ this.validateNoCircularDependencies();
1707
+ return this.data;
1708
+ }
1709
+ /**
1710
+ * Validate that required data is present
1711
+ */
1712
+ validateRequiredData() {
1713
+ if (!this.data.slots || this.data.slots.length === 0) {
1714
+ throw new Error("[FlowBuilder] At least one slot is required. Use .slot() to add slots.");
1715
+ }
1716
+ if (!this.data.pages || this.data.pages.length === 0) {
1717
+ throw new Error("[FlowBuilder] At least one page is required. Use .page() to add pages.");
1718
+ }
1719
+ }
1720
+ /**
1721
+ * Validate that all field slotId references exist
1722
+ */
1723
+ validateFieldReferences(slotIds) {
1724
+ for (const page of this.data.pages ?? []) {
1725
+ for (const row of page.rows) {
1726
+ for (const field of row.fields) {
1727
+ if (!slotIds.has(field.slotId)) {
1728
+ throw new Error(
1729
+ `[FlowBuilder] Field "${field.id}" references unknown slot "${field.slotId}" in page "${page.id}". Available slots: ${[...slotIds].join(", ")}`
1730
+ );
1731
+ }
1732
+ }
1733
+ }
1734
+ }
1735
+ }
1736
+ /**
1737
+ * Validate that all relation references exist and are valid
1738
+ */
1739
+ validateRelationReferences(slotIds) {
1740
+ for (const relation2 of this.data.relations ?? []) {
1741
+ if (!slotIds.has(relation2.sourceSlotId)) {
1742
+ throw new Error(
1743
+ `[FlowBuilder] Relation references unknown source slot "${relation2.sourceSlotId}". Available slots: ${[...slotIds].join(", ")}`
1744
+ );
1745
+ }
1746
+ if (!slotIds.has(relation2.targetSlotId)) {
1747
+ throw new Error(
1748
+ `[FlowBuilder] Relation references unknown target slot "${relation2.targetSlotId}". Available slots: ${[...slotIds].join(", ")}`
1749
+ );
1750
+ }
1751
+ if (relation2.sourceSlotId === relation2.targetSlotId) {
1752
+ throw new Error(
1753
+ `[FlowBuilder] Relation cannot link a slot to itself: "${relation2.sourceSlotId}"`
1754
+ );
1755
+ }
1756
+ }
1757
+ }
1758
+ /**
1759
+ * @internal Used by FlowPageBuilder to finalize a page
1760
+ */
1761
+ _finalizePage(page) {
1762
+ if (page.id) {
1763
+ page.order = (this.data.pages?.length ?? 0) + 1;
1764
+ this.data.pages?.push(page);
1765
+ }
1766
+ return this;
1767
+ }
1768
+ /**
1769
+ * Validate flow name format (kebab-case)
1770
+ */
1771
+ validateName(name) {
1772
+ const flowNameSchema = import_zod.z.string().min(1, "Flow name cannot be empty").max(63, "Flow name is too long (max 63 characters)").regex(/^[a-z][a-z0-9-]*$/, {
1773
+ message: "Invalid flow name format.\nName must be in kebab-case:\n \u2705 Valid: 'couple-creation', 'new-contact', 'onboarding-flow'\n \u274C Invalid: 'CoupleCreation', 'new_contact', 'Onboarding Flow'"
1774
+ });
1775
+ try {
1776
+ flowNameSchema.parse(name);
1777
+ } catch (error) {
1778
+ if (error instanceof import_zod.z.ZodError) {
1779
+ throw new Error(`[FlowBuilder] ${error.issues[0].message}`);
1780
+ }
1781
+ throw error;
1782
+ }
1783
+ }
1784
+ /**
1785
+ * Validate that there are no circular dependencies in relations
1786
+ * Uses DFS to detect cycles in the dependency graph
1787
+ */
1788
+ validateNoCircularDependencies() {
1789
+ const relations = this.data.relations ?? [];
1790
+ if (relations.length === 0) return;
1791
+ const dependsOn = /* @__PURE__ */ new Map();
1792
+ for (const slot of this.data.slots ?? []) {
1793
+ dependsOn.set(slot.id, /* @__PURE__ */ new Set());
1794
+ }
1795
+ for (const rel of relations) {
1796
+ dependsOn.get(rel.sourceSlotId)?.add(rel.targetSlotId);
1797
+ }
1798
+ const visited = /* @__PURE__ */ new Set();
1799
+ const recursionStack = /* @__PURE__ */ new Set();
1800
+ const hasCycle = (slotId) => {
1801
+ visited.add(slotId);
1802
+ recursionStack.add(slotId);
1803
+ for (const dependency of dependsOn.get(slotId) ?? []) {
1804
+ if (!visited.has(dependency)) {
1805
+ if (hasCycle(dependency)) return true;
1806
+ } else if (recursionStack.has(dependency)) {
1807
+ return true;
1808
+ }
1809
+ }
1810
+ recursionStack.delete(slotId);
1811
+ return false;
1812
+ };
1813
+ for (const slot of this.data.slots ?? []) {
1814
+ if (!visited.has(slot.id)) {
1815
+ if (hasCycle(slot.id)) {
1816
+ throw new Error(
1817
+ "[FlowBuilder] Circular dependency detected in relations. Slots cannot have circular dependencies."
1818
+ );
1819
+ }
1820
+ }
1821
+ }
1822
+ }
1823
+ };
1824
+ function flow(name, label) {
1825
+ return new FlowBuilder(name, label);
1826
+ }
1827
+
1287
1828
  // src/builders/object-builder.ts
1288
- var import_zod = __toESM(require("zod"));
1829
+ var import_zod2 = __toESM(require("zod"));
1289
1830
  var ObjectBuilder = class {
1290
1831
  constructor(config) {
1291
1832
  this.validateName(config.name);
@@ -1418,14 +1959,14 @@ The labelExpression defines how records are displayed in lists and relations.
1418
1959
  if (name === void 0) {
1419
1960
  return;
1420
1961
  }
1421
- const objectNameSchema = import_zod.default.string().min(1, "Object name cannot be empty").max(63, "Object name is too long (max 63 characters)").regex(/^[a-z][a-z0-9-]*$/, {
1962
+ const objectNameSchema = import_zod2.default.string().min(1, "Object name cannot be empty").max(63, "Object name is too long (max 63 characters)").regex(/^[a-z][a-z0-9-]*$/, {
1422
1963
  message: "Invalid object name format.\nName must be in kebab-case (slug format):\n \u2705 Valid: 'products', 'user-profiles', 'order-items'\n \u274C Invalid: 'Products', 'userProfiles', 'user_profiles'"
1423
1964
  });
1424
1965
  try {
1425
1966
  objectNameSchema.parse(name);
1426
1967
  } catch (error) {
1427
- if (error instanceof import_zod.default.ZodError) {
1428
- throw new Error(`[ObjectBuilder] ${error.errors[0].message}`);
1968
+ if (error instanceof import_zod2.default.ZodError) {
1969
+ throw new Error(`[ObjectBuilder] ${error.issues[0].message}`);
1429
1970
  }
1430
1971
  throw error;
1431
1972
  }
@@ -1436,7 +1977,7 @@ function object(config) {
1436
1977
  }
1437
1978
 
1438
1979
  // src/builders/view-builder.ts
1439
- var import_zod2 = require("zod");
1980
+ var import_zod3 = require("zod");
1440
1981
  var GroupBuilder = class {
1441
1982
  constructor(id, label) {
1442
1983
  this.data = { fields: [] };
@@ -1792,14 +2333,14 @@ var ViewBuilder = class {
1792
2333
  * Validate view name format (kebab-case)
1793
2334
  */
1794
2335
  validateName(name) {
1795
- const viewNameSchema = import_zod2.z.string().min(1, "View name cannot be empty").max(63, "View name is too long (max 63 characters)").regex(/^[a-z][a-z0-9-]*$/, {
2336
+ const viewNameSchema = import_zod3.z.string().min(1, "View name cannot be empty").max(63, "View name is too long (max 63 characters)").regex(/^[a-z][a-z0-9-]*$/, {
1796
2337
  message: "Invalid view name format.\nName must be in kebab-case:\n \u2705 Valid: 'detail', 'list-view', 'company-detail'\n \u274C Invalid: 'Detail', 'listView', 'list_view'"
1797
2338
  });
1798
2339
  try {
1799
2340
  viewNameSchema.parse(name);
1800
2341
  } catch (error) {
1801
- if (error instanceof import_zod2.z.ZodError) {
1802
- throw new Error(`[ViewBuilder] ${error.errors[0].message}`);
2342
+ if (error instanceof import_zod3.z.ZodError) {
2343
+ throw new Error(`[ViewBuilder] ${error.issues[0].message}`);
1803
2344
  }
1804
2345
  throw error;
1805
2346
  }
@@ -2097,35 +2638,42 @@ Available views: ${availableNames}`
2097
2638
  var viewRegistry = new ViewRegistry();
2098
2639
 
2099
2640
  // src/validation/validators.ts
2100
- var import_zod3 = require("zod");
2101
- var baseConfigSchema = import_zod3.z.object({
2102
- disabled: import_zod3.z.boolean().optional(),
2103
- placeholder: import_zod3.z.string().optional(),
2104
- description: import_zod3.z.string().optional(),
2105
- defaultValue: import_zod3.z.unknown().optional(),
2106
- icon: import_zod3.z.string().optional(),
2107
- order: import_zod3.z.number().int().optional(),
2108
- hidden: import_zod3.z.boolean().optional(),
2109
- archived: import_zod3.z.boolean().optional(),
2110
- deprecated: import_zod3.z.boolean().optional(),
2111
- metadata: import_zod3.z.record(import_zod3.z.unknown()).optional()
2641
+ var import_zod4 = require("zod");
2642
+ var baseConfigSchema = import_zod4.z.object({
2643
+ disabled: import_zod4.z.boolean().optional(),
2644
+ placeholder: import_zod4.z.string().optional(),
2645
+ description: import_zod4.z.string().optional(),
2646
+ defaultValue: import_zod4.z.unknown().optional(),
2647
+ icon: import_zod4.z.string().optional(),
2648
+ order: import_zod4.z.number().int().optional(),
2649
+ hidden: import_zod4.z.boolean().optional(),
2650
+ archived: import_zod4.z.boolean().optional(),
2651
+ deprecated: import_zod4.z.boolean().optional(),
2652
+ metadata: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
2112
2653
  });
2113
- var optionSchema = import_zod3.z.object({
2114
- id: import_zod3.z.string().min(1),
2115
- label: import_zod3.z.string().min(1),
2116
- value: import_zod3.z.string().min(1),
2117
- color: import_zod3.z.string().optional(),
2118
- icon: import_zod3.z.string().optional(),
2119
- description: import_zod3.z.string().optional(),
2120
- group: import_zod3.z.enum(["idle", "in_progress", "finished"]).optional()
2654
+ var optionSchema = import_zod4.z.object({
2655
+ id: import_zod4.z.string().min(1),
2656
+ label: import_zod4.z.string().min(1),
2657
+ value: import_zod4.z.string().min(1),
2658
+ color: import_zod4.z.string().optional(),
2659
+ icon: import_zod4.z.string().optional(),
2660
+ description: import_zod4.z.string().optional(),
2661
+ group: import_zod4.z.enum(["idle", "in_progress", "finished"]).optional()
2121
2662
  });
2122
- var relationTargetSchema = import_zod3.z.object({
2123
- object: import_zod3.z.string().min(1),
2124
- displayTemplate: import_zod3.z.string().optional(),
2125
- filter: import_zod3.z.record(import_zod3.z.unknown()).optional()
2663
+ var optionsArraySchema = import_zod4.z.array(optionSchema).min(1).refine(
2664
+ (options) => {
2665
+ const values = options.map((o) => o.value);
2666
+ return new Set(values).size === values.length;
2667
+ },
2668
+ { message: "Duplicate option values are not allowed" }
2669
+ );
2670
+ var relationTargetSchema = import_zod4.z.object({
2671
+ object: import_zod4.z.string().min(1),
2672
+ displayTemplate: import_zod4.z.string().optional(),
2673
+ filter: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
2126
2674
  });
2127
- var documentTypeConfigSchema = import_zod3.z.object({
2128
- type: import_zod3.z.enum([
2675
+ var documentTypeConfigSchema = import_zod4.z.object({
2676
+ type: import_zod4.z.enum([
2129
2677
  "id_card",
2130
2678
  "passport",
2131
2679
  "incorporation_certificate",
@@ -2136,86 +2684,86 @@ var documentTypeConfigSchema = import_zod3.z.object({
2136
2684
  "proof_of_address",
2137
2685
  "custom"
2138
2686
  ]),
2139
- label: import_zod3.z.string(),
2140
- faces: import_zod3.z.array(import_zod3.z.enum(["front", "back", "single"])),
2141
- attributeMapping: import_zod3.z.array(
2142
- import_zod3.z.object({
2143
- attributeId: import_zod3.z.string(),
2144
- extractedKey: import_zod3.z.string(),
2145
- face: import_zod3.z.enum(["front", "back", "single"]).optional(),
2146
- required: import_zod3.z.boolean().optional()
2687
+ label: import_zod4.z.string(),
2688
+ faces: import_zod4.z.array(import_zod4.z.enum(["front", "back", "single"])),
2689
+ attributeMapping: import_zod4.z.array(
2690
+ import_zod4.z.object({
2691
+ attributeId: import_zod4.z.string(),
2692
+ extractedKey: import_zod4.z.string(),
2693
+ face: import_zod4.z.enum(["front", "back", "single"]).optional(),
2694
+ required: import_zod4.z.boolean().optional()
2147
2695
  })
2148
2696
  ).optional()
2149
2697
  });
2150
- var fileVerificationConfigSchema = import_zod3.z.object({
2151
- enabled: import_zod3.z.boolean(),
2152
- documentTypes: import_zod3.z.array(documentTypeConfigSchema),
2153
- autoExtract: import_zod3.z.boolean().optional(),
2154
- autoValidate: import_zod3.z.boolean().optional()
2698
+ var fileVerificationConfigSchema = import_zod4.z.object({
2699
+ enabled: import_zod4.z.boolean(),
2700
+ documentTypes: import_zod4.z.array(documentTypeConfigSchema),
2701
+ autoExtract: import_zod4.z.boolean().optional(),
2702
+ autoValidate: import_zod4.z.boolean().optional()
2155
2703
  });
2156
2704
  var textConfigSchema = baseConfigSchema.extend({
2157
- minLength: import_zod3.z.number().int().min(0).optional(),
2158
- maxLength: import_zod3.z.number().int().min(1).optional(),
2159
- pattern: import_zod3.z.string().optional()
2705
+ minLength: import_zod4.z.number().int().min(0).optional(),
2706
+ maxLength: import_zod4.z.number().int().min(1).optional(),
2707
+ pattern: import_zod4.z.string().optional()
2160
2708
  });
2161
2709
  var textareaConfigSchema = baseConfigSchema;
2162
2710
  var numberConfigSchema = baseConfigSchema.extend({
2163
- min: import_zod3.z.number().optional(),
2164
- max: import_zod3.z.number().optional(),
2165
- unit: import_zod3.z.enum(["integer", "decimal", "percentage"]).optional(),
2166
- decimals: import_zod3.z.number().int().min(0).optional()
2711
+ min: import_zod4.z.number().optional(),
2712
+ max: import_zod4.z.number().optional(),
2713
+ unit: import_zod4.z.enum(["integer", "decimal", "percentage"]).optional(),
2714
+ decimals: import_zod4.z.number().int().min(0).optional()
2167
2715
  });
2168
2716
  var checkboxConfigSchema = baseConfigSchema;
2169
2717
  var dateConfigSchema = baseConfigSchema.extend({
2170
- dateFormat: import_zod3.z.enum(["short", "long", "full", "relative"]).optional(),
2171
- minDate: import_zod3.z.string().optional(),
2172
- maxDate: import_zod3.z.string().optional()
2718
+ dateFormat: import_zod4.z.enum(["short", "long", "full", "relative"]).optional(),
2719
+ minDate: import_zod4.z.string().optional(),
2720
+ maxDate: import_zod4.z.string().optional()
2173
2721
  });
2174
2722
  var phoneConfigSchema = baseConfigSchema.extend({
2175
- defaultCountryCode: import_zod3.z.string().length(3).optional()
2723
+ defaultCountryCode: import_zod4.z.string().length(3).optional()
2176
2724
  });
2177
2725
  var currencyConfigSchema = baseConfigSchema.extend({
2178
- defaultCurrency: import_zod3.z.string().length(3).optional(),
2179
- allowedCurrencies: import_zod3.z.array(import_zod3.z.string().length(3)).optional()
2726
+ defaultCurrency: import_zod4.z.string().length(3).optional(),
2727
+ allowedCurrencies: import_zod4.z.array(import_zod4.z.string().length(3)).optional()
2180
2728
  });
2181
2729
  var statusConfigSchema = baseConfigSchema.extend({
2182
- options: import_zod3.z.array(optionSchema).min(1)
2730
+ options: optionsArraySchema
2183
2731
  });
2184
2732
  var locationConfigSchema = baseConfigSchema.extend({
2185
- granularity: import_zod3.z.enum(["full", "address", "city", "state", "country", "coordinates"]),
2186
- enableAutocomplete: import_zod3.z.boolean().optional(),
2187
- enableMap: import_zod3.z.boolean().optional(),
2188
- defaultCountry: import_zod3.z.string().length(3).optional(),
2189
- allowedCountries: import_zod3.z.array(import_zod3.z.string().length(3)).optional(),
2190
- displayFormat: import_zod3.z.enum(["single_line", "multi_line", "compact"]).optional()
2733
+ granularity: import_zod4.z.enum(["full", "address", "city", "state", "country", "coordinates"]),
2734
+ enableAutocomplete: import_zod4.z.boolean().optional(),
2735
+ enableMap: import_zod4.z.boolean().optional(),
2736
+ defaultCountry: import_zod4.z.string().length(3).optional(),
2737
+ allowedCountries: import_zod4.z.array(import_zod4.z.string().length(3)).optional(),
2738
+ displayFormat: import_zod4.z.enum(["single_line", "multi_line", "compact"]).optional()
2191
2739
  });
2192
2740
  var timestampConfigSchema = baseConfigSchema.extend({
2193
- autoUpdate: import_zod3.z.boolean().optional()
2741
+ autoUpdate: import_zod4.z.boolean().optional()
2194
2742
  });
2195
2743
  var selectConfigSchema = baseConfigSchema.extend({
2196
- options: import_zod3.z.array(optionSchema).min(1)
2744
+ options: optionsArraySchema
2197
2745
  });
2198
2746
  var multiselectConfigSchema = baseConfigSchema.extend({
2199
- options: import_zod3.z.array(optionSchema).min(1)
2747
+ options: optionsArraySchema
2200
2748
  });
2201
2749
  var fileConfigSchema = baseConfigSchema.extend({
2202
- maxFiles: import_zod3.z.number().int().min(1).optional(),
2203
- maxSize: import_zod3.z.number().int().min(1).optional(),
2204
- allowedTypes: import_zod3.z.array(import_zod3.z.string()).optional(),
2750
+ maxFiles: import_zod4.z.number().int().min(1).optional(),
2751
+ maxSize: import_zod4.z.number().int().min(1).optional(),
2752
+ allowedTypes: import_zod4.z.array(import_zod4.z.string()).optional(),
2205
2753
  verification: fileVerificationConfigSchema.optional()
2206
2754
  });
2207
2755
  var userConfigSchema = baseConfigSchema.extend({
2208
- allowedRoles: import_zod3.z.array(import_zod3.z.string()).optional()
2756
+ allowedRoles: import_zod4.z.array(import_zod4.z.string()).optional()
2209
2757
  });
2210
2758
  var relationConfigSchema = baseConfigSchema.extend({
2211
- targets: import_zod3.z.array(relationTargetSchema).min(1),
2212
- cardinality: import_zod3.z.enum(["one", "many"]),
2213
- minItems: import_zod3.z.number().int().min(0).optional(),
2214
- maxItems: import_zod3.z.number().int().min(1).optional()
2759
+ targets: import_zod4.z.array(relationTargetSchema).min(1),
2760
+ cardinality: import_zod4.z.enum(["one", "many"]),
2761
+ minItems: import_zod4.z.number().int().min(0).optional(),
2762
+ maxItems: import_zod4.z.number().int().min(1).optional()
2215
2763
  });
2216
2764
  var ratingConfigSchema = baseConfigSchema.extend({
2217
- max: import_zod3.z.number().int().min(1).optional(),
2218
- iconType: import_zod3.z.enum(["star", "heart", "thumbs", "number"]).optional()
2765
+ max: import_zod4.z.number().int().min(1).optional(),
2766
+ iconType: import_zod4.z.enum(["star", "heart", "thumbs", "number"]).optional()
2219
2767
  });
2220
2768
  var attributeConfigSchemas = {
2221
2769
  text: textConfigSchema,
@@ -2246,7 +2794,7 @@ function validateAttributeConfig(type, config) {
2246
2794
  }
2247
2795
  return {
2248
2796
  success: false,
2249
- errors: result.error.errors.map((err) => `${err.path.join(".")}: ${err.message}`)
2797
+ errors: result.error.issues.map((err) => `${err.path.join(".")}: ${err.message}`)
2250
2798
  };
2251
2799
  }
2252
2800
  function parseAttributeConfig(type, config) {
@@ -2259,7 +2807,7 @@ function safeParseAttributeConfig(type, config) {
2259
2807
  return result.success ? result.data : void 0;
2260
2808
  }
2261
2809
  function createTextValidator(attr) {
2262
- let schema = import_zod3.z.string();
2810
+ let schema = import_zod4.z.string();
2263
2811
  if (attr.minLength !== void 0) {
2264
2812
  schema = schema.min(
2265
2813
  attr.minLength,
@@ -2278,7 +2826,7 @@ function createTextValidator(attr) {
2278
2826
  return schema;
2279
2827
  }
2280
2828
  function createNumberValidator(attr) {
2281
- let schema = import_zod3.z.number();
2829
+ let schema = import_zod4.z.number();
2282
2830
  if (attr.min !== void 0) {
2283
2831
  schema = schema.min(attr.min, `${attr.label} must be at least ${attr.min}`);
2284
2832
  }
@@ -2291,81 +2839,75 @@ function createNumberValidator(attr) {
2291
2839
  return schema;
2292
2840
  }
2293
2841
  function createCheckboxValidator(_attr) {
2294
- return import_zod3.z.boolean();
2842
+ return import_zod4.z.boolean();
2295
2843
  }
2296
2844
  function createDateValidator(attr) {
2297
- return import_zod3.z.string().datetime({ message: `${attr.label} must be a valid ISO date` });
2845
+ return import_zod4.z.string().datetime({ message: `${attr.label} must be a valid ISO date` });
2298
2846
  }
2299
2847
  function createPhoneValidator(_attr) {
2300
- return import_zod3.z.object({
2301
- countryCode: import_zod3.z.string().length(3),
2302
- phoneNumber: import_zod3.z.string().min(1)
2848
+ return import_zod4.z.object({
2849
+ countryCode: import_zod4.z.string().length(3),
2850
+ phoneNumber: import_zod4.z.string().min(1)
2303
2851
  });
2304
2852
  }
2305
2853
  function createCurrencyValidator(_attr) {
2306
- return import_zod3.z.object({
2307
- code: import_zod3.z.string().length(3),
2308
- value: import_zod3.z.number().min(0)
2854
+ return import_zod4.z.object({
2855
+ code: import_zod4.z.string().length(3),
2856
+ value: import_zod4.z.number().min(0)
2309
2857
  });
2310
2858
  }
2311
2859
  function createStatusValidator(attr) {
2312
2860
  const validValues = attr.options.map((opt) => opt.value);
2313
- return import_zod3.z.enum(validValues, {
2314
- errorMap: () => ({
2315
- message: `${attr.label} must be one of: ${validValues.join(", ")}`
2316
- })
2861
+ return import_zod4.z.enum(validValues, {
2862
+ error: `${attr.label} must be one of: ${validValues.join(", ")}`
2317
2863
  });
2318
2864
  }
2319
2865
  function createSelectValidator(attr) {
2320
2866
  const validValues = attr.options.map((opt) => opt.value);
2321
- return import_zod3.z.enum(validValues, {
2322
- errorMap: () => ({
2323
- message: `${attr.label} must be one of: ${validValues.join(", ")}`
2324
- })
2867
+ return import_zod4.z.enum(validValues, {
2868
+ error: `${attr.label} must be one of: ${validValues.join(", ")}`
2325
2869
  });
2326
2870
  }
2327
2871
  function createMultiselectValidator(attr) {
2328
2872
  const validValues = attr.options.map((opt) => opt.value);
2329
- return import_zod3.z.array(
2330
- import_zod3.z.enum(validValues, {
2331
- errorMap: () => ({
2332
- message: `Each value must be one of: ${validValues.join(", ")}`
2333
- })
2873
+ return import_zod4.z.array(
2874
+ import_zod4.z.enum(validValues, {
2875
+ error: `Each value must be one of: ${validValues.join(", ")}`
2334
2876
  })
2335
2877
  );
2336
2878
  }
2337
2879
  function createLocationValidator(_attr) {
2338
- return import_zod3.z.object({
2339
- address: import_zod3.z.string().optional(),
2340
- address2: import_zod3.z.string().optional(),
2341
- city: import_zod3.z.string().optional(),
2342
- state: import_zod3.z.string().optional(),
2343
- postalCode: import_zod3.z.string().optional(),
2344
- country: import_zod3.z.string().length(3).optional(),
2345
- latitude: import_zod3.z.number().optional(),
2346
- longitude: import_zod3.z.number().optional()
2880
+ return import_zod4.z.object({
2881
+ address: import_zod4.z.string().optional(),
2882
+ address2: import_zod4.z.string().optional(),
2883
+ city: import_zod4.z.string().optional(),
2884
+ state: import_zod4.z.string().optional(),
2885
+ postalCode: import_zod4.z.string().optional(),
2886
+ country: import_zod4.z.string().length(3).optional(),
2887
+ latitude: import_zod4.z.number().optional(),
2888
+ longitude: import_zod4.z.number().optional()
2347
2889
  });
2348
2890
  }
2349
2891
  function createTimestampValidator(_attr) {
2350
- return import_zod3.z.number().int().positive();
2892
+ return import_zod4.z.number().int().positive();
2351
2893
  }
2352
2894
  function createFileValidator(_attr) {
2353
- return import_zod3.z.string().uuid();
2895
+ return import_zod4.z.string().uuid();
2354
2896
  }
2355
2897
  function createUserValidator(_attr) {
2356
- return import_zod3.z.string().uuid();
2898
+ return import_zod4.z.string().uuid();
2357
2899
  }
2358
2900
  function createSingleRelationValidator(attr) {
2359
- const uuidSchema = import_zod3.z.string().uuid({
2901
+ const uuidSchema = import_zod4.z.string().uuid({
2360
2902
  message: `${attr.label} must be a valid record ID`
2361
2903
  });
2362
- return import_zod3.z.union([uuidSchema, import_zod3.z.null()]);
2904
+ return import_zod4.z.union([uuidSchema, import_zod4.z.null()]);
2363
2905
  }
2364
2906
  function createMultiRelationValidator(attr) {
2365
- const uuidSchema = import_zod3.z.string().uuid({
2907
+ const uuidSchema = import_zod4.z.string().uuid({
2366
2908
  message: `Each ${attr.label} item must be a valid record ID`
2367
2909
  });
2368
- let arraySchema = import_zod3.z.array(uuidSchema);
2910
+ let arraySchema = import_zod4.z.array(uuidSchema);
2369
2911
  if (attr.minItems !== void 0) {
2370
2912
  arraySchema = arraySchema.min(
2371
2913
  attr.minItems,
@@ -2387,7 +2929,7 @@ function createRelationValidator(attr) {
2387
2929
  return createSingleRelationValidator(attr);
2388
2930
  }
2389
2931
  function createRatingValidator(attr) {
2390
- let schema = import_zod3.z.number().int().min(0);
2932
+ let schema = import_zod4.z.number().int().min(0);
2391
2933
  if (attr.max !== void 0) {
2392
2934
  schema = schema.max(attr.max, `${attr.label} must be at most ${attr.max}`);
2393
2935
  }
@@ -2426,7 +2968,7 @@ function createAttributeValidator(attr) {
2426
2968
  case "rating":
2427
2969
  return createRatingValidator(attr);
2428
2970
  default:
2429
- return import_zod3.z.unknown();
2971
+ return import_zod4.z.unknown();
2430
2972
  }
2431
2973
  }
2432
2974
  function createObjectValidator(objectDef) {
@@ -2438,7 +2980,7 @@ function createObjectValidator(objectDef) {
2438
2980
  }
2439
2981
  shape[attr.name] = validator;
2440
2982
  }
2441
- return import_zod3.z.object(shape);
2983
+ return import_zod4.z.object(shape);
2442
2984
  }
2443
2985
  function validateAttribute(attr, value) {
2444
2986
  const validator = createAttributeValidator(attr);
@@ -2454,7 +2996,7 @@ function validateAttribute(attr, value) {
2454
2996
  }
2455
2997
  return {
2456
2998
  success: false,
2457
- errors: result.error.errors.map((err) => ({
2999
+ errors: result.error.issues.map((err) => ({
2458
3000
  path: [attr.name, ...err.path.map(String)],
2459
3001
  message: err.message
2460
3002
  }))
@@ -2471,7 +3013,7 @@ function validateObject(objectDef, data) {
2471
3013
  }
2472
3014
  return {
2473
3015
  success: false,
2474
- errors: result.error.errors.map((err) => ({
3016
+ errors: result.error.issues.map((err) => ({
2475
3017
  path: err.path.map(String),
2476
3018
  message: err.message
2477
3019
  }))
@@ -2492,7 +3034,7 @@ function createDraftValidator(objectDef) {
2492
3034
  const validator = createAttributeValidator(attr).optional();
2493
3035
  shape[attr.name] = validator;
2494
3036
  }
2495
- return import_zod3.z.object(shape);
3037
+ return import_zod4.z.object(shape);
2496
3038
  }
2497
3039
  function validateDraft(objectDef, data) {
2498
3040
  const validator = createDraftValidator(objectDef);
@@ -2505,7 +3047,7 @@ function validateDraft(objectDef, data) {
2505
3047
  }
2506
3048
  return {
2507
3049
  success: false,
2508
- errors: result.error.errors.map((err) => ({
3050
+ errors: result.error.issues.map((err) => ({
2509
3051
  path: err.path.map(String),
2510
3052
  message: err.message
2511
3053
  }))
@@ -2607,6 +3149,24 @@ function extractAttributeNames(template) {
2607
3149
  }
2608
3150
  return names;
2609
3151
  }
3152
+ function hasOptions(attr) {
3153
+ return "options" in attr && Array.isArray(attr.options) && attr.options.length > 0;
3154
+ }
3155
+ function enrichValuesWithSelectLabels(values, attributes) {
3156
+ const enriched = { ...values };
3157
+ for (const attr of attributes) {
3158
+ const value = values[attr.name];
3159
+ if (value == null) continue;
3160
+ const isSelectLike = attr.type === "select" || attr.type === "status" || attr.type === "multiselect";
3161
+ if (!(isSelectLike && hasOptions(attr))) continue;
3162
+ if (attr.type === "multiselect" && Array.isArray(value) && value.length === 0) continue;
3163
+ const formatted = formatAttributeValue(value, attr);
3164
+ if (formatted && formatted !== EMPTY_VALUE_PLACEHOLDER) {
3165
+ enriched[attr.name] = formatted;
3166
+ }
3167
+ }
3168
+ return enriched;
3169
+ }
2610
3170
 
2611
3171
  // src/runtime/mock-adapter.ts
2612
3172
  function createMockObjectsRepository(stores) {
@@ -3083,14 +3643,31 @@ function createMockObjectRecordsRepository(stores) {
3083
3643
  (options.offset ?? 0) + options.limit
3084
3644
  );
3085
3645
  }
3646
+ const attributesByObjectId = /* @__PURE__ */ new Map();
3647
+ for (const attr of stores.attributes.values()) {
3648
+ if (!attributesByObjectId.has(attr.objectId)) {
3649
+ attributesByObjectId.set(attr.objectId, []);
3650
+ }
3651
+ attributesByObjectId.get(attr.objectId)?.push(attr);
3652
+ }
3086
3653
  const results = matchingRecords.map((r) => {
3087
3654
  const obj = objectsMap.get(r.objectId);
3088
3655
  const labelExpression = obj?.labelExpression ?? "{{ name }}";
3656
+ const dbAttrs = attributesByObjectId.get(r.objectId) ?? [];
3657
+ const attrs = dbAttrs.map((a) => ({
3658
+ ...a.config,
3659
+ id: a.id,
3660
+ name: a.name,
3661
+ type: a.type,
3662
+ label: a.config.label ?? a.name,
3663
+ required: a.config.required ?? false
3664
+ }));
3665
+ const enrichedValues = enrichValuesWithSelectLabels(r.values, attrs);
3089
3666
  return {
3090
3667
  objectId: r.objectId,
3091
3668
  objectName: obj?.name ?? "unknown",
3092
3669
  objectLabel: obj?.label ?? "Unknown",
3093
- label: renderLabelExpression(labelExpression, r.values),
3670
+ label: renderLabelExpression(labelExpression, enrichedValues),
3094
3671
  recordId: r.id,
3095
3672
  values: r.values,
3096
3673
  completionStatus: r.completionStatus,
@@ -3507,6 +4084,10 @@ var AuditService = class {
3507
4084
  this.options = options;
3508
4085
  this.buffer = [];
3509
4086
  this.flushTimer = null;
4087
+ /** Prevents concurrent flush operations */
4088
+ this.isFlushing = false;
4089
+ /** Pending flush promise to allow waiting on concurrent flush */
4090
+ this.flushPromise = null;
3510
4091
  if (options?.async && options.flushIntervalMs) {
3511
4092
  this.startFlushTimer();
3512
4093
  }
@@ -3659,14 +4240,23 @@ var AuditService = class {
3659
4240
  // ============================================================================
3660
4241
  /**
3661
4242
  * Flush buffered logs to the database
4243
+ * Protected against concurrent flush calls
3662
4244
  */
3663
4245
  async flush() {
4246
+ if (this.isFlushing && this.flushPromise) {
4247
+ return this.flushPromise;
4248
+ }
3664
4249
  if (this.buffer.length === 0 || !this.adapter.audit) {
3665
4250
  return;
3666
4251
  }
4252
+ this.isFlushing = true;
3667
4253
  const entries = [...this.buffer];
3668
4254
  this.buffer = [];
3669
- await this.adapter.audit.createMany(entries);
4255
+ this.flushPromise = this.adapter.audit.createMany(entries).finally(() => {
4256
+ this.isFlushing = false;
4257
+ this.flushPromise = null;
4258
+ });
4259
+ return this.flushPromise;
3670
4260
  }
3671
4261
  /**
3672
4262
  * Clean up resources (stop timer, flush remaining logs)
@@ -3688,6 +4278,12 @@ var AuditService = class {
3688
4278
  if (!this.adapter.audit) {
3689
4279
  return;
3690
4280
  }
4281
+ if (entry.actorId && !entry.actorEmail && entry.actorType === "user") {
4282
+ const profile = await this.adapter.userProfiles.findById(entry.actorId);
4283
+ if (profile) {
4284
+ entry.actorEmail = profile.email;
4285
+ }
4286
+ }
3691
4287
  if (this.options?.async) {
3692
4288
  this.buffer.push(entry);
3693
4289
  const batchSize = this.options.batchSize ?? 10;
@@ -3925,6 +4521,324 @@ var FileService = class {
3925
4521
  }
3926
4522
  };
3927
4523
 
4524
+ // src/runtime/services/flow.service.ts
4525
+ var FlowService = class {
4526
+ constructor(adapter, systemFlows = []) {
4527
+ this.adapter = adapter;
4528
+ this.systemFlows = new Map(systemFlows.map((f) => [f.name, f]));
4529
+ }
4530
+ /**
4531
+ * Get all flows for a tenant (system + custom)
4532
+ */
4533
+ async getAllFlows(tenantId) {
4534
+ const systemFlowsList = Array.from(this.systemFlows.values());
4535
+ if (!this.adapter.flows) {
4536
+ return systemFlowsList;
4537
+ }
4538
+ const dbFlows = await this.adapter.flows.findAllForTenant(tenantId);
4539
+ const customFlows = dbFlows.filter((f) => !f.system).map(this.convertDBFlowToDefinition);
4540
+ return [...systemFlowsList, ...customFlows];
4541
+ }
4542
+ /**
4543
+ * Get published flows only
4544
+ */
4545
+ async getPublishedFlows(tenantId) {
4546
+ const allFlows = await this.getAllFlows(tenantId);
4547
+ return allFlows.filter((f) => f.status === "published");
4548
+ }
4549
+ /**
4550
+ * Get a specific flow by name
4551
+ */
4552
+ async getFlow(name, tenantId) {
4553
+ const systemFlow = this.systemFlows.get(name);
4554
+ if (systemFlow) {
4555
+ return systemFlow;
4556
+ }
4557
+ if (!this.adapter.flows) {
4558
+ return null;
4559
+ }
4560
+ const dbFlow = await this.adapter.flows.findByName(tenantId, name);
4561
+ if (dbFlow) {
4562
+ return this.convertDBFlowToDefinition(dbFlow);
4563
+ }
4564
+ return null;
4565
+ }
4566
+ /**
4567
+ * Get a flow by ID
4568
+ */
4569
+ async getFlowById(flowId) {
4570
+ if (!this.adapter.flows) {
4571
+ return null;
4572
+ }
4573
+ const dbFlow = await this.adapter.flows.findById(flowId);
4574
+ if (dbFlow) {
4575
+ return this.convertDBFlowToDefinition(dbFlow);
4576
+ }
4577
+ return null;
4578
+ }
4579
+ /**
4580
+ * Create a new custom flow (as draft)
4581
+ */
4582
+ async createFlow(input, tenantId) {
4583
+ if (!this.adapter.flows) {
4584
+ throw new SchemaError(
4585
+ "Flows feature is not enabled. Database adapter does not support flows.",
4586
+ SchemaErrorCode.UNKNOWN,
4587
+ { feature: "flows" }
4588
+ );
4589
+ }
4590
+ this.validateFlowName(input.name);
4591
+ const existing = await this.adapter.flows.findByName(tenantId, input.name);
4592
+ if (existing) {
4593
+ throw new SchemaError(
4594
+ `Flow "${input.name}" already exists`,
4595
+ SchemaErrorCode.DUPLICATE_OBJECT,
4596
+ { flowName: input.name }
4597
+ );
4598
+ }
4599
+ if (this.systemFlows.has(input.name)) {
4600
+ throw new SchemaError(
4601
+ `Cannot create flow "${input.name}": a system flow with this name already exists`,
4602
+ SchemaErrorCode.DUPLICATE_OBJECT,
4603
+ { flowName: input.name, system: true }
4604
+ );
4605
+ }
4606
+ this.validateFlowStructure(input);
4607
+ const dbFlow = await this.adapter.flows.create({
4608
+ tenantId,
4609
+ name: input.name,
4610
+ label: input.label,
4611
+ description: input.description,
4612
+ icon: input.icon,
4613
+ status: "draft",
4614
+ version: 1,
4615
+ slots: input.slots,
4616
+ pages: input.pages,
4617
+ relations: input.relations,
4618
+ system: false,
4619
+ metadata: input.metadata
4620
+ });
4621
+ return this.convertDBFlowToDefinition(dbFlow);
4622
+ }
4623
+ /**
4624
+ * Update a custom flow
4625
+ */
4626
+ async updateFlow(flowId, input) {
4627
+ if (!this.adapter.flows) {
4628
+ throw new SchemaError("Flows feature is not enabled.", SchemaErrorCode.UNKNOWN, {
4629
+ feature: "flows"
4630
+ });
4631
+ }
4632
+ const dbFlow = await this.adapter.flows.findById(flowId);
4633
+ if (!dbFlow) {
4634
+ throw new NotFoundError("Flow", flowId);
4635
+ }
4636
+ if (dbFlow.system) {
4637
+ throw new ProtectedResourceError("object", dbFlow.name, "modify");
4638
+ }
4639
+ if (input.slots || input.pages || input.relations) {
4640
+ this.validateFlowStructure({
4641
+ name: dbFlow.name,
4642
+ label: input.label ?? dbFlow.label,
4643
+ slots: input.slots ?? dbFlow.slots,
4644
+ pages: input.pages ?? dbFlow.pages,
4645
+ relations: input.relations ?? dbFlow.relations
4646
+ });
4647
+ }
4648
+ const updated = await this.adapter.flows.update(flowId, {
4649
+ label: input.label,
4650
+ description: input.description,
4651
+ icon: input.icon,
4652
+ slots: input.slots,
4653
+ pages: input.pages,
4654
+ relations: input.relations,
4655
+ metadata: input.metadata
4656
+ });
4657
+ return this.convertDBFlowToDefinition(updated);
4658
+ }
4659
+ /**
4660
+ * Publish a flow
4661
+ */
4662
+ async publishFlow(flowId) {
4663
+ if (!this.adapter.flows) {
4664
+ throw new SchemaError("Flows feature is not enabled.", SchemaErrorCode.UNKNOWN, {
4665
+ feature: "flows"
4666
+ });
4667
+ }
4668
+ const dbFlow = await this.adapter.flows.findById(flowId);
4669
+ if (!dbFlow) {
4670
+ throw new NotFoundError("Flow", flowId);
4671
+ }
4672
+ if (dbFlow.system) {
4673
+ throw new ProtectedResourceError("object", dbFlow.name, "modify");
4674
+ }
4675
+ if (dbFlow.status === "published") {
4676
+ return this.convertDBFlowToDefinition(dbFlow);
4677
+ }
4678
+ this.validateFlowStructure({
4679
+ name: dbFlow.name,
4680
+ label: dbFlow.label,
4681
+ slots: dbFlow.slots,
4682
+ pages: dbFlow.pages,
4683
+ relations: dbFlow.relations
4684
+ });
4685
+ const updated = await this.adapter.flows.update(flowId, {
4686
+ status: "published",
4687
+ version: dbFlow.version + 1
4688
+ });
4689
+ return this.convertDBFlowToDefinition(updated);
4690
+ }
4691
+ /**
4692
+ * Archive a flow
4693
+ */
4694
+ async archiveFlow(flowId) {
4695
+ if (!this.adapter.flows) {
4696
+ throw new SchemaError("Flows feature is not enabled.", SchemaErrorCode.UNKNOWN, {
4697
+ feature: "flows"
4698
+ });
4699
+ }
4700
+ const dbFlow = await this.adapter.flows.findById(flowId);
4701
+ if (!dbFlow) {
4702
+ throw new NotFoundError("Flow", flowId);
4703
+ }
4704
+ if (dbFlow.system) {
4705
+ throw new ProtectedResourceError("object", dbFlow.name, "modify");
4706
+ }
4707
+ const updated = await this.adapter.flows.update(flowId, {
4708
+ status: "archived"
4709
+ });
4710
+ return this.convertDBFlowToDefinition(updated);
4711
+ }
4712
+ /**
4713
+ * Delete a custom flow
4714
+ */
4715
+ async deleteFlow(flowId) {
4716
+ if (!this.adapter.flows) {
4717
+ throw new SchemaError("Flows feature is not enabled.", SchemaErrorCode.UNKNOWN, {
4718
+ feature: "flows"
4719
+ });
4720
+ }
4721
+ const dbFlow = await this.adapter.flows.findById(flowId);
4722
+ if (!dbFlow) {
4723
+ throw new NotFoundError("Flow", flowId);
4724
+ }
4725
+ if (dbFlow.system) {
4726
+ throw new ProtectedResourceError("object", dbFlow.name, "delete");
4727
+ }
4728
+ await this.adapter.flows.delete(flowId);
4729
+ }
4730
+ // ============================================================================
4731
+ // PRIVATE HELPERS
4732
+ // ============================================================================
4733
+ /**
4734
+ * Validate flow name format (kebab-case)
4735
+ */
4736
+ validateFlowName(name) {
4737
+ if (!name || name.length === 0) {
4738
+ throw new ValidationError("Flow name cannot be empty", [
4739
+ { path: ["name"], message: "Flow name cannot be empty" }
4740
+ ]);
4741
+ }
4742
+ if (name.length > 63) {
4743
+ throw new ValidationError("Flow name is too long", [
4744
+ { path: ["name"], message: "Flow name is too long (max 63 characters)" }
4745
+ ]);
4746
+ }
4747
+ const kebabCaseRegex = /^[a-z][a-z0-9-]*$/;
4748
+ if (!kebabCaseRegex.test(name)) {
4749
+ throw new ValidationError("Invalid flow name format", [
4750
+ {
4751
+ path: ["name"],
4752
+ message: "Flow name must be in kebab-case (e.g., 'couple-creation', 'new-contact')"
4753
+ }
4754
+ ]);
4755
+ }
4756
+ }
4757
+ /**
4758
+ * Validate flow structure (slots, pages, relations)
4759
+ */
4760
+ validateFlowStructure(input) {
4761
+ if (!input.slots || input.slots.length === 0) {
4762
+ throw new ValidationError("Flow must have at least one slot", [
4763
+ { path: ["slots"], message: "Flow must have at least one slot" }
4764
+ ]);
4765
+ }
4766
+ if (!input.pages || input.pages.length === 0) {
4767
+ throw new ValidationError("Flow must have at least one page", [
4768
+ { path: ["pages"], message: "Flow must have at least one page" }
4769
+ ]);
4770
+ }
4771
+ const slotIds = new Set(input.slots.map((s) => s.id));
4772
+ if (slotIds.size !== input.slots.length) {
4773
+ throw new ValidationError("Duplicate slot IDs detected", [
4774
+ { path: ["slots"], message: "Duplicate slot IDs detected" }
4775
+ ]);
4776
+ }
4777
+ for (const page of input.pages) {
4778
+ for (const row of page.rows) {
4779
+ for (const field of row.fields) {
4780
+ if (!slotIds.has(field.slotId)) {
4781
+ throw new ValidationError("Field references unknown slot", [
4782
+ {
4783
+ path: ["pages", page.id, "rows", row.id, "fields", field.id],
4784
+ message: `Field "${field.id}" references unknown slot "${field.slotId}"`
4785
+ }
4786
+ ]);
4787
+ }
4788
+ }
4789
+ }
4790
+ }
4791
+ for (const relation2 of input.relations) {
4792
+ if (!slotIds.has(relation2.sourceSlotId)) {
4793
+ throw new ValidationError("Relation references unknown source slot", [
4794
+ {
4795
+ path: ["relations", relation2.id, "sourceSlotId"],
4796
+ message: `Relation references unknown source slot "${relation2.sourceSlotId}"`
4797
+ }
4798
+ ]);
4799
+ }
4800
+ if (!slotIds.has(relation2.targetSlotId)) {
4801
+ throw new ValidationError("Relation references unknown target slot", [
4802
+ {
4803
+ path: ["relations", relation2.id, "targetSlotId"],
4804
+ message: `Relation references unknown target slot "${relation2.targetSlotId}"`
4805
+ }
4806
+ ]);
4807
+ }
4808
+ if (relation2.sourceSlotId === relation2.targetSlotId) {
4809
+ throw new ValidationError("Relation cannot link a slot to itself", [
4810
+ {
4811
+ path: ["relations", relation2.id],
4812
+ message: `Relation cannot link a slot to itself: "${relation2.sourceSlotId}"`
4813
+ }
4814
+ ]);
4815
+ }
4816
+ }
4817
+ }
4818
+ /**
4819
+ * Convert database flow to FlowDefinition
4820
+ */
4821
+ convertDBFlowToDefinition(dbFlow) {
4822
+ return {
4823
+ id: dbFlow.id,
4824
+ name: dbFlow.name,
4825
+ label: dbFlow.label,
4826
+ description: dbFlow.description,
4827
+ icon: dbFlow.icon,
4828
+ status: dbFlow.status,
4829
+ version: dbFlow.version,
4830
+ slots: dbFlow.slots,
4831
+ pages: dbFlow.pages,
4832
+ relations: dbFlow.relations,
4833
+ system: dbFlow.system,
4834
+ tenantId: dbFlow.tenantId,
4835
+ metadata: dbFlow.metadata,
4836
+ createdAt: dbFlow.createdAt,
4837
+ updatedAt: dbFlow.updatedAt
4838
+ };
4839
+ }
4840
+ };
4841
+
3928
4842
  // src/runtime/services/geocoding.service.ts
3929
4843
  var GeocodingService = class {
3930
4844
  constructor(adapter) {
@@ -4390,14 +5304,15 @@ var ObjectSchemaService = class {
4390
5304
  });
4391
5305
  }
4392
5306
  }
5307
+ const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
5308
+ const attributes = dbAttributes.map((attr) => this.convertDBAttributeToAttribute(attr));
4393
5309
  if (updates.labelExpression !== void 0 && updates.labelExpression !== oldValues.labelExpression) {
4394
5310
  const newExpression = updates.labelExpression;
4395
- await this.adapter.objectRecords.batchRefreshLabels(
4396
- objectId,
4397
- (values) => renderLabelExpression(newExpression, values)
4398
- );
5311
+ await this.adapter.objectRecords.batchRefreshLabels(objectId, (values) => {
5312
+ const enrichedValues = enrichValuesWithSelectLabels(values, attributes);
5313
+ return renderLabelExpression(newExpression, enrichedValues);
5314
+ });
4399
5315
  }
4400
- const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
4401
5316
  return this.convertDBObjectToDefinition(updatedDbObject, dbAttributes);
4402
5317
  }
4403
5318
  /**
@@ -4716,6 +5631,8 @@ var PermissionService = class {
4716
5631
  this.adapter = adapter;
4717
5632
  this.tenantId = tenantId;
4718
5633
  this.cache = /* @__PURE__ */ new Map();
5634
+ /** Track pending permission fetches to prevent duplicate concurrent requests */
5635
+ this.pendingFetches = /* @__PURE__ */ new Map();
4719
5636
  if (!adapter.permissions) {
4720
5637
  throw new Error(
4721
5638
  "PermissionService requires a DatabaseAdapter with permissions repository. Make sure your adapter implements the permissions property."
@@ -4864,6 +5781,23 @@ var PermissionService = class {
4864
5781
  if (cached && cached.expiresAt > Date.now()) {
4865
5782
  return cached.permissions;
4866
5783
  }
5784
+ const pendingFetch = this.pendingFetches.get(cacheKey);
5785
+ if (pendingFetch) {
5786
+ return pendingFetch;
5787
+ }
5788
+ const fetchPromise = this.fetchAndCachePermissions(userProfileId, cacheKey);
5789
+ this.pendingFetches.set(cacheKey, fetchPromise);
5790
+ try {
5791
+ return await fetchPromise;
5792
+ } finally {
5793
+ this.pendingFetches.delete(cacheKey);
5794
+ }
5795
+ }
5796
+ /**
5797
+ * Fetch permissions from database and cache the result
5798
+ * @internal
5799
+ */
5800
+ async fetchAndCachePermissions(userProfileId, cacheKey) {
4867
5801
  const permissions = await this.permissionsRepo.getEffectivePermissions(
4868
5802
  userProfileId,
4869
5803
  this.tenantId
@@ -5246,8 +6180,11 @@ var RelationService = class {
5246
6180
  async validateRelationsOrThrow(schema, data) {
5247
6181
  const result = await this.validateRelations(schema, data);
5248
6182
  if (!result.valid) {
5249
- const messages = result.errors.map((e) => `${e.attribute}: ${e.message}`).join("; ");
5250
- throw new Error(`Relation validation failed: ${messages}`);
6183
+ const errors = result.errors.map((e) => ({
6184
+ path: [e.attribute],
6185
+ message: e.message
6186
+ }));
6187
+ throw new ValidationError("Relation validation failed", errors);
5251
6188
  }
5252
6189
  }
5253
6190
  // ============================================================================
@@ -5295,7 +6232,8 @@ var RelationService = class {
5295
6232
  totalCount += result.total;
5296
6233
  for (const record of result.records) {
5297
6234
  const template = target.displayTemplate || objectSchema.labelExpression;
5298
- const label = renderLabelExpression(template, record.values);
6235
+ const enrichedValues = enrichValuesWithSelectLabels(record.values, objectSchema.attributes);
6236
+ const label = renderLabelExpression(template, enrichedValues);
5299
6237
  allOptions.push({
5300
6238
  id: record.id,
5301
6239
  objectId: objectSchema.id,
@@ -5359,7 +6297,8 @@ var RelationService = class {
5359
6297
  }
5360
6298
  }
5361
6299
  for (const record of objectRecords) {
5362
- const label = renderLabelExpression(template, record.values);
6300
+ const enrichedValues = enrichValuesWithSelectLabels(record.values, objectSchema.attributes);
6301
+ const label = renderLabelExpression(template, enrichedValues);
5363
6302
  resolved.push({
5364
6303
  id: record.id,
5365
6304
  objectId: record.objectId,
@@ -5380,10 +6319,14 @@ var RelationService = class {
5380
6319
  if (!attribute || attribute.type !== "relation") {
5381
6320
  return null;
5382
6321
  }
5383
- return {
6322
+ const merged = {
5384
6323
  ...attribute.config,
5385
6324
  ...attribute
5386
6325
  };
6326
+ if (!("targets" in merged && Array.isArray(merged.targets) && "cardinality" in merged)) {
6327
+ return null;
6328
+ }
6329
+ return merged;
5387
6330
  }
5388
6331
  };
5389
6332
 
@@ -5396,7 +6339,7 @@ var RecordService = class {
5396
6339
  this.relationService = new RelationService(adapter, registry);
5397
6340
  this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
5398
6341
  this.permissionService = options?.permissionService;
5399
- this.auditService = options?.auditService;
6342
+ this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter, tenantId) : void 0);
5400
6343
  this.userId = options?.userId;
5401
6344
  this.userEmail = options?.userEmail;
5402
6345
  }
@@ -5463,21 +6406,23 @@ var RecordService = class {
5463
6406
  /**
5464
6407
  * Compute display label from schema expression
5465
6408
  * Automatically resolves relation attribute values to their labels
6409
+ * and select/multiselect values to their option labels
5466
6410
  * @internal
5467
6411
  */
5468
6412
  async computeLabel(schema, values) {
5469
6413
  const attrNames = extractAttributeNames(schema.labelExpression);
6414
+ let enrichedValues = enrichValuesWithSelectLabels(values, schema.attributes);
5470
6415
  const relationAttrs = schema.attributes.filter(
5471
6416
  (attr) => attr.type === "relation" && attrNames.includes(attr.name)
5472
6417
  );
5473
6418
  if (relationAttrs.length === 0) {
5474
- return renderLabelExpression(schema.labelExpression, values);
6419
+ return renderLabelExpression(schema.labelExpression, enrichedValues);
5475
6420
  }
5476
6421
  const resolvedMap = await this.resolveRelationLabels(relationAttrs, values);
5477
6422
  if (resolvedMap.size === 0) {
5478
- return renderLabelExpression(schema.labelExpression, values);
6423
+ return renderLabelExpression(schema.labelExpression, enrichedValues);
5479
6424
  }
5480
- const enrichedValues = { ...values };
6425
+ enrichedValues = { ...enrichedValues };
5481
6426
  for (const attr of relationAttrs) {
5482
6427
  const val = values[attr.name];
5483
6428
  const ids = this.extractRelationIds(val);
@@ -5590,7 +6535,7 @@ var RecordService = class {
5590
6535
  async getRecordOrThrow(recordId) {
5591
6536
  const record = await this.getRecord(recordId);
5592
6537
  if (!record) {
5593
- throw new Error(`Record with id "${recordId}" not found`);
6538
+ throw new RecordNotFoundError(recordId);
5594
6539
  }
5595
6540
  return record;
5596
6541
  }
@@ -5768,7 +6713,7 @@ var RecordService = class {
5768
6713
  await this.checkPermission(schema.name, "delete");
5769
6714
  if (options?.checkSystem) {
5770
6715
  if (schema.system) {
5771
- throw new Error(`Cannot delete record of system object "${schema.label}"`);
6716
+ throw new ProtectedResourceError("object", schema.name, "delete");
5772
6717
  }
5773
6718
  }
5774
6719
  if (!options?.skipReferenceCheck) {
@@ -5817,7 +6762,11 @@ var RecordService = class {
5817
6762
  async restoreRecord(recordId, options) {
5818
6763
  const record = await this.getRecordOrThrow(recordId);
5819
6764
  if (!record.deletedAt) {
5820
- throw new Error(`Record "${recordId}" is not deleted`);
6765
+ throw new SchemaError(
6766
+ `Record "${recordId}" is not deleted`,
6767
+ SchemaErrorCode.VALIDATION_FAILED,
6768
+ { recordId, reason: "not_deleted" }
6769
+ );
5821
6770
  }
5822
6771
  const schema = await this.schemaService.getObjectSchema(record.objectId);
5823
6772
  await this.checkPermission(schema.name, "update");
@@ -6255,16 +7204,19 @@ var ViewService = class {
6255
7204
  this.validateViewName(input.name);
6256
7205
  const existing = await this.adapter.views.findByName(tenantId, input.objectName, input.name);
6257
7206
  if (existing) {
6258
- throw new Error(`View "${input.name}" already exists for object "${input.objectName}"`);
7207
+ throw new SchemaError(
7208
+ `View "${input.name}" already exists for object "${input.objectName}"`,
7209
+ SchemaErrorCode.DUPLICATE_ATTRIBUTE,
7210
+ { viewName: input.name, objectName: input.objectName }
7211
+ );
6259
7212
  }
6260
7213
  if (this.nativeViews.has(input.objectName, input.name)) {
6261
- throw new Error(
6262
- `Cannot create view "${input.name}": a system view with this name already exists`
7214
+ throw new SchemaError(
7215
+ `Cannot create view "${input.name}": a system view with this name already exists`,
7216
+ SchemaErrorCode.DUPLICATE_ATTRIBUTE,
7217
+ { viewName: input.name, objectName: input.objectName, system: true }
6263
7218
  );
6264
7219
  }
6265
- if (!input.tabs || input.tabs.length === 0) {
6266
- throw new Error("View must have at least one tab");
6267
- }
6268
7220
  const dbView = await this.adapter.views.create({
6269
7221
  tenantId,
6270
7222
  objectName: input.objectName,
@@ -6272,7 +7224,7 @@ var ViewService = class {
6272
7224
  label: input.label,
6273
7225
  description: input.description,
6274
7226
  icon: input.icon,
6275
- tabs: input.tabs,
7227
+ tabs: input.tabs ?? [],
6276
7228
  default: input.default ?? false,
6277
7229
  system: false,
6278
7230
  // Custom views are never system
@@ -6290,13 +7242,10 @@ var ViewService = class {
6290
7242
  async updateView(viewId, input) {
6291
7243
  const dbView = await this.adapter.views.findById(viewId);
6292
7244
  if (!dbView) {
6293
- throw new Error(`View with id "${viewId}" not found`);
7245
+ throw new NotFoundError("View", viewId);
6294
7246
  }
6295
7247
  if (dbView.system) {
6296
- throw new Error("Cannot modify system views. System views are protected.");
6297
- }
6298
- if (input.tabs && input.tabs.length === 0) {
6299
- throw new Error("View must have at least one tab");
7248
+ throw new ProtectedResourceError("attribute", dbView.name, "modify");
6300
7249
  }
6301
7250
  const updated = await this.adapter.views.update(viewId, {
6302
7251
  label: input.label,
@@ -6316,10 +7265,10 @@ var ViewService = class {
6316
7265
  async deleteView(viewId) {
6317
7266
  const dbView = await this.adapter.views.findById(viewId);
6318
7267
  if (!dbView) {
6319
- throw new Error(`View with id "${viewId}" not found`);
7268
+ throw new NotFoundError("View", viewId);
6320
7269
  }
6321
7270
  if (dbView.system) {
6322
- throw new Error("Cannot delete system views. System views are protected.");
7271
+ throw new ProtectedResourceError("attribute", dbView.name, "delete");
6323
7272
  }
6324
7273
  await this.adapter.views.delete(viewId);
6325
7274
  }
@@ -6333,7 +7282,7 @@ var ViewService = class {
6333
7282
  async setDefaultView(viewId, tenantId) {
6334
7283
  const dbView = await this.adapter.views.findById(viewId);
6335
7284
  if (!dbView) {
6336
- throw new Error(`View with id "${viewId}" not found`);
7285
+ throw new NotFoundError("View", viewId);
6337
7286
  }
6338
7287
  const currentViews = await this.adapter.views.findByObjectName(tenantId, dbView.objectName);
6339
7288
  for (const v of currentViews) {
@@ -6352,16 +7301,23 @@ var ViewService = class {
6352
7301
  */
6353
7302
  validateViewName(name) {
6354
7303
  if (!name || name.length === 0) {
6355
- throw new Error("View name cannot be empty");
7304
+ throw new ValidationError("View name cannot be empty", [
7305
+ { path: ["name"], message: "View name cannot be empty" }
7306
+ ]);
6356
7307
  }
6357
7308
  if (name.length > 63) {
6358
- throw new Error("View name is too long (max 63 characters)");
7309
+ throw new ValidationError("View name is too long", [
7310
+ { path: ["name"], message: "View name is too long (max 63 characters)" }
7311
+ ]);
6359
7312
  }
6360
7313
  const kebabCaseRegex = /^[a-z][a-z0-9-]*$/;
6361
7314
  if (!kebabCaseRegex.test(name)) {
6362
- throw new Error(
6363
- "Invalid view name format. Name must be in kebab-case (e.g., 'detail', 'list-view')"
6364
- );
7315
+ throw new ValidationError("Invalid view name format", [
7316
+ {
7317
+ path: ["name"],
7318
+ message: "View name must be in kebab-case (e.g., 'detail', 'list-view')"
7319
+ }
7320
+ ]);
6365
7321
  }
6366
7322
  }
6367
7323
  /**
@@ -6723,8 +7679,13 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
6723
7679
  DEFAULT_ROLE_LABELS,
6724
7680
  DEFAULT_ROLE_PERMISSIONS,
6725
7681
  DuplicateError,
7682
+ EMPTY_VALUE_PLACEHOLDER,
6726
7683
  FileNotFoundError,
6727
7684
  FileService,
7685
+ FlowBuilder,
7686
+ FlowPageBuilder,
7687
+ FlowRowBuilder,
7688
+ FlowService,
6728
7689
  ForbiddenError,
6729
7690
  GeocodingService,
6730
7691
  GlobalSearchService,
@@ -6789,9 +7750,12 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
6789
7750
  currencyConfigSchema,
6790
7751
  date,
6791
7752
  dateConfigSchema,
7753
+ enrichValuesWithSelectLabels,
6792
7754
  extractAttributeNames,
6793
7755
  file,
6794
7756
  fileConfigSchema,
7757
+ flow,
7758
+ formatAttributeValue,
6795
7759
  generateId,
6796
7760
  generatePrefixedId,
6797
7761
  getAttributeConfigSchema,
@@ -6799,9 +7763,12 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
6799
7763
  getSyncPreview,
6800
7764
  getViewSyncPreview,
6801
7765
  group,
7766
+ isActivityTab,
6802
7767
  isAdvancedFilterState,
6803
7768
  isCustomTab,
6804
7769
  isDefaultRole,
7770
+ isFlowDefinition,
7771
+ isFlowPublished,
6805
7772
  isForbiddenError,
6806
7773
  isFormTab,
6807
7774
  isLabelExpression,
@@ -6810,6 +7777,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
6810
7777
  isProtectedResourceError,
6811
7778
  isRecordComplete,
6812
7779
  isSchemaError,
7780
+ isSystemFlow,
6813
7781
  isTableTab,
6814
7782
  isValidationError,
6815
7783
  location,