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

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/index.js CHANGED
@@ -131,6 +131,10 @@ __export(index_exports, {
131
131
  DuplicateError: () => DuplicateError,
132
132
  FileNotFoundError: () => FileNotFoundError,
133
133
  FileService: () => FileService,
134
+ FlowBuilder: () => FlowBuilder,
135
+ FlowPageBuilder: () => FlowPageBuilder,
136
+ FlowRowBuilder: () => FlowRowBuilder,
137
+ FlowService: () => FlowService,
134
138
  ForbiddenError: () => ForbiddenError,
135
139
  GeocodingService: () => GeocodingService,
136
140
  GlobalSearchService: () => GlobalSearchService,
@@ -198,6 +202,7 @@ __export(index_exports, {
198
202
  extractAttributeNames: () => extractAttributeNames,
199
203
  file: () => file,
200
204
  fileConfigSchema: () => fileConfigSchema,
205
+ flow: () => flow,
201
206
  generateId: () => generateId,
202
207
  generatePrefixedId: () => generatePrefixedId,
203
208
  getAttributeConfigSchema: () => getAttributeConfigSchema,
@@ -208,6 +213,8 @@ __export(index_exports, {
208
213
  isAdvancedFilterState: () => isAdvancedFilterState,
209
214
  isCustomTab: () => isCustomTab,
210
215
  isDefaultRole: () => isDefaultRole,
216
+ isFlowDefinition: () => isFlowDefinition,
217
+ isFlowPublished: () => isFlowPublished,
211
218
  isForbiddenError: () => isForbiddenError,
212
219
  isFormTab: () => isFormTab,
213
220
  isLabelExpression: () => isLabelExpression,
@@ -216,6 +223,7 @@ __export(index_exports, {
216
223
  isProtectedResourceError: () => isProtectedResourceError,
217
224
  isRecordComplete: () => isRecordComplete,
218
225
  isSchemaError: () => isSchemaError,
226
+ isSystemFlow: () => isSystemFlow,
219
227
  isTableTab: () => isTableTab,
220
228
  isValidationError: () => isValidationError,
221
229
  location: () => location,
@@ -392,6 +400,17 @@ function isNoValueOperator(operator) {
392
400
  return NO_VALUE_OPERATORS.includes(operator);
393
401
  }
394
402
 
403
+ // src/types/flows.ts
404
+ function isFlowDefinition(obj) {
405
+ return typeof obj === "object" && obj !== null && "slots" in obj && "pages" in obj && "relations" in obj && "status" in obj;
406
+ }
407
+ function isFlowPublished(flow2) {
408
+ return flow2.status === "published";
409
+ }
410
+ function isSystemFlow(flow2) {
411
+ return flow2.system === true;
412
+ }
413
+
395
414
  // src/types/geocoding.ts
396
415
  var NoopGeocodingAdapter = class {
397
416
  async autocomplete() {
@@ -1284,8 +1303,361 @@ function rating(config) {
1284
1303
  return new RatingAttributeBuilder(config.name, config.label);
1285
1304
  }
1286
1305
 
1306
+ // src/builders/flow-builder.ts
1307
+ var import_zod = require("zod");
1308
+ var FlowRowBuilder = class {
1309
+ /** @internal */
1310
+ constructor(pageBuilder, id, order) {
1311
+ this.pageBuilder = pageBuilder;
1312
+ this.rowData = {
1313
+ id,
1314
+ order,
1315
+ fields: []
1316
+ };
1317
+ }
1318
+ /**
1319
+ * Add a field to the current row
1320
+ *
1321
+ * @param slotId - Reference to a slot defined with .slot()
1322
+ * @param attribute - Attribute name on the object
1323
+ * @param options - Optional field configuration
1324
+ *
1325
+ * @example
1326
+ * ```typescript
1327
+ * .row("row-1")
1328
+ * .field("mr", "firstName")
1329
+ * .field("mr", "lastName")
1330
+ * ```
1331
+ */
1332
+ field(slotId, attribute, options) {
1333
+ const field = {
1334
+ id: `${this.rowData.id}-${slotId}-${attribute}`,
1335
+ slotId,
1336
+ attribute,
1337
+ label: options?.label,
1338
+ required: options?.required
1339
+ };
1340
+ this.rowData.fields.push(field);
1341
+ return this;
1342
+ }
1343
+ /**
1344
+ * Start a new row on the same page
1345
+ */
1346
+ row(id) {
1347
+ return this.pageBuilder._finalizeRow(this.rowData).row(id);
1348
+ }
1349
+ /**
1350
+ * Start a new page
1351
+ */
1352
+ page(id, label, description) {
1353
+ return this.pageBuilder._finalizeRow(this.rowData)._getFlow().page(id, label, description);
1354
+ }
1355
+ /**
1356
+ * Build the final flow definition
1357
+ */
1358
+ build() {
1359
+ return this.pageBuilder._finalizeRow(this.rowData)._getFlow().build();
1360
+ }
1361
+ };
1362
+ var FlowPageBuilder = class {
1363
+ /** @internal */
1364
+ constructor(flow2, id, label, description) {
1365
+ this.rowOrder = 0;
1366
+ this.flow = flow2;
1367
+ this.pageData = {
1368
+ id,
1369
+ label,
1370
+ description,
1371
+ order: 0,
1372
+ // Will be set when finalized
1373
+ rows: []
1374
+ };
1375
+ }
1376
+ /**
1377
+ * Add a row to the current page
1378
+ *
1379
+ * @param id - Unique row identifier
1380
+ *
1381
+ * @example
1382
+ * ```typescript
1383
+ * .page("identity", "Identité")
1384
+ * .row("name-row")
1385
+ * .field("mr", "firstName")
1386
+ * .field("mr", "lastName")
1387
+ * .row("email-row")
1388
+ * .field("mr", "email")
1389
+ * ```
1390
+ */
1391
+ row(id) {
1392
+ this.rowOrder++;
1393
+ return new FlowRowBuilder(this, id, this.rowOrder);
1394
+ }
1395
+ /**
1396
+ * Start a new page
1397
+ */
1398
+ page(id, label, description) {
1399
+ return this.flow._finalizePage(this.pageData).page(id, label, description);
1400
+ }
1401
+ /**
1402
+ * Build the final flow definition
1403
+ */
1404
+ build() {
1405
+ return this.flow._finalizePage(this.pageData).build();
1406
+ }
1407
+ /** @internal */
1408
+ _finalizeRow(row) {
1409
+ if (row.id && row.fields.length > 0) {
1410
+ this.pageData.rows?.push(row);
1411
+ }
1412
+ return this;
1413
+ }
1414
+ /** @internal */
1415
+ _getFlow() {
1416
+ return this.flow._finalizePage(this.pageData);
1417
+ }
1418
+ /** @internal */
1419
+ _getPageData() {
1420
+ return this.pageData;
1421
+ }
1422
+ };
1423
+ var FlowBuilder = class {
1424
+ constructor(name, label) {
1425
+ this.data = {
1426
+ slots: [],
1427
+ pages: [],
1428
+ relations: [],
1429
+ status: "published",
1430
+ // System flows are always published
1431
+ version: 1,
1432
+ system: true
1433
+ };
1434
+ this.validateName(name);
1435
+ this.data.name = name;
1436
+ this.data.label = label;
1437
+ }
1438
+ /**
1439
+ * Set flow description
1440
+ */
1441
+ description(value) {
1442
+ this.data.description = value;
1443
+ return this;
1444
+ }
1445
+ /**
1446
+ * Set flow icon
1447
+ */
1448
+ icon(value) {
1449
+ this.data.icon = value;
1450
+ return this;
1451
+ }
1452
+ /**
1453
+ * Set extensible metadata
1454
+ */
1455
+ metadata(value) {
1456
+ this.data.metadata = value;
1457
+ return this;
1458
+ }
1459
+ /**
1460
+ * Add a slot (object to create in the flow)
1461
+ *
1462
+ * @param id - Unique slot identifier (used to reference in fields and relations)
1463
+ * @param objectName - Name of the object to create
1464
+ * @param label - Display label for the slot
1465
+ * @param options - Optional slot configuration
1466
+ *
1467
+ * @example
1468
+ * ```typescript
1469
+ * .slot("mr", "contacts", "Monsieur", { color: "blue", icon: "user" })
1470
+ * .slot("company", "companies", "Entreprise", { color: "green" })
1471
+ * ```
1472
+ */
1473
+ slot(id, objectName, label, options) {
1474
+ if (this.data.slots?.some((s) => s.id === id)) {
1475
+ throw new Error(`[FlowBuilder] Duplicate slot id: "${id}"`);
1476
+ }
1477
+ this.data.slots?.push({
1478
+ id,
1479
+ objectName,
1480
+ label,
1481
+ color: options?.color,
1482
+ icon: options?.icon
1483
+ });
1484
+ return this;
1485
+ }
1486
+ /**
1487
+ * Define a relation between two slots
1488
+ * The source slot will be linked to the target slot via the specified attribute
1489
+ *
1490
+ * @param sourceSlotId - Slot that has the relation attribute
1491
+ * @param sourceAttribute - Relation attribute name on the source
1492
+ * @param targetSlotId - Target slot to link to
1493
+ *
1494
+ * @example
1495
+ * ```typescript
1496
+ * // mr.company → company (the contact's company field will be set to the created company)
1497
+ * .relation("mr", "company", "company")
1498
+ * ```
1499
+ */
1500
+ relation(sourceSlotId, sourceAttribute, targetSlotId) {
1501
+ this.data.relations?.push({
1502
+ id: `${sourceSlotId}-${sourceAttribute}-${targetSlotId}`,
1503
+ sourceSlotId,
1504
+ sourceAttribute,
1505
+ targetSlotId
1506
+ });
1507
+ return this;
1508
+ }
1509
+ /**
1510
+ * Start a new page/step in the flow
1511
+ *
1512
+ * @param id - Unique page identifier
1513
+ * @param label - Display label
1514
+ * @param description - Optional description
1515
+ *
1516
+ * @example
1517
+ * ```typescript
1518
+ * .page("identity", "Identité", "Informations personnelles")
1519
+ * .row("name")
1520
+ * .field("mr", "firstName")
1521
+ * .field("mr", "lastName")
1522
+ * ```
1523
+ */
1524
+ page(id, label, description) {
1525
+ return new FlowPageBuilder(this, id, label, description);
1526
+ }
1527
+ /**
1528
+ * Build and validate the flow definition
1529
+ *
1530
+ * @throws Error if validation fails
1531
+ */
1532
+ build() {
1533
+ this.validateRequiredData();
1534
+ const slotIds = new Set(this.data.slots?.map((s) => s.id) ?? []);
1535
+ this.validateFieldReferences(slotIds);
1536
+ this.validateRelationReferences(slotIds);
1537
+ this.validateNoCircularDependencies();
1538
+ return this.data;
1539
+ }
1540
+ /**
1541
+ * Validate that required data is present
1542
+ */
1543
+ validateRequiredData() {
1544
+ if (!this.data.slots || this.data.slots.length === 0) {
1545
+ throw new Error("[FlowBuilder] At least one slot is required. Use .slot() to add slots.");
1546
+ }
1547
+ if (!this.data.pages || this.data.pages.length === 0) {
1548
+ throw new Error("[FlowBuilder] At least one page is required. Use .page() to add pages.");
1549
+ }
1550
+ }
1551
+ /**
1552
+ * Validate that all field slotId references exist
1553
+ */
1554
+ validateFieldReferences(slotIds) {
1555
+ for (const page of this.data.pages ?? []) {
1556
+ for (const row of page.rows) {
1557
+ for (const field of row.fields) {
1558
+ if (!slotIds.has(field.slotId)) {
1559
+ throw new Error(
1560
+ `[FlowBuilder] Field "${field.id}" references unknown slot "${field.slotId}" in page "${page.id}". Available slots: ${[...slotIds].join(", ")}`
1561
+ );
1562
+ }
1563
+ }
1564
+ }
1565
+ }
1566
+ }
1567
+ /**
1568
+ * Validate that all relation references exist and are valid
1569
+ */
1570
+ validateRelationReferences(slotIds) {
1571
+ for (const relation2 of this.data.relations ?? []) {
1572
+ if (!slotIds.has(relation2.sourceSlotId)) {
1573
+ throw new Error(
1574
+ `[FlowBuilder] Relation references unknown source slot "${relation2.sourceSlotId}". Available slots: ${[...slotIds].join(", ")}`
1575
+ );
1576
+ }
1577
+ if (!slotIds.has(relation2.targetSlotId)) {
1578
+ throw new Error(
1579
+ `[FlowBuilder] Relation references unknown target slot "${relation2.targetSlotId}". Available slots: ${[...slotIds].join(", ")}`
1580
+ );
1581
+ }
1582
+ if (relation2.sourceSlotId === relation2.targetSlotId) {
1583
+ throw new Error(
1584
+ `[FlowBuilder] Relation cannot link a slot to itself: "${relation2.sourceSlotId}"`
1585
+ );
1586
+ }
1587
+ }
1588
+ }
1589
+ /**
1590
+ * @internal Used by FlowPageBuilder to finalize a page
1591
+ */
1592
+ _finalizePage(page) {
1593
+ if (page.id) {
1594
+ page.order = (this.data.pages?.length ?? 0) + 1;
1595
+ this.data.pages?.push(page);
1596
+ }
1597
+ return this;
1598
+ }
1599
+ /**
1600
+ * Validate flow name format (kebab-case)
1601
+ */
1602
+ validateName(name) {
1603
+ 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-]*$/, {
1604
+ 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'"
1605
+ });
1606
+ try {
1607
+ flowNameSchema.parse(name);
1608
+ } catch (error) {
1609
+ if (error instanceof import_zod.z.ZodError) {
1610
+ throw new Error(`[FlowBuilder] ${error.issues[0].message}`);
1611
+ }
1612
+ throw error;
1613
+ }
1614
+ }
1615
+ /**
1616
+ * Validate that there are no circular dependencies in relations
1617
+ * Uses DFS to detect cycles in the dependency graph
1618
+ */
1619
+ validateNoCircularDependencies() {
1620
+ const relations = this.data.relations ?? [];
1621
+ if (relations.length === 0) return;
1622
+ const dependsOn = /* @__PURE__ */ new Map();
1623
+ for (const slot of this.data.slots ?? []) {
1624
+ dependsOn.set(slot.id, /* @__PURE__ */ new Set());
1625
+ }
1626
+ for (const rel of relations) {
1627
+ dependsOn.get(rel.sourceSlotId)?.add(rel.targetSlotId);
1628
+ }
1629
+ const visited = /* @__PURE__ */ new Set();
1630
+ const recursionStack = /* @__PURE__ */ new Set();
1631
+ const hasCycle = (slotId) => {
1632
+ visited.add(slotId);
1633
+ recursionStack.add(slotId);
1634
+ for (const dependency of dependsOn.get(slotId) ?? []) {
1635
+ if (!visited.has(dependency)) {
1636
+ if (hasCycle(dependency)) return true;
1637
+ } else if (recursionStack.has(dependency)) {
1638
+ return true;
1639
+ }
1640
+ }
1641
+ recursionStack.delete(slotId);
1642
+ return false;
1643
+ };
1644
+ for (const slot of this.data.slots ?? []) {
1645
+ if (!visited.has(slot.id)) {
1646
+ if (hasCycle(slot.id)) {
1647
+ throw new Error(
1648
+ "[FlowBuilder] Circular dependency detected in relations. Slots cannot have circular dependencies."
1649
+ );
1650
+ }
1651
+ }
1652
+ }
1653
+ }
1654
+ };
1655
+ function flow(name, label) {
1656
+ return new FlowBuilder(name, label);
1657
+ }
1658
+
1287
1659
  // src/builders/object-builder.ts
1288
- var import_zod = __toESM(require("zod"));
1660
+ var import_zod2 = __toESM(require("zod"));
1289
1661
  var ObjectBuilder = class {
1290
1662
  constructor(config) {
1291
1663
  this.validateName(config.name);
@@ -1418,14 +1790,14 @@ The labelExpression defines how records are displayed in lists and relations.
1418
1790
  if (name === void 0) {
1419
1791
  return;
1420
1792
  }
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-]*$/, {
1793
+ 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
1794
  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
1795
  });
1424
1796
  try {
1425
1797
  objectNameSchema.parse(name);
1426
1798
  } catch (error) {
1427
- if (error instanceof import_zod.default.ZodError) {
1428
- throw new Error(`[ObjectBuilder] ${error.errors[0].message}`);
1799
+ if (error instanceof import_zod2.default.ZodError) {
1800
+ throw new Error(`[ObjectBuilder] ${error.issues[0].message}`);
1429
1801
  }
1430
1802
  throw error;
1431
1803
  }
@@ -1436,7 +1808,7 @@ function object(config) {
1436
1808
  }
1437
1809
 
1438
1810
  // src/builders/view-builder.ts
1439
- var import_zod2 = require("zod");
1811
+ var import_zod3 = require("zod");
1440
1812
  var GroupBuilder = class {
1441
1813
  constructor(id, label) {
1442
1814
  this.data = { fields: [] };
@@ -1792,14 +2164,14 @@ var ViewBuilder = class {
1792
2164
  * Validate view name format (kebab-case)
1793
2165
  */
1794
2166
  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-]*$/, {
2167
+ 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
2168
  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
2169
  });
1798
2170
  try {
1799
2171
  viewNameSchema.parse(name);
1800
2172
  } catch (error) {
1801
- if (error instanceof import_zod2.z.ZodError) {
1802
- throw new Error(`[ViewBuilder] ${error.errors[0].message}`);
2173
+ if (error instanceof import_zod3.z.ZodError) {
2174
+ throw new Error(`[ViewBuilder] ${error.issues[0].message}`);
1803
2175
  }
1804
2176
  throw error;
1805
2177
  }
@@ -2097,35 +2469,35 @@ Available views: ${availableNames}`
2097
2469
  var viewRegistry = new ViewRegistry();
2098
2470
 
2099
2471
  // 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()
2472
+ var import_zod4 = require("zod");
2473
+ var baseConfigSchema = import_zod4.z.object({
2474
+ disabled: import_zod4.z.boolean().optional(),
2475
+ placeholder: import_zod4.z.string().optional(),
2476
+ description: import_zod4.z.string().optional(),
2477
+ defaultValue: import_zod4.z.unknown().optional(),
2478
+ icon: import_zod4.z.string().optional(),
2479
+ order: import_zod4.z.number().int().optional(),
2480
+ hidden: import_zod4.z.boolean().optional(),
2481
+ archived: import_zod4.z.boolean().optional(),
2482
+ deprecated: import_zod4.z.boolean().optional(),
2483
+ metadata: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
2112
2484
  });
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()
2485
+ var optionSchema = import_zod4.z.object({
2486
+ id: import_zod4.z.string().min(1),
2487
+ label: import_zod4.z.string().min(1),
2488
+ value: import_zod4.z.string().min(1),
2489
+ color: import_zod4.z.string().optional(),
2490
+ icon: import_zod4.z.string().optional(),
2491
+ description: import_zod4.z.string().optional(),
2492
+ group: import_zod4.z.enum(["idle", "in_progress", "finished"]).optional()
2121
2493
  });
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()
2494
+ var relationTargetSchema = import_zod4.z.object({
2495
+ object: import_zod4.z.string().min(1),
2496
+ displayTemplate: import_zod4.z.string().optional(),
2497
+ filter: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
2126
2498
  });
2127
- var documentTypeConfigSchema = import_zod3.z.object({
2128
- type: import_zod3.z.enum([
2499
+ var documentTypeConfigSchema = import_zod4.z.object({
2500
+ type: import_zod4.z.enum([
2129
2501
  "id_card",
2130
2502
  "passport",
2131
2503
  "incorporation_certificate",
@@ -2136,86 +2508,86 @@ var documentTypeConfigSchema = import_zod3.z.object({
2136
2508
  "proof_of_address",
2137
2509
  "custom"
2138
2510
  ]),
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()
2511
+ label: import_zod4.z.string(),
2512
+ faces: import_zod4.z.array(import_zod4.z.enum(["front", "back", "single"])),
2513
+ attributeMapping: import_zod4.z.array(
2514
+ import_zod4.z.object({
2515
+ attributeId: import_zod4.z.string(),
2516
+ extractedKey: import_zod4.z.string(),
2517
+ face: import_zod4.z.enum(["front", "back", "single"]).optional(),
2518
+ required: import_zod4.z.boolean().optional()
2147
2519
  })
2148
2520
  ).optional()
2149
2521
  });
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()
2522
+ var fileVerificationConfigSchema = import_zod4.z.object({
2523
+ enabled: import_zod4.z.boolean(),
2524
+ documentTypes: import_zod4.z.array(documentTypeConfigSchema),
2525
+ autoExtract: import_zod4.z.boolean().optional(),
2526
+ autoValidate: import_zod4.z.boolean().optional()
2155
2527
  });
2156
2528
  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()
2529
+ minLength: import_zod4.z.number().int().min(0).optional(),
2530
+ maxLength: import_zod4.z.number().int().min(1).optional(),
2531
+ pattern: import_zod4.z.string().optional()
2160
2532
  });
2161
2533
  var textareaConfigSchema = baseConfigSchema;
2162
2534
  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()
2535
+ min: import_zod4.z.number().optional(),
2536
+ max: import_zod4.z.number().optional(),
2537
+ unit: import_zod4.z.enum(["integer", "decimal", "percentage"]).optional(),
2538
+ decimals: import_zod4.z.number().int().min(0).optional()
2167
2539
  });
2168
2540
  var checkboxConfigSchema = baseConfigSchema;
2169
2541
  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()
2542
+ dateFormat: import_zod4.z.enum(["short", "long", "full", "relative"]).optional(),
2543
+ minDate: import_zod4.z.string().optional(),
2544
+ maxDate: import_zod4.z.string().optional()
2173
2545
  });
2174
2546
  var phoneConfigSchema = baseConfigSchema.extend({
2175
- defaultCountryCode: import_zod3.z.string().length(3).optional()
2547
+ defaultCountryCode: import_zod4.z.string().length(3).optional()
2176
2548
  });
2177
2549
  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()
2550
+ defaultCurrency: import_zod4.z.string().length(3).optional(),
2551
+ allowedCurrencies: import_zod4.z.array(import_zod4.z.string().length(3)).optional()
2180
2552
  });
2181
2553
  var statusConfigSchema = baseConfigSchema.extend({
2182
- options: import_zod3.z.array(optionSchema).min(1)
2554
+ options: import_zod4.z.array(optionSchema).min(1)
2183
2555
  });
2184
2556
  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()
2557
+ granularity: import_zod4.z.enum(["full", "address", "city", "state", "country", "coordinates"]),
2558
+ enableAutocomplete: import_zod4.z.boolean().optional(),
2559
+ enableMap: import_zod4.z.boolean().optional(),
2560
+ defaultCountry: import_zod4.z.string().length(3).optional(),
2561
+ allowedCountries: import_zod4.z.array(import_zod4.z.string().length(3)).optional(),
2562
+ displayFormat: import_zod4.z.enum(["single_line", "multi_line", "compact"]).optional()
2191
2563
  });
2192
2564
  var timestampConfigSchema = baseConfigSchema.extend({
2193
- autoUpdate: import_zod3.z.boolean().optional()
2565
+ autoUpdate: import_zod4.z.boolean().optional()
2194
2566
  });
2195
2567
  var selectConfigSchema = baseConfigSchema.extend({
2196
- options: import_zod3.z.array(optionSchema).min(1)
2568
+ options: import_zod4.z.array(optionSchema).min(1)
2197
2569
  });
2198
2570
  var multiselectConfigSchema = baseConfigSchema.extend({
2199
- options: import_zod3.z.array(optionSchema).min(1)
2571
+ options: import_zod4.z.array(optionSchema).min(1)
2200
2572
  });
2201
2573
  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(),
2574
+ maxFiles: import_zod4.z.number().int().min(1).optional(),
2575
+ maxSize: import_zod4.z.number().int().min(1).optional(),
2576
+ allowedTypes: import_zod4.z.array(import_zod4.z.string()).optional(),
2205
2577
  verification: fileVerificationConfigSchema.optional()
2206
2578
  });
2207
2579
  var userConfigSchema = baseConfigSchema.extend({
2208
- allowedRoles: import_zod3.z.array(import_zod3.z.string()).optional()
2580
+ allowedRoles: import_zod4.z.array(import_zod4.z.string()).optional()
2209
2581
  });
2210
2582
  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()
2583
+ targets: import_zod4.z.array(relationTargetSchema).min(1),
2584
+ cardinality: import_zod4.z.enum(["one", "many"]),
2585
+ minItems: import_zod4.z.number().int().min(0).optional(),
2586
+ maxItems: import_zod4.z.number().int().min(1).optional()
2215
2587
  });
2216
2588
  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()
2589
+ max: import_zod4.z.number().int().min(1).optional(),
2590
+ iconType: import_zod4.z.enum(["star", "heart", "thumbs", "number"]).optional()
2219
2591
  });
2220
2592
  var attributeConfigSchemas = {
2221
2593
  text: textConfigSchema,
@@ -2246,7 +2618,7 @@ function validateAttributeConfig(type, config) {
2246
2618
  }
2247
2619
  return {
2248
2620
  success: false,
2249
- errors: result.error.errors.map((err) => `${err.path.join(".")}: ${err.message}`)
2621
+ errors: result.error.issues.map((err) => `${err.path.join(".")}: ${err.message}`)
2250
2622
  };
2251
2623
  }
2252
2624
  function parseAttributeConfig(type, config) {
@@ -2259,7 +2631,7 @@ function safeParseAttributeConfig(type, config) {
2259
2631
  return result.success ? result.data : void 0;
2260
2632
  }
2261
2633
  function createTextValidator(attr) {
2262
- let schema = import_zod3.z.string();
2634
+ let schema = import_zod4.z.string();
2263
2635
  if (attr.minLength !== void 0) {
2264
2636
  schema = schema.min(
2265
2637
  attr.minLength,
@@ -2278,7 +2650,7 @@ function createTextValidator(attr) {
2278
2650
  return schema;
2279
2651
  }
2280
2652
  function createNumberValidator(attr) {
2281
- let schema = import_zod3.z.number();
2653
+ let schema = import_zod4.z.number();
2282
2654
  if (attr.min !== void 0) {
2283
2655
  schema = schema.min(attr.min, `${attr.label} must be at least ${attr.min}`);
2284
2656
  }
@@ -2291,81 +2663,75 @@ function createNumberValidator(attr) {
2291
2663
  return schema;
2292
2664
  }
2293
2665
  function createCheckboxValidator(_attr) {
2294
- return import_zod3.z.boolean();
2666
+ return import_zod4.z.boolean();
2295
2667
  }
2296
2668
  function createDateValidator(attr) {
2297
- return import_zod3.z.string().datetime({ message: `${attr.label} must be a valid ISO date` });
2669
+ return import_zod4.z.string().datetime({ message: `${attr.label} must be a valid ISO date` });
2298
2670
  }
2299
2671
  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)
2672
+ return import_zod4.z.object({
2673
+ countryCode: import_zod4.z.string().length(3),
2674
+ phoneNumber: import_zod4.z.string().min(1)
2303
2675
  });
2304
2676
  }
2305
2677
  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)
2678
+ return import_zod4.z.object({
2679
+ code: import_zod4.z.string().length(3),
2680
+ value: import_zod4.z.number().min(0)
2309
2681
  });
2310
2682
  }
2311
2683
  function createStatusValidator(attr) {
2312
2684
  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
- })
2685
+ return import_zod4.z.enum(validValues, {
2686
+ error: `${attr.label} must be one of: ${validValues.join(", ")}`
2317
2687
  });
2318
2688
  }
2319
2689
  function createSelectValidator(attr) {
2320
2690
  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
- })
2691
+ return import_zod4.z.enum(validValues, {
2692
+ error: `${attr.label} must be one of: ${validValues.join(", ")}`
2325
2693
  });
2326
2694
  }
2327
2695
  function createMultiselectValidator(attr) {
2328
2696
  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
- })
2697
+ return import_zod4.z.array(
2698
+ import_zod4.z.enum(validValues, {
2699
+ error: `Each value must be one of: ${validValues.join(", ")}`
2334
2700
  })
2335
2701
  );
2336
2702
  }
2337
2703
  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()
2704
+ return import_zod4.z.object({
2705
+ address: import_zod4.z.string().optional(),
2706
+ address2: import_zod4.z.string().optional(),
2707
+ city: import_zod4.z.string().optional(),
2708
+ state: import_zod4.z.string().optional(),
2709
+ postalCode: import_zod4.z.string().optional(),
2710
+ country: import_zod4.z.string().length(3).optional(),
2711
+ latitude: import_zod4.z.number().optional(),
2712
+ longitude: import_zod4.z.number().optional()
2347
2713
  });
2348
2714
  }
2349
2715
  function createTimestampValidator(_attr) {
2350
- return import_zod3.z.number().int().positive();
2716
+ return import_zod4.z.number().int().positive();
2351
2717
  }
2352
2718
  function createFileValidator(_attr) {
2353
- return import_zod3.z.string().uuid();
2719
+ return import_zod4.z.string().uuid();
2354
2720
  }
2355
2721
  function createUserValidator(_attr) {
2356
- return import_zod3.z.string().uuid();
2722
+ return import_zod4.z.string().uuid();
2357
2723
  }
2358
2724
  function createSingleRelationValidator(attr) {
2359
- const uuidSchema = import_zod3.z.string().uuid({
2725
+ const uuidSchema = import_zod4.z.string().uuid({
2360
2726
  message: `${attr.label} must be a valid record ID`
2361
2727
  });
2362
- return import_zod3.z.union([uuidSchema, import_zod3.z.null()]);
2728
+ return import_zod4.z.union([uuidSchema, import_zod4.z.null()]);
2363
2729
  }
2364
2730
  function createMultiRelationValidator(attr) {
2365
- const uuidSchema = import_zod3.z.string().uuid({
2731
+ const uuidSchema = import_zod4.z.string().uuid({
2366
2732
  message: `Each ${attr.label} item must be a valid record ID`
2367
2733
  });
2368
- let arraySchema = import_zod3.z.array(uuidSchema);
2734
+ let arraySchema = import_zod4.z.array(uuidSchema);
2369
2735
  if (attr.minItems !== void 0) {
2370
2736
  arraySchema = arraySchema.min(
2371
2737
  attr.minItems,
@@ -2387,7 +2753,7 @@ function createRelationValidator(attr) {
2387
2753
  return createSingleRelationValidator(attr);
2388
2754
  }
2389
2755
  function createRatingValidator(attr) {
2390
- let schema = import_zod3.z.number().int().min(0);
2756
+ let schema = import_zod4.z.number().int().min(0);
2391
2757
  if (attr.max !== void 0) {
2392
2758
  schema = schema.max(attr.max, `${attr.label} must be at most ${attr.max}`);
2393
2759
  }
@@ -2426,7 +2792,7 @@ function createAttributeValidator(attr) {
2426
2792
  case "rating":
2427
2793
  return createRatingValidator(attr);
2428
2794
  default:
2429
- return import_zod3.z.unknown();
2795
+ return import_zod4.z.unknown();
2430
2796
  }
2431
2797
  }
2432
2798
  function createObjectValidator(objectDef) {
@@ -2438,7 +2804,7 @@ function createObjectValidator(objectDef) {
2438
2804
  }
2439
2805
  shape[attr.name] = validator;
2440
2806
  }
2441
- return import_zod3.z.object(shape);
2807
+ return import_zod4.z.object(shape);
2442
2808
  }
2443
2809
  function validateAttribute(attr, value) {
2444
2810
  const validator = createAttributeValidator(attr);
@@ -2454,7 +2820,7 @@ function validateAttribute(attr, value) {
2454
2820
  }
2455
2821
  return {
2456
2822
  success: false,
2457
- errors: result.error.errors.map((err) => ({
2823
+ errors: result.error.issues.map((err) => ({
2458
2824
  path: [attr.name, ...err.path.map(String)],
2459
2825
  message: err.message
2460
2826
  }))
@@ -2471,7 +2837,7 @@ function validateObject(objectDef, data) {
2471
2837
  }
2472
2838
  return {
2473
2839
  success: false,
2474
- errors: result.error.errors.map((err) => ({
2840
+ errors: result.error.issues.map((err) => ({
2475
2841
  path: err.path.map(String),
2476
2842
  message: err.message
2477
2843
  }))
@@ -2492,7 +2858,7 @@ function createDraftValidator(objectDef) {
2492
2858
  const validator = createAttributeValidator(attr).optional();
2493
2859
  shape[attr.name] = validator;
2494
2860
  }
2495
- return import_zod3.z.object(shape);
2861
+ return import_zod4.z.object(shape);
2496
2862
  }
2497
2863
  function validateDraft(objectDef, data) {
2498
2864
  const validator = createDraftValidator(objectDef);
@@ -2505,7 +2871,7 @@ function validateDraft(objectDef, data) {
2505
2871
  }
2506
2872
  return {
2507
2873
  success: false,
2508
- errors: result.error.errors.map((err) => ({
2874
+ errors: result.error.issues.map((err) => ({
2509
2875
  path: err.path.map(String),
2510
2876
  message: err.message
2511
2877
  }))
@@ -3925,6 +4291,273 @@ var FileService = class {
3925
4291
  }
3926
4292
  };
3927
4293
 
4294
+ // src/runtime/services/flow.service.ts
4295
+ var FlowService = class {
4296
+ constructor(adapter, systemFlows = []) {
4297
+ this.adapter = adapter;
4298
+ this.systemFlows = new Map(systemFlows.map((f) => [f.name, f]));
4299
+ }
4300
+ /**
4301
+ * Get all flows for a tenant (system + custom)
4302
+ */
4303
+ async getAllFlows(tenantId) {
4304
+ const systemFlowsList = Array.from(this.systemFlows.values());
4305
+ if (!this.adapter.flows) {
4306
+ return systemFlowsList;
4307
+ }
4308
+ const dbFlows = await this.adapter.flows.findAllForTenant(tenantId);
4309
+ const customFlows = dbFlows.filter((f) => !f.system).map(this.convertDBFlowToDefinition);
4310
+ return [...systemFlowsList, ...customFlows];
4311
+ }
4312
+ /**
4313
+ * Get published flows only
4314
+ */
4315
+ async getPublishedFlows(tenantId) {
4316
+ const allFlows = await this.getAllFlows(tenantId);
4317
+ return allFlows.filter((f) => f.status === "published");
4318
+ }
4319
+ /**
4320
+ * Get a specific flow by name
4321
+ */
4322
+ async getFlow(name, tenantId) {
4323
+ const systemFlow = this.systemFlows.get(name);
4324
+ if (systemFlow) {
4325
+ return systemFlow;
4326
+ }
4327
+ if (!this.adapter.flows) {
4328
+ return null;
4329
+ }
4330
+ const dbFlow = await this.adapter.flows.findByName(tenantId, name);
4331
+ if (dbFlow) {
4332
+ return this.convertDBFlowToDefinition(dbFlow);
4333
+ }
4334
+ return null;
4335
+ }
4336
+ /**
4337
+ * Get a flow by ID
4338
+ */
4339
+ async getFlowById(flowId) {
4340
+ if (!this.adapter.flows) {
4341
+ return null;
4342
+ }
4343
+ const dbFlow = await this.adapter.flows.findById(flowId);
4344
+ if (dbFlow) {
4345
+ return this.convertDBFlowToDefinition(dbFlow);
4346
+ }
4347
+ return null;
4348
+ }
4349
+ /**
4350
+ * Create a new custom flow (as draft)
4351
+ */
4352
+ async createFlow(input, tenantId) {
4353
+ if (!this.adapter.flows) {
4354
+ throw new Error("Flows feature is not enabled. Database adapter does not support flows.");
4355
+ }
4356
+ this.validateFlowName(input.name);
4357
+ const existing = await this.adapter.flows.findByName(tenantId, input.name);
4358
+ if (existing) {
4359
+ throw new Error(`Flow "${input.name}" already exists`);
4360
+ }
4361
+ if (this.systemFlows.has(input.name)) {
4362
+ throw new Error(
4363
+ `Cannot create flow "${input.name}": a system flow with this name already exists`
4364
+ );
4365
+ }
4366
+ this.validateFlowStructure(input);
4367
+ const dbFlow = await this.adapter.flows.create({
4368
+ tenantId,
4369
+ name: input.name,
4370
+ label: input.label,
4371
+ description: input.description,
4372
+ icon: input.icon,
4373
+ status: "draft",
4374
+ version: 1,
4375
+ slots: input.slots,
4376
+ pages: input.pages,
4377
+ relations: input.relations,
4378
+ system: false,
4379
+ metadata: input.metadata
4380
+ });
4381
+ return this.convertDBFlowToDefinition(dbFlow);
4382
+ }
4383
+ /**
4384
+ * Update a custom flow
4385
+ */
4386
+ async updateFlow(flowId, input) {
4387
+ if (!this.adapter.flows) {
4388
+ throw new Error("Flows feature is not enabled.");
4389
+ }
4390
+ const dbFlow = await this.adapter.flows.findById(flowId);
4391
+ if (!dbFlow) {
4392
+ throw new Error(`Flow with id "${flowId}" not found`);
4393
+ }
4394
+ if (dbFlow.system) {
4395
+ throw new Error("Cannot modify system flows. System flows are protected.");
4396
+ }
4397
+ if (input.slots || input.pages || input.relations) {
4398
+ this.validateFlowStructure({
4399
+ name: dbFlow.name,
4400
+ label: input.label ?? dbFlow.label,
4401
+ slots: input.slots ?? dbFlow.slots,
4402
+ pages: input.pages ?? dbFlow.pages,
4403
+ relations: input.relations ?? dbFlow.relations
4404
+ });
4405
+ }
4406
+ const updated = await this.adapter.flows.update(flowId, {
4407
+ label: input.label,
4408
+ description: input.description,
4409
+ icon: input.icon,
4410
+ slots: input.slots,
4411
+ pages: input.pages,
4412
+ relations: input.relations,
4413
+ metadata: input.metadata
4414
+ });
4415
+ return this.convertDBFlowToDefinition(updated);
4416
+ }
4417
+ /**
4418
+ * Publish a flow
4419
+ */
4420
+ async publishFlow(flowId) {
4421
+ if (!this.adapter.flows) {
4422
+ throw new Error("Flows feature is not enabled.");
4423
+ }
4424
+ const dbFlow = await this.adapter.flows.findById(flowId);
4425
+ if (!dbFlow) {
4426
+ throw new Error(`Flow with id "${flowId}" not found`);
4427
+ }
4428
+ if (dbFlow.system) {
4429
+ throw new Error("Cannot publish system flows. They are always published.");
4430
+ }
4431
+ if (dbFlow.status === "published") {
4432
+ return this.convertDBFlowToDefinition(dbFlow);
4433
+ }
4434
+ this.validateFlowStructure({
4435
+ name: dbFlow.name,
4436
+ label: dbFlow.label,
4437
+ slots: dbFlow.slots,
4438
+ pages: dbFlow.pages,
4439
+ relations: dbFlow.relations
4440
+ });
4441
+ const updated = await this.adapter.flows.update(flowId, {
4442
+ status: "published",
4443
+ version: dbFlow.version + 1
4444
+ });
4445
+ return this.convertDBFlowToDefinition(updated);
4446
+ }
4447
+ /**
4448
+ * Archive a flow
4449
+ */
4450
+ async archiveFlow(flowId) {
4451
+ if (!this.adapter.flows) {
4452
+ throw new Error("Flows feature is not enabled.");
4453
+ }
4454
+ const dbFlow = await this.adapter.flows.findById(flowId);
4455
+ if (!dbFlow) {
4456
+ throw new Error(`Flow with id "${flowId}" not found`);
4457
+ }
4458
+ if (dbFlow.system) {
4459
+ throw new Error("Cannot archive system flows.");
4460
+ }
4461
+ const updated = await this.adapter.flows.update(flowId, {
4462
+ status: "archived"
4463
+ });
4464
+ return this.convertDBFlowToDefinition(updated);
4465
+ }
4466
+ /**
4467
+ * Delete a custom flow
4468
+ */
4469
+ async deleteFlow(flowId) {
4470
+ if (!this.adapter.flows) {
4471
+ throw new Error("Flows feature is not enabled.");
4472
+ }
4473
+ const dbFlow = await this.adapter.flows.findById(flowId);
4474
+ if (!dbFlow) {
4475
+ throw new Error(`Flow with id "${flowId}" not found`);
4476
+ }
4477
+ if (dbFlow.system) {
4478
+ throw new Error("Cannot delete system flows. System flows are protected.");
4479
+ }
4480
+ await this.adapter.flows.delete(flowId);
4481
+ }
4482
+ // ============================================================================
4483
+ // PRIVATE HELPERS
4484
+ // ============================================================================
4485
+ /**
4486
+ * Validate flow name format (kebab-case)
4487
+ */
4488
+ validateFlowName(name) {
4489
+ if (!name || name.length === 0) {
4490
+ throw new Error("Flow name cannot be empty");
4491
+ }
4492
+ if (name.length > 63) {
4493
+ throw new Error("Flow name is too long (max 63 characters)");
4494
+ }
4495
+ const kebabCaseRegex = /^[a-z][a-z0-9-]*$/;
4496
+ if (!kebabCaseRegex.test(name)) {
4497
+ throw new Error(
4498
+ "Invalid flow name format. Name must be in kebab-case (e.g., 'couple-creation', 'new-contact')"
4499
+ );
4500
+ }
4501
+ }
4502
+ /**
4503
+ * Validate flow structure (slots, pages, relations)
4504
+ */
4505
+ validateFlowStructure(input) {
4506
+ if (!input.slots || input.slots.length === 0) {
4507
+ throw new Error("Flow must have at least one slot");
4508
+ }
4509
+ if (!input.pages || input.pages.length === 0) {
4510
+ throw new Error("Flow must have at least one page");
4511
+ }
4512
+ const slotIds = new Set(input.slots.map((s) => s.id));
4513
+ if (slotIds.size !== input.slots.length) {
4514
+ throw new Error("Duplicate slot IDs detected");
4515
+ }
4516
+ for (const page of input.pages) {
4517
+ for (const row of page.rows) {
4518
+ for (const field of row.fields) {
4519
+ if (!slotIds.has(field.slotId)) {
4520
+ throw new Error(`Field "${field.id}" references unknown slot "${field.slotId}"`);
4521
+ }
4522
+ }
4523
+ }
4524
+ }
4525
+ for (const relation2 of input.relations) {
4526
+ if (!slotIds.has(relation2.sourceSlotId)) {
4527
+ throw new Error(`Relation references unknown source slot "${relation2.sourceSlotId}"`);
4528
+ }
4529
+ if (!slotIds.has(relation2.targetSlotId)) {
4530
+ throw new Error(`Relation references unknown target slot "${relation2.targetSlotId}"`);
4531
+ }
4532
+ if (relation2.sourceSlotId === relation2.targetSlotId) {
4533
+ throw new Error(`Relation cannot link a slot to itself: "${relation2.sourceSlotId}"`);
4534
+ }
4535
+ }
4536
+ }
4537
+ /**
4538
+ * Convert database flow to FlowDefinition
4539
+ */
4540
+ convertDBFlowToDefinition(dbFlow) {
4541
+ return {
4542
+ id: dbFlow.id,
4543
+ name: dbFlow.name,
4544
+ label: dbFlow.label,
4545
+ description: dbFlow.description,
4546
+ icon: dbFlow.icon,
4547
+ status: dbFlow.status,
4548
+ version: dbFlow.version,
4549
+ slots: dbFlow.slots,
4550
+ pages: dbFlow.pages,
4551
+ relations: dbFlow.relations,
4552
+ system: dbFlow.system,
4553
+ tenantId: dbFlow.tenantId,
4554
+ metadata: dbFlow.metadata,
4555
+ createdAt: dbFlow.createdAt,
4556
+ updatedAt: dbFlow.updatedAt
4557
+ };
4558
+ }
4559
+ };
4560
+
3928
4561
  // src/runtime/services/geocoding.service.ts
3929
4562
  var GeocodingService = class {
3930
4563
  constructor(adapter) {
@@ -6262,9 +6895,6 @@ var ViewService = class {
6262
6895
  `Cannot create view "${input.name}": a system view with this name already exists`
6263
6896
  );
6264
6897
  }
6265
- if (!input.tabs || input.tabs.length === 0) {
6266
- throw new Error("View must have at least one tab");
6267
- }
6268
6898
  const dbView = await this.adapter.views.create({
6269
6899
  tenantId,
6270
6900
  objectName: input.objectName,
@@ -6272,7 +6902,7 @@ var ViewService = class {
6272
6902
  label: input.label,
6273
6903
  description: input.description,
6274
6904
  icon: input.icon,
6275
- tabs: input.tabs,
6905
+ tabs: input.tabs ?? [],
6276
6906
  default: input.default ?? false,
6277
6907
  system: false,
6278
6908
  // Custom views are never system
@@ -6295,9 +6925,6 @@ var ViewService = class {
6295
6925
  if (dbView.system) {
6296
6926
  throw new Error("Cannot modify system views. System views are protected.");
6297
6927
  }
6298
- if (input.tabs && input.tabs.length === 0) {
6299
- throw new Error("View must have at least one tab");
6300
- }
6301
6928
  const updated = await this.adapter.views.update(viewId, {
6302
6929
  label: input.label,
6303
6930
  description: input.description,
@@ -6725,6 +7352,10 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
6725
7352
  DuplicateError,
6726
7353
  FileNotFoundError,
6727
7354
  FileService,
7355
+ FlowBuilder,
7356
+ FlowPageBuilder,
7357
+ FlowRowBuilder,
7358
+ FlowService,
6728
7359
  ForbiddenError,
6729
7360
  GeocodingService,
6730
7361
  GlobalSearchService,
@@ -6792,6 +7423,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
6792
7423
  extractAttributeNames,
6793
7424
  file,
6794
7425
  fileConfigSchema,
7426
+ flow,
6795
7427
  generateId,
6796
7428
  generatePrefixedId,
6797
7429
  getAttributeConfigSchema,
@@ -6802,6 +7434,8 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
6802
7434
  isAdvancedFilterState,
6803
7435
  isCustomTab,
6804
7436
  isDefaultRole,
7437
+ isFlowDefinition,
7438
+ isFlowPublished,
6805
7439
  isForbiddenError,
6806
7440
  isFormTab,
6807
7441
  isLabelExpression,
@@ -6810,6 +7444,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
6810
7444
  isProtectedResourceError,
6811
7445
  isRecordComplete,
6812
7446
  isSchemaError,
7447
+ isSystemFlow,
6813
7448
  isTableTab,
6814
7449
  isValidationError,
6815
7450
  location,