@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.
- package/dist/chunk-4D23X5XN.mjs +7257 -0
- package/dist/chunk-6RPWHMTH.mjs +7101 -0
- package/dist/chunk-BTVPMGYW.mjs +6327 -0
- package/dist/chunk-C7HZ55GJ.mjs +6882 -0
- package/dist/chunk-CMO3JEJL.mjs +7259 -0
- package/dist/chunk-JDUTFH3J.mjs +6940 -0
- package/dist/chunk-JWRGX3UW.mjs +7265 -0
- package/dist/chunk-OUSLILLE.mjs +7259 -0
- package/dist/chunk-PMJZPJDP.mjs +7265 -0
- package/dist/chunk-QAGN53HE.mjs +6327 -0
- package/dist/chunk-UUOAVXAI.mjs +6327 -0
- package/dist/chunk-VYZ3BOSB.mjs +6946 -0
- package/dist/chunk-XAX75NSP.mjs +6321 -0
- package/dist/chunk-YEV46K3M.mjs +7259 -0
- package/dist/chunk-ZG3PT3RL.mjs +6944 -0
- package/dist/index.d.mts +268 -4
- package/dist/index.d.ts +268 -4
- package/dist/index.js +1136 -168
- package/dist/index.mjs +35 -1
- package/dist/runtime--rp-TG-h.d.mts +5181 -0
- package/dist/runtime--rp-TG-h.d.ts +5181 -0
- package/dist/runtime-BPYJWZfC.d.mts +5130 -0
- package/dist/runtime-BPYJWZfC.d.ts +5130 -0
- package/dist/runtime-DRc_OhnM.d.mts +5142 -0
- package/dist/runtime-DRc_OhnM.d.ts +5142 -0
- package/dist/runtime-D_S3m9jf.d.mts +5142 -0
- package/dist/runtime-D_S3m9jf.d.ts +5142 -0
- package/dist/runtime-Db3rRxbJ.d.mts +4821 -0
- package/dist/runtime-Db3rRxbJ.d.ts +4821 -0
- package/dist/runtime-DlCiIIfr.d.mts +5130 -0
- package/dist/runtime-DlCiIIfr.d.ts +5130 -0
- package/dist/runtime-WqYxjhjJ.d.mts +5125 -0
- package/dist/runtime-WqYxjhjJ.d.ts +5125 -0
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +855 -222
- package/dist/runtime.mjs +5 -1
- 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
|
|
638
|
-
var VALID_ICONS = new Set(
|
|
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
|
|
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 =
|
|
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
|
|
1428
|
-
throw new Error(`[ObjectBuilder] ${error.
|
|
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
|
|
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 =
|
|
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
|
|
1802
|
-
throw new Error(`[ViewBuilder] ${error.
|
|
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
|
|
2101
|
-
var baseConfigSchema =
|
|
2102
|
-
disabled:
|
|
2103
|
-
placeholder:
|
|
2104
|
-
description:
|
|
2105
|
-
defaultValue:
|
|
2106
|
-
icon:
|
|
2107
|
-
order:
|
|
2108
|
-
hidden:
|
|
2109
|
-
archived:
|
|
2110
|
-
deprecated:
|
|
2111
|
-
metadata:
|
|
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 =
|
|
2114
|
-
id:
|
|
2115
|
-
label:
|
|
2116
|
-
value:
|
|
2117
|
-
color:
|
|
2118
|
-
icon:
|
|
2119
|
-
description:
|
|
2120
|
-
group:
|
|
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
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
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 =
|
|
2128
|
-
type:
|
|
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:
|
|
2140
|
-
faces:
|
|
2141
|
-
attributeMapping:
|
|
2142
|
-
|
|
2143
|
-
attributeId:
|
|
2144
|
-
extractedKey:
|
|
2145
|
-
face:
|
|
2146
|
-
required:
|
|
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 =
|
|
2151
|
-
enabled:
|
|
2152
|
-
documentTypes:
|
|
2153
|
-
autoExtract:
|
|
2154
|
-
autoValidate:
|
|
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:
|
|
2158
|
-
maxLength:
|
|
2159
|
-
pattern:
|
|
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:
|
|
2164
|
-
max:
|
|
2165
|
-
unit:
|
|
2166
|
-
decimals:
|
|
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:
|
|
2171
|
-
minDate:
|
|
2172
|
-
maxDate:
|
|
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:
|
|
2723
|
+
defaultCountryCode: import_zod4.z.string().length(3).optional()
|
|
2176
2724
|
});
|
|
2177
2725
|
var currencyConfigSchema = baseConfigSchema.extend({
|
|
2178
|
-
defaultCurrency:
|
|
2179
|
-
allowedCurrencies:
|
|
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:
|
|
2730
|
+
options: optionsArraySchema
|
|
2183
2731
|
});
|
|
2184
2732
|
var locationConfigSchema = baseConfigSchema.extend({
|
|
2185
|
-
granularity:
|
|
2186
|
-
enableAutocomplete:
|
|
2187
|
-
enableMap:
|
|
2188
|
-
defaultCountry:
|
|
2189
|
-
allowedCountries:
|
|
2190
|
-
displayFormat:
|
|
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:
|
|
2741
|
+
autoUpdate: import_zod4.z.boolean().optional()
|
|
2194
2742
|
});
|
|
2195
2743
|
var selectConfigSchema = baseConfigSchema.extend({
|
|
2196
|
-
options:
|
|
2744
|
+
options: optionsArraySchema
|
|
2197
2745
|
});
|
|
2198
2746
|
var multiselectConfigSchema = baseConfigSchema.extend({
|
|
2199
|
-
options:
|
|
2747
|
+
options: optionsArraySchema
|
|
2200
2748
|
});
|
|
2201
2749
|
var fileConfigSchema = baseConfigSchema.extend({
|
|
2202
|
-
maxFiles:
|
|
2203
|
-
maxSize:
|
|
2204
|
-
allowedTypes:
|
|
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:
|
|
2756
|
+
allowedRoles: import_zod4.z.array(import_zod4.z.string()).optional()
|
|
2209
2757
|
});
|
|
2210
2758
|
var relationConfigSchema = baseConfigSchema.extend({
|
|
2211
|
-
targets:
|
|
2212
|
-
cardinality:
|
|
2213
|
-
minItems:
|
|
2214
|
-
maxItems:
|
|
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:
|
|
2218
|
-
iconType:
|
|
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.
|
|
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 =
|
|
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 =
|
|
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
|
|
2842
|
+
return import_zod4.z.boolean();
|
|
2295
2843
|
}
|
|
2296
2844
|
function createDateValidator(attr) {
|
|
2297
|
-
return
|
|
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
|
|
2301
|
-
countryCode:
|
|
2302
|
-
phoneNumber:
|
|
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
|
|
2307
|
-
code:
|
|
2308
|
-
value:
|
|
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
|
|
2314
|
-
|
|
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
|
|
2322
|
-
|
|
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
|
|
2330
|
-
|
|
2331
|
-
|
|
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
|
|
2339
|
-
address:
|
|
2340
|
-
address2:
|
|
2341
|
-
city:
|
|
2342
|
-
state:
|
|
2343
|
-
postalCode:
|
|
2344
|
-
country:
|
|
2345
|
-
latitude:
|
|
2346
|
-
longitude:
|
|
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
|
|
2892
|
+
return import_zod4.z.number().int().positive();
|
|
2351
2893
|
}
|
|
2352
2894
|
function createFileValidator(_attr) {
|
|
2353
|
-
return
|
|
2895
|
+
return import_zod4.z.string().uuid();
|
|
2354
2896
|
}
|
|
2355
2897
|
function createUserValidator(_attr) {
|
|
2356
|
-
return
|
|
2898
|
+
return import_zod4.z.string().uuid();
|
|
2357
2899
|
}
|
|
2358
2900
|
function createSingleRelationValidator(attr) {
|
|
2359
|
-
const uuidSchema =
|
|
2901
|
+
const uuidSchema = import_zod4.z.string().uuid({
|
|
2360
2902
|
message: `${attr.label} must be a valid record ID`
|
|
2361
2903
|
});
|
|
2362
|
-
return
|
|
2904
|
+
return import_zod4.z.union([uuidSchema, import_zod4.z.null()]);
|
|
2363
2905
|
}
|
|
2364
2906
|
function createMultiRelationValidator(attr) {
|
|
2365
|
-
const uuidSchema =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
4397
|
-
|
|
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
|
|
5250
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
6423
|
+
return renderLabelExpression(schema.labelExpression, enrichedValues);
|
|
5479
6424
|
}
|
|
5480
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7245
|
+
throw new NotFoundError("View", viewId);
|
|
6294
7246
|
}
|
|
6295
7247
|
if (dbView.system) {
|
|
6296
|
-
throw new
|
|
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
|
|
7268
|
+
throw new NotFoundError("View", viewId);
|
|
6320
7269
|
}
|
|
6321
7270
|
if (dbView.system) {
|
|
6322
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
6363
|
-
|
|
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,
|