@stndrds/schema 0.1.0-alpha.56 → 0.1.0-alpha.58

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.
@@ -1,40 +1,22 @@
1
+ import {
2
+ asTenantId,
3
+ asUserId,
4
+ generateId
5
+ } from "./chunk-V2RPPE2Y.mjs";
6
+ import {
7
+ computeRecordStatus,
8
+ formatZodErrors,
9
+ parseAttributeConfig,
10
+ validateDraftOrThrow,
11
+ validateObject,
12
+ validateObjectOrThrow
13
+ } from "./chunk-SV4BCGQU.mjs";
1
14
  import {
2
15
  __require
3
16
  } from "./chunk-Y6FXYEAI.mjs";
4
17
 
5
18
  // src/runtime/auth/workflow-jwt.service.ts
6
19
  import { SignJWT, importPKCS8, importSPKI, jwtVerify } from "jose";
7
-
8
- // src/utils.ts
9
- function asTenantId(id) {
10
- return id;
11
- }
12
- function asUserId(id) {
13
- return id;
14
- }
15
- function generateId() {
16
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
17
- return crypto.randomUUID();
18
- }
19
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
20
- const r = Math.random() * 16 | 0;
21
- const v = c === "x" ? r : r & 3 | 8;
22
- return v.toString(16);
23
- });
24
- }
25
- function generatePrefixedId(prefix) {
26
- return `${prefix}_${generateId()}`;
27
- }
28
- function slugify(input) {
29
- return input.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^a-z0-9\s_-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-");
30
- }
31
- function generateTemplateName(label) {
32
- const slug = slugify(label) || "template";
33
- const suffix = generateId().slice(0, 8);
34
- return `${slug}-${suffix}`;
35
- }
36
-
37
- // src/runtime/auth/workflow-jwt.service.ts
38
20
  var WorkflowJwtService = class _WorkflowJwtService {
39
21
  constructor(config) {
40
22
  this.config = config;
@@ -212,6 +194,26 @@ var WorkflowJwtService = class _WorkflowJwtService {
212
194
  }
213
195
  };
214
196
 
197
+ // src/lib/object-helpers.ts
198
+ function isEmpty(obj) {
199
+ if (obj === null || obj === void 0) {
200
+ return true;
201
+ }
202
+ if (typeof obj !== "object") {
203
+ return false;
204
+ }
205
+ return Object.keys(obj).length === 0;
206
+ }
207
+ function isNotEmpty(obj) {
208
+ return obj !== null && obj !== void 0 && typeof obj === "object" && Object.keys(obj).length > 0;
209
+ }
210
+ function toUndefinedIfEmpty(obj) {
211
+ return isNotEmpty(obj) ? obj : void 0;
212
+ }
213
+ function hasProperties(obj) {
214
+ return isNotEmpty(obj);
215
+ }
216
+
215
217
  // src/runtime/cache.ts
216
218
  function fnv1aHash(str) {
217
219
  let hash = 2166136261;
@@ -222,7 +224,7 @@ function fnv1aHash(str) {
222
224
  return hash.toString(16).padStart(8, "0");
223
225
  }
224
226
  function hashOptions(options) {
225
- if (options === null || options === void 0 || typeof options === "object" && Object.keys(options).length === 0) {
227
+ if (isEmpty(options)) {
226
228
  return "default";
227
229
  }
228
230
  const sortedJson = JSON.stringify(options, (_, value) => {
@@ -543,7 +545,7 @@ var TenantContextError = class _TenantContextError extends Error {
543
545
  }
544
546
  };
545
547
 
546
- // src/runtime/context/schema-context.ts
548
+ // src/runtime/context/feature-flags-context.ts
547
549
  var browserStub = {
548
550
  getStore: () => void 0,
549
551
  run: (_store, callback) => callback()
@@ -581,22 +583,106 @@ function getStorage() {
581
583
  storageInstance = browserStub;
582
584
  return storageInstance;
583
585
  }
584
- function getSchemaFromContext(objectId) {
586
+ var FeatureFlagsContextError = class _FeatureFlagsContextError extends Error {
587
+ constructor(message) {
588
+ super(
589
+ message ?? "No feature flags context found. This usually means:\n - HTTP request: Missing FeatureFlagsInterceptor\n - Background job: Wrap with runWithFeatureFlags()\n - Test: Wrap test body with runWithFeatureFlags()\n\nTip: FeatureFlagsInterceptor must run AFTER TenantContextInterceptor."
590
+ );
591
+ this.name = "FeatureFlagsContextError";
592
+ if ("captureStackTrace" in Error) {
593
+ Error.captureStackTrace(
594
+ this,
595
+ _FeatureFlagsContextError
596
+ );
597
+ }
598
+ }
599
+ };
600
+ function getContext() {
601
+ const ctx = getStorage().getStore();
602
+ if (!ctx) {
603
+ throw new FeatureFlagsContextError();
604
+ }
605
+ return ctx;
606
+ }
607
+ function isFeatureEnabled(flagName) {
608
+ return getContext().flags.get(flagName) === true;
609
+ }
610
+ function getFeatureValue(flagName, defaultValue) {
611
+ const value = getContext().flags.get(flagName);
612
+ return value !== void 0 ? value : defaultValue;
613
+ }
614
+ function getFeatureFlags() {
615
+ return getContext().flags;
616
+ }
617
+ function tryGetFeatureValue(flagName) {
585
618
  const ctx = getStorage().getStore();
619
+ return ctx?.flags.get(flagName);
620
+ }
621
+ function hasFeatureFlagsContext() {
622
+ return getStorage().getStore() !== void 0;
623
+ }
624
+ function runWithFeatureFlags(flags, fn) {
625
+ const frozenContext = Object.freeze({ flags });
626
+ return getStorage().run(frozenContext, fn);
627
+ }
628
+ function withFeatureFlags(resolvedFlags, fn) {
629
+ return runWithFeatureFlags(resolvedFlags, fn);
630
+ }
631
+
632
+ // src/runtime/context/schema-context.ts
633
+ var browserStub2 = {
634
+ getStore: () => void 0,
635
+ run: (_store, callback) => callback()
636
+ };
637
+ var AsyncLocalStorageClass2 = null;
638
+ if (typeof process !== "undefined" && process.versions?.node) {
639
+ try {
640
+ if (typeof __require !== "undefined") {
641
+ const asyncHooks = __require("async_hooks");
642
+ AsyncLocalStorageClass2 = asyncHooks.AsyncLocalStorage;
643
+ }
644
+ } catch {
645
+ try {
646
+ const dynamicRequire = new Function(
647
+ "m",
648
+ 'return typeof require!=="undefined"?require(m):null'
649
+ );
650
+ const asyncHooks = dynamicRequire("node:async_hooks");
651
+ if (asyncHooks) {
652
+ AsyncLocalStorageClass2 = asyncHooks.AsyncLocalStorage;
653
+ }
654
+ } catch {
655
+ }
656
+ }
657
+ }
658
+ var storageInstance2 = null;
659
+ function getStorage2() {
660
+ if (storageInstance2 !== null) {
661
+ return storageInstance2;
662
+ }
663
+ if (AsyncLocalStorageClass2) {
664
+ storageInstance2 = new AsyncLocalStorageClass2();
665
+ return storageInstance2;
666
+ }
667
+ storageInstance2 = browserStub2;
668
+ return storageInstance2;
669
+ }
670
+ function getSchemaFromContext(objectId) {
671
+ const ctx = getStorage2().getStore();
586
672
  return ctx?.objectsById.get(objectId);
587
673
  }
588
674
  function getSchemaByNameFromContext(objectName) {
589
- const ctx = getStorage().getStore();
675
+ const ctx = getStorage2().getStore();
590
676
  return ctx?.objectsByName.get(objectName);
591
677
  }
592
678
  function hasSchemaContext() {
593
- return getStorage().getStore() !== void 0;
679
+ return getStorage2().getStore() !== void 0;
594
680
  }
595
681
  function getSchemaContext() {
596
- return getStorage().getStore();
682
+ return getStorage2().getStore();
597
683
  }
598
684
  function addSchemaToContext(schema) {
599
- const ctx = getStorage().getStore();
685
+ const ctx = getStorage2().getStore();
600
686
  if (!ctx) {
601
687
  return;
602
688
  }
@@ -618,10 +704,10 @@ function buildSchemaContext(schemas) {
618
704
  }
619
705
  function runWithSchemaContext(schemas, fn) {
620
706
  const context = buildSchemaContext(schemas);
621
- return getStorage().run(context, fn);
707
+ return getStorage2().run(context, fn);
622
708
  }
623
709
  function runWithMergedSchemaContext(schemas, fn) {
624
- const existing = getStorage().getStore();
710
+ const existing = getStorage2().getStore();
625
711
  const objectsById = new Map(existing?.objectsById);
626
712
  const objectsByName = new Map(existing?.objectsByName);
627
713
  for (const schema of schemas) {
@@ -634,20 +720,20 @@ function runWithMergedSchemaContext(schemas, fn) {
634
720
  objectsById,
635
721
  objectsByName
636
722
  };
637
- return getStorage().run(context, fn);
723
+ return getStorage2().run(context, fn);
638
724
  }
639
725
 
640
726
  // src/runtime/context/tenant-context.ts
641
- var browserStub2 = {
727
+ var browserStub3 = {
642
728
  getStore: () => void 0,
643
729
  run: (_store, callback) => callback()
644
730
  };
645
- var AsyncLocalStorageClass2 = null;
731
+ var AsyncLocalStorageClass3 = null;
646
732
  if (typeof process !== "undefined" && process.versions?.node) {
647
733
  try {
648
734
  if (typeof __require !== "undefined") {
649
735
  const asyncHooks = __require("async_hooks");
650
- AsyncLocalStorageClass2 = asyncHooks.AsyncLocalStorage;
736
+ AsyncLocalStorageClass3 = asyncHooks.AsyncLocalStorage;
651
737
  }
652
738
  } catch {
653
739
  try {
@@ -657,43 +743,43 @@ if (typeof process !== "undefined" && process.versions?.node) {
657
743
  );
658
744
  const asyncHooks = dynamicRequire("node:async_hooks");
659
745
  if (asyncHooks) {
660
- AsyncLocalStorageClass2 = asyncHooks.AsyncLocalStorage;
746
+ AsyncLocalStorageClass3 = asyncHooks.AsyncLocalStorage;
661
747
  }
662
748
  } catch {
663
749
  }
664
750
  }
665
751
  }
666
- var storageInstance2 = null;
667
- function getStorage2() {
668
- if (storageInstance2 !== null) {
669
- return storageInstance2;
752
+ var storageInstance3 = null;
753
+ function getStorage3() {
754
+ if (storageInstance3 !== null) {
755
+ return storageInstance3;
670
756
  }
671
- if (AsyncLocalStorageClass2) {
672
- storageInstance2 = new AsyncLocalStorageClass2();
673
- return storageInstance2;
757
+ if (AsyncLocalStorageClass3) {
758
+ storageInstance3 = new AsyncLocalStorageClass3();
759
+ return storageInstance3;
674
760
  }
675
- storageInstance2 = browserStub2;
676
- return storageInstance2;
761
+ storageInstance3 = browserStub3;
762
+ return storageInstance3;
677
763
  }
678
- function getContext() {
679
- const ctx = getStorage2().getStore();
764
+ function getContext2() {
765
+ const ctx = getStorage3().getStore();
680
766
  if (!ctx) {
681
767
  throw new TenantContextError();
682
768
  }
683
769
  return ctx;
684
770
  }
685
771
  function getTenantId() {
686
- return getContext().tenantId;
772
+ return getContext2().tenantId;
687
773
  }
688
774
  function getUserId() {
689
- return getContext().userId;
775
+ return getContext2().userId;
690
776
  }
691
777
  function hasContext() {
692
- return getStorage2().getStore() !== void 0;
778
+ return getStorage3().getStore() !== void 0;
693
779
  }
694
780
  function runWithContext(context, fn) {
695
781
  const frozenContext = Object.freeze({ ...context });
696
- return getStorage2().run(frozenContext, fn);
782
+ return getStorage3().run(frozenContext, fn);
697
783
  }
698
784
  function withTenantContext(tenantId, fn, userId) {
699
785
  return runWithContext({ tenantId, userId }, fn);
@@ -1261,12 +1347,6 @@ function or(...rules) {
1261
1347
  function inValues(field, values) {
1262
1348
  return { field, operator: "in", value: values };
1263
1349
  }
1264
- function isEmpty(field) {
1265
- return { field, operator: "isEmpty", value: null };
1266
- }
1267
- function isNotEmpty(field) {
1268
- return { field, operator: "isNotEmpty", value: null };
1269
- }
1270
1350
 
1271
1351
  // src/types/workflows/definition.ts
1272
1352
  function isWorkflowDefinition(obj) {
@@ -1376,16 +1456,6 @@ function setContextValue(context, path, value) {
1376
1456
  }
1377
1457
  current[parts[parts.length - 1]] = value;
1378
1458
  }
1379
- function mergeFormToSlot(context, nodeId, slotId) {
1380
- const formData = context.forms[nodeId];
1381
- if (!formData) {
1382
- return;
1383
- }
1384
- if (!context.slots[slotId]) {
1385
- context.slots[slotId] = {};
1386
- }
1387
- Object.assign(context.slots[slotId], formData);
1388
- }
1389
1459
 
1390
1460
  // src/types/workflows/theme.ts
1391
1461
  var DEFAULT_THEME = {
@@ -1739,9 +1809,9 @@ function compareValues(actual, operator, expected) {
1739
1809
  return typeof actual === "string" && typeof expected === "string" ? actual.endsWith(expected) : false;
1740
1810
  // Presence
1741
1811
  case "isEmpty":
1742
- return isEmpty2(actual);
1812
+ return isEmptyValue(actual);
1743
1813
  case "isNotEmpty":
1744
- return !isEmpty2(actual);
1814
+ return !isEmptyValue(actual);
1745
1815
  // Set membership
1746
1816
  case "in":
1747
1817
  if (Array.isArray(expected)) {
@@ -1757,7 +1827,7 @@ function compareValues(actual, operator, expected) {
1757
1827
  return false;
1758
1828
  }
1759
1829
  }
1760
- function isEmpty2(value) {
1830
+ function isEmptyValue(value) {
1761
1831
  if (value === null || value === void 0) {
1762
1832
  return true;
1763
1833
  }
@@ -1767,7 +1837,7 @@ function isEmpty2(value) {
1767
1837
  if (Array.isArray(value) && value.length === 0) {
1768
1838
  return true;
1769
1839
  }
1770
- if (typeof value === "object" && Object.keys(value).length === 0) {
1840
+ if (typeof value === "object" && isEmpty(value)) {
1771
1841
  return true;
1772
1842
  }
1773
1843
  return false;
@@ -2012,7 +2082,7 @@ var DocumentExecutor = class {
2012
2082
  validateSlotReferences(node, workflowSlots) {
2013
2083
  const errors = [];
2014
2084
  if (node.targetSlotIds && node.targetSlotIds.length > 0) {
2015
- const slotIdSet = new Set(workflowSlots.map((s) => s.id));
2085
+ const slotIdSet = workflowSlots.reduce((set, s) => set.add(s.id), /* @__PURE__ */ new Set());
2016
2086
  for (const slotId of node.targetSlotIds) {
2017
2087
  if (!slotIdSet.has(slotId)) {
2018
2088
  errors.push(
@@ -2048,24 +2118,25 @@ var FormExecutor = class {
2048
2118
  }
2049
2119
  execute(node, context) {
2050
2120
  const { input } = context;
2051
- if (!input || Object.keys(input).length === 0) {
2121
+ if (isEmpty(input)) {
2052
2122
  const requiredParticipationId = node.participantId ?? void 0;
2053
2123
  return wait(`Waiting for form submission: ${node.label}`, {
2054
2124
  requiredParticipationId
2055
2125
  });
2056
2126
  }
2127
+ const formInput = input;
2057
2128
  const slotIds = this.extractSlotIds(node);
2058
2129
  if (slotIds.size === 0) {
2059
2130
  return error("MISSING_FIELDS", "FormNode must have fields or rows with slot references");
2060
2131
  }
2061
2132
  const contextUpdates = {
2062
2133
  forms: {
2063
- [node.id]: input
2134
+ [node.id]: formInput
2064
2135
  },
2065
2136
  slots: {}
2066
2137
  };
2067
2138
  for (const slotId of slotIds) {
2068
- const slotInput = input[slotId] ?? {};
2139
+ const slotInput = formInput[slotId] ?? {};
2069
2140
  if (!contextUpdates.slots) {
2070
2141
  contextUpdates.slots = {};
2071
2142
  }
@@ -2093,9 +2164,9 @@ var FormExecutor = class {
2093
2164
  }
2094
2165
  canExecute(node, context) {
2095
2166
  if (node.participantId && context.executorId) {
2096
- return context.input !== void 0 && Object.keys(context.input).length > 0;
2167
+ return hasProperties(context.input);
2097
2168
  }
2098
- return context.input !== void 0 && Object.keys(context.input).length > 0;
2169
+ return hasProperties(context.input);
2099
2170
  }
2100
2171
  validate(node) {
2101
2172
  const errors = [];
@@ -3387,9 +3458,6 @@ var ConcurrentModificationError = class extends SchemaError {
3387
3458
  );
3388
3459
  }
3389
3460
  };
3390
- function isConcurrentModificationError(error2) {
3391
- return error2 instanceof ConcurrentModificationError;
3392
- }
3393
3461
 
3394
3462
  // src/format.ts
3395
3463
  import { getCountryByIso3 } from "@stndrds/constants";
@@ -4000,6 +4068,7 @@ function createEmptyStores() {
4000
4068
  files: /* @__PURE__ */ new Map(),
4001
4069
  objectRecords: /* @__PURE__ */ new Map(),
4002
4070
  views: /* @__PURE__ */ new Map(),
4071
+ viewOverlays: /* @__PURE__ */ new Map(),
4003
4072
  roles: /* @__PURE__ */ new Map(),
4004
4073
  permissions: /* @__PURE__ */ new Map(),
4005
4074
  userRoles: /* @__PURE__ */ new Map(),
@@ -4217,7 +4286,8 @@ function createMockPermissionsRepository(stores) {
4217
4286
  getUserRoles(userProfileId) {
4218
4287
  const tenantId = getTenantId();
4219
4288
  const roleIds = Array.from(stores.userRoles.values()).filter((ur) => ur.userProfileId === userProfileId && ur.tenantId === tenantId).map((ur) => ur.roleId);
4220
- const roles = Array.from(stores.roles.values()).filter((r) => roleIds.includes(r.id));
4289
+ const roleIdsSet = new Set(roleIds);
4290
+ const roles = Array.from(stores.roles.values()).filter((r) => roleIdsSet.has(r.id));
4221
4291
  return Promise.resolve(roles);
4222
4292
  },
4223
4293
  assignRole(input) {
@@ -4297,32 +4367,38 @@ function createMockViewsRepository(stores) {
4297
4367
  ) ?? null
4298
4368
  );
4299
4369
  },
4300
- findByObjectName(objectName) {
4370
+ findByNameAndType(objectName, viewName, type) {
4371
+ const tenantId = getTenantId();
4372
+ return Promise.resolve(
4373
+ Array.from(stores.views.values()).find(
4374
+ (v) => v.tenantId === tenantId && v.objectName === objectName && v.name === viewName && v.type === type
4375
+ ) ?? null
4376
+ );
4377
+ },
4378
+ findByObjectName(objectName, type) {
4301
4379
  const tenantId = getTenantId();
4302
4380
  return Promise.resolve(
4303
4381
  Array.from(stores.views.values()).filter(
4304
- (v) => v.tenantId === tenantId && v.objectName === objectName
4382
+ (v) => v.tenantId === tenantId && v.objectName === objectName && (type === void 0 || v.type === type)
4305
4383
  )
4306
4384
  );
4307
4385
  },
4308
- findAllForTenant() {
4386
+ findAllForTenant(type) {
4309
4387
  const tenantId = getTenantId();
4310
4388
  return Promise.resolve(
4311
- Array.from(stores.views.values()).filter((v) => v.tenantId === tenantId)
4389
+ Array.from(stores.views.values()).filter(
4390
+ (v) => v.tenantId === tenantId && (type === void 0 || v.type === type)
4391
+ )
4312
4392
  );
4313
4393
  },
4314
- findSystemByName(objectName, viewName) {
4394
+ findDefault(objectName, type) {
4395
+ const tenantId = getTenantId();
4315
4396
  return Promise.resolve(
4316
4397
  Array.from(stores.views.values()).find(
4317
- (v) => v.objectName === objectName && v.name === viewName && v.system
4398
+ (v) => v.tenantId === tenantId && v.objectName === objectName && v.type === type && v.default === true
4318
4399
  ) ?? null
4319
4400
  );
4320
4401
  },
4321
- findSystemByObjectName(objectName) {
4322
- return Promise.resolve(
4323
- Array.from(stores.views.values()).filter((v) => v.objectName === objectName && v.system)
4324
- );
4325
- },
4326
4402
  create(data) {
4327
4403
  const tenantId = getTenantId();
4328
4404
  const id = generateId();
@@ -4331,13 +4407,13 @@ function createMockViewsRepository(stores) {
4331
4407
  id,
4332
4408
  tenantId,
4333
4409
  objectName: data.objectName,
4410
+ type: data.type,
4334
4411
  name: data.name,
4335
4412
  label: data.label,
4336
4413
  description: data.description,
4337
4414
  icon: data.icon,
4338
- tabs: data.tabs,
4415
+ config: data.config,
4339
4416
  default: data.default ?? false,
4340
- system: data.system ?? false,
4341
4417
  metadata: data.metadata,
4342
4418
  createdAt: now,
4343
4419
  updatedAt: now
@@ -4362,10 +4438,11 @@ function createMockViewsRepository(stores) {
4362
4438
  stores.views.delete(id);
4363
4439
  return Promise.resolve();
4364
4440
  },
4365
- deleteNotIn(objectName, keepViewNames) {
4441
+ deleteNotIn(objectName, type, keepViewNames) {
4442
+ const tenantId = getTenantId();
4366
4443
  let deleted = 0;
4367
4444
  for (const [id, view2] of stores.views.entries()) {
4368
- if (view2.objectName === objectName && view2.system && !keepViewNames.includes(view2.name)) {
4445
+ if (view2.tenantId === tenantId && view2.objectName === objectName && view2.type === type && !keepViewNames.includes(view2.name)) {
4369
4446
  stores.views.delete(id);
4370
4447
  deleted++;
4371
4448
  }
@@ -4373,18 +4450,151 @@ function createMockViewsRepository(stores) {
4373
4450
  return Promise.resolve(deleted);
4374
4451
  },
4375
4452
  async upsert(data) {
4376
- const existing = data.system ? await this.findSystemByName(data.objectName, data.name) : await this.findByName(data.objectName, data.name);
4453
+ const existing = await this.findByNameAndType(data.objectName, data.name, data.type);
4377
4454
  if (existing) {
4378
4455
  return this.update(existing.id, {
4379
4456
  label: data.label,
4380
4457
  description: data.description,
4381
4458
  icon: data.icon,
4382
- tabs: data.tabs,
4459
+ config: data.config,
4383
4460
  default: data.default,
4384
4461
  metadata: data.metadata
4385
4462
  });
4386
4463
  }
4387
4464
  return this.create(data);
4465
+ },
4466
+ async exists(objectName, viewName, type) {
4467
+ const existing = await this.findByNameAndType(objectName, viewName, type);
4468
+ return existing !== null;
4469
+ }
4470
+ };
4471
+ }
4472
+ function createMockViewOverlaysRepository(stores) {
4473
+ return {
4474
+ findById(id) {
4475
+ return Promise.resolve(stores.viewOverlays.get(id) ?? null);
4476
+ },
4477
+ findByViewAndUser(viewId, userId) {
4478
+ const tenantId = getTenantId();
4479
+ return Promise.resolve(
4480
+ Array.from(stores.viewOverlays.values()).find(
4481
+ (o) => o.tenantId === tenantId && o.viewId === viewId && o.userId === userId
4482
+ ) ?? null
4483
+ );
4484
+ },
4485
+ findByUser(userId) {
4486
+ const tenantId = getTenantId();
4487
+ return Promise.resolve(
4488
+ Array.from(stores.viewOverlays.values()).filter(
4489
+ (o) => o.tenantId === tenantId && o.userId === userId
4490
+ )
4491
+ );
4492
+ },
4493
+ findByView(viewId) {
4494
+ const tenantId = getTenantId();
4495
+ return Promise.resolve(
4496
+ Array.from(stores.viewOverlays.values()).filter(
4497
+ (o) => o.tenantId === tenantId && o.viewId === viewId
4498
+ )
4499
+ );
4500
+ },
4501
+ async findUserDefault(userId, objectName, type) {
4502
+ const tenantId = getTenantId();
4503
+ const views = Array.from(stores.views.values()).filter(
4504
+ (v) => v.tenantId === tenantId && v.objectName === objectName && v.type === type
4505
+ );
4506
+ const viewIds = views.reduce((set, v) => set.add(v.id), /* @__PURE__ */ new Set());
4507
+ return Promise.resolve(
4508
+ Array.from(stores.viewOverlays.values()).find(
4509
+ (o) => o.tenantId === tenantId && o.userId === userId && o.isUserDefault === true && viewIds.has(o.viewId)
4510
+ ) ?? null
4511
+ );
4512
+ },
4513
+ create(data) {
4514
+ const tenantId = getTenantId();
4515
+ const id = generateId();
4516
+ const now = /* @__PURE__ */ new Date();
4517
+ const overlay = {
4518
+ id,
4519
+ tenantId,
4520
+ viewId: data.viewId,
4521
+ userId: data.userId,
4522
+ configOverrides: data.configOverrides,
4523
+ isUserDefault: data.isUserDefault,
4524
+ createdAt: now,
4525
+ updatedAt: now
4526
+ };
4527
+ stores.viewOverlays.set(id, overlay);
4528
+ return Promise.resolve(overlay);
4529
+ },
4530
+ update(id, data) {
4531
+ const overlay = stores.viewOverlays.get(id);
4532
+ if (!overlay) {
4533
+ return Promise.reject(new Error(`ViewOverlay not found: ${id}`));
4534
+ }
4535
+ const updated = {
4536
+ ...overlay,
4537
+ ...data,
4538
+ updatedAt: /* @__PURE__ */ new Date()
4539
+ };
4540
+ stores.viewOverlays.set(id, updated);
4541
+ return Promise.resolve(updated);
4542
+ },
4543
+ delete(id) {
4544
+ stores.viewOverlays.delete(id);
4545
+ return Promise.resolve();
4546
+ },
4547
+ async deleteByViewAndUser(viewId, userId) {
4548
+ const overlay = await this.findByViewAndUser(viewId, userId);
4549
+ if (overlay) {
4550
+ stores.viewOverlays.delete(overlay.id);
4551
+ }
4552
+ },
4553
+ deleteByView(viewId) {
4554
+ const tenantId = getTenantId();
4555
+ let deleted = 0;
4556
+ for (const [id, overlay] of stores.viewOverlays.entries()) {
4557
+ if (overlay.tenantId === tenantId && overlay.viewId === viewId) {
4558
+ stores.viewOverlays.delete(id);
4559
+ deleted++;
4560
+ }
4561
+ }
4562
+ return Promise.resolve(deleted);
4563
+ },
4564
+ migrateViewId(fromViewId, toViewId) {
4565
+ const tenantId = getTenantId();
4566
+ let migrated = 0;
4567
+ for (const overlay of stores.viewOverlays.values()) {
4568
+ if (overlay.tenantId === tenantId && overlay.viewId === fromViewId) {
4569
+ overlay.viewId = toViewId;
4570
+ overlay.updatedAt = /* @__PURE__ */ new Date();
4571
+ migrated++;
4572
+ }
4573
+ }
4574
+ return Promise.resolve(migrated);
4575
+ },
4576
+ async upsert(data) {
4577
+ const existing = await this.findByViewAndUser(data.viewId, data.userId);
4578
+ if (existing) {
4579
+ return this.update(existing.id, {
4580
+ configOverrides: data.configOverrides,
4581
+ isUserDefault: data.isUserDefault
4582
+ });
4583
+ }
4584
+ return this.create(data);
4585
+ },
4586
+ async clearUserDefault(userId, objectName, type) {
4587
+ const tenantId = getTenantId();
4588
+ const views = Array.from(stores.views.values()).filter(
4589
+ (v) => v.tenantId === tenantId && v.objectName === objectName && v.type === type
4590
+ );
4591
+ const viewIds = views.reduce((set, v) => set.add(v.id), /* @__PURE__ */ new Set());
4592
+ for (const overlay of stores.viewOverlays.values()) {
4593
+ if (overlay.tenantId === tenantId && overlay.userId === userId && overlay.isUserDefault === true && viewIds.has(overlay.viewId)) {
4594
+ overlay.isUserDefault = false;
4595
+ overlay.updatedAt = /* @__PURE__ */ new Date();
4596
+ }
4597
+ }
4388
4598
  }
4389
4599
  };
4390
4600
  }
@@ -4774,6 +4984,7 @@ function createMockAdapter() {
4774
4984
  files: createMockFilesRepository(stores),
4775
4985
  objectRecords: createMockObjectRecordsRepository(stores),
4776
4986
  views: createMockViewsRepository(stores),
4987
+ viewOverlays: createMockViewOverlaysRepository(stores),
4777
4988
  permissions: createMockPermissionsRepository(stores),
4778
4989
  workflows: createMockWorkflowsRepository(stores),
4779
4990
  workflowInstances: createMockWorkflowInstancesRepository(stores),
@@ -4796,6 +5007,7 @@ function createMockAdapter() {
4796
5007
  stores.files.clear();
4797
5008
  stores.objectRecords.clear();
4798
5009
  stores.views.clear();
5010
+ stores.viewOverlays.clear();
4799
5011
  stores.roles.clear();
4800
5012
  stores.permissions.clear();
4801
5013
  stores.userRoles.clear();
@@ -5339,6 +5551,32 @@ var BaseAttributeBuilder = class {
5339
5551
  this.attr.metadata = value;
5340
5552
  return this;
5341
5553
  }
5554
+ /**
5555
+ * Add a feature gate to conditionally show/hide/disable this attribute.
5556
+ *
5557
+ * @param flagName - Name of the feature flag to check
5558
+ * @param options - Optional configuration for expected value and fallback behavior
5559
+ *
5560
+ * @example Hide attribute when flag is disabled
5561
+ * ```typescript
5562
+ * text({ name: "aiSummary", label: "AI Summary" })
5563
+ * .featureGate("ai-features")
5564
+ * ```
5565
+ *
5566
+ * @example Disable attribute when tier is not enterprise
5567
+ * ```typescript
5568
+ * text({ name: "advancedField", label: "Advanced Field" })
5569
+ * .featureGate("tier", { expectedValue: "enterprise", fallback: "disable" })
5570
+ * ```
5571
+ */
5572
+ featureGate(flagName, options) {
5573
+ this.attr.featureGate = {
5574
+ flag: flagName,
5575
+ expectedValue: options?.expectedValue,
5576
+ fallback: options?.fallback
5577
+ };
5578
+ return this;
5579
+ }
5342
5580
  build() {
5343
5581
  return this.attr;
5344
5582
  }
@@ -6261,6 +6499,7 @@ function object(config) {
6261
6499
  }
6262
6500
 
6263
6501
  // src/builders/view-builder.ts
6502
+ import { randomUUID } from "crypto";
6264
6503
  import { z as z3 } from "zod";
6265
6504
  var GroupBuilder = class {
6266
6505
  constructor(id, label) {
@@ -6411,7 +6650,7 @@ var BaseTableTabConfig = class {
6411
6650
  return new TabBuilder(this.view, name, label);
6412
6651
  }
6413
6652
  /**
6414
- * Finish this tab and return to ViewBuilder
6653
+ * Finish this tab and return to DetailViewBuilder
6415
6654
  */
6416
6655
  done() {
6417
6656
  this.finalize();
@@ -6477,7 +6716,7 @@ var CustomTabConfig = class {
6477
6716
  return new TabBuilder(this.view, name, label);
6478
6717
  }
6479
6718
  /**
6480
- * Finish this tab and return to ViewBuilder
6719
+ * Finish this tab and return to DetailViewBuilder
6481
6720
  */
6482
6721
  done() {
6483
6722
  this.view._addTab(this.tabData);
@@ -6519,7 +6758,7 @@ var NotesTabConfig = class {
6519
6758
  return new TabBuilder(this.view, name, label);
6520
6759
  }
6521
6760
  /**
6522
- * Finish this tab and return to ViewBuilder
6761
+ * Finish this tab and return to DetailViewBuilder
6523
6762
  */
6524
6763
  done() {
6525
6764
  this.view._addTab(this.tabData);
@@ -6554,7 +6793,7 @@ var ActivityTabConfig = class {
6554
6793
  return new TabBuilder(this.view, name, label);
6555
6794
  }
6556
6795
  /**
6557
- * Finish this tab and return to ViewBuilder
6796
+ * Finish this tab and return to DetailViewBuilder
6558
6797
  */
6559
6798
  done() {
6560
6799
  this.view._addTab(this.tabData);
@@ -6617,7 +6856,7 @@ var FlowsTabConfig = class {
6617
6856
  return new TabBuilder(this.view, name, label);
6618
6857
  }
6619
6858
  /**
6620
- * Finish this tab and return to ViewBuilder
6859
+ * Finish this tab and return to DetailViewBuilder
6621
6860
  */
6622
6861
  done() {
6623
6862
  this.view._addTab(this.tabData);
@@ -6688,7 +6927,7 @@ var DocumentsTabConfig = class {
6688
6927
  return new TabBuilder(this.view, name, label);
6689
6928
  }
6690
6929
  /**
6691
- * Finish this tab and return to ViewBuilder
6930
+ * Finish this tab and return to DetailViewBuilder
6692
6931
  */
6693
6932
  done() {
6694
6933
  this.view._addTab(this.tabData);
@@ -6726,13 +6965,6 @@ var TabBuilder = class {
6726
6965
  this.base.order = value;
6727
6966
  return this;
6728
6967
  }
6729
- /**
6730
- * Mark as system tab (protected)
6731
- */
6732
- system() {
6733
- this.base.system = true;
6734
- return this;
6735
- }
6736
6968
  // ─────────────────────────────────────────────────────────────────────────
6737
6969
  // TYPE DISCRIMINATORS
6738
6970
  // ─────────────────────────────────────────────────────────────────────────
@@ -6812,13 +7044,15 @@ var TabBuilder = class {
6812
7044
  return new DocumentsTabConfig(this.view, this.base);
6813
7045
  }
6814
7046
  };
6815
- var ViewBuilder = class {
7047
+ var DetailViewBuilder = class {
6816
7048
  constructor(name, label) {
6817
- this.data = { tabs: [] };
6818
- this.validated = false;
6819
7049
  this.validateName(name);
6820
- this.data.name = name;
6821
- this.data.label = label;
7050
+ this.data = {
7051
+ name,
7052
+ label,
7053
+ layout: "page",
7054
+ tabs: []
7055
+ };
6822
7056
  }
6823
7057
  /**
6824
7058
  * Set view description
@@ -6843,19 +7077,12 @@ var ViewBuilder = class {
6843
7077
  return this;
6844
7078
  }
6845
7079
  /**
6846
- * Mark as default view for the object (within its layout)
7080
+ * Mark as default view for the object
6847
7081
  */
6848
7082
  default() {
6849
7083
  this.data.default = true;
6850
7084
  return this;
6851
7085
  }
6852
- /**
6853
- * Mark as system view (protected, defined by developer)
6854
- */
6855
- system() {
6856
- this.data.system = true;
6857
- return this;
6858
- }
6859
7086
  /**
6860
7087
  * Set the layout mode for the view
6861
7088
  * @param value - "page" for full tabs, "modal" for single form
@@ -6890,14 +7117,14 @@ var ViewBuilder = class {
6890
7117
  * Add a pre-built tab
6891
7118
  */
6892
7119
  addTab(tab) {
6893
- this.data.tabs?.push(tab);
7120
+ this.data.tabs.push(tab);
6894
7121
  return this;
6895
7122
  }
6896
7123
  /**
6897
7124
  * @internal Used by TabBuilder to add tabs
6898
7125
  */
6899
7126
  _addTab(tab) {
6900
- this.data.tabs?.push(tab);
7127
+ this.data.tabs.push(tab);
6901
7128
  return this;
6902
7129
  }
6903
7130
  /**
@@ -6905,28 +7132,41 @@ var ViewBuilder = class {
6905
7132
  */
6906
7133
  build() {
6907
7134
  if (!this.data.object) {
6908
- throw new Error("[ViewBuilder] for() is required - specify the target object");
7135
+ throw new Error("[DetailViewBuilder] for() is required - specify the target object");
6909
7136
  }
6910
- if (!this.data.tabs || this.data.tabs.length === 0) {
6911
- throw new Error("[ViewBuilder] At least one tab is required");
7137
+ if (this.data.tabs.length === 0) {
7138
+ throw new Error("[DetailViewBuilder] At least one tab is required");
6912
7139
  }
6913
7140
  const tabNames = /* @__PURE__ */ new Set();
6914
7141
  for (const tab of this.data.tabs) {
6915
7142
  if (tabNames.has(tab.name)) {
6916
- throw new Error(`[ViewBuilder] Duplicate tab name "${tab.name}"`);
7143
+ throw new Error(`[DetailViewBuilder] Duplicate tab name "${tab.name}"`);
6917
7144
  }
6918
7145
  tabNames.add(tab.name);
6919
7146
  }
6920
7147
  if (this.data.layout === "modal") {
6921
7148
  if (this.data.tabs.length !== 1) {
6922
- throw new Error("[ViewBuilder] Modal views must have exactly one tab");
7149
+ throw new Error("[DetailViewBuilder] Modal views must have exactly one tab");
6923
7150
  }
6924
7151
  if (this.data.tabs[0].type !== "form") {
6925
- throw new Error("[ViewBuilder] Modal views must have a form tab");
7152
+ throw new Error("[DetailViewBuilder] Modal views must have a form tab");
6926
7153
  }
6927
7154
  }
6928
- this.validated = true;
6929
- return this.data;
7155
+ const config = {
7156
+ layout: this.data.layout,
7157
+ tabs: this.data.tabs
7158
+ };
7159
+ return {
7160
+ name: this.data.name,
7161
+ label: this.data.label,
7162
+ description: this.data.description,
7163
+ icon: this.data.icon,
7164
+ object: this.data.object,
7165
+ type: "detail",
7166
+ config,
7167
+ default: this.data.default,
7168
+ metadata: this.data.metadata
7169
+ };
6930
7170
  }
6931
7171
  /**
6932
7172
  * Validate view name format (kebab-case)
@@ -6939,24 +7179,295 @@ var ViewBuilder = class {
6939
7179
  viewNameSchema.parse(name);
6940
7180
  } catch (error2) {
6941
7181
  if (error2 instanceof z3.ZodError) {
6942
- throw new Error(`[ViewBuilder] ${error2.issues[0].message}`);
7182
+ throw new Error(`[DetailViewBuilder] ${error2.issues[0].message}`);
6943
7183
  }
6944
7184
  throw error2;
6945
7185
  }
6946
7186
  }
6947
7187
  };
6948
- function view(name, label) {
6949
- return new ViewBuilder(name, label);
7188
+ var ViewBuilder = DetailViewBuilder;
7189
+ function detailView(name, label) {
7190
+ return new DetailViewBuilder(name, label);
6950
7191
  }
6951
- function group(id, label) {
6952
- return new GroupBuilder(id, label);
7192
+ function view(name, label) {
7193
+ return new DetailViewBuilder(name, label);
6953
7194
  }
6954
-
6955
- // src/builders/workflow-builder.ts
6956
- import { z as z4 } from "zod";
6957
- var WorkflowFormRowBuilder = class {
6958
- /** @internal */
6959
- constructor(formBuilder, rowId, order) {
7195
+ var ListViewBuilder = class {
7196
+ constructor(name, label) {
7197
+ this.validateName(name);
7198
+ this.data = {
7199
+ name,
7200
+ label,
7201
+ layout: "table",
7202
+ columns: [],
7203
+ tabs: []
7204
+ };
7205
+ }
7206
+ /**
7207
+ * Set view description
7208
+ */
7209
+ description(value) {
7210
+ this.data.description = value;
7211
+ return this;
7212
+ }
7213
+ /**
7214
+ * Set view icon
7215
+ */
7216
+ icon(value) {
7217
+ this.data.icon = value;
7218
+ return this;
7219
+ }
7220
+ /**
7221
+ * Associate view with an object
7222
+ * @param objectName - Object name (kebab-case)
7223
+ */
7224
+ for(objectName) {
7225
+ this.data.object = objectName;
7226
+ return this;
7227
+ }
7228
+ /**
7229
+ * Mark as default view for the object
7230
+ */
7231
+ default() {
7232
+ this.data.default = true;
7233
+ return this;
7234
+ }
7235
+ /**
7236
+ * Set metadata
7237
+ */
7238
+ metadata(value) {
7239
+ this.data.metadata = value;
7240
+ return this;
7241
+ }
7242
+ /**
7243
+ * Set the layout to table (default)
7244
+ */
7245
+ table() {
7246
+ this.data.layout = "table";
7247
+ return this;
7248
+ }
7249
+ /**
7250
+ * Set the layout to kanban and specify the grouping attribute
7251
+ * @param groupByAttribute - Attribute to group by (must be a select/status type)
7252
+ */
7253
+ kanban(groupByAttribute) {
7254
+ this.data.layout = "kanban";
7255
+ this.data.groupByAttribute = groupByAttribute;
7256
+ return this;
7257
+ }
7258
+ /**
7259
+ * Set columns to display
7260
+ * @example .columns("name", "email", "status", "createdAt")
7261
+ */
7262
+ columns(...names) {
7263
+ this.data.columns = names;
7264
+ return this;
7265
+ }
7266
+ /**
7267
+ * Set width for a specific column in pixels
7268
+ * @example .columnWidth("email", 200)
7269
+ */
7270
+ columnWidth(columnName, width) {
7271
+ if (!this.data.columnSizing) {
7272
+ this.data.columnSizing = {};
7273
+ }
7274
+ this.data.columnSizing[columnName] = width;
7275
+ return this;
7276
+ }
7277
+ /**
7278
+ * Set widths for multiple columns
7279
+ * @example .columnWidths({ email: 200, name: 300, status: 100 })
7280
+ */
7281
+ columnWidths(widths) {
7282
+ this.data.columnSizing = { ...this.data.columnSizing, ...widths };
7283
+ return this;
7284
+ }
7285
+ /**
7286
+ * Set default filters
7287
+ * @example .filter({ combinator: "and", rules: [{ attribute: "status", operator: "is", value: "active" }] })
7288
+ */
7289
+ filter(filters) {
7290
+ this.data.defaultFilters = filters;
7291
+ return this;
7292
+ }
7293
+ /**
7294
+ * Add a single sort rule
7295
+ * @example .sort("lastName", "asc")
7296
+ */
7297
+ sort(attribute, direction = "asc") {
7298
+ if (!this.data.defaultSorts) {
7299
+ this.data.defaultSorts = [];
7300
+ }
7301
+ this.data.defaultSorts.push({ attribute, direction });
7302
+ return this;
7303
+ }
7304
+ /**
7305
+ * Set multiple sort rules
7306
+ * @example .sorts([{ attribute: "lastName", direction: "asc" }, { attribute: "firstName", direction: "asc" }])
7307
+ */
7308
+ sorts(rules) {
7309
+ this.data.defaultSorts = rules;
7310
+ return this;
7311
+ }
7312
+ /**
7313
+ * Add an internal tab (filter preset)
7314
+ * Returns a TabConfigBuilder for chaining tab configuration
7315
+ * @example
7316
+ * ```typescript
7317
+ * .tab("all", "All Contacts").icon("users").default()
7318
+ * .tab("active", "Active").icon("check").filter({ ... })
7319
+ * ```
7320
+ */
7321
+ tab(id, label) {
7322
+ return new ListViewTabConfigBuilder(this, id, label);
7323
+ }
7324
+ /**
7325
+ * @internal Used by ListViewTabConfigBuilder to add tabs
7326
+ */
7327
+ _addTab(tab) {
7328
+ this.data.tabs.push(tab);
7329
+ return this;
7330
+ }
7331
+ /**
7332
+ * Build the final list view definition
7333
+ */
7334
+ build() {
7335
+ if (!this.data.object) {
7336
+ throw new Error("[ListViewBuilder] for() is required - specify the target object");
7337
+ }
7338
+ if (this.data.columns.length === 0) {
7339
+ throw new Error("[ListViewBuilder] columns() is required - specify at least one column");
7340
+ }
7341
+ if (this.data.layout === "kanban" && !this.data.groupByAttribute) {
7342
+ throw new Error(
7343
+ "[ListViewBuilder] kanban() requires a groupByAttribute - specify the attribute to group by"
7344
+ );
7345
+ }
7346
+ const tabIds = /* @__PURE__ */ new Set();
7347
+ for (const tab of this.data.tabs) {
7348
+ if (tabIds.has(tab.id)) {
7349
+ throw new Error(`[ListViewBuilder] Duplicate tab id "${tab.id}"`);
7350
+ }
7351
+ tabIds.add(tab.id);
7352
+ }
7353
+ const defaultTabs = this.data.tabs.filter((t) => t.default);
7354
+ if (defaultTabs.length > 1) {
7355
+ throw new Error("[ListViewBuilder] Only one tab can be marked as default");
7356
+ }
7357
+ const config = {
7358
+ layout: this.data.layout,
7359
+ columns: this.data.columns,
7360
+ columnSizing: this.data.columnSizing,
7361
+ defaultFilters: this.data.defaultFilters ? {
7362
+ id: randomUUID(),
7363
+ combinator: this.data.defaultFilters.combinator,
7364
+ rules: this.data.defaultFilters.rules
7365
+ } : void 0,
7366
+ defaultSorts: this.data.defaultSorts,
7367
+ groupByAttribute: this.data.groupByAttribute,
7368
+ tabs: this.data.tabs.length > 0 ? this.data.tabs.map((tab) => ({
7369
+ id: tab.id,
7370
+ label: tab.label,
7371
+ icon: tab.icon,
7372
+ filters: tab.filters ? {
7373
+ id: randomUUID(),
7374
+ combinator: tab.filters.combinator,
7375
+ rules: tab.filters.rules
7376
+ } : void 0,
7377
+ default: tab.default
7378
+ })) : void 0
7379
+ };
7380
+ return {
7381
+ name: this.data.name,
7382
+ label: this.data.label,
7383
+ description: this.data.description,
7384
+ icon: this.data.icon,
7385
+ object: this.data.object,
7386
+ type: "list",
7387
+ config,
7388
+ default: this.data.default,
7389
+ metadata: this.data.metadata
7390
+ };
7391
+ }
7392
+ /**
7393
+ * Validate view name format (kebab-case)
7394
+ */
7395
+ validateName(name) {
7396
+ const viewNameSchema = z3.string().min(1, "View name cannot be empty").max(63, "View name is too long (max 63 characters)").regex(/^[a-z][a-z0-9-]*$/, {
7397
+ message: "Invalid view name format.\nName must be in kebab-case:\n \u2705 Valid: 'default', 'list-view', 'active-contacts'\n \u274C Invalid: 'Default', 'listView', 'list_view'"
7398
+ });
7399
+ try {
7400
+ viewNameSchema.parse(name);
7401
+ } catch (error2) {
7402
+ if (error2 instanceof z3.ZodError) {
7403
+ throw new Error(`[ListViewBuilder] ${error2.issues[0].message}`);
7404
+ }
7405
+ throw error2;
7406
+ }
7407
+ }
7408
+ };
7409
+ var ListViewTabConfigBuilder = class _ListViewTabConfigBuilder {
7410
+ /** @internal */
7411
+ constructor(view2, id, label) {
7412
+ this.view = view2;
7413
+ this.tabData = { id, label };
7414
+ }
7415
+ /**
7416
+ * Set tab icon
7417
+ */
7418
+ icon(value) {
7419
+ this.tabData.icon = value;
7420
+ return this;
7421
+ }
7422
+ /**
7423
+ * Set filters for this tab (filter preset)
7424
+ * @example .filter({ combinator: "and", rules: [{ attribute: "status", operator: "is", value: "active" }] })
7425
+ */
7426
+ filter(filters) {
7427
+ this.tabData.filters = filters;
7428
+ return this;
7429
+ }
7430
+ /**
7431
+ * Mark this tab as the default tab
7432
+ */
7433
+ default() {
7434
+ this.tabData.default = true;
7435
+ return this;
7436
+ }
7437
+ /**
7438
+ * Continue building with a new tab
7439
+ */
7440
+ tab(id, label) {
7441
+ this.view._addTab(this.tabData);
7442
+ return new _ListViewTabConfigBuilder(this.view, id, label);
7443
+ }
7444
+ /**
7445
+ * Finish this tab and return to ListViewBuilder
7446
+ */
7447
+ done() {
7448
+ this.view._addTab(this.tabData);
7449
+ return this.view;
7450
+ }
7451
+ /**
7452
+ * Build the final view definition
7453
+ */
7454
+ build() {
7455
+ this.view._addTab(this.tabData);
7456
+ return this.view.build();
7457
+ }
7458
+ };
7459
+ function listView(name, label) {
7460
+ return new ListViewBuilder(name, label);
7461
+ }
7462
+ function group(id, label) {
7463
+ return new GroupBuilder(id, label);
7464
+ }
7465
+
7466
+ // src/builders/workflow-builder.ts
7467
+ import { z as z4 } from "zod";
7468
+ var WorkflowFormRowBuilder = class {
7469
+ /** @internal */
7470
+ constructor(formBuilder, rowId, order) {
6960
7471
  this.formBuilder = formBuilder;
6961
7472
  this.rowData = {
6962
7473
  id: rowId,
@@ -7373,13 +7884,13 @@ var WorkflowBuilder = class {
7373
7884
  if (!this.startNodeId) {
7374
7885
  throw new Error("[WorkflowBuilder] A start node is required. Use .start() to add one.");
7375
7886
  }
7376
- if (!this.data.nodes || Object.keys(this.data.nodes).length === 0) {
7887
+ if (isEmpty(this.data.nodes)) {
7377
7888
  throw new Error("[WorkflowBuilder] At least one node is required.");
7378
7889
  }
7379
7890
  if (!this.data.slots || this.data.slots.length === 0) {
7380
7891
  throw new Error("[WorkflowBuilder] At least one slot is required. Use .slot() to add slots.");
7381
7892
  }
7382
- const hasEndNode = Object.values(this.data.nodes).some((n) => n.type === "end");
7893
+ const hasEndNode = this.data.nodes ? Object.values(this.data.nodes).some((n) => n.type === "end") : false;
7383
7894
  if (!hasEndNode) {
7384
7895
  throw new Error(
7385
7896
  "[WorkflowBuilder] At least one end node is required. Use .end() to add one."
@@ -7410,7 +7921,7 @@ var WorkflowBuilder = class {
7410
7921
  }
7411
7922
  }
7412
7923
  validateSlotReferences() {
7413
- const slotIds = new Set(this.data.slots?.map((s) => s.id) ?? []);
7924
+ const slotIds = this.data.slots?.reduce((set, s) => set.add(s.id), /* @__PURE__ */ new Set()) ?? /* @__PURE__ */ new Set();
7414
7925
  for (const node of Object.values(this.data.nodes ?? {})) {
7415
7926
  if (node.type === "form") {
7416
7927
  const referencedSlots = /* @__PURE__ */ new Set();
@@ -7492,6 +8003,30 @@ function workflow(name, label) {
7492
8003
  return new WorkflowBuilder(name, label);
7493
8004
  }
7494
8005
 
8006
+ // src/types/attribute-protection.ts
8007
+ var IDENTITY_PROPERTIES = ["name", "type"];
8008
+ var BEHAVIOR_PROPERTIES = [
8009
+ "required",
8010
+ "disabled",
8011
+ "hidden",
8012
+ "archived",
8013
+ "deprecated",
8014
+ "defaultValue",
8015
+ "config",
8016
+ "order",
8017
+ "unique"
8018
+ ];
8019
+ var PRESENTATION_PROPERTIES = ["label", "description", "placeholder", "icon"];
8020
+ function isIdentityProperty(property) {
8021
+ return IDENTITY_PROPERTIES.includes(property);
8022
+ }
8023
+ function isBehaviorProperty(property) {
8024
+ return BEHAVIOR_PROPERTIES.includes(property);
8025
+ }
8026
+ function isPresentationProperty(property) {
8027
+ return PRESENTATION_PROPERTIES.includes(property);
8028
+ }
8029
+
7495
8030
  // src/types/errors.ts
7496
8031
  var RecordReferencedError = class extends Error {
7497
8032
  constructor(recordId, references) {
@@ -7583,582 +8118,25 @@ var SYSTEM_ATTRIBUTES = {
7583
8118
  label: "Pi\xE8ces jointes",
7584
8119
  type: "document",
7585
8120
  required: false,
7586
- disabled: false,
7587
- // Editable via Documents tab
7588
- system: true,
7589
- hidden: true,
7590
- // Hidden from regular form views
7591
- multiple: true,
7592
- icon: "paperclip",
7593
- description: "Free-form document attachments"
7594
- // No templateId = all templates allowed
7595
- }
7596
- };
7597
- function getSystemAttributeList() {
7598
- return Object.values(SYSTEM_ATTRIBUTES);
7599
- }
7600
- function isSystemAttribute(name) {
7601
- return name in SYSTEM_ATTRIBUTES;
7602
- }
7603
- function isSystemAttributeObject(attr) {
7604
- return attr.system === true;
7605
- }
7606
-
7607
- // src/validation/validators.ts
7608
- import { z as z5 } from "zod";
7609
- var regexPatternCache = /* @__PURE__ */ new Map();
7610
- function getCachedRegex(pattern) {
7611
- let cached = regexPatternCache.get(pattern);
7612
- if (!cached) {
7613
- cached = new RegExp(pattern);
7614
- regexPatternCache.set(pattern, cached);
7615
- }
7616
- return cached;
7617
- }
7618
- var DEFAULT_VALIDATION_MESSAGES = {
7619
- required: (attr) => `${attr.label} is required`,
7620
- invalidType: (attr, expected) => `${attr.label} must be a ${expected}`,
7621
- minLength: (attr, min) => `${attr.label} must be at least ${min} characters`,
7622
- maxLength: (attr, max) => `${attr.label} must be at most ${max} characters`,
7623
- invalidPattern: (attr) => `${attr.label} format is invalid`,
7624
- minValue: (attr, min) => `${attr.label} must be at least ${min}`,
7625
- maxValue: (attr, max) => `${attr.label} must be at most ${max}`,
7626
- mustBeInteger: (attr) => `${attr.label} must be an integer`,
7627
- invalidDate: (attr) => `${attr.label} must be a valid date`,
7628
- invalidOption: (attr, options) => `${attr.label} must be one of: ${options.join(", ")}`,
7629
- invalidId: (attr) => `${attr.label} must be a valid ID`,
7630
- minItems: (attr, min) => `${attr.label} must have at least ${min} item${min > 1 ? "s" : ""}`,
7631
- maxItems: (attr, max) => `${attr.label} must have at most ${max} item${max > 1 ? "s" : ""}`,
7632
- invalidRichtext: (attr) => `${attr.label} must be valid rich text content`,
7633
- invalidPhone: (attr) => `${attr.label} must be a valid phone number`,
7634
- invalidCurrency: (attr) => `${attr.label} must be a valid currency value`,
7635
- invalidLocation: (attr) => `${attr.label} must be a valid location`
7636
- };
7637
- var baseConfigSchema = z5.object({
7638
- disabled: z5.boolean().optional(),
7639
- placeholder: z5.string().optional(),
7640
- description: z5.string().optional(),
7641
- defaultValue: z5.unknown().optional(),
7642
- icon: z5.string().optional(),
7643
- order: z5.number().int().optional(),
7644
- hidden: z5.boolean().optional(),
7645
- archived: z5.boolean().optional(),
7646
- deprecated: z5.boolean().optional(),
7647
- metadata: z5.record(z5.string(), z5.unknown()).optional()
7648
- });
7649
- var optionSchema = z5.object({
7650
- id: z5.string().min(1),
7651
- label: z5.string().min(1),
7652
- value: z5.string().min(1),
7653
- color: z5.string().optional(),
7654
- icon: z5.string().optional(),
7655
- description: z5.string().optional(),
7656
- group: z5.enum(["idle", "in_progress", "finished"]).optional()
7657
- });
7658
- var optionsArraySchema = z5.array(optionSchema).min(1).refine(
7659
- (options) => {
7660
- const values = options.map((o) => o.value);
7661
- return new Set(values).size === values.length;
7662
- },
7663
- { message: "Duplicate option values are not allowed" }
7664
- );
7665
- var relationTargetSchema = z5.object({
7666
- object: z5.string().min(1),
7667
- displayTemplate: z5.string().optional(),
7668
- filter: z5.record(z5.string(), z5.unknown()).optional()
7669
- });
7670
- var textConfigSchema = baseConfigSchema.extend({
7671
- minLength: z5.number().int().min(0).optional(),
7672
- maxLength: z5.number().int().min(1).optional(),
7673
- pattern: z5.string().optional()
7674
- });
7675
- var textareaConfigSchema = baseConfigSchema;
7676
- var richtextConfigSchema = baseConfigSchema.extend({
7677
- features: z5.array(
7678
- z5.enum(["headings", "bold", "italic", "lists", "links", "images", "codeBlocks", "tables"])
7679
- ).optional()
7680
- });
7681
- var numberConfigSchema = baseConfigSchema.extend({
7682
- min: z5.number().optional(),
7683
- max: z5.number().optional(),
7684
- unit: z5.enum(["integer", "decimal", "percentage"]).optional(),
7685
- decimals: z5.number().int().min(0).optional()
7686
- });
7687
- var checkboxConfigSchema = baseConfigSchema;
7688
- var dateConfigSchema = baseConfigSchema.extend({
7689
- dateFormat: z5.enum(["short", "long", "full", "relative"]).optional(),
7690
- minDate: z5.string().optional(),
7691
- maxDate: z5.string().optional()
7692
- });
7693
- var phoneConfigSchema = baseConfigSchema.extend({
7694
- defaultCountryCode: z5.string().length(3).optional()
7695
- });
7696
- var currencyConfigSchema = baseConfigSchema.extend({
7697
- defaultCurrency: z5.string().length(3).optional(),
7698
- allowedCurrencies: z5.array(z5.string().length(3)).optional()
7699
- });
7700
- var statusConfigSchema = baseConfigSchema.extend({
7701
- options: optionsArraySchema
7702
- });
7703
- var locationConfigSchema = baseConfigSchema.extend({
7704
- granularity: z5.enum(["full", "address", "city", "state", "country", "coordinates"]),
7705
- enableAutocomplete: z5.boolean().optional(),
7706
- enableMap: z5.boolean().optional(),
7707
- defaultCountry: z5.string().length(3).optional(),
7708
- allowedCountries: z5.array(z5.string().length(3)).optional(),
7709
- displayFormat: z5.enum(["single_line", "multi_line", "compact"]).optional()
7710
- });
7711
- var selectConfigSchema = baseConfigSchema.extend({
7712
- options: optionsArraySchema
7713
- });
7714
- var multiselectConfigSchema = baseConfigSchema.extend({
7715
- options: optionsArraySchema
7716
- });
7717
- var fileConfigSchema = baseConfigSchema.extend({
7718
- maxFiles: z5.number().int().min(1).optional(),
7719
- maxSize: z5.number().int().min(1).optional(),
7720
- allowedTypes: z5.array(z5.string()).optional(),
7721
- multiple: z5.boolean().optional()
7722
- });
7723
- var userConfigSchema = baseConfigSchema.extend({
7724
- allowedRoles: z5.array(z5.string()).optional(),
7725
- multiple: z5.boolean().optional()
7726
- });
7727
- var relationConfigSchema = baseConfigSchema.extend({
7728
- targets: z5.array(relationTargetSchema).min(1),
7729
- cardinality: z5.enum(["one", "many"]),
7730
- minItems: z5.number().int().min(0).optional(),
7731
- maxItems: z5.number().int().min(1).optional()
7732
- });
7733
- var ratingConfigSchema = baseConfigSchema.extend({
7734
- max: z5.number().int().min(1).optional(),
7735
- iconType: z5.enum(["star", "heart", "thumbs", "number"]).optional()
7736
- });
7737
- var formulaConfigSchema = baseConfigSchema.extend({
7738
- expression: z5.string().min(1),
7739
- returnType: z5.enum(["text", "number", "boolean", "date"]),
7740
- decimals: z5.number().int().min(0).max(10).optional(),
7741
- allowRelations: z5.boolean().optional()
7742
- });
7743
- var rollupConfigSchema = baseConfigSchema.extend({
7744
- relationAttribute: z5.string().min(1).optional(),
7745
- relationPath: z5.string().optional(),
7746
- targetAttribute: z5.string().min(1),
7747
- function: z5.enum([
7748
- // Numeric aggregates
7749
- "sum",
7750
- "avg",
7751
- // Date aggregates
7752
- "earliest",
7753
- "latest",
7754
- // Count (universal)
7755
- "count",
7756
- "countValues",
7757
- "countUniqueValues",
7758
- "countEmpty",
7759
- // Percent (universal)
7760
- "percentEmpty",
7761
- "percentNotEmpty",
7762
- // Lookup (universal)
7763
- "original"
7764
- ]),
7765
- decimals: z5.number().int().min(0).max(10).optional(),
7766
- targetAttributeType: z5.string().optional(),
7767
- targetAttributeOptions: z5.array(
7768
- z5.object({
7769
- id: z5.string(),
7770
- label: z5.string(),
7771
- value: z5.string(),
7772
- color: z5.string().optional(),
7773
- icon: z5.string().optional(),
7774
- description: z5.string().optional(),
7775
- group: z5.enum(["idle", "in_progress", "finished"]).optional()
7776
- })
7777
- ).optional()
7778
- });
7779
- var documentConfigSchema = baseConfigSchema.extend({
7780
- templateId: z5.string().optional(),
7781
- allowedTemplates: z5.array(z5.string()).optional(),
7782
- multiple: z5.boolean().optional(),
7783
- maxDocuments: z5.number().int().min(1).optional(),
7784
- autoProcess: z5.boolean().optional()
7785
- });
7786
- var attributeConfigSchemas = {
7787
- text: textConfigSchema,
7788
- textarea: textareaConfigSchema,
7789
- richtext: richtextConfigSchema,
7790
- number: numberConfigSchema,
7791
- checkbox: checkboxConfigSchema,
7792
- date: dateConfigSchema,
7793
- phone: phoneConfigSchema,
7794
- currency: currencyConfigSchema,
7795
- status: statusConfigSchema,
7796
- location: locationConfigSchema,
7797
- select: selectConfigSchema,
7798
- multiselect: multiselectConfigSchema,
7799
- file: fileConfigSchema,
7800
- user: userConfigSchema,
7801
- relation: relationConfigSchema,
7802
- rating: ratingConfigSchema,
7803
- formula: formulaConfigSchema,
7804
- rollup: rollupConfigSchema,
7805
- document: documentConfigSchema
7806
- };
7807
- function getAttributeConfigSchema(type) {
7808
- return attributeConfigSchemas[type];
7809
- }
7810
- function validateAttributeConfig(type, config) {
7811
- const schema = getAttributeConfigSchema(type);
7812
- const result = schema.safeParse(config);
7813
- if (result.success) {
7814
- return { success: true, data: result.data };
7815
- }
7816
- return {
7817
- success: false,
7818
- errors: result.error.issues.map((err) => `${err.path.join(".")}: ${err.message}`)
7819
- };
7820
- }
7821
- function parseAttributeConfig(type, config) {
7822
- const schema = getAttributeConfigSchema(type);
7823
- return schema.strip().parse(config);
7824
- }
7825
- function safeParseAttributeConfig(type, config) {
7826
- const schema = getAttributeConfigSchema(type);
7827
- const result = schema.strip().safeParse(config);
7828
- return result.success ? result.data : void 0;
7829
- }
7830
- function createTextValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7831
- let schema = z5.string();
7832
- if (attr.minLength !== void 0) {
7833
- schema = schema.min(attr.minLength, messages.minLength(attr, attr.minLength));
7834
- }
7835
- if (attr.maxLength !== void 0) {
7836
- schema = schema.max(attr.maxLength, messages.maxLength(attr, attr.maxLength));
7837
- }
7838
- if (attr.pattern) {
7839
- schema = schema.regex(getCachedRegex(attr.pattern), messages.invalidPattern(attr));
7840
- }
7841
- return schema;
7842
- }
7843
- function createNumberValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7844
- let schema = z5.number();
7845
- if (attr.min !== void 0) {
7846
- schema = schema.min(attr.min, messages.minValue(attr, attr.min));
7847
- }
7848
- if (attr.max !== void 0) {
7849
- schema = schema.max(attr.max, messages.maxValue(attr, attr.max));
7850
- }
7851
- if (attr.unit === "integer") {
7852
- schema = schema.int(messages.mustBeInteger(attr));
7853
- }
7854
- return schema;
7855
- }
7856
- function createCheckboxValidator(_attr, _messages = DEFAULT_VALIDATION_MESSAGES) {
7857
- return z5.boolean();
7858
- }
7859
- function createDateValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7860
- return z5.coerce.date({ message: messages.invalidDate(attr) });
7861
- }
7862
- function createPhoneValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7863
- return z5.object(
7864
- {
7865
- countryCode: z5.string().length(3),
7866
- phoneNumber: z5.string().min(1)
7867
- },
7868
- { message: messages.invalidPhone(attr) }
7869
- );
7870
- }
7871
- function createCurrencyValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7872
- return z5.object(
7873
- {
7874
- code: z5.string().length(3),
7875
- value: z5.number().min(0)
7876
- },
7877
- { message: messages.invalidCurrency(attr) }
7878
- );
7879
- }
7880
- function createStatusValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7881
- const validValues = attr.options.map((opt) => opt.value);
7882
- return z5.enum(validValues, {
7883
- message: messages.invalidOption(attr, validValues)
7884
- });
7885
- }
7886
- function createSelectValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7887
- const validValues = attr.options.map((opt) => opt.value);
7888
- return z5.enum(validValues, {
7889
- message: messages.invalidOption(attr, validValues)
7890
- });
7891
- }
7892
- function createMultiselectValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7893
- const validValues = attr.options.map((opt) => opt.value);
7894
- return z5.array(
7895
- z5.enum(validValues, {
7896
- message: messages.invalidOption(attr, validValues)
7897
- })
7898
- );
7899
- }
7900
- function createLocationValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7901
- return z5.object(
7902
- {
7903
- address: z5.string().optional(),
7904
- address2: z5.string().optional(),
7905
- city: z5.string().optional(),
7906
- state: z5.string().optional(),
7907
- postalCode: z5.string().optional(),
7908
- country: z5.string().length(3).optional(),
7909
- latitude: z5.number().optional(),
7910
- longitude: z5.number().optional()
7911
- },
7912
- { message: messages.invalidLocation(attr) }
7913
- );
7914
- }
7915
- function createFileValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7916
- const uuidSchema = z5.uuid({
7917
- message: messages.invalidId(attr)
7918
- });
7919
- if (attr.multiple) {
7920
- let arraySchema = z5.array(uuidSchema);
7921
- if (attr.maxFiles) {
7922
- arraySchema = arraySchema.max(attr.maxFiles, messages.maxItems(attr, attr.maxFiles));
7923
- }
7924
- return arraySchema;
7925
- }
7926
- return uuidSchema;
7927
- }
7928
- function createUserValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7929
- const uuidSchema = z5.uuid({
7930
- message: messages.invalidId(attr)
7931
- });
7932
- if (attr.multiple) {
7933
- return z5.array(uuidSchema);
7934
- }
7935
- return uuidSchema;
7936
- }
7937
- function createSingleRelationValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7938
- const uuidSchema = z5.uuid({
7939
- message: messages.invalidId(attr)
7940
- });
7941
- return z5.union([uuidSchema, z5.null()]);
7942
- }
7943
- function createMultiRelationValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7944
- const uuidSchema = z5.uuid({
7945
- message: messages.invalidId(attr)
7946
- });
7947
- let arraySchema = z5.array(uuidSchema);
7948
- if (attr.minItems !== void 0) {
7949
- arraySchema = arraySchema.min(attr.minItems, messages.minItems(attr, attr.minItems));
7950
- }
7951
- if (attr.maxItems !== void 0) {
7952
- arraySchema = arraySchema.max(attr.maxItems, messages.maxItems(attr, attr.maxItems));
7953
- }
7954
- return arraySchema;
7955
- }
7956
- function createRelationValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7957
- if (attr.cardinality === "many") {
7958
- return createMultiRelationValidator(attr, messages);
7959
- }
7960
- return createSingleRelationValidator(attr, messages);
7961
- }
7962
- function createRatingValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7963
- let schema = z5.number().min(0);
7964
- if (attr.max !== void 0) {
7965
- schema = schema.max(attr.max, messages.maxValue(attr, attr.max));
7966
- }
7967
- return schema;
7968
- }
7969
- function createFormulaValidator(_attr, _messages = DEFAULT_VALIDATION_MESSAGES) {
7970
- return z5.unknown();
7971
- }
7972
- function createRollupValidator(_attr, _messages = DEFAULT_VALIDATION_MESSAGES) {
7973
- return z5.unknown();
7974
- }
7975
- function createTextAreaValidator(_attr, _messages = DEFAULT_VALIDATION_MESSAGES) {
7976
- return z5.string();
7977
- }
7978
- function createRichtextValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7979
- return z5.string({
7980
- message: messages.invalidRichtext(attr)
7981
- });
7982
- }
7983
- function createAttributeValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
7984
- switch (attr.type) {
7985
- case "text":
7986
- return createTextValidator(attr, messages);
7987
- case "textarea":
7988
- return createTextAreaValidator(attr, messages);
7989
- case "richtext":
7990
- return createRichtextValidator(attr, messages);
7991
- case "number":
7992
- return createNumberValidator(attr, messages);
7993
- case "checkbox":
7994
- return createCheckboxValidator(attr, messages);
7995
- case "date":
7996
- return createDateValidator(attr, messages);
7997
- case "phone":
7998
- return createPhoneValidator(attr, messages);
7999
- case "currency":
8000
- return createCurrencyValidator(attr, messages);
8001
- case "status":
8002
- return createStatusValidator(attr, messages);
8003
- case "location":
8004
- return createLocationValidator(attr, messages);
8005
- case "select":
8006
- return createSelectValidator(attr, messages);
8007
- case "multiselect":
8008
- return createMultiselectValidator(attr, messages);
8009
- case "file":
8010
- return createFileValidator(attr, messages);
8011
- case "user":
8012
- return createUserValidator(attr, messages);
8013
- case "relation":
8014
- return createRelationValidator(attr, messages);
8015
- case "rating":
8016
- return createRatingValidator(attr, messages);
8017
- case "formula":
8018
- return createFormulaValidator(attr, messages);
8019
- case "rollup":
8020
- return createRollupValidator(attr, messages);
8021
- default:
8022
- return z5.unknown();
8023
- }
8024
- }
8025
- function isEmptyValue(value) {
8026
- if (value === null || value === void 0) return true;
8027
- if (typeof value === "string" && value.trim() === "") return true;
8028
- if (value instanceof Date) return false;
8029
- if (typeof value === "object" && !Array.isArray(value)) {
8030
- return Object.values(value).every(
8031
- (v) => v === null || v === void 0 || typeof v === "string" && v.trim() === ""
8032
- );
8033
- }
8034
- return false;
8035
- }
8036
- function withEmptyToNull(validator) {
8037
- return z5.preprocess((val) => isEmptyValue(val) ? null : val, validator.nullish());
8038
- }
8039
- function createFormAttributeValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
8040
- const validator = createAttributeValidator(attr, messages);
8041
- if (!attr.required) {
8042
- return withEmptyToNull(validator);
8043
- }
8044
- return validator;
8045
- }
8046
- function createObjectValidator(objectDef) {
8047
- const shape = {};
8048
- for (const attr of objectDef.attributes) {
8049
- const validator = createAttributeValidator(attr);
8050
- shape[attr.name] = attr.required ? validator : withEmptyToNull(validator);
8051
- }
8052
- return z5.object(shape).passthrough();
8053
- }
8054
- function validateAttribute(attr, value) {
8055
- const validator = createAttributeValidator(attr);
8056
- if (!attr.required && (value === void 0 || value === null)) {
8057
- return { success: true, data: { [attr.name]: value } };
8058
- }
8059
- const result = validator.safeParse(value);
8060
- if (result.success) {
8061
- return {
8062
- success: true,
8063
- data: { [attr.name]: result.data }
8064
- };
8065
- }
8066
- return {
8067
- success: false,
8068
- errors: result.error.issues.map((err) => ({
8069
- path: [attr.name, ...err.path.map(String)],
8070
- message: err.message
8071
- }))
8072
- };
8073
- }
8074
- function validateObject(objectDef, data) {
8075
- const validator = createObjectValidator(objectDef);
8076
- const result = validator.safeParse(data);
8077
- if (result.success) {
8078
- return {
8079
- success: true,
8080
- data: result.data
8081
- };
8082
- }
8083
- return {
8084
- success: false,
8085
- errors: result.error.issues.map((err) => ({
8086
- path: err.path.map(String),
8087
- message: err.message
8088
- }))
8089
- };
8090
- }
8091
- function validateObjectOrThrow(objectDef, data) {
8092
- const result = validateObject(objectDef, data);
8093
- if (!result.success) {
8094
- const errorMessages = result.errors?.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n") || "Unknown validation error";
8095
- throw new Error(`Validation failed for ${objectDef.label}:
8096
- ${errorMessages}`);
8097
- }
8098
- return result.data;
8099
- }
8100
- function createDraftValidator(objectDef) {
8101
- const shape = {};
8102
- for (const attr of objectDef.attributes) {
8103
- const validator = createAttributeValidator(attr);
8104
- shape[attr.name] = withEmptyToNull(validator);
8105
- }
8106
- return z5.object(shape).passthrough();
8107
- }
8108
- function validateDraft(objectDef, data) {
8109
- const validator = createDraftValidator(objectDef);
8110
- const result = validator.safeParse(data);
8111
- if (result.success) {
8112
- return {
8113
- success: true,
8114
- data: result.data
8115
- };
8116
- }
8117
- return {
8118
- success: false,
8119
- errors: result.error.issues.map((err) => ({
8120
- path: err.path.map(String),
8121
- message: err.message
8122
- }))
8123
- };
8124
- }
8125
- function validateDraftOrThrow(objectDef, data) {
8126
- const result = validateDraft(objectDef, data);
8127
- if (!result.success) {
8128
- const errorMessages = result.errors?.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n") || "Unknown validation error";
8129
- throw new Error(`Draft validation failed for ${objectDef.label}:
8130
- ${errorMessages}`);
8131
- }
8132
- return result.data;
8133
- }
8134
- function isValuePresent(value) {
8135
- if (value === void 0 || value === null) {
8136
- return false;
8137
- }
8138
- if (typeof value === "string" && value.trim() === "") {
8139
- return false;
8140
- }
8141
- return true;
8142
- }
8143
- function getMissingRequiredAttributes(objectDef, data) {
8144
- const missing = [];
8145
- for (const attr of objectDef.attributes) {
8146
- if (attr.required && !isValuePresent(data[attr.name])) {
8147
- missing.push(attr);
8148
- }
8121
+ disabled: false,
8122
+ // Editable via Documents tab
8123
+ system: true,
8124
+ hidden: true,
8125
+ // Hidden from regular form views
8126
+ multiple: true,
8127
+ icon: "paperclip",
8128
+ description: "Free-form document attachments"
8129
+ // No templateId = all templates allowed
8149
8130
  }
8150
- return missing;
8131
+ };
8132
+ function getSystemAttributeList() {
8133
+ return Object.values(SYSTEM_ATTRIBUTES);
8151
8134
  }
8152
- function isRecordComplete(objectDef, data) {
8153
- const missing = getMissingRequiredAttributes(objectDef, data);
8154
- if (missing.length > 0) {
8155
- return false;
8156
- }
8157
- const validation = validateObject(objectDef, data);
8158
- return validation.success;
8135
+ function isSystemAttribute(name) {
8136
+ return name in SYSTEM_ATTRIBUTES;
8159
8137
  }
8160
- function computeRecordStatus(objectDef, data) {
8161
- return isRecordComplete(objectDef, data) ? "complete" : "draft";
8138
+ function isSystemAttributeObject(attr) {
8139
+ return attr.system === true;
8162
8140
  }
8163
8141
 
8164
8142
  // src/runtime/services/audit/helpers.ts
@@ -8321,7 +8299,8 @@ var ObjectSchemaService = class extends BaseService {
8321
8299
  }
8322
8300
  /**
8323
8301
  * Update an attribute.
8324
- * Can only update custom attributes (system=false).
8302
+ * Custom attributes can be fully updated.
8303
+ * System attributes can only have presentation properties modified (label, description, placeholder, icon).
8325
8304
  * Automatically uses tenant context from AsyncLocalStorage.
8326
8305
  *
8327
8306
  * @param attributeId - Attribute UUID
@@ -8333,20 +8312,34 @@ var ObjectSchemaService = class extends BaseService {
8333
8312
  if (!dbAttr) {
8334
8313
  throw new Error(`Attribute with id "${attributeId}" not found`);
8335
8314
  }
8336
- if (dbAttr.system) {
8337
- throw new Error(
8338
- `Cannot modify system attribute "${dbAttr.name}". System attributes are protected.`
8339
- );
8340
- }
8341
- if (updates.name && updates.name !== dbAttr.name) {
8342
- throw new Error(
8343
- "Attribute name cannot be changed after creation. Create a new attribute instead."
8344
- );
8315
+ const requestedKeys = Object.keys(updates);
8316
+ const identityChanges = requestedKeys.filter(
8317
+ (k) => IDENTITY_PROPERTIES.includes(k)
8318
+ );
8319
+ if (identityChanges.length > 0) {
8320
+ const actualChanges = identityChanges.filter((k) => {
8321
+ const key = k;
8322
+ return updates[key] !== void 0 && updates[key] !== dbAttr[key];
8323
+ });
8324
+ if (actualChanges.length > 0) {
8325
+ throw new Error(
8326
+ `Cannot modify identity properties: ${actualChanges.join(", ")}. These properties cannot be changed after creation.`
8327
+ );
8328
+ }
8345
8329
  }
8346
- if (updates.type && updates.type !== dbAttr.type) {
8347
- throw new Error(
8348
- "Attribute type cannot be changed after creation. Create a new attribute instead."
8330
+ if (dbAttr.system) {
8331
+ const behaviorChanges = requestedKeys.filter(
8332
+ (k) => BEHAVIOR_PROPERTIES.includes(k)
8349
8333
  );
8334
+ const actualBehaviorChanges = behaviorChanges.filter((k) => {
8335
+ const key = k;
8336
+ return updates[key] !== void 0;
8337
+ });
8338
+ if (actualBehaviorChanges.length > 0) {
8339
+ throw new Error(
8340
+ `Cannot modify behavior properties on system attribute "${dbAttr.name}": ${actualBehaviorChanges.join(", ")}. Only presentation properties (${PRESENTATION_PROPERTIES.join(", ")}) are editable.`
8341
+ );
8342
+ }
8350
8343
  }
8351
8344
  const mergedInput = {
8352
8345
  name: dbAttr.name,
@@ -9657,7 +9650,7 @@ async function preloadSchemas(records, schemaService) {
9657
9650
  if (records.length === 0) {
9658
9651
  return schemasByObjectId;
9659
9652
  }
9660
- const uniqueObjectIds = [...new Set(records.map((r) => r.objectId))];
9653
+ const uniqueObjectIds = [...records.reduce((set, r) => set.add(r.objectId), /* @__PURE__ */ new Set())];
9661
9654
  await Promise.all(
9662
9655
  uniqueObjectIds.map(async (objId) => {
9663
9656
  const schema = await schemaService.getObjectSchema(objId);
@@ -10304,8 +10297,10 @@ var RelationService = class extends BaseService {
10304
10297
  const [attributeId, recordId] = c.split(":");
10305
10298
  return { compositeId: c, attributeId, recordId };
10306
10299
  });
10307
- const uniqueRecordIds = [...new Set(parsed.map((p) => p.recordId))];
10308
- const uniqueAttributeIds = [...new Set(parsed.map((p) => p.attributeId))];
10300
+ const uniqueRecordIds = [...parsed.reduce((set, p) => set.add(p.recordId), /* @__PURE__ */ new Set())];
10301
+ const uniqueAttributeIds = [
10302
+ ...parsed.reduce((set, p) => set.add(p.attributeId), /* @__PURE__ */ new Set())
10303
+ ];
10309
10304
  const records = await this.recordResolver.findByIds(uniqueRecordIds);
10310
10305
  if (records.length === 0) {
10311
10306
  return [];
@@ -10314,7 +10309,7 @@ var RelationService = class extends BaseService {
10314
10309
  const attributePromises = uniqueAttributeIds.map((id) => this.findAttributeById(id));
10315
10310
  const attributes = await Promise.all(attributePromises);
10316
10311
  const attributeMap = new Map(uniqueAttributeIds.map((id, i) => [id, attributes[i]]));
10317
- const uniqueObjectIds = [...new Set(records.map((r) => r.objectId))];
10312
+ const uniqueObjectIds = [...records.reduce((set, r) => set.add(r.objectId), /* @__PURE__ */ new Set())];
10318
10313
  const schemaPromises = uniqueObjectIds.map((id) => this.schemaService.getObjectSchema(id));
10319
10314
  const schemas = await Promise.all(schemaPromises);
10320
10315
  const schemaMap = new Map(uniqueObjectIds.map((id, i) => [id, schemas[i]]));
@@ -10567,7 +10562,10 @@ var RollupService = class extends BaseService {
10567
10562
  return values.filter((v) => v != null && v !== "").length;
10568
10563
  case "countUniqueValues": {
10569
10564
  const nonEmpty = values.filter((v) => v != null && v !== "");
10570
- return new Set(nonEmpty.map((v) => JSON.stringify(v))).size;
10565
+ return nonEmpty.reduce(
10566
+ (set, v) => set.add(JSON.stringify(v)),
10567
+ /* @__PURE__ */ new Set()
10568
+ ).size;
10571
10569
  }
10572
10570
  case "countEmpty":
10573
10571
  return values.filter((v) => v == null || v === "").length;
@@ -12231,7 +12229,6 @@ var WorkflowAccessGrantService = class extends BaseService {
12231
12229
  };
12232
12230
 
12233
12231
  // src/runtime/services/workflow/instance.service.ts
12234
- import { randomUUID } from "crypto";
12235
12232
  var WorkflowInstanceService = class extends BaseService {
12236
12233
  constructor(adapter, workflowService, options) {
12237
12234
  super(adapter);
@@ -12497,7 +12494,7 @@ var WorkflowInstanceService = class extends BaseService {
12497
12494
  updatedAt: /* @__PURE__ */ new Date()
12498
12495
  };
12499
12496
  }
12500
- const executionId = randomUUID();
12497
+ const executionId = generateId();
12501
12498
  let current = {
12502
12499
  ...instance,
12503
12500
  context: {
@@ -13407,7 +13404,7 @@ var WorkflowService = class extends BaseService {
13407
13404
  };
13408
13405
  const validationResult = WorkflowDefinitionSchema.safeParse(definition);
13409
13406
  if (!validationResult.success) {
13410
- const errors = validationResult.error.issues.map((i) => i.message);
13407
+ const errors = formatZodErrors(validationResult.error).map((err) => err.message);
13411
13408
  throw new SchemaError(
13412
13409
  `Invalid workflow definition: ${errors.join(", ")}`,
13413
13410
  SchemaErrorCode.VALIDATION_FAILED
@@ -13449,7 +13446,7 @@ var WorkflowService = class extends BaseService {
13449
13446
  };
13450
13447
  const validationResult = WorkflowDefinitionSchema.safeParse(updated);
13451
13448
  if (!validationResult.success) {
13452
- const errors = validationResult.error.issues.map((i) => i.message);
13449
+ const errors = formatZodErrors(validationResult.error).map((err) => err.message);
13453
13450
  throw new SchemaError(
13454
13451
  `Invalid workflow definition: ${errors.join(", ")}`,
13455
13452
  SchemaErrorCode.VALIDATION_FAILED
@@ -13478,7 +13475,7 @@ var WorkflowService = class extends BaseService {
13478
13475
  }
13479
13476
  const validationResult = WorkflowDefinitionSchema.safeParse(existing);
13480
13477
  if (!validationResult.success) {
13481
- const errors = validationResult.error.issues.map((i) => i.message);
13478
+ const errors = formatZodErrors(validationResult.error).map((err) => err.message);
13482
13479
  throw new SchemaError(
13483
13480
  `Cannot publish invalid workflow: ${errors.join(", ")}`,
13484
13481
  SchemaErrorCode.VALIDATION_FAILED
@@ -14563,7 +14560,7 @@ var DocumentService = class extends BaseService {
14563
14560
  const template = await this.templateService.getTemplateOrThrow(document2.templateId);
14564
14561
  const slots = await this.getSlots(documentId);
14565
14562
  const requiredSlots = template.slots.filter((s) => s.required);
14566
- const filledSlotNames = new Set(slots.map((s) => s.slotName));
14563
+ const filledSlotNames = slots.reduce((set, s) => set.add(s.slotName), /* @__PURE__ */ new Set());
14567
14564
  const allRequiredFilled = requiredSlots.every((s) => filledSlotNames.has(s.name));
14568
14565
  if (!allRequiredFilled) {
14569
14566
  return await this.updateStatus(documentId, "draft");
@@ -16137,7 +16134,7 @@ var PermissionService = class extends BaseService {
16137
16134
  DEFAULT_ROLE_PERMISSIONS
16138
16135
  } = await import("./default-roles-42X3TJI5.mjs");
16139
16136
  const existingRoles = await this.getRoles();
16140
- const existingRoleNames = new Set(existingRoles.map((r) => r.name));
16137
+ const existingRoleNames = existingRoles.reduce((set, r) => set.add(r.name), /* @__PURE__ */ new Set());
16141
16138
  for (const roleName of Object.values(DEFAULT_ROLES)) {
16142
16139
  if (existingRoleNames.has(roleName)) {
16143
16140
  continue;
@@ -16172,124 +16169,340 @@ var PermissionService = class extends BaseService {
16172
16169
 
16173
16170
  // src/runtime/services/view.service.ts
16174
16171
  var ViewService = class extends BaseService {
16175
- constructor(adapter, nativeViews) {
16172
+ constructor(adapter) {
16176
16173
  super(adapter);
16177
- this.nativeViews = nativeViews;
16178
16174
  }
16179
16175
  // ============================================================================
16180
16176
  // CACHE MANAGEMENT
16181
16177
  // ============================================================================
16182
- /**
16183
- * Invalidate cached views for an object.
16184
- * Called automatically after view mutations.
16185
- */
16186
16178
  async invalidateViewCache(objectName) {
16187
16179
  await this.invalidateCache(cacheKeys.viewsByObject(this.tenantId, objectName));
16188
16180
  }
16181
+ // ============================================================================
16182
+ // READ METHODS
16183
+ // ============================================================================
16189
16184
  /**
16190
- * Get all views for an object (native + custom).
16191
- * Automatically uses tenant context from AsyncLocalStorage.
16185
+ * Get all views for the current tenant.
16186
+ * Optionally filter by view type.
16192
16187
  *
16193
- * Results are cached if a CacheAdapter is configured.
16188
+ * @param type - Optional view type filter
16189
+ * @returns All views for the tenant
16190
+ */
16191
+ async getAllViews(type) {
16192
+ const dbViews = await this.adapter.views.findAllForTenant(type);
16193
+ return dbViews.map((v) => this.convertDBViewToDefinition(v));
16194
+ }
16195
+ /**
16196
+ * Get a specific view by its ID.
16197
+ * Returns null if not found.
16194
16198
  *
16195
- * @param objectName - Object name
16196
- * @returns All views for the object
16199
+ * @param viewId - View ID (UUID)
16200
+ * @returns View definition or null
16197
16201
  */
16198
- async getViewsForObject(objectName) {
16199
- return this.cachedBy("viewsByObject", objectName, () => this.fetchViewsForObject(objectName));
16202
+ async getViewById(viewId) {
16203
+ const dbView = await this.adapter.views.findById(viewId);
16204
+ if (!dbView) {
16205
+ return null;
16206
+ }
16207
+ return this.convertDBViewToDefinition(dbView);
16200
16208
  }
16201
16209
  /**
16202
- * Internal method to fetch views for an object (no caching)
16210
+ * Get all views for an object from the database.
16211
+ * Optionally filter by type and merge with user overlays.
16212
+ *
16213
+ * @param objectName - Object name
16214
+ * @param options - Filter and overlay options
16215
+ * @returns Views from database
16203
16216
  */
16204
- async fetchViewsForObject(objectName) {
16205
- const nativeViewDefs = this.nativeViews.getByObjectName(objectName);
16206
- const dbViews = await this.adapter.views.findByObjectName(objectName);
16207
- const customViews = dbViews.filter((v) => !v.system).map(this.convertDBViewToDefinition);
16208
- return [...nativeViewDefs, ...customViews];
16217
+ async getViews(objectName, options = {}) {
16218
+ const { type, userId } = options;
16219
+ const dbViews = await this.adapter.views.findByObjectName(objectName, type);
16220
+ const views = dbViews.map((v) => this.convertDBViewToDefinition(v));
16221
+ if (!userId) {
16222
+ return views;
16223
+ }
16224
+ const userOverlays = await this.adapter.viewOverlays.findByUser(userId);
16225
+ return views.map((view2) => {
16226
+ const overlay = userOverlays.find((o) => o.viewId === view2.id);
16227
+ return overlay ? this.applyOverlay(view2, overlay) : view2;
16228
+ });
16209
16229
  }
16210
16230
  /**
16211
16231
  * Get a specific view by name.
16212
- * Automatically uses tenant context from AsyncLocalStorage.
16232
+ * Returns null if not found (use getDefaultView for fallback behavior).
16213
16233
  *
16214
16234
  * @param objectName - Object name
16215
16235
  * @param viewName - View name
16216
- * @returns View definition or null
16217
- */
16218
- async getView(objectName, viewName) {
16219
- const nativeView = this.nativeViews.get(objectName, viewName);
16220
- if (nativeView) {
16221
- return nativeView;
16236
+ * @param options - Type filter and overlay options
16237
+ * @returns View or null
16238
+ */
16239
+ async getView(objectName, viewName, options = {}) {
16240
+ const { type, userId } = options;
16241
+ let dbView;
16242
+ if (type) {
16243
+ dbView = await this.adapter.views.findByNameAndType(objectName, viewName, type);
16244
+ } else {
16245
+ dbView = await this.adapter.views.findByName(objectName, viewName);
16246
+ }
16247
+ if (!dbView) {
16248
+ return null;
16222
16249
  }
16223
- const dbView = await this.adapter.views.findByName(objectName, viewName);
16224
- if (dbView) {
16225
- return this.convertDBViewToDefinition(dbView);
16250
+ const view2 = this.convertDBViewToDefinition(dbView);
16251
+ if (!userId) {
16252
+ return view2;
16226
16253
  }
16227
- return null;
16254
+ const overlay = await this.adapter.viewOverlays.findByViewAndUser(dbView.id, userId);
16255
+ return overlay ? this.applyOverlay(view2, overlay) : view2;
16228
16256
  }
16229
16257
  /**
16230
- * Get the default view for an object
16258
+ * Get the default view for an object and type.
16259
+ * If no view exists in DB, auto-creates and persists a default view.
16231
16260
  *
16232
16261
  * Priority:
16233
- * 1. Custom view marked as default (for the specified layout)
16234
- * 2. Native view marked as default (for the specified layout)
16235
- * 3. First available view (for the specified layout)
16262
+ * 1. User's preferred view (from overlay with isUserDefault=true)
16263
+ * 2. View marked as default in DB (with matching layout if specified)
16264
+ * 3. First available view (with matching layout if specified)
16265
+ * 4. Auto-created default view (persisted to DB)
16236
16266
  *
16237
16267
  * @param objectName - Object name
16238
- * @param layout - Optional layout filter ("page" or "modal")
16239
- * @returns Default view or null
16268
+ * @param type - View type
16269
+ * @param objectDefinition - Object definition (for default generation)
16270
+ * @param options - Optional filters (userId, layout for detail views)
16271
+ * @returns View definition (existing or auto-created)
16272
+ */
16273
+ async getDefaultView(objectName, type, objectDefinition, options) {
16274
+ const { userId, layout } = options ?? {};
16275
+ const matchesLayout = (view2) => {
16276
+ if (!layout || type !== "detail") return true;
16277
+ const detailView2 = view2;
16278
+ return detailView2.config.layout === layout;
16279
+ };
16280
+ const applyUserOverlay = async (view2, viewId) => {
16281
+ if (!userId) return view2;
16282
+ const overlay = await this.adapter.viewOverlays.findByViewAndUser(viewId, userId);
16283
+ return overlay ? this.applyOverlay(view2, overlay) : view2;
16284
+ };
16285
+ if (userId) {
16286
+ const userDefaultOverlay = await this.adapter.viewOverlays.findUserDefault(
16287
+ userId,
16288
+ objectName,
16289
+ type
16290
+ );
16291
+ if (userDefaultOverlay) {
16292
+ const dbView = await this.adapter.views.findById(userDefaultOverlay.viewId);
16293
+ if (dbView && dbView.type === type) {
16294
+ const view2 = this.convertDBViewToDefinition(dbView);
16295
+ if (matchesLayout(view2)) {
16296
+ return this.applyOverlay(view2, userDefaultOverlay);
16297
+ }
16298
+ }
16299
+ }
16300
+ }
16301
+ const defaultDbView = await this.adapter.views.findDefault(objectName, type);
16302
+ if (defaultDbView) {
16303
+ const view2 = this.convertDBViewToDefinition(defaultDbView);
16304
+ if (matchesLayout(view2)) {
16305
+ return await applyUserOverlay(view2, defaultDbView.id);
16306
+ }
16307
+ }
16308
+ const dbViews = await this.adapter.views.findByObjectName(objectName, type);
16309
+ for (const dbView of dbViews) {
16310
+ const view2 = this.convertDBViewToDefinition(dbView);
16311
+ if (matchesLayout(view2)) {
16312
+ return await applyUserOverlay(view2, dbView.id);
16313
+ }
16314
+ }
16315
+ if (dbViews.length > 0) {
16316
+ const firstView = dbViews[0];
16317
+ const view2 = this.convertDBViewToDefinition(firstView);
16318
+ return await applyUserOverlay(view2, firstView.id);
16319
+ }
16320
+ const created = await this.ensureDefaultView(objectName, type, objectDefinition, layout);
16321
+ if (userId) {
16322
+ if (!created.id) {
16323
+ throw new Error("Created view missing ID - this should never happen");
16324
+ }
16325
+ const overlay = await this.adapter.viewOverlays.findByViewAndUser(created.id, userId);
16326
+ return overlay ? this.applyOverlay(created, overlay) : created;
16327
+ }
16328
+ return created;
16329
+ }
16330
+ // ============================================================================
16331
+ // DEFAULT VIEW GENERATION
16332
+ // ============================================================================
16333
+ /**
16334
+ * Ensure a default view exists in DB for the given object and type.
16335
+ * If no view exists, generates and persists one.
16336
+ * Idempotent — safe to call concurrently (uses upsert).
16240
16337
  */
16241
- async getDefaultView(objectName, layout) {
16242
- const views = await this.getViewsForObject(objectName);
16243
- const filtered = layout ? views.filter((v) => (v.layout ?? "page") === layout) : views;
16244
- const customDefault = filtered.find((v) => v.default && !v.system);
16245
- if (customDefault) return customDefault;
16246
- const nativeDefault = filtered.find((v) => v.default && v.system);
16247
- if (nativeDefault) return nativeDefault;
16248
- return filtered[0] ?? null;
16338
+ async ensureDefaultView(objectName, type, objectDefinition, layout) {
16339
+ const generated = this.generateDefaultViewConfig(objectName, type, objectDefinition, layout);
16340
+ const viewName = type === "detail" && layout === "modal" ? "default-modal" : "default";
16341
+ const dbView = await this.adapter.views.upsert({
16342
+ objectName,
16343
+ type,
16344
+ name: viewName,
16345
+ label: generated.label,
16346
+ config: generated.config,
16347
+ default: true
16348
+ });
16349
+ const legacyFallbackId = `fallback:${objectName}:${type}`;
16350
+ await this.adapter.viewOverlays.migrateViewId(legacyFallbackId, dbView.id);
16351
+ await this.invalidateViewCache(objectName);
16352
+ return this.convertDBViewToDefinition(dbView);
16249
16353
  }
16250
16354
  /**
16251
- * Create a custom view.
16252
- * Automatically uses tenant context from AsyncLocalStorage.
16355
+ * Generate default view config for an object.
16356
+ * Used by ensureDefaultView() and resetViewToDefault().
16357
+ */
16358
+ generateDefaultViewConfig(objectName, type, objectDefinition, layout) {
16359
+ switch (type) {
16360
+ case "detail":
16361
+ return this.generateDefaultDetailConfig(objectName, objectDefinition, layout);
16362
+ case "list":
16363
+ return this.generateDefaultListConfig(objectName, objectDefinition);
16364
+ default:
16365
+ throw new SchemaError(
16366
+ `Default view generation not supported for type: ${type}`,
16367
+ SchemaErrorCode.VALIDATION_FAILED,
16368
+ { type }
16369
+ );
16370
+ }
16371
+ }
16372
+ generateDefaultDetailConfig(objectName, object2, layout = "page") {
16373
+ const visibleAttrs = object2.attributes.filter(
16374
+ (attr) => !(attr.hidden || attr.archived || attr.system)
16375
+ );
16376
+ const fields = visibleAttrs.sort((a, b) => (a.order ?? 999) - (b.order ?? 999)).map((attr) => ({
16377
+ attribute: attr.name,
16378
+ span: this.getDefaultFieldSpan(attr.type)
16379
+ }));
16380
+ const formTab = {
16381
+ id: "form",
16382
+ name: "form",
16383
+ label: "Details",
16384
+ type: "form",
16385
+ groups: [
16386
+ {
16387
+ id: "general",
16388
+ label: "General",
16389
+ fields,
16390
+ order: 0
16391
+ }
16392
+ ],
16393
+ order: 0
16394
+ };
16395
+ const tabs = [formTab];
16396
+ tabs.push({
16397
+ id: "activity",
16398
+ name: "activity",
16399
+ label: "Activity",
16400
+ type: "activity",
16401
+ order: 1
16402
+ });
16403
+ tabs.push({
16404
+ id: "notes",
16405
+ name: "notes",
16406
+ label: "Notes",
16407
+ type: "notes",
16408
+ order: 2,
16409
+ allowCreate: true
16410
+ });
16411
+ const hasDocuments = object2.attributes.some((attr) => attr.type === "document");
16412
+ if (hasDocuments) {
16413
+ tabs.push({
16414
+ id: "documents",
16415
+ name: "documents",
16416
+ label: "Documents",
16417
+ type: "documents",
16418
+ order: 3,
16419
+ allowUpload: true,
16420
+ allowRemove: true,
16421
+ showProcessing: true
16422
+ });
16423
+ }
16424
+ const config = {
16425
+ layout,
16426
+ tabs
16427
+ };
16428
+ return {
16429
+ name: "default",
16430
+ label: "Default View",
16431
+ object: objectName,
16432
+ type: "detail",
16433
+ config,
16434
+ default: true
16435
+ };
16436
+ }
16437
+ generateDefaultListConfig(objectName, object2) {
16438
+ const visibleAttrs = object2.attributes.filter(
16439
+ (attr) => !(attr.hidden || attr.archived || attr.system)
16440
+ );
16441
+ const columns = visibleAttrs.sort((a, b) => (a.order ?? 999) - (b.order ?? 999)).slice(0, 5).map((attr) => attr.name);
16442
+ const config = {
16443
+ layout: "table",
16444
+ columns
16445
+ };
16446
+ return {
16447
+ name: "default",
16448
+ label: "All Records",
16449
+ object: objectName,
16450
+ type: "list",
16451
+ config,
16452
+ default: true
16453
+ };
16454
+ }
16455
+ getDefaultFieldSpan(type) {
16456
+ switch (type) {
16457
+ case "textarea":
16458
+ case "richtext":
16459
+ case "location":
16460
+ return 12;
16461
+ case "checkbox":
16462
+ return 4;
16463
+ default:
16464
+ return 6;
16465
+ }
16466
+ }
16467
+ // ============================================================================
16468
+ // WRITE METHODS (Architect Mode)
16469
+ // ============================================================================
16470
+ /**
16471
+ * Create a new view (Architect Mode).
16253
16472
  *
16254
16473
  * @param input - View definition
16255
16474
  * @returns Created view
16256
16475
  */
16257
16476
  async createView(input) {
16258
16477
  this.validateViewName(input.name);
16259
- this.validateModalLayout(input.tabs, input.layout);
16260
- const existing = await this.adapter.views.findByName(input.objectName, input.name);
16478
+ const existing = await this.adapter.views.findByNameAndType(
16479
+ input.objectName,
16480
+ input.name,
16481
+ input.type
16482
+ );
16261
16483
  if (existing) {
16262
16484
  throw new SchemaError(
16263
- `View "${input.name}" already exists for object "${input.objectName}"`,
16264
- SchemaErrorCode.DUPLICATE_ATTRIBUTE,
16265
- { viewName: input.name, objectName: input.objectName }
16266
- );
16267
- }
16268
- if (this.nativeViews.has(input.objectName, input.name)) {
16269
- throw new SchemaError(
16270
- `Cannot create view "${input.name}": a system view with this name already exists`,
16485
+ `View "${input.name}" of type "${input.type}" already exists for object "${input.objectName}"`,
16271
16486
  SchemaErrorCode.DUPLICATE_ATTRIBUTE,
16272
- { viewName: input.name, objectName: input.objectName, system: true }
16487
+ { viewName: input.name, type: input.type, objectName: input.objectName }
16273
16488
  );
16274
16489
  }
16275
16490
  const dbView = await this.adapter.views.create({
16276
16491
  objectName: input.objectName,
16492
+ type: input.type,
16277
16493
  name: input.name,
16278
16494
  label: input.label,
16279
16495
  description: input.description,
16280
16496
  icon: input.icon,
16281
- layout: input.layout,
16282
- tabs: input.tabs ?? [],
16283
- default: input.default ?? false,
16284
- system: false,
16285
- // Custom views are never system
16497
+ config: input.config,
16498
+ default: input.default,
16286
16499
  metadata: input.metadata
16287
16500
  });
16288
16501
  await this.invalidateViewCache(input.objectName);
16289
16502
  return this.convertDBViewToDefinition(dbView);
16290
16503
  }
16291
16504
  /**
16292
- * Update a custom view
16505
+ * Update an existing view.
16293
16506
  *
16294
16507
  * @param viewId - View ID
16295
16508
  * @param input - Update data
@@ -16300,18 +16513,11 @@ var ViewService = class extends BaseService {
16300
16513
  if (!dbView) {
16301
16514
  throw new NotFoundError("View", viewId);
16302
16515
  }
16303
- if (dbView.system) {
16304
- throw new ProtectedResourceError("view", dbView.name, "modify");
16305
- }
16306
- const effectiveLayout = input.layout ?? dbView.layout;
16307
- const effectiveTabs = input.tabs ?? dbView.tabs;
16308
- this.validateModalLayout(effectiveTabs, effectiveLayout);
16309
16516
  const updated = await this.adapter.views.update(viewId, {
16310
16517
  label: input.label,
16311
16518
  description: input.description,
16312
16519
  icon: input.icon,
16313
- layout: input.layout,
16314
- tabs: input.tabs,
16520
+ config: input.config,
16315
16521
  default: input.default,
16316
16522
  metadata: input.metadata
16317
16523
  });
@@ -16319,7 +16525,8 @@ var ViewService = class extends BaseService {
16319
16525
  return this.convertDBViewToDefinition(updated);
16320
16526
  }
16321
16527
  /**
16322
- * Delete a custom view
16528
+ * Delete a view.
16529
+ * Overlays are automatically deleted (cascade).
16323
16530
  *
16324
16531
  * @param viewId - View ID
16325
16532
  */
@@ -16328,16 +16535,12 @@ var ViewService = class extends BaseService {
16328
16535
  if (!dbView) {
16329
16536
  throw new NotFoundError("View", viewId);
16330
16537
  }
16331
- if (dbView.system) {
16332
- throw new ProtectedResourceError("view", dbView.name, "delete");
16333
- }
16538
+ await this.adapter.viewOverlays.deleteByView(viewId);
16334
16539
  await this.adapter.views.delete(viewId);
16335
16540
  await this.invalidateViewCache(dbView.objectName);
16336
16541
  }
16337
16542
  /**
16338
- * Set a view as default for its object and layout.
16339
- * Only unsets other defaults for the same layout.
16340
- * Automatically uses tenant context from AsyncLocalStorage.
16543
+ * Set a view as default for its object and type.
16341
16544
  *
16342
16545
  * @param viewId - View ID
16343
16546
  * @returns Updated view
@@ -16347,11 +16550,9 @@ var ViewService = class extends BaseService {
16347
16550
  if (!dbView) {
16348
16551
  throw new NotFoundError("View", viewId);
16349
16552
  }
16350
- const viewLayout = dbView.layout ?? "page";
16351
- const currentViews = await this.adapter.views.findByObjectName(dbView.objectName);
16553
+ const currentViews = await this.adapter.views.findByObjectName(dbView.objectName, dbView.type);
16352
16554
  for (const v of currentViews) {
16353
- const vLayout = v.layout ?? "page";
16354
- if (v.default && v.id !== viewId && !v.system && vLayout === viewLayout) {
16555
+ if (v.default && v.id !== viewId) {
16355
16556
  await this.adapter.views.update(v.id, { default: false });
16356
16557
  }
16357
16558
  }
@@ -16359,12 +16560,140 @@ var ViewService = class extends BaseService {
16359
16560
  await this.invalidateViewCache(dbView.objectName);
16360
16561
  return this.convertDBViewToDefinition(updated);
16361
16562
  }
16563
+ /**
16564
+ * Reset a view to its default (auto-generated) state.
16565
+ * Regenerates the view config based on the object definition.
16566
+ *
16567
+ * @param viewId - View ID
16568
+ * @param objectDefinition - Object definition for regeneration
16569
+ * @returns Updated view
16570
+ */
16571
+ async resetViewToDefault(viewId, objectDefinition) {
16572
+ const dbView = await this.adapter.views.findById(viewId);
16573
+ if (!dbView) {
16574
+ throw new NotFoundError("View", viewId);
16575
+ }
16576
+ const generated = this.generateDefaultViewConfig(
16577
+ dbView.objectName,
16578
+ dbView.type,
16579
+ objectDefinition,
16580
+ dbView.type === "detail" ? dbView.config?.layout ?? "page" : void 0
16581
+ );
16582
+ const newConfig = generated.config;
16583
+ const updated = await this.adapter.views.update(viewId, { config: newConfig });
16584
+ await this.invalidateViewCache(dbView.objectName);
16585
+ return this.convertDBViewToDefinition(updated);
16586
+ }
16362
16587
  // ============================================================================
16363
- // PRIVATE HELPERS
16588
+ // USER CUSTOMIZATION METHODS
16364
16589
  // ============================================================================
16365
16590
  /**
16366
- * Validate view name format (kebab-case)
16591
+ * Reset user customizations for a view.
16592
+ * Deletes the overlay, returning to source/fallback view.
16593
+ *
16594
+ * @param viewId - View ID (can be UUID or fallback ID)
16595
+ * @param userId - User ID
16596
+ */
16597
+ async resetUserCustomizations(viewId, userId) {
16598
+ await this.adapter.viewOverlays.deleteByViewAndUser(viewId, userId);
16599
+ }
16600
+ /**
16601
+ * Set a view as the user's default for an object and type.
16602
+ *
16603
+ * @param viewId - View ID (can be UUID or fallback ID)
16604
+ * @param userId - User ID
16605
+ * @param objectName - Object name
16606
+ * @param type - View type
16607
+ */
16608
+ async setUserDefaultView(viewId, userId, objectName, type) {
16609
+ await this.adapter.viewOverlays.clearUserDefault(userId, objectName, type);
16610
+ const existingOverlay = await this.adapter.viewOverlays.findByViewAndUser(viewId, userId);
16611
+ if (existingOverlay) {
16612
+ await this.adapter.viewOverlays.update(existingOverlay.id, { isUserDefault: true });
16613
+ } else {
16614
+ await this.adapter.viewOverlays.create({
16615
+ viewId,
16616
+ userId,
16617
+ configOverrides: {},
16618
+ isUserDefault: true
16619
+ });
16620
+ }
16621
+ }
16622
+ /**
16623
+ * Check if a user has customized a view.
16624
+ *
16625
+ * @param viewId - View ID
16626
+ * @param userId - User ID
16627
+ * @returns True if overlay exists
16628
+ */
16629
+ async hasUserCustomizations(viewId, userId) {
16630
+ const overlay = await this.adapter.viewOverlays.findByViewAndUser(viewId, userId);
16631
+ return overlay !== null && hasProperties(overlay.configOverrides);
16632
+ }
16633
+ // ============================================================================
16634
+ // OVERLAY MERGE LOGIC
16635
+ // ============================================================================
16636
+ /**
16637
+ * Apply an overlay to a view definition.
16638
+ * Implements merge semantics defined in the plan.
16639
+ *
16640
+ * @param view - Source view definition
16641
+ * @param overlay - User overlay
16642
+ * @returns Merged view definition
16643
+ */
16644
+ applyOverlay(view2, overlay) {
16645
+ const overrides = overlay.configOverrides;
16646
+ switch (view2.type) {
16647
+ case "list":
16648
+ return this.applyListViewOverlay(view2, overrides);
16649
+ case "detail":
16650
+ return this.applyDetailViewOverlay(view2, overrides);
16651
+ default:
16652
+ return view2;
16653
+ }
16654
+ }
16655
+ applyListViewOverlay(view2, overrides) {
16656
+ const mergedConfig = {
16657
+ ...view2.config,
16658
+ tabs: this.mergeViewTabs(view2.config.tabs, overrides.tabs, overrides.hiddenTabIds)
16659
+ };
16660
+ return { ...view2, config: mergedConfig };
16661
+ }
16662
+ applyDetailViewOverlay(view2, overrides) {
16663
+ const mergedConfig = {
16664
+ ...view2.config,
16665
+ // Append for tabs (detail tabs)
16666
+ tabs: this.mergeDetailTabs(
16667
+ view2.config.tabs,
16668
+ overrides.detailTabs,
16669
+ overrides.hiddenDetailTabIds
16670
+ )
16671
+ };
16672
+ return {
16673
+ ...view2,
16674
+ config: mergedConfig
16675
+ };
16676
+ }
16677
+ /**
16678
+ * Merge source tabs with overlay tabs.
16679
+ * - Source tabs are visible to all
16680
+ * - Overlay tabs are appended (user-private)
16681
+ * - hiddenTabIds allows hiding source tabs
16682
+ */
16683
+ mergeViewTabs(sourceTabs = [], overlayTabs = [], hiddenTabIds = []) {
16684
+ const visibleSourceTabs = sourceTabs.filter((t) => !hiddenTabIds.includes(t.id));
16685
+ return [...visibleSourceTabs, ...overlayTabs];
16686
+ }
16687
+ /**
16688
+ * Merge detail tabs (form, activity, etc.)
16367
16689
  */
16690
+ mergeDetailTabs(sourceTabs = [], overlayTabs = [], hiddenTabIds = []) {
16691
+ const visibleSourceTabs = sourceTabs.filter((t) => !hiddenTabIds.includes(t.id));
16692
+ return [...visibleSourceTabs, ...overlayTabs];
16693
+ }
16694
+ // ============================================================================
16695
+ // PRIVATE HELPERS
16696
+ // ============================================================================
16368
16697
  validateViewName(name) {
16369
16698
  if (!name || name.length === 0) {
16370
16699
  throw new ValidationError("View name cannot be empty", [
@@ -16386,66 +16715,72 @@ var ViewService = class extends BaseService {
16386
16715
  ]);
16387
16716
  }
16388
16717
  }
16389
- /**
16390
- * Validate modal layout constraints.
16391
- * Modal views must have exactly one form tab.
16392
- */
16393
- validateModalLayout(tabs, layout) {
16394
- if (layout !== "modal") return;
16395
- if (!tabs || tabs.length === 0) {
16396
- throw new ValidationError("Modal views must have exactly one form tab", [
16397
- { path: ["tabs"], message: "Modal views must have exactly one form tab" }
16398
- ]);
16399
- }
16400
- if (tabs.length > 1) {
16401
- throw new ValidationError("Modal views can only have one tab", [
16402
- { path: ["tabs"], message: "Modal views can only have one tab" }
16403
- ]);
16404
- }
16405
- if (tabs[0].type !== "form") {
16406
- throw new ValidationError("Modal views must have a form tab", [
16407
- { path: ["tabs"], message: `Modal views must have a form tab, not a ${tabs[0].type}` }
16408
- ]);
16409
- }
16410
- }
16411
16718
  /**
16412
16719
  * Convert database view to ViewDefinition
16413
16720
  */
16414
16721
  convertDBViewToDefinition(dbView) {
16415
- return {
16722
+ const base = {
16416
16723
  id: dbView.id,
16417
16724
  name: dbView.name,
16418
16725
  label: dbView.label,
16419
16726
  description: dbView.description,
16420
16727
  icon: dbView.icon,
16421
16728
  object: dbView.objectName,
16422
- layout: dbView.layout,
16423
- tabs: dbView.tabs,
16424
16729
  default: dbView.default,
16425
- system: dbView.system,
16426
16730
  metadata: dbView.metadata
16427
16731
  };
16732
+ switch (dbView.type) {
16733
+ case "detail": {
16734
+ const detailView2 = {
16735
+ ...base,
16736
+ type: "detail",
16737
+ config: dbView.config
16738
+ };
16739
+ return detailView2;
16740
+ }
16741
+ case "list": {
16742
+ const listView2 = {
16743
+ ...base,
16744
+ type: "list",
16745
+ config: dbView.config
16746
+ };
16747
+ return listView2;
16748
+ }
16749
+ // TODO: Add proper config types for calendar, timeline, gallery when implemented
16750
+ case "calendar":
16751
+ case "timeline":
16752
+ case "gallery":
16753
+ return { ...base, type: dbView.type, config: dbView.config };
16754
+ default:
16755
+ throw new SchemaError(
16756
+ `Unknown view type: ${dbView.type}`,
16757
+ SchemaErrorCode.VALIDATION_FAILED,
16758
+ { type: dbView.type }
16759
+ );
16760
+ }
16428
16761
  }
16429
16762
  };
16430
16763
 
16431
16764
  // src/runtime/view-sync.ts
16432
- async function syncNativeViews(adapter, nativeViewRegistry, options = {}) {
16765
+ async function seedRegistryViews(adapter, registry2, options = {}) {
16433
16766
  const result = {
16434
16767
  success: true,
16435
16768
  viewsSynced: 0,
16436
16769
  viewsCreated: 0,
16437
- viewsUpdated: 0,
16770
+ viewsSkipped: 0,
16438
16771
  viewsDeleted: 0,
16439
16772
  errors: []
16440
16773
  };
16441
- const nativeViews = nativeViewRegistry.getAll();
16774
+ const registryViews = registry2.getAll();
16442
16775
  if (options.verbose && options.logger) {
16443
- options.logger.info(`[ViewSync] Starting sync for ${nativeViews.length} native views...`);
16776
+ options.logger.info(`[ViewSync] Starting seed for ${registryViews.length} registry views...`);
16444
16777
  }
16445
16778
  try {
16446
16779
  await adapter.transaction(async (tx) => {
16447
- const viewsByObject = await syncAllViews(tx, nativeViews, result, options);
16448
- await cleanupRemovedViews(tx, viewsByObject, result, options);
16780
+ const viewsByObjectAndType = await seedAllViews(tx, registryViews, result, options);
16781
+ if (options.deleteOrphans) {
16782
+ await cleanupOrphanViews(tx, viewsByObjectAndType, result, options);
16783
+ }
16449
16784
  });
16450
16785
  } catch (error2) {
16451
16786
  handleTransactionError(result, error2);
@@ -16453,38 +16788,78 @@ async function syncNativeViews(adapter, nativeViewRegistry, options = {}) {
16453
16788
  logSyncComplete(result, options);
16454
16789
  return result;
16455
16790
  }
16456
- async function syncAllViews(tx, nativeViews, result, options) {
16457
- const viewsByObject = /* @__PURE__ */ new Map();
16458
- for (const nativeView of nativeViews) {
16791
+ var syncNativeViews = seedRegistryViews;
16792
+ async function seedAllViews(tx, registryViews, result, options) {
16793
+ const viewsByObjectAndType = /* @__PURE__ */ new Map();
16794
+ for (const view2 of registryViews) {
16459
16795
  try {
16460
- await syncSingleView(tx, nativeView, result, options);
16461
- trackViewByObject(viewsByObject, nativeView);
16796
+ await seedSingleView(tx, view2, result, options);
16797
+ trackViewByObjectAndType(viewsByObjectAndType, view2);
16462
16798
  } catch (error2) {
16463
- handleViewSyncError(result, nativeView, error2, options);
16799
+ handleViewSyncError(result, view2, error2);
16800
+ }
16801
+ }
16802
+ return viewsByObjectAndType;
16803
+ }
16804
+ async function seedSingleView(adapter, view2, result, options) {
16805
+ const exists = await adapter.views.exists(view2.object, view2.name, view2.type);
16806
+ if (exists) {
16807
+ result.viewsSkipped++;
16808
+ result.viewsSynced++;
16809
+ if (options.verbose && options.logger) {
16810
+ options.logger.info(`[ViewSync] Skipped (exists): ${view2.object}:${view2.name}:${view2.type}`);
16811
+ }
16812
+ return;
16813
+ }
16814
+ if (options.dryRun) {
16815
+ result.viewsCreated++;
16816
+ result.viewsSynced++;
16817
+ if (options.verbose && options.logger) {
16818
+ options.logger.info(`[ViewSync] Would create: ${view2.object}:${view2.name}:${view2.type}`);
16464
16819
  }
16820
+ return;
16821
+ }
16822
+ await adapter.views.upsert({
16823
+ objectName: view2.object,
16824
+ type: view2.type,
16825
+ name: view2.name,
16826
+ label: view2.label,
16827
+ description: view2.description,
16828
+ icon: view2.icon,
16829
+ config: view2.config,
16830
+ default: view2.default ?? false,
16831
+ metadata: view2.metadata
16832
+ });
16833
+ result.viewsCreated++;
16834
+ result.viewsSynced++;
16835
+ if (options.verbose && options.logger) {
16836
+ options.logger.info(`[ViewSync] \u2713 Created: ${view2.object}:${view2.name}:${view2.type}`);
16465
16837
  }
16466
- return viewsByObject;
16467
16838
  }
16468
- function trackViewByObject(viewsByObject, nativeView) {
16469
- const objectViews = viewsByObject.get(nativeView.object) ?? [];
16470
- objectViews.push(nativeView.name);
16471
- viewsByObject.set(nativeView.object, objectViews);
16839
+ function trackViewByObjectAndType(viewsByObjectAndType, view2) {
16840
+ const key = `${view2.object}:${view2.type}`;
16841
+ const viewNames = viewsByObjectAndType.get(key) ?? [];
16842
+ viewNames.push(view2.name);
16843
+ viewsByObjectAndType.set(key, viewNames);
16472
16844
  }
16473
- function handleViewSyncError(result, nativeView, error2, _options) {
16845
+ function handleViewSyncError(result, view2, error2) {
16474
16846
  result.success = false;
16475
16847
  result.errors.push({
16476
- viewName: nativeView.name,
16477
- objectName: nativeView.object,
16848
+ viewName: view2.name,
16849
+ objectName: view2.object,
16478
16850
  error: error2 instanceof Error ? error2.message : String(error2)
16479
16851
  });
16480
16852
  }
16481
- async function cleanupRemovedViews(tx, viewsByObject, result, options) {
16853
+ async function cleanupOrphanViews(tx, viewsByObjectAndType, result, options) {
16482
16854
  if (options.dryRun) return;
16483
- for (const [objectName, viewNames] of viewsByObject) {
16484
- const deletedCount = await tx.views.deleteNotIn(objectName, viewNames);
16855
+ for (const [key, viewNames] of viewsByObjectAndType) {
16856
+ const [objectName, type] = key.split(":");
16857
+ const deletedCount = await tx.views.deleteNotIn(objectName, type, viewNames);
16485
16858
  result.viewsDeleted += deletedCount;
16486
16859
  if (options.verbose && deletedCount > 0 && options.logger) {
16487
- options.logger.info(`[ViewSync] Deleted ${deletedCount} obsolete views for ${objectName}`);
16860
+ options.logger.info(
16861
+ `[ViewSync] Deleted ${deletedCount} orphan ${type} views for ${objectName}`
16862
+ );
16488
16863
  }
16489
16864
  }
16490
16865
  }
@@ -16499,63 +16874,27 @@ function handleTransactionError(result, error2) {
16499
16874
  function logSyncComplete(result, options) {
16500
16875
  if (options.verbose && options.logger) {
16501
16876
  options.logger.info(
16502
- `[ViewSync] ${result.success ? "\u2713" : "\u2717"} Sync complete:
16503
- Views: ${result.viewsCreated} created, ${result.viewsUpdated} updated, ${result.viewsDeleted} deleted
16877
+ `[ViewSync] ${result.success ? "\u2713" : "\u2717"} Seed complete:
16878
+ Views: ${result.viewsCreated} created, ${result.viewsSkipped} skipped, ${result.viewsDeleted} deleted
16504
16879
  Errors: ${result.errors.length}`
16505
16880
  );
16506
16881
  }
16507
16882
  }
16508
- async function syncSingleView(adapter, nativeView, result, options) {
16509
- if (!nativeView.system) {
16510
- throw new Error(`View ${nativeView.name} is not marked as system`);
16511
- }
16512
- const existingView = await adapter.views.findSystemByName(nativeView.object, nativeView.name);
16513
- const isNew = !existingView;
16514
- if (isNew) {
16515
- result.viewsCreated++;
16516
- } else {
16517
- result.viewsUpdated++;
16518
- }
16519
- result.viewsSynced++;
16520
- if (options.dryRun) {
16521
- if (options.verbose && options.logger) {
16522
- options.logger.info(
16523
- `[ViewSync] Would ${isNew ? "create" : "update"} view: ${nativeView.object}:${nativeView.name}`
16524
- );
16525
- }
16526
- return;
16527
- }
16528
- await adapter.views.upsert({
16529
- objectName: nativeView.object,
16530
- name: nativeView.name,
16531
- label: nativeView.label,
16532
- description: nativeView.description,
16533
- icon: nativeView.icon,
16534
- tabs: nativeView.tabs,
16535
- default: nativeView.default ?? false,
16536
- system: true,
16537
- metadata: nativeView.metadata
16538
- });
16539
- if (options.verbose && options.logger) {
16540
- options.logger.info(
16541
- `[ViewSync] \u2713 Synced ${nativeView.object}:${nativeView.name}: ${nativeView.tabs.length} tabs`
16542
- );
16543
- }
16544
- }
16545
- async function verifyNativeViewsSync(adapter, nativeViewRegistry) {
16546
- const nativeViews = nativeViewRegistry.getAll();
16547
- for (const view2 of nativeViews) {
16548
- if (!view2.system) continue;
16549
- const existing = await adapter.views.findSystemByName(view2.object, view2.name);
16550
- if (!existing) {
16883
+ async function verifyRegistryViewsSeeded(adapter, registry2) {
16884
+ const registryViews = registry2.getAll();
16885
+ for (const view2 of registryViews) {
16886
+ const exists = await adapter.views.exists(view2.object, view2.name, view2.type);
16887
+ if (!exists) {
16551
16888
  return false;
16552
16889
  }
16553
16890
  }
16554
16891
  return true;
16555
16892
  }
16556
- async function getViewSyncPreview(adapter, nativeViewRegistry) {
16557
- return await syncNativeViews(adapter, nativeViewRegistry, { dryRun: true });
16893
+ var verifyNativeViewsSync = verifyRegistryViewsSeeded;
16894
+ async function getViewSeedPreview(adapter, registry2) {
16895
+ return await seedRegistryViews(adapter, registry2, { dryRun: true });
16558
16896
  }
16897
+ var getViewSyncPreview = getViewSeedPreview;
16559
16898
 
16560
16899
  // src/runtime/sync.ts
16561
16900
  var BASE_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
@@ -16664,8 +17003,9 @@ async function handleDryRun(adapter, nativeObject, existingObject, result, optio
16664
17003
  if (options.verbose) {
16665
17004
  console.info(`[SyncService] Would ${isNew ? "create" : "update"} object: ${nativeObject.name}`);
16666
17005
  }
17006
+ const existingAttrs = existingObject ? await adapter.attributes.findByObjectId(existingObject.id) : [];
16667
17007
  for (const attr of nativeObject.attributes) {
16668
- const existingAttr = existingObject ? await adapter.attributes.findByObjectId(existingObject.id).then((attrs) => attrs.find((a) => a.name === attr.name)) : null;
17008
+ const existingAttr = existingAttrs.find((a) => a.name === attr.name) ?? null;
16669
17009
  if (existingAttr) {
16670
17010
  result.attributesUpdated++;
16671
17011
  } else {
@@ -16687,8 +17027,9 @@ async function upsertObject(adapter, nativeObject, _options) {
16687
17027
  });
16688
17028
  }
16689
17029
  async function syncAttributes(adapter, nativeObject, dbObject, existingObject, result) {
17030
+ const existingAttrs = existingObject ? await adapter.attributes.findByObjectId(dbObject.id) : [];
16690
17031
  for (const [index, attr] of nativeObject.attributes.entries()) {
16691
- const existingAttr = existingObject ? await adapter.attributes.findByObjectId(dbObject.id).then((attrs) => attrs.find((a) => a.name === attr.name)) : null;
17032
+ const existingAttr = existingAttrs.find((a) => a.name === attr.name) ?? null;
16692
17033
  await adapter.attributes.upsert({
16693
17034
  objectId: dbObject.id,
16694
17035
  name: attr.name,
@@ -16755,7 +17096,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
16755
17096
  console.info(
16756
17097
  `[SyncAll] ${result.success ? "\u2713" : "\u2717"} Full sync complete:
16757
17098
  Objects: ${objectsResult.objectsSynced} synced (${objectsResult.objectsCreated} created, ${objectsResult.objectsUpdated} updated)
16758
- Views: ${viewsResult.viewsSynced} synced (${viewsResult.viewsCreated} created, ${viewsResult.viewsUpdated} updated)
17099
+ Views: ${viewsResult.viewsSynced} synced (${viewsResult.viewsCreated} created, ${viewsResult.viewsSkipped} skipped)
16759
17100
  Errors: ${objectsResult.errors.length + viewsResult.errors.length}`
16760
17101
  );
16761
17102
  }
@@ -16776,6 +17117,12 @@ var NoopGeocodingAdapter = class {
16776
17117
  };
16777
17118
 
16778
17119
  export {
17120
+ IDENTITY_PROPERTIES,
17121
+ BEHAVIOR_PROPERTIES,
17122
+ PRESENTATION_PROPERTIES,
17123
+ isIdentityProperty,
17124
+ isBehaviorProperty,
17125
+ isPresentationProperty,
16779
17126
  RELATION_TARGET_ANY,
16780
17127
  isUniversalRelation,
16781
17128
  RecordReferencedError,
@@ -16804,8 +17151,6 @@ export {
16804
17151
  and,
16805
17152
  or,
16806
17153
  inValues,
16807
- isEmpty,
16808
- isNotEmpty,
16809
17154
  isWorkflowDefinition,
16810
17155
  isWorkflowPublished,
16811
17156
  isSystemWorkflow,
@@ -16824,7 +17169,6 @@ export {
16824
17169
  createEmptyContext,
16825
17170
  getContextValue,
16826
17171
  setContextValue,
16827
- mergeFormToSlot,
16828
17172
  DEFAULT_THEME,
16829
17173
  mergeWithDefaults,
16830
17174
  generateCssVariables,
@@ -16858,12 +17202,10 @@ export {
16858
17202
  WorkflowConfigSchema,
16859
17203
  WorkflowStatusSchema,
16860
17204
  WorkflowDefinitionSchema,
16861
- asTenantId,
16862
- asUserId,
16863
- generateId,
16864
- generatePrefixedId,
16865
- slugify,
16866
- generateTemplateName,
17205
+ isEmpty,
17206
+ isNotEmpty,
17207
+ toUndefinedIfEmpty,
17208
+ hasProperties,
16867
17209
  EMPTY_VALUE_PLACEHOLDER,
16868
17210
  formatAttributeValue,
16869
17211
  SchemaErrorCode,
@@ -16888,7 +17230,6 @@ export {
16888
17230
  RoleNotFoundError,
16889
17231
  isForbiddenError,
16890
17232
  ConcurrentModificationError,
16891
- isConcurrentModificationError,
16892
17233
  text,
16893
17234
  textarea,
16894
17235
  richtext,
@@ -16919,8 +17260,13 @@ export {
16919
17260
  FlowsTabConfig,
16920
17261
  DocumentsTabConfig,
16921
17262
  TabBuilder,
17263
+ DetailViewBuilder,
16922
17264
  ViewBuilder,
17265
+ detailView,
16923
17266
  view,
17267
+ ListViewBuilder,
17268
+ ListViewTabConfigBuilder,
17269
+ listView,
16924
17270
  group,
16925
17271
  WorkflowFormRowBuilder,
16926
17272
  WorkflowFormBuilder,
@@ -16941,63 +17287,6 @@ export {
16941
17287
  SYSTEM_TEMPLATES,
16942
17288
  getSystemTemplate,
16943
17289
  isSystemTemplate,
16944
- DEFAULT_VALIDATION_MESSAGES,
16945
- textConfigSchema,
16946
- textareaConfigSchema,
16947
- richtextConfigSchema,
16948
- numberConfigSchema,
16949
- checkboxConfigSchema,
16950
- dateConfigSchema,
16951
- phoneConfigSchema,
16952
- currencyConfigSchema,
16953
- statusConfigSchema,
16954
- locationConfigSchema,
16955
- selectConfigSchema,
16956
- multiselectConfigSchema,
16957
- fileConfigSchema,
16958
- userConfigSchema,
16959
- relationConfigSchema,
16960
- ratingConfigSchema,
16961
- formulaConfigSchema,
16962
- rollupConfigSchema,
16963
- documentConfigSchema,
16964
- attributeConfigSchemas,
16965
- getAttributeConfigSchema,
16966
- validateAttributeConfig,
16967
- parseAttributeConfig,
16968
- safeParseAttributeConfig,
16969
- createTextValidator,
16970
- createNumberValidator,
16971
- createCheckboxValidator,
16972
- createDateValidator,
16973
- createPhoneValidator,
16974
- createCurrencyValidator,
16975
- createStatusValidator,
16976
- createSelectValidator,
16977
- createMultiselectValidator,
16978
- createLocationValidator,
16979
- createFileValidator,
16980
- createUserValidator,
16981
- createSingleRelationValidator,
16982
- createMultiRelationValidator,
16983
- createRelationValidator,
16984
- createRatingValidator,
16985
- createFormulaValidator,
16986
- createRollupValidator,
16987
- createTextAreaValidator,
16988
- createRichtextValidator,
16989
- createAttributeValidator,
16990
- createFormAttributeValidator,
16991
- createObjectValidator,
16992
- validateAttribute,
16993
- validateObject,
16994
- validateObjectOrThrow,
16995
- createDraftValidator,
16996
- validateDraft,
16997
- validateDraftOrThrow,
16998
- getMissingRequiredAttributes,
16999
- isRecordComplete,
17000
- computeRecordStatus,
17001
17290
  WorkflowJwtService,
17002
17291
  hashOptions,
17003
17292
  cacheKeys,
@@ -17011,6 +17300,14 @@ export {
17011
17300
  QueryNoResultError,
17012
17301
  QueryMultipleResultsError,
17013
17302
  TenantContextError,
17303
+ FeatureFlagsContextError,
17304
+ isFeatureEnabled,
17305
+ getFeatureValue,
17306
+ getFeatureFlags,
17307
+ tryGetFeatureValue,
17308
+ hasFeatureFlagsContext,
17309
+ runWithFeatureFlags,
17310
+ withFeatureFlags,
17014
17311
  getSchemaFromContext,
17015
17312
  getSchemaByNameFromContext,
17016
17313
  hasSchemaContext,
@@ -17018,7 +17315,7 @@ export {
17018
17315
  addSchemaToContext,
17019
17316
  runWithSchemaContext,
17020
17317
  runWithMergedSchemaContext,
17021
- getContext,
17318
+ getContext2 as getContext,
17022
17319
  getTenantId,
17023
17320
  getUserId,
17024
17321
  hasContext,
@@ -17136,8 +17433,11 @@ export {
17136
17433
  GlobalSearchService,
17137
17434
  PermissionService,
17138
17435
  ViewService,
17436
+ seedRegistryViews,
17139
17437
  syncNativeViews,
17438
+ verifyRegistryViewsSeeded,
17140
17439
  verifyNativeViewsSync,
17440
+ getViewSeedPreview,
17141
17441
  getViewSyncPreview,
17142
17442
  syncNativeObjects,
17143
17443
  verifyNativeObjectsSync,