@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/runtime.js
CHANGED
|
@@ -121,6 +121,7 @@ __export(runtime_exports, {
|
|
|
121
121
|
AuditService: () => AuditService,
|
|
122
122
|
DEFAULT_LABEL_FALLBACK: () => DEFAULT_LABEL_FALLBACK,
|
|
123
123
|
FileService: () => FileService,
|
|
124
|
+
FlowService: () => FlowService,
|
|
124
125
|
GeocodingService: () => GeocodingService,
|
|
125
126
|
GlobalSearchService: () => GlobalSearchService,
|
|
126
127
|
NoopGeocodingAdapter: () => NoopGeocodingAdapter,
|
|
@@ -133,6 +134,7 @@ __export(runtime_exports, {
|
|
|
133
134
|
ViewService: () => ViewService,
|
|
134
135
|
buildAuditChanges: () => buildAuditChanges,
|
|
135
136
|
createMockAdapter: () => createMockAdapter,
|
|
137
|
+
enrichValuesWithSelectLabels: () => enrichValuesWithSelectLabels,
|
|
136
138
|
extractAttributeNames: () => extractAttributeNames,
|
|
137
139
|
getSyncPreview: () => getSyncPreview,
|
|
138
140
|
getViewSyncPreview: () => getViewSyncPreview,
|
|
@@ -164,6 +166,168 @@ function generateId() {
|
|
|
164
166
|
});
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
// src/format.ts
|
|
170
|
+
var import_constants = require("@stndrds/constants");
|
|
171
|
+
var EMPTY_VALUE_PLACEHOLDER = "\u2014";
|
|
172
|
+
function formatText(value) {
|
|
173
|
+
return String(value);
|
|
174
|
+
}
|
|
175
|
+
function formatCheckbox(value) {
|
|
176
|
+
return value ? "Yes" : "No";
|
|
177
|
+
}
|
|
178
|
+
function formatNumber(value, attribute) {
|
|
179
|
+
if (typeof value !== "number") return String(value);
|
|
180
|
+
const decimals = attribute.decimals;
|
|
181
|
+
return value.toLocaleString(void 0, {
|
|
182
|
+
minimumFractionDigits: decimals,
|
|
183
|
+
maximumFractionDigits: decimals
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function formatCurrency(value, _attribute) {
|
|
187
|
+
if (typeof value !== "object" || value === null) return String(value);
|
|
188
|
+
const currency = value;
|
|
189
|
+
if (!("value" in currency && "code" in currency)) return String(value);
|
|
190
|
+
const formattedValue = currency.value.toLocaleString(void 0, {
|
|
191
|
+
minimumFractionDigits: 2,
|
|
192
|
+
maximumFractionDigits: 2
|
|
193
|
+
});
|
|
194
|
+
return `${formattedValue} ${currency.code}`;
|
|
195
|
+
}
|
|
196
|
+
function formatDate(value) {
|
|
197
|
+
if (value instanceof Date) {
|
|
198
|
+
return value.toISOString().split("T")[0];
|
|
199
|
+
}
|
|
200
|
+
if (typeof value === "string") {
|
|
201
|
+
const date = new Date(value);
|
|
202
|
+
if (!Number.isNaN(date.getTime())) {
|
|
203
|
+
return date.toISOString().split("T")[0];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return String(value);
|
|
207
|
+
}
|
|
208
|
+
function formatTimestamp(value) {
|
|
209
|
+
if (typeof value === "number") {
|
|
210
|
+
return new Date(value).toISOString();
|
|
211
|
+
}
|
|
212
|
+
if (value instanceof Date) {
|
|
213
|
+
return value.toISOString();
|
|
214
|
+
}
|
|
215
|
+
return String(value);
|
|
216
|
+
}
|
|
217
|
+
function formatPhone(value) {
|
|
218
|
+
if (typeof value !== "object" || value === null) return String(value);
|
|
219
|
+
const phone = value;
|
|
220
|
+
if (!("phoneNumber" in phone)) return String(value);
|
|
221
|
+
if (phone.countryCode) {
|
|
222
|
+
const country = (0, import_constants.getCountryByIso3)(phone.countryCode);
|
|
223
|
+
const dial = country?.phoneCode ?? "";
|
|
224
|
+
return `${dial} ${phone.phoneNumber}`.trim();
|
|
225
|
+
}
|
|
226
|
+
return phone.phoneNumber;
|
|
227
|
+
}
|
|
228
|
+
function formatLocation(value, attribute) {
|
|
229
|
+
if (typeof value !== "object" || value === null) return String(value);
|
|
230
|
+
const loc = value;
|
|
231
|
+
const granularity = attribute.granularity ?? "full";
|
|
232
|
+
const parts = [];
|
|
233
|
+
switch (granularity) {
|
|
234
|
+
case "country":
|
|
235
|
+
if (loc.country) parts.push(loc.country);
|
|
236
|
+
break;
|
|
237
|
+
case "state":
|
|
238
|
+
if (loc.state) parts.push(loc.state);
|
|
239
|
+
if (loc.country) parts.push(loc.country);
|
|
240
|
+
break;
|
|
241
|
+
case "city":
|
|
242
|
+
if (loc.city) parts.push(loc.city);
|
|
243
|
+
if (loc.state) parts.push(loc.state);
|
|
244
|
+
if (loc.country) parts.push(loc.country);
|
|
245
|
+
break;
|
|
246
|
+
case "coordinates":
|
|
247
|
+
if (loc.latitude !== void 0 && loc.longitude !== void 0) {
|
|
248
|
+
parts.push(`${loc.latitude}, ${loc.longitude}`);
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
case "address":
|
|
252
|
+
if (loc.address) parts.push(loc.address);
|
|
253
|
+
if (loc.city) parts.push(loc.city);
|
|
254
|
+
if (loc.state) parts.push(loc.state);
|
|
255
|
+
if (loc.country) parts.push(loc.country);
|
|
256
|
+
break;
|
|
257
|
+
default:
|
|
258
|
+
if (loc.address) parts.push(loc.address);
|
|
259
|
+
if (loc.city) parts.push(loc.city);
|
|
260
|
+
if (loc.state) parts.push(loc.state);
|
|
261
|
+
if (loc.postalCode) parts.push(loc.postalCode);
|
|
262
|
+
if (loc.country) parts.push(loc.country);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
return parts.join(", ") || EMPTY_VALUE_PLACEHOLDER;
|
|
266
|
+
}
|
|
267
|
+
function formatSelect(value, attribute) {
|
|
268
|
+
if (typeof value !== "string") return String(value);
|
|
269
|
+
const option = attribute.options?.find((o) => o.value === value);
|
|
270
|
+
return option?.label ?? String(value);
|
|
271
|
+
}
|
|
272
|
+
function formatMultiselect(value, attribute) {
|
|
273
|
+
if (!Array.isArray(value)) return String(value);
|
|
274
|
+
if (attribute.options) {
|
|
275
|
+
const labels = value.map((v) => attribute.options.find((o) => o.value === v)?.label).filter(Boolean);
|
|
276
|
+
return labels.join(", ");
|
|
277
|
+
}
|
|
278
|
+
return value.join(", ");
|
|
279
|
+
}
|
|
280
|
+
function formatRating(value, attribute) {
|
|
281
|
+
if (typeof value !== "number") return String(value);
|
|
282
|
+
const max = attribute.max ?? 5;
|
|
283
|
+
return `${value}/${max}`;
|
|
284
|
+
}
|
|
285
|
+
function formatAttributeValue(value, attribute) {
|
|
286
|
+
if (value === null || value === void 0 || value === "") {
|
|
287
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
288
|
+
}
|
|
289
|
+
switch (attribute.type) {
|
|
290
|
+
case "text":
|
|
291
|
+
case "textarea":
|
|
292
|
+
return formatText(value);
|
|
293
|
+
case "checkbox":
|
|
294
|
+
return formatCheckbox(value);
|
|
295
|
+
case "number":
|
|
296
|
+
return formatNumber(value, attribute);
|
|
297
|
+
case "currency":
|
|
298
|
+
return formatCurrency(value, attribute);
|
|
299
|
+
case "date":
|
|
300
|
+
return formatDate(value);
|
|
301
|
+
case "timestamp":
|
|
302
|
+
return formatTimestamp(value);
|
|
303
|
+
case "phone":
|
|
304
|
+
return formatPhone(value);
|
|
305
|
+
case "location":
|
|
306
|
+
return formatLocation(value, attribute);
|
|
307
|
+
case "select":
|
|
308
|
+
case "status":
|
|
309
|
+
return formatSelect(value, attribute);
|
|
310
|
+
case "multiselect":
|
|
311
|
+
return formatMultiselect(value, attribute);
|
|
312
|
+
case "rating":
|
|
313
|
+
return formatRating(value, attribute);
|
|
314
|
+
// Unsupported types - return value as-is or placeholder
|
|
315
|
+
case "file":
|
|
316
|
+
case "user":
|
|
317
|
+
case "relation":
|
|
318
|
+
if (Array.isArray(value)) {
|
|
319
|
+
return value.join(", ");
|
|
320
|
+
}
|
|
321
|
+
return String(value);
|
|
322
|
+
default: {
|
|
323
|
+
if (Array.isArray(value)) {
|
|
324
|
+
return value.join(", ");
|
|
325
|
+
}
|
|
326
|
+
return String(value);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
167
331
|
// src/runtime/template.ts
|
|
168
332
|
var pipes = {
|
|
169
333
|
/** Convert to uppercase */
|
|
@@ -215,6 +379,24 @@ function extractAttributeNames(template) {
|
|
|
215
379
|
}
|
|
216
380
|
return names;
|
|
217
381
|
}
|
|
382
|
+
function hasOptions(attr) {
|
|
383
|
+
return "options" in attr && Array.isArray(attr.options) && attr.options.length > 0;
|
|
384
|
+
}
|
|
385
|
+
function enrichValuesWithSelectLabels(values, attributes) {
|
|
386
|
+
const enriched = { ...values };
|
|
387
|
+
for (const attr of attributes) {
|
|
388
|
+
const value = values[attr.name];
|
|
389
|
+
if (value == null) continue;
|
|
390
|
+
const isSelectLike = attr.type === "select" || attr.type === "status" || attr.type === "multiselect";
|
|
391
|
+
if (!(isSelectLike && hasOptions(attr))) continue;
|
|
392
|
+
if (attr.type === "multiselect" && Array.isArray(value) && value.length === 0) continue;
|
|
393
|
+
const formatted = formatAttributeValue(value, attr);
|
|
394
|
+
if (formatted && formatted !== EMPTY_VALUE_PLACEHOLDER) {
|
|
395
|
+
enriched[attr.name] = formatted;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return enriched;
|
|
399
|
+
}
|
|
218
400
|
|
|
219
401
|
// src/runtime/mock-adapter.ts
|
|
220
402
|
function createMockObjectsRepository(stores) {
|
|
@@ -691,14 +873,31 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
691
873
|
(options.offset ?? 0) + options.limit
|
|
692
874
|
);
|
|
693
875
|
}
|
|
876
|
+
const attributesByObjectId = /* @__PURE__ */ new Map();
|
|
877
|
+
for (const attr of stores.attributes.values()) {
|
|
878
|
+
if (!attributesByObjectId.has(attr.objectId)) {
|
|
879
|
+
attributesByObjectId.set(attr.objectId, []);
|
|
880
|
+
}
|
|
881
|
+
attributesByObjectId.get(attr.objectId)?.push(attr);
|
|
882
|
+
}
|
|
694
883
|
const results = matchingRecords.map((r) => {
|
|
695
884
|
const obj = objectsMap.get(r.objectId);
|
|
696
885
|
const labelExpression = obj?.labelExpression ?? "{{ name }}";
|
|
886
|
+
const dbAttrs = attributesByObjectId.get(r.objectId) ?? [];
|
|
887
|
+
const attrs = dbAttrs.map((a) => ({
|
|
888
|
+
...a.config,
|
|
889
|
+
id: a.id,
|
|
890
|
+
name: a.name,
|
|
891
|
+
type: a.type,
|
|
892
|
+
label: a.config.label ?? a.name,
|
|
893
|
+
required: a.config.required ?? false
|
|
894
|
+
}));
|
|
895
|
+
const enrichedValues = enrichValuesWithSelectLabels(r.values, attrs);
|
|
697
896
|
return {
|
|
698
897
|
objectId: r.objectId,
|
|
699
898
|
objectName: obj?.name ?? "unknown",
|
|
700
899
|
objectLabel: obj?.label ?? "Unknown",
|
|
701
|
-
label: renderLabelExpression(labelExpression,
|
|
900
|
+
label: renderLabelExpression(labelExpression, enrichedValues),
|
|
702
901
|
recordId: r.id,
|
|
703
902
|
values: r.values,
|
|
704
903
|
completionStatus: r.completionStatus,
|
|
@@ -1115,6 +1314,10 @@ var AuditService = class {
|
|
|
1115
1314
|
this.options = options;
|
|
1116
1315
|
this.buffer = [];
|
|
1117
1316
|
this.flushTimer = null;
|
|
1317
|
+
/** Prevents concurrent flush operations */
|
|
1318
|
+
this.isFlushing = false;
|
|
1319
|
+
/** Pending flush promise to allow waiting on concurrent flush */
|
|
1320
|
+
this.flushPromise = null;
|
|
1118
1321
|
if (options?.async && options.flushIntervalMs) {
|
|
1119
1322
|
this.startFlushTimer();
|
|
1120
1323
|
}
|
|
@@ -1267,14 +1470,23 @@ var AuditService = class {
|
|
|
1267
1470
|
// ============================================================================
|
|
1268
1471
|
/**
|
|
1269
1472
|
* Flush buffered logs to the database
|
|
1473
|
+
* Protected against concurrent flush calls
|
|
1270
1474
|
*/
|
|
1271
1475
|
async flush() {
|
|
1476
|
+
if (this.isFlushing && this.flushPromise) {
|
|
1477
|
+
return this.flushPromise;
|
|
1478
|
+
}
|
|
1272
1479
|
if (this.buffer.length === 0 || !this.adapter.audit) {
|
|
1273
1480
|
return;
|
|
1274
1481
|
}
|
|
1482
|
+
this.isFlushing = true;
|
|
1275
1483
|
const entries = [...this.buffer];
|
|
1276
1484
|
this.buffer = [];
|
|
1277
|
-
|
|
1485
|
+
this.flushPromise = this.adapter.audit.createMany(entries).finally(() => {
|
|
1486
|
+
this.isFlushing = false;
|
|
1487
|
+
this.flushPromise = null;
|
|
1488
|
+
});
|
|
1489
|
+
return this.flushPromise;
|
|
1278
1490
|
}
|
|
1279
1491
|
/**
|
|
1280
1492
|
* Clean up resources (stop timer, flush remaining logs)
|
|
@@ -1296,6 +1508,12 @@ var AuditService = class {
|
|
|
1296
1508
|
if (!this.adapter.audit) {
|
|
1297
1509
|
return;
|
|
1298
1510
|
}
|
|
1511
|
+
if (entry.actorId && !entry.actorEmail && entry.actorType === "user") {
|
|
1512
|
+
const profile = await this.adapter.userProfiles.findById(entry.actorId);
|
|
1513
|
+
if (profile) {
|
|
1514
|
+
entry.actorEmail = profile.email;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1299
1517
|
if (this.options?.async) {
|
|
1300
1518
|
this.buffer.push(entry);
|
|
1301
1519
|
const batchSize = this.options.batchSize ?? 10;
|
|
@@ -1533,6 +1751,432 @@ var FileService = class {
|
|
|
1533
1751
|
}
|
|
1534
1752
|
};
|
|
1535
1753
|
|
|
1754
|
+
// src/exceptions.ts
|
|
1755
|
+
var SchemaErrorCode = {
|
|
1756
|
+
// Generic
|
|
1757
|
+
UNKNOWN: "SCHEMA_UNKNOWN_ERROR",
|
|
1758
|
+
// Not Found
|
|
1759
|
+
OBJECT_NOT_FOUND: "SCHEMA_OBJECT_NOT_FOUND",
|
|
1760
|
+
ATTRIBUTE_NOT_FOUND: "SCHEMA_ATTRIBUTE_NOT_FOUND",
|
|
1761
|
+
RECORD_NOT_FOUND: "SCHEMA_RECORD_NOT_FOUND",
|
|
1762
|
+
USER_PROFILE_NOT_FOUND: "SCHEMA_USER_PROFILE_NOT_FOUND",
|
|
1763
|
+
FILE_NOT_FOUND: "SCHEMA_FILE_NOT_FOUND",
|
|
1764
|
+
ROLE_NOT_FOUND: "SCHEMA_ROLE_NOT_FOUND",
|
|
1765
|
+
// Validation
|
|
1766
|
+
VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED",
|
|
1767
|
+
INVALID_ATTRIBUTE_NAME: "SCHEMA_INVALID_ATTRIBUTE_NAME",
|
|
1768
|
+
INVALID_OBJECT_NAME: "SCHEMA_INVALID_OBJECT_NAME",
|
|
1769
|
+
// Protected Resources
|
|
1770
|
+
PROTECTED_OBJECT: "SCHEMA_PROTECTED_OBJECT",
|
|
1771
|
+
PROTECTED_ATTRIBUTE: "SCHEMA_PROTECTED_ATTRIBUTE",
|
|
1772
|
+
PROTECTED_ROLE: "SCHEMA_PROTECTED_ROLE",
|
|
1773
|
+
// Permissions
|
|
1774
|
+
FORBIDDEN: "SCHEMA_FORBIDDEN",
|
|
1775
|
+
// Sync
|
|
1776
|
+
SYNC_FAILED: "SCHEMA_SYNC_FAILED",
|
|
1777
|
+
NOT_SYSTEM_OBJECT: "SCHEMA_NOT_SYSTEM_OBJECT",
|
|
1778
|
+
// Duplicates
|
|
1779
|
+
DUPLICATE_OBJECT: "SCHEMA_DUPLICATE_OBJECT",
|
|
1780
|
+
DUPLICATE_ATTRIBUTE: "SCHEMA_DUPLICATE_ATTRIBUTE"
|
|
1781
|
+
};
|
|
1782
|
+
var SchemaError = class extends Error {
|
|
1783
|
+
constructor(message, code = SchemaErrorCode.UNKNOWN, details) {
|
|
1784
|
+
super(message);
|
|
1785
|
+
this.name = "SchemaError";
|
|
1786
|
+
this.code = code;
|
|
1787
|
+
this.details = details;
|
|
1788
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
1789
|
+
}
|
|
1790
|
+
toJSON() {
|
|
1791
|
+
return {
|
|
1792
|
+
name: this.name,
|
|
1793
|
+
code: this.code,
|
|
1794
|
+
message: this.message,
|
|
1795
|
+
details: this.details
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
var NotFoundError = class extends SchemaError {
|
|
1800
|
+
constructor(resourceType, resourceId, code = SchemaErrorCode.RECORD_NOT_FOUND) {
|
|
1801
|
+
super(`${resourceType} with id "${resourceId}" not found`, code, {
|
|
1802
|
+
resourceType,
|
|
1803
|
+
resourceId
|
|
1804
|
+
});
|
|
1805
|
+
this.name = "NotFoundError";
|
|
1806
|
+
this.resourceType = resourceType;
|
|
1807
|
+
this.resourceId = resourceId;
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
var RecordNotFoundError = class extends NotFoundError {
|
|
1811
|
+
constructor(recordId) {
|
|
1812
|
+
super("Record", recordId, SchemaErrorCode.RECORD_NOT_FOUND);
|
|
1813
|
+
this.name = "RecordNotFoundError";
|
|
1814
|
+
}
|
|
1815
|
+
};
|
|
1816
|
+
var ValidationError = class _ValidationError extends SchemaError {
|
|
1817
|
+
constructor(message, errors) {
|
|
1818
|
+
super(message, SchemaErrorCode.VALIDATION_FAILED, { errors });
|
|
1819
|
+
this.name = "ValidationError";
|
|
1820
|
+
this.errors = errors;
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Create a validation error from Zod-style errors
|
|
1824
|
+
*/
|
|
1825
|
+
static fromZodErrors(errors) {
|
|
1826
|
+
const details = errors.map((err) => ({
|
|
1827
|
+
path: err.path.map(String),
|
|
1828
|
+
message: err.message
|
|
1829
|
+
}));
|
|
1830
|
+
const message = `Validation failed: ${details.map((d) => `${d.path.join(".")}: ${d.message}`).join(", ")}`;
|
|
1831
|
+
return new _ValidationError(message, details);
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
var ProtectedResourceError = class extends SchemaError {
|
|
1835
|
+
constructor(resourceType, resourceName, operation) {
|
|
1836
|
+
const code = resourceType === "object" ? SchemaErrorCode.PROTECTED_OBJECT : SchemaErrorCode.PROTECTED_ATTRIBUTE;
|
|
1837
|
+
super(`Cannot ${operation} system ${resourceType} "${resourceName}"`, code, {
|
|
1838
|
+
resourceType,
|
|
1839
|
+
resourceName,
|
|
1840
|
+
operation
|
|
1841
|
+
});
|
|
1842
|
+
this.name = "ProtectedResourceError";
|
|
1843
|
+
this.resourceType = resourceType;
|
|
1844
|
+
this.resourceName = resourceName;
|
|
1845
|
+
this.operation = operation;
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
var ForbiddenError = class extends SchemaError {
|
|
1849
|
+
constructor(objectName, action, userId) {
|
|
1850
|
+
super(`No ${action} permission on object "${objectName}"`, SchemaErrorCode.FORBIDDEN, {
|
|
1851
|
+
objectName,
|
|
1852
|
+
action,
|
|
1853
|
+
userId
|
|
1854
|
+
});
|
|
1855
|
+
this.name = "ForbiddenError";
|
|
1856
|
+
this.objectName = objectName;
|
|
1857
|
+
this.action = action;
|
|
1858
|
+
this.userId = userId;
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
|
|
1862
|
+
// src/runtime/services/flow.service.ts
|
|
1863
|
+
var FlowService = class {
|
|
1864
|
+
constructor(adapter, systemFlows = []) {
|
|
1865
|
+
this.adapter = adapter;
|
|
1866
|
+
this.systemFlows = new Map(systemFlows.map((f) => [f.name, f]));
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Get all flows for a tenant (system + custom)
|
|
1870
|
+
*/
|
|
1871
|
+
async getAllFlows(tenantId) {
|
|
1872
|
+
const systemFlowsList = Array.from(this.systemFlows.values());
|
|
1873
|
+
if (!this.adapter.flows) {
|
|
1874
|
+
return systemFlowsList;
|
|
1875
|
+
}
|
|
1876
|
+
const dbFlows = await this.adapter.flows.findAllForTenant(tenantId);
|
|
1877
|
+
const customFlows = dbFlows.filter((f) => !f.system).map(this.convertDBFlowToDefinition);
|
|
1878
|
+
return [...systemFlowsList, ...customFlows];
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* Get published flows only
|
|
1882
|
+
*/
|
|
1883
|
+
async getPublishedFlows(tenantId) {
|
|
1884
|
+
const allFlows = await this.getAllFlows(tenantId);
|
|
1885
|
+
return allFlows.filter((f) => f.status === "published");
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Get a specific flow by name
|
|
1889
|
+
*/
|
|
1890
|
+
async getFlow(name, tenantId) {
|
|
1891
|
+
const systemFlow = this.systemFlows.get(name);
|
|
1892
|
+
if (systemFlow) {
|
|
1893
|
+
return systemFlow;
|
|
1894
|
+
}
|
|
1895
|
+
if (!this.adapter.flows) {
|
|
1896
|
+
return null;
|
|
1897
|
+
}
|
|
1898
|
+
const dbFlow = await this.adapter.flows.findByName(tenantId, name);
|
|
1899
|
+
if (dbFlow) {
|
|
1900
|
+
return this.convertDBFlowToDefinition(dbFlow);
|
|
1901
|
+
}
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Get a flow by ID
|
|
1906
|
+
*/
|
|
1907
|
+
async getFlowById(flowId) {
|
|
1908
|
+
if (!this.adapter.flows) {
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
const dbFlow = await this.adapter.flows.findById(flowId);
|
|
1912
|
+
if (dbFlow) {
|
|
1913
|
+
return this.convertDBFlowToDefinition(dbFlow);
|
|
1914
|
+
}
|
|
1915
|
+
return null;
|
|
1916
|
+
}
|
|
1917
|
+
/**
|
|
1918
|
+
* Create a new custom flow (as draft)
|
|
1919
|
+
*/
|
|
1920
|
+
async createFlow(input, tenantId) {
|
|
1921
|
+
if (!this.adapter.flows) {
|
|
1922
|
+
throw new SchemaError(
|
|
1923
|
+
"Flows feature is not enabled. Database adapter does not support flows.",
|
|
1924
|
+
SchemaErrorCode.UNKNOWN,
|
|
1925
|
+
{ feature: "flows" }
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
this.validateFlowName(input.name);
|
|
1929
|
+
const existing = await this.adapter.flows.findByName(tenantId, input.name);
|
|
1930
|
+
if (existing) {
|
|
1931
|
+
throw new SchemaError(
|
|
1932
|
+
`Flow "${input.name}" already exists`,
|
|
1933
|
+
SchemaErrorCode.DUPLICATE_OBJECT,
|
|
1934
|
+
{ flowName: input.name }
|
|
1935
|
+
);
|
|
1936
|
+
}
|
|
1937
|
+
if (this.systemFlows.has(input.name)) {
|
|
1938
|
+
throw new SchemaError(
|
|
1939
|
+
`Cannot create flow "${input.name}": a system flow with this name already exists`,
|
|
1940
|
+
SchemaErrorCode.DUPLICATE_OBJECT,
|
|
1941
|
+
{ flowName: input.name, system: true }
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
this.validateFlowStructure(input);
|
|
1945
|
+
const dbFlow = await this.adapter.flows.create({
|
|
1946
|
+
tenantId,
|
|
1947
|
+
name: input.name,
|
|
1948
|
+
label: input.label,
|
|
1949
|
+
description: input.description,
|
|
1950
|
+
icon: input.icon,
|
|
1951
|
+
status: "draft",
|
|
1952
|
+
version: 1,
|
|
1953
|
+
slots: input.slots,
|
|
1954
|
+
pages: input.pages,
|
|
1955
|
+
relations: input.relations,
|
|
1956
|
+
system: false,
|
|
1957
|
+
metadata: input.metadata
|
|
1958
|
+
});
|
|
1959
|
+
return this.convertDBFlowToDefinition(dbFlow);
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Update a custom flow
|
|
1963
|
+
*/
|
|
1964
|
+
async updateFlow(flowId, input) {
|
|
1965
|
+
if (!this.adapter.flows) {
|
|
1966
|
+
throw new SchemaError("Flows feature is not enabled.", SchemaErrorCode.UNKNOWN, {
|
|
1967
|
+
feature: "flows"
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
const dbFlow = await this.adapter.flows.findById(flowId);
|
|
1971
|
+
if (!dbFlow) {
|
|
1972
|
+
throw new NotFoundError("Flow", flowId);
|
|
1973
|
+
}
|
|
1974
|
+
if (dbFlow.system) {
|
|
1975
|
+
throw new ProtectedResourceError("object", dbFlow.name, "modify");
|
|
1976
|
+
}
|
|
1977
|
+
if (input.slots || input.pages || input.relations) {
|
|
1978
|
+
this.validateFlowStructure({
|
|
1979
|
+
name: dbFlow.name,
|
|
1980
|
+
label: input.label ?? dbFlow.label,
|
|
1981
|
+
slots: input.slots ?? dbFlow.slots,
|
|
1982
|
+
pages: input.pages ?? dbFlow.pages,
|
|
1983
|
+
relations: input.relations ?? dbFlow.relations
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
const updated = await this.adapter.flows.update(flowId, {
|
|
1987
|
+
label: input.label,
|
|
1988
|
+
description: input.description,
|
|
1989
|
+
icon: input.icon,
|
|
1990
|
+
slots: input.slots,
|
|
1991
|
+
pages: input.pages,
|
|
1992
|
+
relations: input.relations,
|
|
1993
|
+
metadata: input.metadata
|
|
1994
|
+
});
|
|
1995
|
+
return this.convertDBFlowToDefinition(updated);
|
|
1996
|
+
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Publish a flow
|
|
1999
|
+
*/
|
|
2000
|
+
async publishFlow(flowId) {
|
|
2001
|
+
if (!this.adapter.flows) {
|
|
2002
|
+
throw new SchemaError("Flows feature is not enabled.", SchemaErrorCode.UNKNOWN, {
|
|
2003
|
+
feature: "flows"
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
const dbFlow = await this.adapter.flows.findById(flowId);
|
|
2007
|
+
if (!dbFlow) {
|
|
2008
|
+
throw new NotFoundError("Flow", flowId);
|
|
2009
|
+
}
|
|
2010
|
+
if (dbFlow.system) {
|
|
2011
|
+
throw new ProtectedResourceError("object", dbFlow.name, "modify");
|
|
2012
|
+
}
|
|
2013
|
+
if (dbFlow.status === "published") {
|
|
2014
|
+
return this.convertDBFlowToDefinition(dbFlow);
|
|
2015
|
+
}
|
|
2016
|
+
this.validateFlowStructure({
|
|
2017
|
+
name: dbFlow.name,
|
|
2018
|
+
label: dbFlow.label,
|
|
2019
|
+
slots: dbFlow.slots,
|
|
2020
|
+
pages: dbFlow.pages,
|
|
2021
|
+
relations: dbFlow.relations
|
|
2022
|
+
});
|
|
2023
|
+
const updated = await this.adapter.flows.update(flowId, {
|
|
2024
|
+
status: "published",
|
|
2025
|
+
version: dbFlow.version + 1
|
|
2026
|
+
});
|
|
2027
|
+
return this.convertDBFlowToDefinition(updated);
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Archive a flow
|
|
2031
|
+
*/
|
|
2032
|
+
async archiveFlow(flowId) {
|
|
2033
|
+
if (!this.adapter.flows) {
|
|
2034
|
+
throw new SchemaError("Flows feature is not enabled.", SchemaErrorCode.UNKNOWN, {
|
|
2035
|
+
feature: "flows"
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
const dbFlow = await this.adapter.flows.findById(flowId);
|
|
2039
|
+
if (!dbFlow) {
|
|
2040
|
+
throw new NotFoundError("Flow", flowId);
|
|
2041
|
+
}
|
|
2042
|
+
if (dbFlow.system) {
|
|
2043
|
+
throw new ProtectedResourceError("object", dbFlow.name, "modify");
|
|
2044
|
+
}
|
|
2045
|
+
const updated = await this.adapter.flows.update(flowId, {
|
|
2046
|
+
status: "archived"
|
|
2047
|
+
});
|
|
2048
|
+
return this.convertDBFlowToDefinition(updated);
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Delete a custom flow
|
|
2052
|
+
*/
|
|
2053
|
+
async deleteFlow(flowId) {
|
|
2054
|
+
if (!this.adapter.flows) {
|
|
2055
|
+
throw new SchemaError("Flows feature is not enabled.", SchemaErrorCode.UNKNOWN, {
|
|
2056
|
+
feature: "flows"
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
const dbFlow = await this.adapter.flows.findById(flowId);
|
|
2060
|
+
if (!dbFlow) {
|
|
2061
|
+
throw new NotFoundError("Flow", flowId);
|
|
2062
|
+
}
|
|
2063
|
+
if (dbFlow.system) {
|
|
2064
|
+
throw new ProtectedResourceError("object", dbFlow.name, "delete");
|
|
2065
|
+
}
|
|
2066
|
+
await this.adapter.flows.delete(flowId);
|
|
2067
|
+
}
|
|
2068
|
+
// ============================================================================
|
|
2069
|
+
// PRIVATE HELPERS
|
|
2070
|
+
// ============================================================================
|
|
2071
|
+
/**
|
|
2072
|
+
* Validate flow name format (kebab-case)
|
|
2073
|
+
*/
|
|
2074
|
+
validateFlowName(name) {
|
|
2075
|
+
if (!name || name.length === 0) {
|
|
2076
|
+
throw new ValidationError("Flow name cannot be empty", [
|
|
2077
|
+
{ path: ["name"], message: "Flow name cannot be empty" }
|
|
2078
|
+
]);
|
|
2079
|
+
}
|
|
2080
|
+
if (name.length > 63) {
|
|
2081
|
+
throw new ValidationError("Flow name is too long", [
|
|
2082
|
+
{ path: ["name"], message: "Flow name is too long (max 63 characters)" }
|
|
2083
|
+
]);
|
|
2084
|
+
}
|
|
2085
|
+
const kebabCaseRegex = /^[a-z][a-z0-9-]*$/;
|
|
2086
|
+
if (!kebabCaseRegex.test(name)) {
|
|
2087
|
+
throw new ValidationError("Invalid flow name format", [
|
|
2088
|
+
{
|
|
2089
|
+
path: ["name"],
|
|
2090
|
+
message: "Flow name must be in kebab-case (e.g., 'couple-creation', 'new-contact')"
|
|
2091
|
+
}
|
|
2092
|
+
]);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Validate flow structure (slots, pages, relations)
|
|
2097
|
+
*/
|
|
2098
|
+
validateFlowStructure(input) {
|
|
2099
|
+
if (!input.slots || input.slots.length === 0) {
|
|
2100
|
+
throw new ValidationError("Flow must have at least one slot", [
|
|
2101
|
+
{ path: ["slots"], message: "Flow must have at least one slot" }
|
|
2102
|
+
]);
|
|
2103
|
+
}
|
|
2104
|
+
if (!input.pages || input.pages.length === 0) {
|
|
2105
|
+
throw new ValidationError("Flow must have at least one page", [
|
|
2106
|
+
{ path: ["pages"], message: "Flow must have at least one page" }
|
|
2107
|
+
]);
|
|
2108
|
+
}
|
|
2109
|
+
const slotIds = new Set(input.slots.map((s) => s.id));
|
|
2110
|
+
if (slotIds.size !== input.slots.length) {
|
|
2111
|
+
throw new ValidationError("Duplicate slot IDs detected", [
|
|
2112
|
+
{ path: ["slots"], message: "Duplicate slot IDs detected" }
|
|
2113
|
+
]);
|
|
2114
|
+
}
|
|
2115
|
+
for (const page of input.pages) {
|
|
2116
|
+
for (const row of page.rows) {
|
|
2117
|
+
for (const field of row.fields) {
|
|
2118
|
+
if (!slotIds.has(field.slotId)) {
|
|
2119
|
+
throw new ValidationError("Field references unknown slot", [
|
|
2120
|
+
{
|
|
2121
|
+
path: ["pages", page.id, "rows", row.id, "fields", field.id],
|
|
2122
|
+
message: `Field "${field.id}" references unknown slot "${field.slotId}"`
|
|
2123
|
+
}
|
|
2124
|
+
]);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
for (const relation of input.relations) {
|
|
2130
|
+
if (!slotIds.has(relation.sourceSlotId)) {
|
|
2131
|
+
throw new ValidationError("Relation references unknown source slot", [
|
|
2132
|
+
{
|
|
2133
|
+
path: ["relations", relation.id, "sourceSlotId"],
|
|
2134
|
+
message: `Relation references unknown source slot "${relation.sourceSlotId}"`
|
|
2135
|
+
}
|
|
2136
|
+
]);
|
|
2137
|
+
}
|
|
2138
|
+
if (!slotIds.has(relation.targetSlotId)) {
|
|
2139
|
+
throw new ValidationError("Relation references unknown target slot", [
|
|
2140
|
+
{
|
|
2141
|
+
path: ["relations", relation.id, "targetSlotId"],
|
|
2142
|
+
message: `Relation references unknown target slot "${relation.targetSlotId}"`
|
|
2143
|
+
}
|
|
2144
|
+
]);
|
|
2145
|
+
}
|
|
2146
|
+
if (relation.sourceSlotId === relation.targetSlotId) {
|
|
2147
|
+
throw new ValidationError("Relation cannot link a slot to itself", [
|
|
2148
|
+
{
|
|
2149
|
+
path: ["relations", relation.id],
|
|
2150
|
+
message: `Relation cannot link a slot to itself: "${relation.sourceSlotId}"`
|
|
2151
|
+
}
|
|
2152
|
+
]);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2157
|
+
* Convert database flow to FlowDefinition
|
|
2158
|
+
*/
|
|
2159
|
+
convertDBFlowToDefinition(dbFlow) {
|
|
2160
|
+
return {
|
|
2161
|
+
id: dbFlow.id,
|
|
2162
|
+
name: dbFlow.name,
|
|
2163
|
+
label: dbFlow.label,
|
|
2164
|
+
description: dbFlow.description,
|
|
2165
|
+
icon: dbFlow.icon,
|
|
2166
|
+
status: dbFlow.status,
|
|
2167
|
+
version: dbFlow.version,
|
|
2168
|
+
slots: dbFlow.slots,
|
|
2169
|
+
pages: dbFlow.pages,
|
|
2170
|
+
relations: dbFlow.relations,
|
|
2171
|
+
system: dbFlow.system,
|
|
2172
|
+
tenantId: dbFlow.tenantId,
|
|
2173
|
+
metadata: dbFlow.metadata,
|
|
2174
|
+
createdAt: dbFlow.createdAt,
|
|
2175
|
+
updatedAt: dbFlow.updatedAt
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
2179
|
+
|
|
1536
2180
|
// src/runtime/services/geocoding.service.ts
|
|
1537
2181
|
var GeocodingService = class {
|
|
1538
2182
|
constructor(adapter) {
|
|
@@ -1649,11 +2293,14 @@ var GlobalSearchService = class {
|
|
|
1649
2293
|
};
|
|
1650
2294
|
|
|
1651
2295
|
// src/builders/attribute-validators.ts
|
|
1652
|
-
var
|
|
1653
|
-
var VALID_ICONS = new Set(
|
|
2296
|
+
var import_constants2 = require("@stndrds/constants");
|
|
2297
|
+
var VALID_ICONS = new Set(import_constants2.ICONS);
|
|
2298
|
+
|
|
2299
|
+
// src/builders/flow-builder.ts
|
|
2300
|
+
var import_zod = require("zod");
|
|
1654
2301
|
|
|
1655
2302
|
// src/builders/object-builder.ts
|
|
1656
|
-
var
|
|
2303
|
+
var import_zod2 = __toESM(require("zod"));
|
|
1657
2304
|
var ObjectBuilder = class {
|
|
1658
2305
|
constructor(config) {
|
|
1659
2306
|
this.validateName(config.name);
|
|
@@ -1786,14 +2433,14 @@ The labelExpression defines how records are displayed in lists and relations.
|
|
|
1786
2433
|
if (name === void 0) {
|
|
1787
2434
|
return;
|
|
1788
2435
|
}
|
|
1789
|
-
const objectNameSchema =
|
|
2436
|
+
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-]*$/, {
|
|
1790
2437
|
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'"
|
|
1791
2438
|
});
|
|
1792
2439
|
try {
|
|
1793
2440
|
objectNameSchema.parse(name);
|
|
1794
2441
|
} catch (error) {
|
|
1795
|
-
if (error instanceof
|
|
1796
|
-
throw new Error(`[ObjectBuilder] ${error.
|
|
2442
|
+
if (error instanceof import_zod2.default.ZodError) {
|
|
2443
|
+
throw new Error(`[ObjectBuilder] ${error.issues[0].message}`);
|
|
1797
2444
|
}
|
|
1798
2445
|
throw error;
|
|
1799
2446
|
}
|
|
@@ -1804,7 +2451,7 @@ function object(config) {
|
|
|
1804
2451
|
}
|
|
1805
2452
|
|
|
1806
2453
|
// src/builders/view-builder.ts
|
|
1807
|
-
var
|
|
2454
|
+
var import_zod3 = require("zod");
|
|
1808
2455
|
|
|
1809
2456
|
// src/types/errors.ts
|
|
1810
2457
|
var RecordReferencedError = class extends Error {
|
|
@@ -1839,35 +2486,42 @@ var ObjectReferencedError = class extends Error {
|
|
|
1839
2486
|
};
|
|
1840
2487
|
|
|
1841
2488
|
// src/validation/validators.ts
|
|
1842
|
-
var
|
|
1843
|
-
var baseConfigSchema =
|
|
1844
|
-
disabled:
|
|
1845
|
-
placeholder:
|
|
1846
|
-
description:
|
|
1847
|
-
defaultValue:
|
|
1848
|
-
icon:
|
|
1849
|
-
order:
|
|
1850
|
-
hidden:
|
|
1851
|
-
archived:
|
|
1852
|
-
deprecated:
|
|
1853
|
-
metadata:
|
|
2489
|
+
var import_zod4 = require("zod");
|
|
2490
|
+
var baseConfigSchema = import_zod4.z.object({
|
|
2491
|
+
disabled: import_zod4.z.boolean().optional(),
|
|
2492
|
+
placeholder: import_zod4.z.string().optional(),
|
|
2493
|
+
description: import_zod4.z.string().optional(),
|
|
2494
|
+
defaultValue: import_zod4.z.unknown().optional(),
|
|
2495
|
+
icon: import_zod4.z.string().optional(),
|
|
2496
|
+
order: import_zod4.z.number().int().optional(),
|
|
2497
|
+
hidden: import_zod4.z.boolean().optional(),
|
|
2498
|
+
archived: import_zod4.z.boolean().optional(),
|
|
2499
|
+
deprecated: import_zod4.z.boolean().optional(),
|
|
2500
|
+
metadata: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
|
|
1854
2501
|
});
|
|
1855
|
-
var optionSchema =
|
|
1856
|
-
id:
|
|
1857
|
-
label:
|
|
1858
|
-
value:
|
|
1859
|
-
color:
|
|
1860
|
-
icon:
|
|
1861
|
-
description:
|
|
1862
|
-
group:
|
|
2502
|
+
var optionSchema = import_zod4.z.object({
|
|
2503
|
+
id: import_zod4.z.string().min(1),
|
|
2504
|
+
label: import_zod4.z.string().min(1),
|
|
2505
|
+
value: import_zod4.z.string().min(1),
|
|
2506
|
+
color: import_zod4.z.string().optional(),
|
|
2507
|
+
icon: import_zod4.z.string().optional(),
|
|
2508
|
+
description: import_zod4.z.string().optional(),
|
|
2509
|
+
group: import_zod4.z.enum(["idle", "in_progress", "finished"]).optional()
|
|
1863
2510
|
});
|
|
1864
|
-
var
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2511
|
+
var optionsArraySchema = import_zod4.z.array(optionSchema).min(1).refine(
|
|
2512
|
+
(options) => {
|
|
2513
|
+
const values = options.map((o) => o.value);
|
|
2514
|
+
return new Set(values).size === values.length;
|
|
2515
|
+
},
|
|
2516
|
+
{ message: "Duplicate option values are not allowed" }
|
|
2517
|
+
);
|
|
2518
|
+
var relationTargetSchema = import_zod4.z.object({
|
|
2519
|
+
object: import_zod4.z.string().min(1),
|
|
2520
|
+
displayTemplate: import_zod4.z.string().optional(),
|
|
2521
|
+
filter: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
|
|
1868
2522
|
});
|
|
1869
|
-
var documentTypeConfigSchema =
|
|
1870
|
-
type:
|
|
2523
|
+
var documentTypeConfigSchema = import_zod4.z.object({
|
|
2524
|
+
type: import_zod4.z.enum([
|
|
1871
2525
|
"id_card",
|
|
1872
2526
|
"passport",
|
|
1873
2527
|
"incorporation_certificate",
|
|
@@ -1878,86 +2532,86 @@ var documentTypeConfigSchema = import_zod3.z.object({
|
|
|
1878
2532
|
"proof_of_address",
|
|
1879
2533
|
"custom"
|
|
1880
2534
|
]),
|
|
1881
|
-
label:
|
|
1882
|
-
faces:
|
|
1883
|
-
attributeMapping:
|
|
1884
|
-
|
|
1885
|
-
attributeId:
|
|
1886
|
-
extractedKey:
|
|
1887
|
-
face:
|
|
1888
|
-
required:
|
|
2535
|
+
label: import_zod4.z.string(),
|
|
2536
|
+
faces: import_zod4.z.array(import_zod4.z.enum(["front", "back", "single"])),
|
|
2537
|
+
attributeMapping: import_zod4.z.array(
|
|
2538
|
+
import_zod4.z.object({
|
|
2539
|
+
attributeId: import_zod4.z.string(),
|
|
2540
|
+
extractedKey: import_zod4.z.string(),
|
|
2541
|
+
face: import_zod4.z.enum(["front", "back", "single"]).optional(),
|
|
2542
|
+
required: import_zod4.z.boolean().optional()
|
|
1889
2543
|
})
|
|
1890
2544
|
).optional()
|
|
1891
2545
|
});
|
|
1892
|
-
var fileVerificationConfigSchema =
|
|
1893
|
-
enabled:
|
|
1894
|
-
documentTypes:
|
|
1895
|
-
autoExtract:
|
|
1896
|
-
autoValidate:
|
|
2546
|
+
var fileVerificationConfigSchema = import_zod4.z.object({
|
|
2547
|
+
enabled: import_zod4.z.boolean(),
|
|
2548
|
+
documentTypes: import_zod4.z.array(documentTypeConfigSchema),
|
|
2549
|
+
autoExtract: import_zod4.z.boolean().optional(),
|
|
2550
|
+
autoValidate: import_zod4.z.boolean().optional()
|
|
1897
2551
|
});
|
|
1898
2552
|
var textConfigSchema = baseConfigSchema.extend({
|
|
1899
|
-
minLength:
|
|
1900
|
-
maxLength:
|
|
1901
|
-
pattern:
|
|
2553
|
+
minLength: import_zod4.z.number().int().min(0).optional(),
|
|
2554
|
+
maxLength: import_zod4.z.number().int().min(1).optional(),
|
|
2555
|
+
pattern: import_zod4.z.string().optional()
|
|
1902
2556
|
});
|
|
1903
2557
|
var textareaConfigSchema = baseConfigSchema;
|
|
1904
2558
|
var numberConfigSchema = baseConfigSchema.extend({
|
|
1905
|
-
min:
|
|
1906
|
-
max:
|
|
1907
|
-
unit:
|
|
1908
|
-
decimals:
|
|
2559
|
+
min: import_zod4.z.number().optional(),
|
|
2560
|
+
max: import_zod4.z.number().optional(),
|
|
2561
|
+
unit: import_zod4.z.enum(["integer", "decimal", "percentage"]).optional(),
|
|
2562
|
+
decimals: import_zod4.z.number().int().min(0).optional()
|
|
1909
2563
|
});
|
|
1910
2564
|
var checkboxConfigSchema = baseConfigSchema;
|
|
1911
2565
|
var dateConfigSchema = baseConfigSchema.extend({
|
|
1912
|
-
dateFormat:
|
|
1913
|
-
minDate:
|
|
1914
|
-
maxDate:
|
|
2566
|
+
dateFormat: import_zod4.z.enum(["short", "long", "full", "relative"]).optional(),
|
|
2567
|
+
minDate: import_zod4.z.string().optional(),
|
|
2568
|
+
maxDate: import_zod4.z.string().optional()
|
|
1915
2569
|
});
|
|
1916
2570
|
var phoneConfigSchema = baseConfigSchema.extend({
|
|
1917
|
-
defaultCountryCode:
|
|
2571
|
+
defaultCountryCode: import_zod4.z.string().length(3).optional()
|
|
1918
2572
|
});
|
|
1919
2573
|
var currencyConfigSchema = baseConfigSchema.extend({
|
|
1920
|
-
defaultCurrency:
|
|
1921
|
-
allowedCurrencies:
|
|
2574
|
+
defaultCurrency: import_zod4.z.string().length(3).optional(),
|
|
2575
|
+
allowedCurrencies: import_zod4.z.array(import_zod4.z.string().length(3)).optional()
|
|
1922
2576
|
});
|
|
1923
2577
|
var statusConfigSchema = baseConfigSchema.extend({
|
|
1924
|
-
options:
|
|
2578
|
+
options: optionsArraySchema
|
|
1925
2579
|
});
|
|
1926
2580
|
var locationConfigSchema = baseConfigSchema.extend({
|
|
1927
|
-
granularity:
|
|
1928
|
-
enableAutocomplete:
|
|
1929
|
-
enableMap:
|
|
1930
|
-
defaultCountry:
|
|
1931
|
-
allowedCountries:
|
|
1932
|
-
displayFormat:
|
|
2581
|
+
granularity: import_zod4.z.enum(["full", "address", "city", "state", "country", "coordinates"]),
|
|
2582
|
+
enableAutocomplete: import_zod4.z.boolean().optional(),
|
|
2583
|
+
enableMap: import_zod4.z.boolean().optional(),
|
|
2584
|
+
defaultCountry: import_zod4.z.string().length(3).optional(),
|
|
2585
|
+
allowedCountries: import_zod4.z.array(import_zod4.z.string().length(3)).optional(),
|
|
2586
|
+
displayFormat: import_zod4.z.enum(["single_line", "multi_line", "compact"]).optional()
|
|
1933
2587
|
});
|
|
1934
2588
|
var timestampConfigSchema = baseConfigSchema.extend({
|
|
1935
|
-
autoUpdate:
|
|
2589
|
+
autoUpdate: import_zod4.z.boolean().optional()
|
|
1936
2590
|
});
|
|
1937
2591
|
var selectConfigSchema = baseConfigSchema.extend({
|
|
1938
|
-
options:
|
|
2592
|
+
options: optionsArraySchema
|
|
1939
2593
|
});
|
|
1940
2594
|
var multiselectConfigSchema = baseConfigSchema.extend({
|
|
1941
|
-
options:
|
|
2595
|
+
options: optionsArraySchema
|
|
1942
2596
|
});
|
|
1943
2597
|
var fileConfigSchema = baseConfigSchema.extend({
|
|
1944
|
-
maxFiles:
|
|
1945
|
-
maxSize:
|
|
1946
|
-
allowedTypes:
|
|
2598
|
+
maxFiles: import_zod4.z.number().int().min(1).optional(),
|
|
2599
|
+
maxSize: import_zod4.z.number().int().min(1).optional(),
|
|
2600
|
+
allowedTypes: import_zod4.z.array(import_zod4.z.string()).optional(),
|
|
1947
2601
|
verification: fileVerificationConfigSchema.optional()
|
|
1948
2602
|
});
|
|
1949
2603
|
var userConfigSchema = baseConfigSchema.extend({
|
|
1950
|
-
allowedRoles:
|
|
2604
|
+
allowedRoles: import_zod4.z.array(import_zod4.z.string()).optional()
|
|
1951
2605
|
});
|
|
1952
2606
|
var relationConfigSchema = baseConfigSchema.extend({
|
|
1953
|
-
targets:
|
|
1954
|
-
cardinality:
|
|
1955
|
-
minItems:
|
|
1956
|
-
maxItems:
|
|
2607
|
+
targets: import_zod4.z.array(relationTargetSchema).min(1),
|
|
2608
|
+
cardinality: import_zod4.z.enum(["one", "many"]),
|
|
2609
|
+
minItems: import_zod4.z.number().int().min(0).optional(),
|
|
2610
|
+
maxItems: import_zod4.z.number().int().min(1).optional()
|
|
1957
2611
|
});
|
|
1958
2612
|
var ratingConfigSchema = baseConfigSchema.extend({
|
|
1959
|
-
max:
|
|
1960
|
-
iconType:
|
|
2613
|
+
max: import_zod4.z.number().int().min(1).optional(),
|
|
2614
|
+
iconType: import_zod4.z.enum(["star", "heart", "thumbs", "number"]).optional()
|
|
1961
2615
|
});
|
|
1962
2616
|
var attributeConfigSchemas = {
|
|
1963
2617
|
text: textConfigSchema,
|
|
@@ -1985,7 +2639,7 @@ function parseAttributeConfig(type, config) {
|
|
|
1985
2639
|
return schema.strip().parse(config);
|
|
1986
2640
|
}
|
|
1987
2641
|
function createTextValidator(attr) {
|
|
1988
|
-
let schema =
|
|
2642
|
+
let schema = import_zod4.z.string();
|
|
1989
2643
|
if (attr.minLength !== void 0) {
|
|
1990
2644
|
schema = schema.min(
|
|
1991
2645
|
attr.minLength,
|
|
@@ -2004,7 +2658,7 @@ function createTextValidator(attr) {
|
|
|
2004
2658
|
return schema;
|
|
2005
2659
|
}
|
|
2006
2660
|
function createNumberValidator(attr) {
|
|
2007
|
-
let schema =
|
|
2661
|
+
let schema = import_zod4.z.number();
|
|
2008
2662
|
if (attr.min !== void 0) {
|
|
2009
2663
|
schema = schema.min(attr.min, `${attr.label} must be at least ${attr.min}`);
|
|
2010
2664
|
}
|
|
@@ -2017,81 +2671,75 @@ function createNumberValidator(attr) {
|
|
|
2017
2671
|
return schema;
|
|
2018
2672
|
}
|
|
2019
2673
|
function createCheckboxValidator(_attr) {
|
|
2020
|
-
return
|
|
2674
|
+
return import_zod4.z.boolean();
|
|
2021
2675
|
}
|
|
2022
2676
|
function createDateValidator(attr) {
|
|
2023
|
-
return
|
|
2677
|
+
return import_zod4.z.string().datetime({ message: `${attr.label} must be a valid ISO date` });
|
|
2024
2678
|
}
|
|
2025
2679
|
function createPhoneValidator(_attr) {
|
|
2026
|
-
return
|
|
2027
|
-
countryCode:
|
|
2028
|
-
phoneNumber:
|
|
2680
|
+
return import_zod4.z.object({
|
|
2681
|
+
countryCode: import_zod4.z.string().length(3),
|
|
2682
|
+
phoneNumber: import_zod4.z.string().min(1)
|
|
2029
2683
|
});
|
|
2030
2684
|
}
|
|
2031
2685
|
function createCurrencyValidator(_attr) {
|
|
2032
|
-
return
|
|
2033
|
-
code:
|
|
2034
|
-
value:
|
|
2686
|
+
return import_zod4.z.object({
|
|
2687
|
+
code: import_zod4.z.string().length(3),
|
|
2688
|
+
value: import_zod4.z.number().min(0)
|
|
2035
2689
|
});
|
|
2036
2690
|
}
|
|
2037
2691
|
function createStatusValidator(attr) {
|
|
2038
2692
|
const validValues = attr.options.map((opt) => opt.value);
|
|
2039
|
-
return
|
|
2040
|
-
|
|
2041
|
-
message: `${attr.label} must be one of: ${validValues.join(", ")}`
|
|
2042
|
-
})
|
|
2693
|
+
return import_zod4.z.enum(validValues, {
|
|
2694
|
+
error: `${attr.label} must be one of: ${validValues.join(", ")}`
|
|
2043
2695
|
});
|
|
2044
2696
|
}
|
|
2045
2697
|
function createSelectValidator(attr) {
|
|
2046
2698
|
const validValues = attr.options.map((opt) => opt.value);
|
|
2047
|
-
return
|
|
2048
|
-
|
|
2049
|
-
message: `${attr.label} must be one of: ${validValues.join(", ")}`
|
|
2050
|
-
})
|
|
2699
|
+
return import_zod4.z.enum(validValues, {
|
|
2700
|
+
error: `${attr.label} must be one of: ${validValues.join(", ")}`
|
|
2051
2701
|
});
|
|
2052
2702
|
}
|
|
2053
2703
|
function createMultiselectValidator(attr) {
|
|
2054
2704
|
const validValues = attr.options.map((opt) => opt.value);
|
|
2055
|
-
return
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
message: `Each value must be one of: ${validValues.join(", ")}`
|
|
2059
|
-
})
|
|
2705
|
+
return import_zod4.z.array(
|
|
2706
|
+
import_zod4.z.enum(validValues, {
|
|
2707
|
+
error: `Each value must be one of: ${validValues.join(", ")}`
|
|
2060
2708
|
})
|
|
2061
2709
|
);
|
|
2062
2710
|
}
|
|
2063
2711
|
function createLocationValidator(_attr) {
|
|
2064
|
-
return
|
|
2065
|
-
address:
|
|
2066
|
-
address2:
|
|
2067
|
-
city:
|
|
2068
|
-
state:
|
|
2069
|
-
postalCode:
|
|
2070
|
-
country:
|
|
2071
|
-
latitude:
|
|
2072
|
-
longitude:
|
|
2712
|
+
return import_zod4.z.object({
|
|
2713
|
+
address: import_zod4.z.string().optional(),
|
|
2714
|
+
address2: import_zod4.z.string().optional(),
|
|
2715
|
+
city: import_zod4.z.string().optional(),
|
|
2716
|
+
state: import_zod4.z.string().optional(),
|
|
2717
|
+
postalCode: import_zod4.z.string().optional(),
|
|
2718
|
+
country: import_zod4.z.string().length(3).optional(),
|
|
2719
|
+
latitude: import_zod4.z.number().optional(),
|
|
2720
|
+
longitude: import_zod4.z.number().optional()
|
|
2073
2721
|
});
|
|
2074
2722
|
}
|
|
2075
2723
|
function createTimestampValidator(_attr) {
|
|
2076
|
-
return
|
|
2724
|
+
return import_zod4.z.number().int().positive();
|
|
2077
2725
|
}
|
|
2078
2726
|
function createFileValidator(_attr) {
|
|
2079
|
-
return
|
|
2727
|
+
return import_zod4.z.string().uuid();
|
|
2080
2728
|
}
|
|
2081
2729
|
function createUserValidator(_attr) {
|
|
2082
|
-
return
|
|
2730
|
+
return import_zod4.z.string().uuid();
|
|
2083
2731
|
}
|
|
2084
2732
|
function createSingleRelationValidator(attr) {
|
|
2085
|
-
const uuidSchema =
|
|
2733
|
+
const uuidSchema = import_zod4.z.string().uuid({
|
|
2086
2734
|
message: `${attr.label} must be a valid record ID`
|
|
2087
2735
|
});
|
|
2088
|
-
return
|
|
2736
|
+
return import_zod4.z.union([uuidSchema, import_zod4.z.null()]);
|
|
2089
2737
|
}
|
|
2090
2738
|
function createMultiRelationValidator(attr) {
|
|
2091
|
-
const uuidSchema =
|
|
2739
|
+
const uuidSchema = import_zod4.z.string().uuid({
|
|
2092
2740
|
message: `Each ${attr.label} item must be a valid record ID`
|
|
2093
2741
|
});
|
|
2094
|
-
let arraySchema =
|
|
2742
|
+
let arraySchema = import_zod4.z.array(uuidSchema);
|
|
2095
2743
|
if (attr.minItems !== void 0) {
|
|
2096
2744
|
arraySchema = arraySchema.min(
|
|
2097
2745
|
attr.minItems,
|
|
@@ -2113,7 +2761,7 @@ function createRelationValidator(attr) {
|
|
|
2113
2761
|
return createSingleRelationValidator(attr);
|
|
2114
2762
|
}
|
|
2115
2763
|
function createRatingValidator(attr) {
|
|
2116
|
-
let schema =
|
|
2764
|
+
let schema = import_zod4.z.number().int().min(0);
|
|
2117
2765
|
if (attr.max !== void 0) {
|
|
2118
2766
|
schema = schema.max(attr.max, `${attr.label} must be at most ${attr.max}`);
|
|
2119
2767
|
}
|
|
@@ -2152,7 +2800,7 @@ function createAttributeValidator(attr) {
|
|
|
2152
2800
|
case "rating":
|
|
2153
2801
|
return createRatingValidator(attr);
|
|
2154
2802
|
default:
|
|
2155
|
-
return
|
|
2803
|
+
return import_zod4.z.unknown();
|
|
2156
2804
|
}
|
|
2157
2805
|
}
|
|
2158
2806
|
function createObjectValidator(objectDef) {
|
|
@@ -2164,7 +2812,7 @@ function createObjectValidator(objectDef) {
|
|
|
2164
2812
|
}
|
|
2165
2813
|
shape[attr.name] = validator;
|
|
2166
2814
|
}
|
|
2167
|
-
return
|
|
2815
|
+
return import_zod4.z.object(shape);
|
|
2168
2816
|
}
|
|
2169
2817
|
function validateObject(objectDef, data) {
|
|
2170
2818
|
const validator = createObjectValidator(objectDef);
|
|
@@ -2177,7 +2825,7 @@ function validateObject(objectDef, data) {
|
|
|
2177
2825
|
}
|
|
2178
2826
|
return {
|
|
2179
2827
|
success: false,
|
|
2180
|
-
errors: result.error.
|
|
2828
|
+
errors: result.error.issues.map((err) => ({
|
|
2181
2829
|
path: err.path.map(String),
|
|
2182
2830
|
message: err.message
|
|
2183
2831
|
}))
|
|
@@ -2198,7 +2846,7 @@ function createDraftValidator(objectDef) {
|
|
|
2198
2846
|
const validator = createAttributeValidator(attr).optional();
|
|
2199
2847
|
shape[attr.name] = validator;
|
|
2200
2848
|
}
|
|
2201
|
-
return
|
|
2849
|
+
return import_zod4.z.object(shape);
|
|
2202
2850
|
}
|
|
2203
2851
|
function validateDraft(objectDef, data) {
|
|
2204
2852
|
const validator = createDraftValidator(objectDef);
|
|
@@ -2211,7 +2859,7 @@ function validateDraft(objectDef, data) {
|
|
|
2211
2859
|
}
|
|
2212
2860
|
return {
|
|
2213
2861
|
success: false,
|
|
2214
|
-
errors: result.error.
|
|
2862
|
+
errors: result.error.issues.map((err) => ({
|
|
2215
2863
|
path: err.path.map(String),
|
|
2216
2864
|
message: err.message
|
|
2217
2865
|
}))
|
|
@@ -2606,14 +3254,15 @@ var ObjectSchemaService = class {
|
|
|
2606
3254
|
});
|
|
2607
3255
|
}
|
|
2608
3256
|
}
|
|
3257
|
+
const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
|
|
3258
|
+
const attributes = dbAttributes.map((attr) => this.convertDBAttributeToAttribute(attr));
|
|
2609
3259
|
if (updates.labelExpression !== void 0 && updates.labelExpression !== oldValues.labelExpression) {
|
|
2610
3260
|
const newExpression = updates.labelExpression;
|
|
2611
|
-
await this.adapter.objectRecords.batchRefreshLabels(
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
);
|
|
3261
|
+
await this.adapter.objectRecords.batchRefreshLabels(objectId, (values) => {
|
|
3262
|
+
const enrichedValues = enrichValuesWithSelectLabels(values, attributes);
|
|
3263
|
+
return renderLabelExpression(newExpression, enrichedValues);
|
|
3264
|
+
});
|
|
2615
3265
|
}
|
|
2616
|
-
const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
|
|
2617
3266
|
return this.convertDBObjectToDefinition(updatedDbObject, dbAttributes);
|
|
2618
3267
|
}
|
|
2619
3268
|
/**
|
|
@@ -2926,71 +3575,14 @@ var ObjectSchemaService = class {
|
|
|
2926
3575
|
}
|
|
2927
3576
|
};
|
|
2928
3577
|
|
|
2929
|
-
// src/exceptions.ts
|
|
2930
|
-
var SchemaErrorCode = {
|
|
2931
|
-
// Generic
|
|
2932
|
-
UNKNOWN: "SCHEMA_UNKNOWN_ERROR",
|
|
2933
|
-
// Not Found
|
|
2934
|
-
OBJECT_NOT_FOUND: "SCHEMA_OBJECT_NOT_FOUND",
|
|
2935
|
-
ATTRIBUTE_NOT_FOUND: "SCHEMA_ATTRIBUTE_NOT_FOUND",
|
|
2936
|
-
RECORD_NOT_FOUND: "SCHEMA_RECORD_NOT_FOUND",
|
|
2937
|
-
USER_PROFILE_NOT_FOUND: "SCHEMA_USER_PROFILE_NOT_FOUND",
|
|
2938
|
-
FILE_NOT_FOUND: "SCHEMA_FILE_NOT_FOUND",
|
|
2939
|
-
ROLE_NOT_FOUND: "SCHEMA_ROLE_NOT_FOUND",
|
|
2940
|
-
// Validation
|
|
2941
|
-
VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED",
|
|
2942
|
-
INVALID_ATTRIBUTE_NAME: "SCHEMA_INVALID_ATTRIBUTE_NAME",
|
|
2943
|
-
INVALID_OBJECT_NAME: "SCHEMA_INVALID_OBJECT_NAME",
|
|
2944
|
-
// Protected Resources
|
|
2945
|
-
PROTECTED_OBJECT: "SCHEMA_PROTECTED_OBJECT",
|
|
2946
|
-
PROTECTED_ATTRIBUTE: "SCHEMA_PROTECTED_ATTRIBUTE",
|
|
2947
|
-
PROTECTED_ROLE: "SCHEMA_PROTECTED_ROLE",
|
|
2948
|
-
// Permissions
|
|
2949
|
-
FORBIDDEN: "SCHEMA_FORBIDDEN",
|
|
2950
|
-
// Sync
|
|
2951
|
-
SYNC_FAILED: "SCHEMA_SYNC_FAILED",
|
|
2952
|
-
NOT_SYSTEM_OBJECT: "SCHEMA_NOT_SYSTEM_OBJECT",
|
|
2953
|
-
// Duplicates
|
|
2954
|
-
DUPLICATE_OBJECT: "SCHEMA_DUPLICATE_OBJECT",
|
|
2955
|
-
DUPLICATE_ATTRIBUTE: "SCHEMA_DUPLICATE_ATTRIBUTE"
|
|
2956
|
-
};
|
|
2957
|
-
var SchemaError = class extends Error {
|
|
2958
|
-
constructor(message, code = SchemaErrorCode.UNKNOWN, details) {
|
|
2959
|
-
super(message);
|
|
2960
|
-
this.name = "SchemaError";
|
|
2961
|
-
this.code = code;
|
|
2962
|
-
this.details = details;
|
|
2963
|
-
Object.setPrototypeOf(this, new.target.prototype);
|
|
2964
|
-
}
|
|
2965
|
-
toJSON() {
|
|
2966
|
-
return {
|
|
2967
|
-
name: this.name,
|
|
2968
|
-
code: this.code,
|
|
2969
|
-
message: this.message,
|
|
2970
|
-
details: this.details
|
|
2971
|
-
};
|
|
2972
|
-
}
|
|
2973
|
-
};
|
|
2974
|
-
var ForbiddenError = class extends SchemaError {
|
|
2975
|
-
constructor(objectName, action, userId) {
|
|
2976
|
-
super(`No ${action} permission on object "${objectName}"`, SchemaErrorCode.FORBIDDEN, {
|
|
2977
|
-
objectName,
|
|
2978
|
-
action,
|
|
2979
|
-
userId
|
|
2980
|
-
});
|
|
2981
|
-
this.name = "ForbiddenError";
|
|
2982
|
-
this.objectName = objectName;
|
|
2983
|
-
this.action = action;
|
|
2984
|
-
this.userId = userId;
|
|
2985
|
-
}
|
|
2986
|
-
};
|
|
2987
|
-
|
|
2988
3578
|
// src/runtime/services/permission.service.ts
|
|
2989
3579
|
var PermissionService = class {
|
|
2990
3580
|
constructor(adapter, tenantId, options) {
|
|
2991
3581
|
this.adapter = adapter;
|
|
2992
3582
|
this.tenantId = tenantId;
|
|
2993
3583
|
this.cache = /* @__PURE__ */ new Map();
|
|
3584
|
+
/** Track pending permission fetches to prevent duplicate concurrent requests */
|
|
3585
|
+
this.pendingFetches = /* @__PURE__ */ new Map();
|
|
2994
3586
|
if (!adapter.permissions) {
|
|
2995
3587
|
throw new Error(
|
|
2996
3588
|
"PermissionService requires a DatabaseAdapter with permissions repository. Make sure your adapter implements the permissions property."
|
|
@@ -3139,6 +3731,23 @@ var PermissionService = class {
|
|
|
3139
3731
|
if (cached && cached.expiresAt > Date.now()) {
|
|
3140
3732
|
return cached.permissions;
|
|
3141
3733
|
}
|
|
3734
|
+
const pendingFetch = this.pendingFetches.get(cacheKey);
|
|
3735
|
+
if (pendingFetch) {
|
|
3736
|
+
return pendingFetch;
|
|
3737
|
+
}
|
|
3738
|
+
const fetchPromise = this.fetchAndCachePermissions(userProfileId, cacheKey);
|
|
3739
|
+
this.pendingFetches.set(cacheKey, fetchPromise);
|
|
3740
|
+
try {
|
|
3741
|
+
return await fetchPromise;
|
|
3742
|
+
} finally {
|
|
3743
|
+
this.pendingFetches.delete(cacheKey);
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
/**
|
|
3747
|
+
* Fetch permissions from database and cache the result
|
|
3748
|
+
* @internal
|
|
3749
|
+
*/
|
|
3750
|
+
async fetchAndCachePermissions(userProfileId, cacheKey) {
|
|
3142
3751
|
const permissions = await this.permissionsRepo.getEffectivePermissions(
|
|
3143
3752
|
userProfileId,
|
|
3144
3753
|
this.tenantId
|
|
@@ -3667,8 +4276,11 @@ var RelationService = class {
|
|
|
3667
4276
|
async validateRelationsOrThrow(schema, data) {
|
|
3668
4277
|
const result = await this.validateRelations(schema, data);
|
|
3669
4278
|
if (!result.valid) {
|
|
3670
|
-
const
|
|
3671
|
-
|
|
4279
|
+
const errors = result.errors.map((e) => ({
|
|
4280
|
+
path: [e.attribute],
|
|
4281
|
+
message: e.message
|
|
4282
|
+
}));
|
|
4283
|
+
throw new ValidationError("Relation validation failed", errors);
|
|
3672
4284
|
}
|
|
3673
4285
|
}
|
|
3674
4286
|
// ============================================================================
|
|
@@ -3716,7 +4328,8 @@ var RelationService = class {
|
|
|
3716
4328
|
totalCount += result.total;
|
|
3717
4329
|
for (const record of result.records) {
|
|
3718
4330
|
const template = target.displayTemplate || objectSchema.labelExpression;
|
|
3719
|
-
const
|
|
4331
|
+
const enrichedValues = enrichValuesWithSelectLabels(record.values, objectSchema.attributes);
|
|
4332
|
+
const label = renderLabelExpression(template, enrichedValues);
|
|
3720
4333
|
allOptions.push({
|
|
3721
4334
|
id: record.id,
|
|
3722
4335
|
objectId: objectSchema.id,
|
|
@@ -3780,7 +4393,8 @@ var RelationService = class {
|
|
|
3780
4393
|
}
|
|
3781
4394
|
}
|
|
3782
4395
|
for (const record of objectRecords) {
|
|
3783
|
-
const
|
|
4396
|
+
const enrichedValues = enrichValuesWithSelectLabels(record.values, objectSchema.attributes);
|
|
4397
|
+
const label = renderLabelExpression(template, enrichedValues);
|
|
3784
4398
|
resolved.push({
|
|
3785
4399
|
id: record.id,
|
|
3786
4400
|
objectId: record.objectId,
|
|
@@ -3801,10 +4415,14 @@ var RelationService = class {
|
|
|
3801
4415
|
if (!attribute || attribute.type !== "relation") {
|
|
3802
4416
|
return null;
|
|
3803
4417
|
}
|
|
3804
|
-
|
|
4418
|
+
const merged = {
|
|
3805
4419
|
...attribute.config,
|
|
3806
4420
|
...attribute
|
|
3807
4421
|
};
|
|
4422
|
+
if (!("targets" in merged && Array.isArray(merged.targets) && "cardinality" in merged)) {
|
|
4423
|
+
return null;
|
|
4424
|
+
}
|
|
4425
|
+
return merged;
|
|
3808
4426
|
}
|
|
3809
4427
|
};
|
|
3810
4428
|
|
|
@@ -3817,7 +4435,7 @@ var RecordService = class {
|
|
|
3817
4435
|
this.relationService = new RelationService(adapter, registry);
|
|
3818
4436
|
this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
|
|
3819
4437
|
this.permissionService = options?.permissionService;
|
|
3820
|
-
this.auditService = options?.auditService;
|
|
4438
|
+
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter, tenantId) : void 0);
|
|
3821
4439
|
this.userId = options?.userId;
|
|
3822
4440
|
this.userEmail = options?.userEmail;
|
|
3823
4441
|
}
|
|
@@ -3884,21 +4502,23 @@ var RecordService = class {
|
|
|
3884
4502
|
/**
|
|
3885
4503
|
* Compute display label from schema expression
|
|
3886
4504
|
* Automatically resolves relation attribute values to their labels
|
|
4505
|
+
* and select/multiselect values to their option labels
|
|
3887
4506
|
* @internal
|
|
3888
4507
|
*/
|
|
3889
4508
|
async computeLabel(schema, values) {
|
|
3890
4509
|
const attrNames = extractAttributeNames(schema.labelExpression);
|
|
4510
|
+
let enrichedValues = enrichValuesWithSelectLabels(values, schema.attributes);
|
|
3891
4511
|
const relationAttrs = schema.attributes.filter(
|
|
3892
4512
|
(attr) => attr.type === "relation" && attrNames.includes(attr.name)
|
|
3893
4513
|
);
|
|
3894
4514
|
if (relationAttrs.length === 0) {
|
|
3895
|
-
return renderLabelExpression(schema.labelExpression,
|
|
4515
|
+
return renderLabelExpression(schema.labelExpression, enrichedValues);
|
|
3896
4516
|
}
|
|
3897
4517
|
const resolvedMap = await this.resolveRelationLabels(relationAttrs, values);
|
|
3898
4518
|
if (resolvedMap.size === 0) {
|
|
3899
|
-
return renderLabelExpression(schema.labelExpression,
|
|
4519
|
+
return renderLabelExpression(schema.labelExpression, enrichedValues);
|
|
3900
4520
|
}
|
|
3901
|
-
|
|
4521
|
+
enrichedValues = { ...enrichedValues };
|
|
3902
4522
|
for (const attr of relationAttrs) {
|
|
3903
4523
|
const val = values[attr.name];
|
|
3904
4524
|
const ids = this.extractRelationIds(val);
|
|
@@ -4011,7 +4631,7 @@ var RecordService = class {
|
|
|
4011
4631
|
async getRecordOrThrow(recordId) {
|
|
4012
4632
|
const record = await this.getRecord(recordId);
|
|
4013
4633
|
if (!record) {
|
|
4014
|
-
throw new
|
|
4634
|
+
throw new RecordNotFoundError(recordId);
|
|
4015
4635
|
}
|
|
4016
4636
|
return record;
|
|
4017
4637
|
}
|
|
@@ -4189,7 +4809,7 @@ var RecordService = class {
|
|
|
4189
4809
|
await this.checkPermission(schema.name, "delete");
|
|
4190
4810
|
if (options?.checkSystem) {
|
|
4191
4811
|
if (schema.system) {
|
|
4192
|
-
throw new
|
|
4812
|
+
throw new ProtectedResourceError("object", schema.name, "delete");
|
|
4193
4813
|
}
|
|
4194
4814
|
}
|
|
4195
4815
|
if (!options?.skipReferenceCheck) {
|
|
@@ -4238,7 +4858,11 @@ var RecordService = class {
|
|
|
4238
4858
|
async restoreRecord(recordId, options) {
|
|
4239
4859
|
const record = await this.getRecordOrThrow(recordId);
|
|
4240
4860
|
if (!record.deletedAt) {
|
|
4241
|
-
throw new
|
|
4861
|
+
throw new SchemaError(
|
|
4862
|
+
`Record "${recordId}" is not deleted`,
|
|
4863
|
+
SchemaErrorCode.VALIDATION_FAILED,
|
|
4864
|
+
{ recordId, reason: "not_deleted" }
|
|
4865
|
+
);
|
|
4242
4866
|
}
|
|
4243
4867
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
4244
4868
|
await this.checkPermission(schema.name, "update");
|
|
@@ -4676,16 +5300,19 @@ var ViewService = class {
|
|
|
4676
5300
|
this.validateViewName(input.name);
|
|
4677
5301
|
const existing = await this.adapter.views.findByName(tenantId, input.objectName, input.name);
|
|
4678
5302
|
if (existing) {
|
|
4679
|
-
throw new
|
|
5303
|
+
throw new SchemaError(
|
|
5304
|
+
`View "${input.name}" already exists for object "${input.objectName}"`,
|
|
5305
|
+
SchemaErrorCode.DUPLICATE_ATTRIBUTE,
|
|
5306
|
+
{ viewName: input.name, objectName: input.objectName }
|
|
5307
|
+
);
|
|
4680
5308
|
}
|
|
4681
5309
|
if (this.nativeViews.has(input.objectName, input.name)) {
|
|
4682
|
-
throw new
|
|
4683
|
-
`Cannot create view "${input.name}": a system view with this name already exists
|
|
5310
|
+
throw new SchemaError(
|
|
5311
|
+
`Cannot create view "${input.name}": a system view with this name already exists`,
|
|
5312
|
+
SchemaErrorCode.DUPLICATE_ATTRIBUTE,
|
|
5313
|
+
{ viewName: input.name, objectName: input.objectName, system: true }
|
|
4684
5314
|
);
|
|
4685
5315
|
}
|
|
4686
|
-
if (!input.tabs || input.tabs.length === 0) {
|
|
4687
|
-
throw new Error("View must have at least one tab");
|
|
4688
|
-
}
|
|
4689
5316
|
const dbView = await this.adapter.views.create({
|
|
4690
5317
|
tenantId,
|
|
4691
5318
|
objectName: input.objectName,
|
|
@@ -4693,7 +5320,7 @@ var ViewService = class {
|
|
|
4693
5320
|
label: input.label,
|
|
4694
5321
|
description: input.description,
|
|
4695
5322
|
icon: input.icon,
|
|
4696
|
-
tabs: input.tabs,
|
|
5323
|
+
tabs: input.tabs ?? [],
|
|
4697
5324
|
default: input.default ?? false,
|
|
4698
5325
|
system: false,
|
|
4699
5326
|
// Custom views are never system
|
|
@@ -4711,13 +5338,10 @@ var ViewService = class {
|
|
|
4711
5338
|
async updateView(viewId, input) {
|
|
4712
5339
|
const dbView = await this.adapter.views.findById(viewId);
|
|
4713
5340
|
if (!dbView) {
|
|
4714
|
-
throw new
|
|
5341
|
+
throw new NotFoundError("View", viewId);
|
|
4715
5342
|
}
|
|
4716
5343
|
if (dbView.system) {
|
|
4717
|
-
throw new
|
|
4718
|
-
}
|
|
4719
|
-
if (input.tabs && input.tabs.length === 0) {
|
|
4720
|
-
throw new Error("View must have at least one tab");
|
|
5344
|
+
throw new ProtectedResourceError("attribute", dbView.name, "modify");
|
|
4721
5345
|
}
|
|
4722
5346
|
const updated = await this.adapter.views.update(viewId, {
|
|
4723
5347
|
label: input.label,
|
|
@@ -4737,10 +5361,10 @@ var ViewService = class {
|
|
|
4737
5361
|
async deleteView(viewId) {
|
|
4738
5362
|
const dbView = await this.adapter.views.findById(viewId);
|
|
4739
5363
|
if (!dbView) {
|
|
4740
|
-
throw new
|
|
5364
|
+
throw new NotFoundError("View", viewId);
|
|
4741
5365
|
}
|
|
4742
5366
|
if (dbView.system) {
|
|
4743
|
-
throw new
|
|
5367
|
+
throw new ProtectedResourceError("attribute", dbView.name, "delete");
|
|
4744
5368
|
}
|
|
4745
5369
|
await this.adapter.views.delete(viewId);
|
|
4746
5370
|
}
|
|
@@ -4754,7 +5378,7 @@ var ViewService = class {
|
|
|
4754
5378
|
async setDefaultView(viewId, tenantId) {
|
|
4755
5379
|
const dbView = await this.adapter.views.findById(viewId);
|
|
4756
5380
|
if (!dbView) {
|
|
4757
|
-
throw new
|
|
5381
|
+
throw new NotFoundError("View", viewId);
|
|
4758
5382
|
}
|
|
4759
5383
|
const currentViews = await this.adapter.views.findByObjectName(tenantId, dbView.objectName);
|
|
4760
5384
|
for (const v of currentViews) {
|
|
@@ -4773,16 +5397,23 @@ var ViewService = class {
|
|
|
4773
5397
|
*/
|
|
4774
5398
|
validateViewName(name) {
|
|
4775
5399
|
if (!name || name.length === 0) {
|
|
4776
|
-
throw new
|
|
5400
|
+
throw new ValidationError("View name cannot be empty", [
|
|
5401
|
+
{ path: ["name"], message: "View name cannot be empty" }
|
|
5402
|
+
]);
|
|
4777
5403
|
}
|
|
4778
5404
|
if (name.length > 63) {
|
|
4779
|
-
throw new
|
|
5405
|
+
throw new ValidationError("View name is too long", [
|
|
5406
|
+
{ path: ["name"], message: "View name is too long (max 63 characters)" }
|
|
5407
|
+
]);
|
|
4780
5408
|
}
|
|
4781
5409
|
const kebabCaseRegex = /^[a-z][a-z0-9-]*$/;
|
|
4782
5410
|
if (!kebabCaseRegex.test(name)) {
|
|
4783
|
-
throw new
|
|
4784
|
-
|
|
4785
|
-
|
|
5411
|
+
throw new ValidationError("Invalid view name format", [
|
|
5412
|
+
{
|
|
5413
|
+
path: ["name"],
|
|
5414
|
+
message: "View name must be in kebab-case (e.g., 'detail', 'list-view')"
|
|
5415
|
+
}
|
|
5416
|
+
]);
|
|
4786
5417
|
}
|
|
4787
5418
|
}
|
|
4788
5419
|
/**
|
|
@@ -5149,6 +5780,7 @@ var NoopGeocodingAdapter = class {
|
|
|
5149
5780
|
AuditService,
|
|
5150
5781
|
DEFAULT_LABEL_FALLBACK,
|
|
5151
5782
|
FileService,
|
|
5783
|
+
FlowService,
|
|
5152
5784
|
GeocodingService,
|
|
5153
5785
|
GlobalSearchService,
|
|
5154
5786
|
NoopGeocodingAdapter,
|
|
@@ -5161,6 +5793,7 @@ var NoopGeocodingAdapter = class {
|
|
|
5161
5793
|
ViewService,
|
|
5162
5794
|
buildAuditChanges,
|
|
5163
5795
|
createMockAdapter,
|
|
5796
|
+
enrichValuesWithSelectLabels,
|
|
5164
5797
|
extractAttributeNames,
|
|
5165
5798
|
getSyncPreview,
|
|
5166
5799
|
getViewSyncPreview,
|