@stndrds/schema 0.1.0-alpha.56 → 0.1.0-alpha.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-XBF4EUC4.js → chunk-DPRLHGPO.js} +1485 -539
- package/dist/{chunk-LOOGQL6M.mjs → chunk-YVUSATKC.mjs} +1240 -294
- package/dist/index.d.mts +919 -54
- package/dist/index.d.ts +919 -54
- package/dist/index.js +976 -33
- package/dist/index.mjs +970 -27
- package/dist/{runtime-D5MuaVQs.d.mts → runtime-BqJdmg_4.d.mts} +896 -276
- package/dist/{runtime-D5MuaVQs.d.ts → runtime-BqJdmg_4.d.ts} +896 -276
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +24 -2
- package/dist/runtime.mjs +23 -1
- package/package.json +2 -2
|
@@ -543,7 +543,7 @@ var TenantContextError = class _TenantContextError extends Error {
|
|
|
543
543
|
}
|
|
544
544
|
};
|
|
545
545
|
|
|
546
|
-
// src/runtime/context/
|
|
546
|
+
// src/runtime/context/feature-flags-context.ts
|
|
547
547
|
var browserStub = {
|
|
548
548
|
getStore: () => void 0,
|
|
549
549
|
run: (_store, callback) => callback()
|
|
@@ -581,22 +581,106 @@ function getStorage() {
|
|
|
581
581
|
storageInstance = browserStub;
|
|
582
582
|
return storageInstance;
|
|
583
583
|
}
|
|
584
|
-
|
|
584
|
+
var FeatureFlagsContextError = class _FeatureFlagsContextError extends Error {
|
|
585
|
+
constructor(message) {
|
|
586
|
+
super(
|
|
587
|
+
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."
|
|
588
|
+
);
|
|
589
|
+
this.name = "FeatureFlagsContextError";
|
|
590
|
+
if ("captureStackTrace" in Error) {
|
|
591
|
+
Error.captureStackTrace(
|
|
592
|
+
this,
|
|
593
|
+
_FeatureFlagsContextError
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
function getContext() {
|
|
599
|
+
const ctx = getStorage().getStore();
|
|
600
|
+
if (!ctx) {
|
|
601
|
+
throw new FeatureFlagsContextError();
|
|
602
|
+
}
|
|
603
|
+
return ctx;
|
|
604
|
+
}
|
|
605
|
+
function isFeatureEnabled(flagName) {
|
|
606
|
+
return getContext().flags.get(flagName) === true;
|
|
607
|
+
}
|
|
608
|
+
function getFeatureValue(flagName, defaultValue) {
|
|
609
|
+
const value = getContext().flags.get(flagName);
|
|
610
|
+
return value !== void 0 ? value : defaultValue;
|
|
611
|
+
}
|
|
612
|
+
function getFeatureFlags() {
|
|
613
|
+
return getContext().flags;
|
|
614
|
+
}
|
|
615
|
+
function tryGetFeatureValue(flagName) {
|
|
585
616
|
const ctx = getStorage().getStore();
|
|
617
|
+
return ctx?.flags.get(flagName);
|
|
618
|
+
}
|
|
619
|
+
function hasFeatureFlagsContext() {
|
|
620
|
+
return getStorage().getStore() !== void 0;
|
|
621
|
+
}
|
|
622
|
+
function runWithFeatureFlags(flags, fn) {
|
|
623
|
+
const frozenContext = Object.freeze({ flags });
|
|
624
|
+
return getStorage().run(frozenContext, fn);
|
|
625
|
+
}
|
|
626
|
+
function withFeatureFlags(resolvedFlags, fn) {
|
|
627
|
+
return runWithFeatureFlags(resolvedFlags, fn);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/runtime/context/schema-context.ts
|
|
631
|
+
var browserStub2 = {
|
|
632
|
+
getStore: () => void 0,
|
|
633
|
+
run: (_store, callback) => callback()
|
|
634
|
+
};
|
|
635
|
+
var AsyncLocalStorageClass2 = null;
|
|
636
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
637
|
+
try {
|
|
638
|
+
if (typeof __require !== "undefined") {
|
|
639
|
+
const asyncHooks = __require("async_hooks");
|
|
640
|
+
AsyncLocalStorageClass2 = asyncHooks.AsyncLocalStorage;
|
|
641
|
+
}
|
|
642
|
+
} catch {
|
|
643
|
+
try {
|
|
644
|
+
const dynamicRequire = new Function(
|
|
645
|
+
"m",
|
|
646
|
+
'return typeof require!=="undefined"?require(m):null'
|
|
647
|
+
);
|
|
648
|
+
const asyncHooks = dynamicRequire("node:async_hooks");
|
|
649
|
+
if (asyncHooks) {
|
|
650
|
+
AsyncLocalStorageClass2 = asyncHooks.AsyncLocalStorage;
|
|
651
|
+
}
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
var storageInstance2 = null;
|
|
657
|
+
function getStorage2() {
|
|
658
|
+
if (storageInstance2 !== null) {
|
|
659
|
+
return storageInstance2;
|
|
660
|
+
}
|
|
661
|
+
if (AsyncLocalStorageClass2) {
|
|
662
|
+
storageInstance2 = new AsyncLocalStorageClass2();
|
|
663
|
+
return storageInstance2;
|
|
664
|
+
}
|
|
665
|
+
storageInstance2 = browserStub2;
|
|
666
|
+
return storageInstance2;
|
|
667
|
+
}
|
|
668
|
+
function getSchemaFromContext(objectId) {
|
|
669
|
+
const ctx = getStorage2().getStore();
|
|
586
670
|
return ctx?.objectsById.get(objectId);
|
|
587
671
|
}
|
|
588
672
|
function getSchemaByNameFromContext(objectName) {
|
|
589
|
-
const ctx =
|
|
673
|
+
const ctx = getStorage2().getStore();
|
|
590
674
|
return ctx?.objectsByName.get(objectName);
|
|
591
675
|
}
|
|
592
676
|
function hasSchemaContext() {
|
|
593
|
-
return
|
|
677
|
+
return getStorage2().getStore() !== void 0;
|
|
594
678
|
}
|
|
595
679
|
function getSchemaContext() {
|
|
596
|
-
return
|
|
680
|
+
return getStorage2().getStore();
|
|
597
681
|
}
|
|
598
682
|
function addSchemaToContext(schema) {
|
|
599
|
-
const ctx =
|
|
683
|
+
const ctx = getStorage2().getStore();
|
|
600
684
|
if (!ctx) {
|
|
601
685
|
return;
|
|
602
686
|
}
|
|
@@ -618,10 +702,10 @@ function buildSchemaContext(schemas) {
|
|
|
618
702
|
}
|
|
619
703
|
function runWithSchemaContext(schemas, fn) {
|
|
620
704
|
const context = buildSchemaContext(schemas);
|
|
621
|
-
return
|
|
705
|
+
return getStorage2().run(context, fn);
|
|
622
706
|
}
|
|
623
707
|
function runWithMergedSchemaContext(schemas, fn) {
|
|
624
|
-
const existing =
|
|
708
|
+
const existing = getStorage2().getStore();
|
|
625
709
|
const objectsById = new Map(existing?.objectsById);
|
|
626
710
|
const objectsByName = new Map(existing?.objectsByName);
|
|
627
711
|
for (const schema of schemas) {
|
|
@@ -634,20 +718,20 @@ function runWithMergedSchemaContext(schemas, fn) {
|
|
|
634
718
|
objectsById,
|
|
635
719
|
objectsByName
|
|
636
720
|
};
|
|
637
|
-
return
|
|
721
|
+
return getStorage2().run(context, fn);
|
|
638
722
|
}
|
|
639
723
|
|
|
640
724
|
// src/runtime/context/tenant-context.ts
|
|
641
|
-
var
|
|
725
|
+
var browserStub3 = {
|
|
642
726
|
getStore: () => void 0,
|
|
643
727
|
run: (_store, callback) => callback()
|
|
644
728
|
};
|
|
645
|
-
var
|
|
729
|
+
var AsyncLocalStorageClass3 = null;
|
|
646
730
|
if (typeof process !== "undefined" && process.versions?.node) {
|
|
647
731
|
try {
|
|
648
732
|
if (typeof __require !== "undefined") {
|
|
649
733
|
const asyncHooks = __require("async_hooks");
|
|
650
|
-
|
|
734
|
+
AsyncLocalStorageClass3 = asyncHooks.AsyncLocalStorage;
|
|
651
735
|
}
|
|
652
736
|
} catch {
|
|
653
737
|
try {
|
|
@@ -657,43 +741,43 @@ if (typeof process !== "undefined" && process.versions?.node) {
|
|
|
657
741
|
);
|
|
658
742
|
const asyncHooks = dynamicRequire("node:async_hooks");
|
|
659
743
|
if (asyncHooks) {
|
|
660
|
-
|
|
744
|
+
AsyncLocalStorageClass3 = asyncHooks.AsyncLocalStorage;
|
|
661
745
|
}
|
|
662
746
|
} catch {
|
|
663
747
|
}
|
|
664
748
|
}
|
|
665
749
|
}
|
|
666
|
-
var
|
|
667
|
-
function
|
|
668
|
-
if (
|
|
669
|
-
return
|
|
750
|
+
var storageInstance3 = null;
|
|
751
|
+
function getStorage3() {
|
|
752
|
+
if (storageInstance3 !== null) {
|
|
753
|
+
return storageInstance3;
|
|
670
754
|
}
|
|
671
|
-
if (
|
|
672
|
-
|
|
673
|
-
return
|
|
755
|
+
if (AsyncLocalStorageClass3) {
|
|
756
|
+
storageInstance3 = new AsyncLocalStorageClass3();
|
|
757
|
+
return storageInstance3;
|
|
674
758
|
}
|
|
675
|
-
|
|
676
|
-
return
|
|
759
|
+
storageInstance3 = browserStub3;
|
|
760
|
+
return storageInstance3;
|
|
677
761
|
}
|
|
678
|
-
function
|
|
679
|
-
const ctx =
|
|
762
|
+
function getContext2() {
|
|
763
|
+
const ctx = getStorage3().getStore();
|
|
680
764
|
if (!ctx) {
|
|
681
765
|
throw new TenantContextError();
|
|
682
766
|
}
|
|
683
767
|
return ctx;
|
|
684
768
|
}
|
|
685
769
|
function getTenantId() {
|
|
686
|
-
return
|
|
770
|
+
return getContext2().tenantId;
|
|
687
771
|
}
|
|
688
772
|
function getUserId() {
|
|
689
|
-
return
|
|
773
|
+
return getContext2().userId;
|
|
690
774
|
}
|
|
691
775
|
function hasContext() {
|
|
692
|
-
return
|
|
776
|
+
return getStorage3().getStore() !== void 0;
|
|
693
777
|
}
|
|
694
778
|
function runWithContext(context, fn) {
|
|
695
779
|
const frozenContext = Object.freeze({ ...context });
|
|
696
|
-
return
|
|
780
|
+
return getStorage3().run(frozenContext, fn);
|
|
697
781
|
}
|
|
698
782
|
function withTenantContext(tenantId, fn, userId) {
|
|
699
783
|
return runWithContext({ tenantId, userId }, fn);
|
|
@@ -4000,6 +4084,7 @@ function createEmptyStores() {
|
|
|
4000
4084
|
files: /* @__PURE__ */ new Map(),
|
|
4001
4085
|
objectRecords: /* @__PURE__ */ new Map(),
|
|
4002
4086
|
views: /* @__PURE__ */ new Map(),
|
|
4087
|
+
viewOverlays: /* @__PURE__ */ new Map(),
|
|
4003
4088
|
roles: /* @__PURE__ */ new Map(),
|
|
4004
4089
|
permissions: /* @__PURE__ */ new Map(),
|
|
4005
4090
|
userRoles: /* @__PURE__ */ new Map(),
|
|
@@ -4297,32 +4382,38 @@ function createMockViewsRepository(stores) {
|
|
|
4297
4382
|
) ?? null
|
|
4298
4383
|
);
|
|
4299
4384
|
},
|
|
4300
|
-
|
|
4385
|
+
findByNameAndType(objectName, viewName, type) {
|
|
4386
|
+
const tenantId = getTenantId();
|
|
4387
|
+
return Promise.resolve(
|
|
4388
|
+
Array.from(stores.views.values()).find(
|
|
4389
|
+
(v) => v.tenantId === tenantId && v.objectName === objectName && v.name === viewName && v.type === type
|
|
4390
|
+
) ?? null
|
|
4391
|
+
);
|
|
4392
|
+
},
|
|
4393
|
+
findByObjectName(objectName, type) {
|
|
4301
4394
|
const tenantId = getTenantId();
|
|
4302
4395
|
return Promise.resolve(
|
|
4303
4396
|
Array.from(stores.views.values()).filter(
|
|
4304
|
-
(v) => v.tenantId === tenantId && v.objectName === objectName
|
|
4397
|
+
(v) => v.tenantId === tenantId && v.objectName === objectName && (type === void 0 || v.type === type)
|
|
4305
4398
|
)
|
|
4306
4399
|
);
|
|
4307
4400
|
},
|
|
4308
|
-
findAllForTenant() {
|
|
4401
|
+
findAllForTenant(type) {
|
|
4309
4402
|
const tenantId = getTenantId();
|
|
4310
4403
|
return Promise.resolve(
|
|
4311
|
-
Array.from(stores.views.values()).filter(
|
|
4404
|
+
Array.from(stores.views.values()).filter(
|
|
4405
|
+
(v) => v.tenantId === tenantId && (type === void 0 || v.type === type)
|
|
4406
|
+
)
|
|
4312
4407
|
);
|
|
4313
4408
|
},
|
|
4314
|
-
|
|
4409
|
+
findDefault(objectName, type) {
|
|
4410
|
+
const tenantId = getTenantId();
|
|
4315
4411
|
return Promise.resolve(
|
|
4316
4412
|
Array.from(stores.views.values()).find(
|
|
4317
|
-
(v) => v.objectName === objectName && v.
|
|
4413
|
+
(v) => v.tenantId === tenantId && v.objectName === objectName && v.type === type && v.default === true
|
|
4318
4414
|
) ?? null
|
|
4319
4415
|
);
|
|
4320
4416
|
},
|
|
4321
|
-
findSystemByObjectName(objectName) {
|
|
4322
|
-
return Promise.resolve(
|
|
4323
|
-
Array.from(stores.views.values()).filter((v) => v.objectName === objectName && v.system)
|
|
4324
|
-
);
|
|
4325
|
-
},
|
|
4326
4417
|
create(data) {
|
|
4327
4418
|
const tenantId = getTenantId();
|
|
4328
4419
|
const id = generateId();
|
|
@@ -4331,13 +4422,13 @@ function createMockViewsRepository(stores) {
|
|
|
4331
4422
|
id,
|
|
4332
4423
|
tenantId,
|
|
4333
4424
|
objectName: data.objectName,
|
|
4425
|
+
type: data.type,
|
|
4334
4426
|
name: data.name,
|
|
4335
4427
|
label: data.label,
|
|
4336
4428
|
description: data.description,
|
|
4337
4429
|
icon: data.icon,
|
|
4338
|
-
|
|
4430
|
+
config: data.config,
|
|
4339
4431
|
default: data.default ?? false,
|
|
4340
|
-
system: data.system ?? false,
|
|
4341
4432
|
metadata: data.metadata,
|
|
4342
4433
|
createdAt: now,
|
|
4343
4434
|
updatedAt: now
|
|
@@ -4362,10 +4453,11 @@ function createMockViewsRepository(stores) {
|
|
|
4362
4453
|
stores.views.delete(id);
|
|
4363
4454
|
return Promise.resolve();
|
|
4364
4455
|
},
|
|
4365
|
-
deleteNotIn(objectName, keepViewNames) {
|
|
4456
|
+
deleteNotIn(objectName, type, keepViewNames) {
|
|
4457
|
+
const tenantId = getTenantId();
|
|
4366
4458
|
let deleted = 0;
|
|
4367
4459
|
for (const [id, view2] of stores.views.entries()) {
|
|
4368
|
-
if (view2.objectName === objectName && view2.
|
|
4460
|
+
if (view2.tenantId === tenantId && view2.objectName === objectName && view2.type === type && !keepViewNames.includes(view2.name)) {
|
|
4369
4461
|
stores.views.delete(id);
|
|
4370
4462
|
deleted++;
|
|
4371
4463
|
}
|
|
@@ -4373,18 +4465,151 @@ function createMockViewsRepository(stores) {
|
|
|
4373
4465
|
return Promise.resolve(deleted);
|
|
4374
4466
|
},
|
|
4375
4467
|
async upsert(data) {
|
|
4376
|
-
const existing =
|
|
4468
|
+
const existing = await this.findByNameAndType(data.objectName, data.name, data.type);
|
|
4377
4469
|
if (existing) {
|
|
4378
4470
|
return this.update(existing.id, {
|
|
4379
4471
|
label: data.label,
|
|
4380
4472
|
description: data.description,
|
|
4381
4473
|
icon: data.icon,
|
|
4382
|
-
|
|
4474
|
+
config: data.config,
|
|
4383
4475
|
default: data.default,
|
|
4384
4476
|
metadata: data.metadata
|
|
4385
4477
|
});
|
|
4386
4478
|
}
|
|
4387
4479
|
return this.create(data);
|
|
4480
|
+
},
|
|
4481
|
+
async exists(objectName, viewName, type) {
|
|
4482
|
+
const existing = await this.findByNameAndType(objectName, viewName, type);
|
|
4483
|
+
return existing !== null;
|
|
4484
|
+
}
|
|
4485
|
+
};
|
|
4486
|
+
}
|
|
4487
|
+
function createMockViewOverlaysRepository(stores) {
|
|
4488
|
+
return {
|
|
4489
|
+
findById(id) {
|
|
4490
|
+
return Promise.resolve(stores.viewOverlays.get(id) ?? null);
|
|
4491
|
+
},
|
|
4492
|
+
findByViewAndUser(viewId, userId) {
|
|
4493
|
+
const tenantId = getTenantId();
|
|
4494
|
+
return Promise.resolve(
|
|
4495
|
+
Array.from(stores.viewOverlays.values()).find(
|
|
4496
|
+
(o) => o.tenantId === tenantId && o.viewId === viewId && o.userId === userId
|
|
4497
|
+
) ?? null
|
|
4498
|
+
);
|
|
4499
|
+
},
|
|
4500
|
+
findByUser(userId) {
|
|
4501
|
+
const tenantId = getTenantId();
|
|
4502
|
+
return Promise.resolve(
|
|
4503
|
+
Array.from(stores.viewOverlays.values()).filter(
|
|
4504
|
+
(o) => o.tenantId === tenantId && o.userId === userId
|
|
4505
|
+
)
|
|
4506
|
+
);
|
|
4507
|
+
},
|
|
4508
|
+
findByView(viewId) {
|
|
4509
|
+
const tenantId = getTenantId();
|
|
4510
|
+
return Promise.resolve(
|
|
4511
|
+
Array.from(stores.viewOverlays.values()).filter(
|
|
4512
|
+
(o) => o.tenantId === tenantId && o.viewId === viewId
|
|
4513
|
+
)
|
|
4514
|
+
);
|
|
4515
|
+
},
|
|
4516
|
+
async findUserDefault(userId, objectName, type) {
|
|
4517
|
+
const tenantId = getTenantId();
|
|
4518
|
+
const views = Array.from(stores.views.values()).filter(
|
|
4519
|
+
(v) => v.tenantId === tenantId && v.objectName === objectName && v.type === type
|
|
4520
|
+
);
|
|
4521
|
+
const viewIds = new Set(views.map((v) => v.id));
|
|
4522
|
+
return Promise.resolve(
|
|
4523
|
+
Array.from(stores.viewOverlays.values()).find(
|
|
4524
|
+
(o) => o.tenantId === tenantId && o.userId === userId && o.isUserDefault === true && viewIds.has(o.viewId)
|
|
4525
|
+
) ?? null
|
|
4526
|
+
);
|
|
4527
|
+
},
|
|
4528
|
+
create(data) {
|
|
4529
|
+
const tenantId = getTenantId();
|
|
4530
|
+
const id = generateId();
|
|
4531
|
+
const now = /* @__PURE__ */ new Date();
|
|
4532
|
+
const overlay = {
|
|
4533
|
+
id,
|
|
4534
|
+
tenantId,
|
|
4535
|
+
viewId: data.viewId,
|
|
4536
|
+
userId: data.userId,
|
|
4537
|
+
configOverrides: data.configOverrides,
|
|
4538
|
+
isUserDefault: data.isUserDefault,
|
|
4539
|
+
createdAt: now,
|
|
4540
|
+
updatedAt: now
|
|
4541
|
+
};
|
|
4542
|
+
stores.viewOverlays.set(id, overlay);
|
|
4543
|
+
return Promise.resolve(overlay);
|
|
4544
|
+
},
|
|
4545
|
+
update(id, data) {
|
|
4546
|
+
const overlay = stores.viewOverlays.get(id);
|
|
4547
|
+
if (!overlay) {
|
|
4548
|
+
return Promise.reject(new Error(`ViewOverlay not found: ${id}`));
|
|
4549
|
+
}
|
|
4550
|
+
const updated = {
|
|
4551
|
+
...overlay,
|
|
4552
|
+
...data,
|
|
4553
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
4554
|
+
};
|
|
4555
|
+
stores.viewOverlays.set(id, updated);
|
|
4556
|
+
return Promise.resolve(updated);
|
|
4557
|
+
},
|
|
4558
|
+
delete(id) {
|
|
4559
|
+
stores.viewOverlays.delete(id);
|
|
4560
|
+
return Promise.resolve();
|
|
4561
|
+
},
|
|
4562
|
+
async deleteByViewAndUser(viewId, userId) {
|
|
4563
|
+
const overlay = await this.findByViewAndUser(viewId, userId);
|
|
4564
|
+
if (overlay) {
|
|
4565
|
+
stores.viewOverlays.delete(overlay.id);
|
|
4566
|
+
}
|
|
4567
|
+
},
|
|
4568
|
+
deleteByView(viewId) {
|
|
4569
|
+
const tenantId = getTenantId();
|
|
4570
|
+
let deleted = 0;
|
|
4571
|
+
for (const [id, overlay] of stores.viewOverlays.entries()) {
|
|
4572
|
+
if (overlay.tenantId === tenantId && overlay.viewId === viewId) {
|
|
4573
|
+
stores.viewOverlays.delete(id);
|
|
4574
|
+
deleted++;
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
return Promise.resolve(deleted);
|
|
4578
|
+
},
|
|
4579
|
+
migrateViewId(fromViewId, toViewId) {
|
|
4580
|
+
const tenantId = getTenantId();
|
|
4581
|
+
let migrated = 0;
|
|
4582
|
+
for (const overlay of stores.viewOverlays.values()) {
|
|
4583
|
+
if (overlay.tenantId === tenantId && overlay.viewId === fromViewId) {
|
|
4584
|
+
overlay.viewId = toViewId;
|
|
4585
|
+
overlay.updatedAt = /* @__PURE__ */ new Date();
|
|
4586
|
+
migrated++;
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
return Promise.resolve(migrated);
|
|
4590
|
+
},
|
|
4591
|
+
async upsert(data) {
|
|
4592
|
+
const existing = await this.findByViewAndUser(data.viewId, data.userId);
|
|
4593
|
+
if (existing) {
|
|
4594
|
+
return this.update(existing.id, {
|
|
4595
|
+
configOverrides: data.configOverrides,
|
|
4596
|
+
isUserDefault: data.isUserDefault
|
|
4597
|
+
});
|
|
4598
|
+
}
|
|
4599
|
+
return this.create(data);
|
|
4600
|
+
},
|
|
4601
|
+
async clearUserDefault(userId, objectName, type) {
|
|
4602
|
+
const tenantId = getTenantId();
|
|
4603
|
+
const views = Array.from(stores.views.values()).filter(
|
|
4604
|
+
(v) => v.tenantId === tenantId && v.objectName === objectName && v.type === type
|
|
4605
|
+
);
|
|
4606
|
+
const viewIds = new Set(views.map((v) => v.id));
|
|
4607
|
+
for (const overlay of stores.viewOverlays.values()) {
|
|
4608
|
+
if (overlay.tenantId === tenantId && overlay.userId === userId && overlay.isUserDefault === true && viewIds.has(overlay.viewId)) {
|
|
4609
|
+
overlay.isUserDefault = false;
|
|
4610
|
+
overlay.updatedAt = /* @__PURE__ */ new Date();
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4388
4613
|
}
|
|
4389
4614
|
};
|
|
4390
4615
|
}
|
|
@@ -4774,6 +4999,7 @@ function createMockAdapter() {
|
|
|
4774
4999
|
files: createMockFilesRepository(stores),
|
|
4775
5000
|
objectRecords: createMockObjectRecordsRepository(stores),
|
|
4776
5001
|
views: createMockViewsRepository(stores),
|
|
5002
|
+
viewOverlays: createMockViewOverlaysRepository(stores),
|
|
4777
5003
|
permissions: createMockPermissionsRepository(stores),
|
|
4778
5004
|
workflows: createMockWorkflowsRepository(stores),
|
|
4779
5005
|
workflowInstances: createMockWorkflowInstancesRepository(stores),
|
|
@@ -4796,6 +5022,7 @@ function createMockAdapter() {
|
|
|
4796
5022
|
stores.files.clear();
|
|
4797
5023
|
stores.objectRecords.clear();
|
|
4798
5024
|
stores.views.clear();
|
|
5025
|
+
stores.viewOverlays.clear();
|
|
4799
5026
|
stores.roles.clear();
|
|
4800
5027
|
stores.permissions.clear();
|
|
4801
5028
|
stores.userRoles.clear();
|
|
@@ -5339,6 +5566,32 @@ var BaseAttributeBuilder = class {
|
|
|
5339
5566
|
this.attr.metadata = value;
|
|
5340
5567
|
return this;
|
|
5341
5568
|
}
|
|
5569
|
+
/**
|
|
5570
|
+
* Add a feature gate to conditionally show/hide/disable this attribute.
|
|
5571
|
+
*
|
|
5572
|
+
* @param flagName - Name of the feature flag to check
|
|
5573
|
+
* @param options - Optional configuration for expected value and fallback behavior
|
|
5574
|
+
*
|
|
5575
|
+
* @example Hide attribute when flag is disabled
|
|
5576
|
+
* ```typescript
|
|
5577
|
+
* text({ name: "aiSummary", label: "AI Summary" })
|
|
5578
|
+
* .featureGate("ai-features")
|
|
5579
|
+
* ```
|
|
5580
|
+
*
|
|
5581
|
+
* @example Disable attribute when tier is not enterprise
|
|
5582
|
+
* ```typescript
|
|
5583
|
+
* text({ name: "advancedField", label: "Advanced Field" })
|
|
5584
|
+
* .featureGate("tier", { expectedValue: "enterprise", fallback: "disable" })
|
|
5585
|
+
* ```
|
|
5586
|
+
*/
|
|
5587
|
+
featureGate(flagName, options) {
|
|
5588
|
+
this.attr.featureGate = {
|
|
5589
|
+
flag: flagName,
|
|
5590
|
+
expectedValue: options?.expectedValue,
|
|
5591
|
+
fallback: options?.fallback
|
|
5592
|
+
};
|
|
5593
|
+
return this;
|
|
5594
|
+
}
|
|
5342
5595
|
build() {
|
|
5343
5596
|
return this.attr;
|
|
5344
5597
|
}
|
|
@@ -6261,6 +6514,7 @@ function object(config) {
|
|
|
6261
6514
|
}
|
|
6262
6515
|
|
|
6263
6516
|
// src/builders/view-builder.ts
|
|
6517
|
+
import { randomUUID } from "crypto";
|
|
6264
6518
|
import { z as z3 } from "zod";
|
|
6265
6519
|
var GroupBuilder = class {
|
|
6266
6520
|
constructor(id, label) {
|
|
@@ -6411,7 +6665,7 @@ var BaseTableTabConfig = class {
|
|
|
6411
6665
|
return new TabBuilder(this.view, name, label);
|
|
6412
6666
|
}
|
|
6413
6667
|
/**
|
|
6414
|
-
* Finish this tab and return to
|
|
6668
|
+
* Finish this tab and return to DetailViewBuilder
|
|
6415
6669
|
*/
|
|
6416
6670
|
done() {
|
|
6417
6671
|
this.finalize();
|
|
@@ -6477,7 +6731,7 @@ var CustomTabConfig = class {
|
|
|
6477
6731
|
return new TabBuilder(this.view, name, label);
|
|
6478
6732
|
}
|
|
6479
6733
|
/**
|
|
6480
|
-
* Finish this tab and return to
|
|
6734
|
+
* Finish this tab and return to DetailViewBuilder
|
|
6481
6735
|
*/
|
|
6482
6736
|
done() {
|
|
6483
6737
|
this.view._addTab(this.tabData);
|
|
@@ -6519,7 +6773,7 @@ var NotesTabConfig = class {
|
|
|
6519
6773
|
return new TabBuilder(this.view, name, label);
|
|
6520
6774
|
}
|
|
6521
6775
|
/**
|
|
6522
|
-
* Finish this tab and return to
|
|
6776
|
+
* Finish this tab and return to DetailViewBuilder
|
|
6523
6777
|
*/
|
|
6524
6778
|
done() {
|
|
6525
6779
|
this.view._addTab(this.tabData);
|
|
@@ -6554,7 +6808,7 @@ var ActivityTabConfig = class {
|
|
|
6554
6808
|
return new TabBuilder(this.view, name, label);
|
|
6555
6809
|
}
|
|
6556
6810
|
/**
|
|
6557
|
-
* Finish this tab and return to
|
|
6811
|
+
* Finish this tab and return to DetailViewBuilder
|
|
6558
6812
|
*/
|
|
6559
6813
|
done() {
|
|
6560
6814
|
this.view._addTab(this.tabData);
|
|
@@ -6617,7 +6871,7 @@ var FlowsTabConfig = class {
|
|
|
6617
6871
|
return new TabBuilder(this.view, name, label);
|
|
6618
6872
|
}
|
|
6619
6873
|
/**
|
|
6620
|
-
* Finish this tab and return to
|
|
6874
|
+
* Finish this tab and return to DetailViewBuilder
|
|
6621
6875
|
*/
|
|
6622
6876
|
done() {
|
|
6623
6877
|
this.view._addTab(this.tabData);
|
|
@@ -6688,7 +6942,7 @@ var DocumentsTabConfig = class {
|
|
|
6688
6942
|
return new TabBuilder(this.view, name, label);
|
|
6689
6943
|
}
|
|
6690
6944
|
/**
|
|
6691
|
-
* Finish this tab and return to
|
|
6945
|
+
* Finish this tab and return to DetailViewBuilder
|
|
6692
6946
|
*/
|
|
6693
6947
|
done() {
|
|
6694
6948
|
this.view._addTab(this.tabData);
|
|
@@ -6726,13 +6980,6 @@ var TabBuilder = class {
|
|
|
6726
6980
|
this.base.order = value;
|
|
6727
6981
|
return this;
|
|
6728
6982
|
}
|
|
6729
|
-
/**
|
|
6730
|
-
* Mark as system tab (protected)
|
|
6731
|
-
*/
|
|
6732
|
-
system() {
|
|
6733
|
-
this.base.system = true;
|
|
6734
|
-
return this;
|
|
6735
|
-
}
|
|
6736
6983
|
// ─────────────────────────────────────────────────────────────────────────
|
|
6737
6984
|
// TYPE DISCRIMINATORS
|
|
6738
6985
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -6812,13 +7059,15 @@ var TabBuilder = class {
|
|
|
6812
7059
|
return new DocumentsTabConfig(this.view, this.base);
|
|
6813
7060
|
}
|
|
6814
7061
|
};
|
|
6815
|
-
var
|
|
7062
|
+
var DetailViewBuilder = class {
|
|
6816
7063
|
constructor(name, label) {
|
|
6817
|
-
this.data = { tabs: [] };
|
|
6818
|
-
this.validated = false;
|
|
6819
7064
|
this.validateName(name);
|
|
6820
|
-
this.data
|
|
6821
|
-
|
|
7065
|
+
this.data = {
|
|
7066
|
+
name,
|
|
7067
|
+
label,
|
|
7068
|
+
layout: "page",
|
|
7069
|
+
tabs: []
|
|
7070
|
+
};
|
|
6822
7071
|
}
|
|
6823
7072
|
/**
|
|
6824
7073
|
* Set view description
|
|
@@ -6843,19 +7092,12 @@ var ViewBuilder = class {
|
|
|
6843
7092
|
return this;
|
|
6844
7093
|
}
|
|
6845
7094
|
/**
|
|
6846
|
-
* Mark as default view for the object
|
|
7095
|
+
* Mark as default view for the object
|
|
6847
7096
|
*/
|
|
6848
7097
|
default() {
|
|
6849
7098
|
this.data.default = true;
|
|
6850
7099
|
return this;
|
|
6851
7100
|
}
|
|
6852
|
-
/**
|
|
6853
|
-
* Mark as system view (protected, defined by developer)
|
|
6854
|
-
*/
|
|
6855
|
-
system() {
|
|
6856
|
-
this.data.system = true;
|
|
6857
|
-
return this;
|
|
6858
|
-
}
|
|
6859
7101
|
/**
|
|
6860
7102
|
* Set the layout mode for the view
|
|
6861
7103
|
* @param value - "page" for full tabs, "modal" for single form
|
|
@@ -6890,14 +7132,14 @@ var ViewBuilder = class {
|
|
|
6890
7132
|
* Add a pre-built tab
|
|
6891
7133
|
*/
|
|
6892
7134
|
addTab(tab) {
|
|
6893
|
-
this.data.tabs
|
|
7135
|
+
this.data.tabs.push(tab);
|
|
6894
7136
|
return this;
|
|
6895
7137
|
}
|
|
6896
7138
|
/**
|
|
6897
7139
|
* @internal Used by TabBuilder to add tabs
|
|
6898
7140
|
*/
|
|
6899
7141
|
_addTab(tab) {
|
|
6900
|
-
this.data.tabs
|
|
7142
|
+
this.data.tabs.push(tab);
|
|
6901
7143
|
return this;
|
|
6902
7144
|
}
|
|
6903
7145
|
/**
|
|
@@ -6905,48 +7147,332 @@ var ViewBuilder = class {
|
|
|
6905
7147
|
*/
|
|
6906
7148
|
build() {
|
|
6907
7149
|
if (!this.data.object) {
|
|
6908
|
-
throw new Error("[
|
|
7150
|
+
throw new Error("[DetailViewBuilder] for() is required - specify the target object");
|
|
6909
7151
|
}
|
|
6910
|
-
if (
|
|
6911
|
-
throw new Error("[
|
|
7152
|
+
if (this.data.tabs.length === 0) {
|
|
7153
|
+
throw new Error("[DetailViewBuilder] At least one tab is required");
|
|
6912
7154
|
}
|
|
6913
7155
|
const tabNames = /* @__PURE__ */ new Set();
|
|
6914
7156
|
for (const tab of this.data.tabs) {
|
|
6915
7157
|
if (tabNames.has(tab.name)) {
|
|
6916
|
-
throw new Error(`[
|
|
7158
|
+
throw new Error(`[DetailViewBuilder] Duplicate tab name "${tab.name}"`);
|
|
6917
7159
|
}
|
|
6918
7160
|
tabNames.add(tab.name);
|
|
6919
7161
|
}
|
|
6920
7162
|
if (this.data.layout === "modal") {
|
|
6921
7163
|
if (this.data.tabs.length !== 1) {
|
|
6922
|
-
throw new Error("[
|
|
7164
|
+
throw new Error("[DetailViewBuilder] Modal views must have exactly one tab");
|
|
6923
7165
|
}
|
|
6924
7166
|
if (this.data.tabs[0].type !== "form") {
|
|
6925
|
-
throw new Error("[
|
|
7167
|
+
throw new Error("[DetailViewBuilder] Modal views must have a form tab");
|
|
6926
7168
|
}
|
|
6927
7169
|
}
|
|
6928
|
-
|
|
6929
|
-
|
|
7170
|
+
const config = {
|
|
7171
|
+
layout: this.data.layout,
|
|
7172
|
+
tabs: this.data.tabs
|
|
7173
|
+
};
|
|
7174
|
+
return {
|
|
7175
|
+
name: this.data.name,
|
|
7176
|
+
label: this.data.label,
|
|
7177
|
+
description: this.data.description,
|
|
7178
|
+
icon: this.data.icon,
|
|
7179
|
+
object: this.data.object,
|
|
7180
|
+
type: "detail",
|
|
7181
|
+
config,
|
|
7182
|
+
default: this.data.default,
|
|
7183
|
+
metadata: this.data.metadata
|
|
7184
|
+
};
|
|
7185
|
+
}
|
|
7186
|
+
/**
|
|
7187
|
+
* Validate view name format (kebab-case)
|
|
7188
|
+
*/
|
|
7189
|
+
validateName(name) {
|
|
7190
|
+
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-]*$/, {
|
|
7191
|
+
message: "Invalid view name format.\nName must be in kebab-case:\n \u2705 Valid: 'detail', 'list-view', 'company-detail'\n \u274C Invalid: 'Detail', 'listView', 'list_view'"
|
|
7192
|
+
});
|
|
7193
|
+
try {
|
|
7194
|
+
viewNameSchema.parse(name);
|
|
7195
|
+
} catch (error2) {
|
|
7196
|
+
if (error2 instanceof z3.ZodError) {
|
|
7197
|
+
throw new Error(`[DetailViewBuilder] ${error2.issues[0].message}`);
|
|
7198
|
+
}
|
|
7199
|
+
throw error2;
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
};
|
|
7203
|
+
var ViewBuilder = DetailViewBuilder;
|
|
7204
|
+
function detailView(name, label) {
|
|
7205
|
+
return new DetailViewBuilder(name, label);
|
|
7206
|
+
}
|
|
7207
|
+
function view(name, label) {
|
|
7208
|
+
return new DetailViewBuilder(name, label);
|
|
7209
|
+
}
|
|
7210
|
+
var ListViewBuilder = class {
|
|
7211
|
+
constructor(name, label) {
|
|
7212
|
+
this.validateName(name);
|
|
7213
|
+
this.data = {
|
|
7214
|
+
name,
|
|
7215
|
+
label,
|
|
7216
|
+
layout: "table",
|
|
7217
|
+
columns: [],
|
|
7218
|
+
tabs: []
|
|
7219
|
+
};
|
|
7220
|
+
}
|
|
7221
|
+
/**
|
|
7222
|
+
* Set view description
|
|
7223
|
+
*/
|
|
7224
|
+
description(value) {
|
|
7225
|
+
this.data.description = value;
|
|
7226
|
+
return this;
|
|
7227
|
+
}
|
|
7228
|
+
/**
|
|
7229
|
+
* Set view icon
|
|
7230
|
+
*/
|
|
7231
|
+
icon(value) {
|
|
7232
|
+
this.data.icon = value;
|
|
7233
|
+
return this;
|
|
7234
|
+
}
|
|
7235
|
+
/**
|
|
7236
|
+
* Associate view with an object
|
|
7237
|
+
* @param objectName - Object name (kebab-case)
|
|
7238
|
+
*/
|
|
7239
|
+
for(objectName) {
|
|
7240
|
+
this.data.object = objectName;
|
|
7241
|
+
return this;
|
|
7242
|
+
}
|
|
7243
|
+
/**
|
|
7244
|
+
* Mark as default view for the object
|
|
7245
|
+
*/
|
|
7246
|
+
default() {
|
|
7247
|
+
this.data.default = true;
|
|
7248
|
+
return this;
|
|
7249
|
+
}
|
|
7250
|
+
/**
|
|
7251
|
+
* Set metadata
|
|
7252
|
+
*/
|
|
7253
|
+
metadata(value) {
|
|
7254
|
+
this.data.metadata = value;
|
|
7255
|
+
return this;
|
|
7256
|
+
}
|
|
7257
|
+
/**
|
|
7258
|
+
* Set the layout to table (default)
|
|
7259
|
+
*/
|
|
7260
|
+
table() {
|
|
7261
|
+
this.data.layout = "table";
|
|
7262
|
+
return this;
|
|
7263
|
+
}
|
|
7264
|
+
/**
|
|
7265
|
+
* Set the layout to kanban and specify the grouping attribute
|
|
7266
|
+
* @param groupByAttribute - Attribute to group by (must be a select/status type)
|
|
7267
|
+
*/
|
|
7268
|
+
kanban(groupByAttribute) {
|
|
7269
|
+
this.data.layout = "kanban";
|
|
7270
|
+
this.data.groupByAttribute = groupByAttribute;
|
|
7271
|
+
return this;
|
|
7272
|
+
}
|
|
7273
|
+
/**
|
|
7274
|
+
* Set columns to display
|
|
7275
|
+
* @example .columns("name", "email", "status", "createdAt")
|
|
7276
|
+
*/
|
|
7277
|
+
columns(...names) {
|
|
7278
|
+
this.data.columns = names;
|
|
7279
|
+
return this;
|
|
7280
|
+
}
|
|
7281
|
+
/**
|
|
7282
|
+
* Set width for a specific column in pixels
|
|
7283
|
+
* @example .columnWidth("email", 200)
|
|
7284
|
+
*/
|
|
7285
|
+
columnWidth(columnName, width) {
|
|
7286
|
+
if (!this.data.columnSizing) {
|
|
7287
|
+
this.data.columnSizing = {};
|
|
7288
|
+
}
|
|
7289
|
+
this.data.columnSizing[columnName] = width;
|
|
7290
|
+
return this;
|
|
7291
|
+
}
|
|
7292
|
+
/**
|
|
7293
|
+
* Set widths for multiple columns
|
|
7294
|
+
* @example .columnWidths({ email: 200, name: 300, status: 100 })
|
|
7295
|
+
*/
|
|
7296
|
+
columnWidths(widths) {
|
|
7297
|
+
this.data.columnSizing = { ...this.data.columnSizing, ...widths };
|
|
7298
|
+
return this;
|
|
7299
|
+
}
|
|
7300
|
+
/**
|
|
7301
|
+
* Set default filters
|
|
7302
|
+
* @example .filter({ combinator: "and", rules: [{ attribute: "status", operator: "is", value: "active" }] })
|
|
7303
|
+
*/
|
|
7304
|
+
filter(filters) {
|
|
7305
|
+
this.data.defaultFilters = filters;
|
|
7306
|
+
return this;
|
|
7307
|
+
}
|
|
7308
|
+
/**
|
|
7309
|
+
* Add a single sort rule
|
|
7310
|
+
* @example .sort("lastName", "asc")
|
|
7311
|
+
*/
|
|
7312
|
+
sort(attribute, direction = "asc") {
|
|
7313
|
+
if (!this.data.defaultSorts) {
|
|
7314
|
+
this.data.defaultSorts = [];
|
|
7315
|
+
}
|
|
7316
|
+
this.data.defaultSorts.push({ attribute, direction });
|
|
7317
|
+
return this;
|
|
7318
|
+
}
|
|
7319
|
+
/**
|
|
7320
|
+
* Set multiple sort rules
|
|
7321
|
+
* @example .sorts([{ attribute: "lastName", direction: "asc" }, { attribute: "firstName", direction: "asc" }])
|
|
7322
|
+
*/
|
|
7323
|
+
sorts(rules) {
|
|
7324
|
+
this.data.defaultSorts = rules;
|
|
7325
|
+
return this;
|
|
7326
|
+
}
|
|
7327
|
+
/**
|
|
7328
|
+
* Add an internal tab (filter preset)
|
|
7329
|
+
* Returns a TabConfigBuilder for chaining tab configuration
|
|
7330
|
+
* @example
|
|
7331
|
+
* ```typescript
|
|
7332
|
+
* .tab("all", "All Contacts").icon("users").default()
|
|
7333
|
+
* .tab("active", "Active").icon("check").filter({ ... })
|
|
7334
|
+
* ```
|
|
7335
|
+
*/
|
|
7336
|
+
tab(id, label) {
|
|
7337
|
+
return new ListViewTabConfigBuilder(this, id, label);
|
|
7338
|
+
}
|
|
7339
|
+
/**
|
|
7340
|
+
* @internal Used by ListViewTabConfigBuilder to add tabs
|
|
7341
|
+
*/
|
|
7342
|
+
_addTab(tab) {
|
|
7343
|
+
this.data.tabs.push(tab);
|
|
7344
|
+
return this;
|
|
7345
|
+
}
|
|
7346
|
+
/**
|
|
7347
|
+
* Build the final list view definition
|
|
7348
|
+
*/
|
|
7349
|
+
build() {
|
|
7350
|
+
if (!this.data.object) {
|
|
7351
|
+
throw new Error("[ListViewBuilder] for() is required - specify the target object");
|
|
7352
|
+
}
|
|
7353
|
+
if (this.data.columns.length === 0) {
|
|
7354
|
+
throw new Error("[ListViewBuilder] columns() is required - specify at least one column");
|
|
7355
|
+
}
|
|
7356
|
+
if (this.data.layout === "kanban" && !this.data.groupByAttribute) {
|
|
7357
|
+
throw new Error(
|
|
7358
|
+
"[ListViewBuilder] kanban() requires a groupByAttribute - specify the attribute to group by"
|
|
7359
|
+
);
|
|
7360
|
+
}
|
|
7361
|
+
const tabIds = /* @__PURE__ */ new Set();
|
|
7362
|
+
for (const tab of this.data.tabs) {
|
|
7363
|
+
if (tabIds.has(tab.id)) {
|
|
7364
|
+
throw new Error(`[ListViewBuilder] Duplicate tab id "${tab.id}"`);
|
|
7365
|
+
}
|
|
7366
|
+
tabIds.add(tab.id);
|
|
7367
|
+
}
|
|
7368
|
+
const defaultTabs = this.data.tabs.filter((t) => t.default);
|
|
7369
|
+
if (defaultTabs.length > 1) {
|
|
7370
|
+
throw new Error("[ListViewBuilder] Only one tab can be marked as default");
|
|
7371
|
+
}
|
|
7372
|
+
const config = {
|
|
7373
|
+
layout: this.data.layout,
|
|
7374
|
+
columns: this.data.columns,
|
|
7375
|
+
columnSizing: this.data.columnSizing,
|
|
7376
|
+
defaultFilters: this.data.defaultFilters ? {
|
|
7377
|
+
id: randomUUID(),
|
|
7378
|
+
combinator: this.data.defaultFilters.combinator,
|
|
7379
|
+
rules: this.data.defaultFilters.rules
|
|
7380
|
+
} : void 0,
|
|
7381
|
+
defaultSorts: this.data.defaultSorts,
|
|
7382
|
+
groupByAttribute: this.data.groupByAttribute,
|
|
7383
|
+
tabs: this.data.tabs.length > 0 ? this.data.tabs.map((tab) => ({
|
|
7384
|
+
id: tab.id,
|
|
7385
|
+
label: tab.label,
|
|
7386
|
+
icon: tab.icon,
|
|
7387
|
+
filters: tab.filters ? {
|
|
7388
|
+
id: randomUUID(),
|
|
7389
|
+
combinator: tab.filters.combinator,
|
|
7390
|
+
rules: tab.filters.rules
|
|
7391
|
+
} : void 0,
|
|
7392
|
+
default: tab.default
|
|
7393
|
+
})) : void 0
|
|
7394
|
+
};
|
|
7395
|
+
return {
|
|
7396
|
+
name: this.data.name,
|
|
7397
|
+
label: this.data.label,
|
|
7398
|
+
description: this.data.description,
|
|
7399
|
+
icon: this.data.icon,
|
|
7400
|
+
object: this.data.object,
|
|
7401
|
+
type: "list",
|
|
7402
|
+
config,
|
|
7403
|
+
default: this.data.default,
|
|
7404
|
+
metadata: this.data.metadata
|
|
7405
|
+
};
|
|
7406
|
+
}
|
|
7407
|
+
/**
|
|
7408
|
+
* Validate view name format (kebab-case)
|
|
7409
|
+
*/
|
|
7410
|
+
validateName(name) {
|
|
7411
|
+
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-]*$/, {
|
|
7412
|
+
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'"
|
|
7413
|
+
});
|
|
7414
|
+
try {
|
|
7415
|
+
viewNameSchema.parse(name);
|
|
7416
|
+
} catch (error2) {
|
|
7417
|
+
if (error2 instanceof z3.ZodError) {
|
|
7418
|
+
throw new Error(`[ListViewBuilder] ${error2.issues[0].message}`);
|
|
7419
|
+
}
|
|
7420
|
+
throw error2;
|
|
7421
|
+
}
|
|
7422
|
+
}
|
|
7423
|
+
};
|
|
7424
|
+
var ListViewTabConfigBuilder = class _ListViewTabConfigBuilder {
|
|
7425
|
+
/** @internal */
|
|
7426
|
+
constructor(view2, id, label) {
|
|
7427
|
+
this.view = view2;
|
|
7428
|
+
this.tabData = { id, label };
|
|
7429
|
+
}
|
|
7430
|
+
/**
|
|
7431
|
+
* Set tab icon
|
|
7432
|
+
*/
|
|
7433
|
+
icon(value) {
|
|
7434
|
+
this.tabData.icon = value;
|
|
7435
|
+
return this;
|
|
7436
|
+
}
|
|
7437
|
+
/**
|
|
7438
|
+
* Set filters for this tab (filter preset)
|
|
7439
|
+
* @example .filter({ combinator: "and", rules: [{ attribute: "status", operator: "is", value: "active" }] })
|
|
7440
|
+
*/
|
|
7441
|
+
filter(filters) {
|
|
7442
|
+
this.tabData.filters = filters;
|
|
7443
|
+
return this;
|
|
7444
|
+
}
|
|
7445
|
+
/**
|
|
7446
|
+
* Mark this tab as the default tab
|
|
7447
|
+
*/
|
|
7448
|
+
default() {
|
|
7449
|
+
this.tabData.default = true;
|
|
7450
|
+
return this;
|
|
7451
|
+
}
|
|
7452
|
+
/**
|
|
7453
|
+
* Continue building with a new tab
|
|
7454
|
+
*/
|
|
7455
|
+
tab(id, label) {
|
|
7456
|
+
this.view._addTab(this.tabData);
|
|
7457
|
+
return new _ListViewTabConfigBuilder(this.view, id, label);
|
|
7458
|
+
}
|
|
7459
|
+
/**
|
|
7460
|
+
* Finish this tab and return to ListViewBuilder
|
|
7461
|
+
*/
|
|
7462
|
+
done() {
|
|
7463
|
+
this.view._addTab(this.tabData);
|
|
7464
|
+
return this.view;
|
|
6930
7465
|
}
|
|
6931
7466
|
/**
|
|
6932
|
-
*
|
|
7467
|
+
* Build the final view definition
|
|
6933
7468
|
*/
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
});
|
|
6938
|
-
try {
|
|
6939
|
-
viewNameSchema.parse(name);
|
|
6940
|
-
} catch (error2) {
|
|
6941
|
-
if (error2 instanceof z3.ZodError) {
|
|
6942
|
-
throw new Error(`[ViewBuilder] ${error2.issues[0].message}`);
|
|
6943
|
-
}
|
|
6944
|
-
throw error2;
|
|
6945
|
-
}
|
|
7469
|
+
build() {
|
|
7470
|
+
this.view._addTab(this.tabData);
|
|
7471
|
+
return this.view.build();
|
|
6946
7472
|
}
|
|
6947
7473
|
};
|
|
6948
|
-
function
|
|
6949
|
-
return new
|
|
7474
|
+
function listView(name, label) {
|
|
7475
|
+
return new ListViewBuilder(name, label);
|
|
6950
7476
|
}
|
|
6951
7477
|
function group(id, label) {
|
|
6952
7478
|
return new GroupBuilder(id, label);
|
|
@@ -7492,6 +8018,46 @@ function workflow(name, label) {
|
|
|
7492
8018
|
return new WorkflowBuilder(name, label);
|
|
7493
8019
|
}
|
|
7494
8020
|
|
|
8021
|
+
// src/types/attribute-protection.ts
|
|
8022
|
+
var IDENTITY_PROPERTIES = ["name", "type"];
|
|
8023
|
+
var BEHAVIOR_PROPERTIES = [
|
|
8024
|
+
"required",
|
|
8025
|
+
"disabled",
|
|
8026
|
+
"hidden",
|
|
8027
|
+
"archived",
|
|
8028
|
+
"deprecated",
|
|
8029
|
+
"defaultValue",
|
|
8030
|
+
"config",
|
|
8031
|
+
"order",
|
|
8032
|
+
"unique"
|
|
8033
|
+
];
|
|
8034
|
+
var PRESENTATION_PROPERTIES = ["label", "description", "placeholder", "icon"];
|
|
8035
|
+
function isIdentityProperty(property) {
|
|
8036
|
+
return IDENTITY_PROPERTIES.includes(property);
|
|
8037
|
+
}
|
|
8038
|
+
function isBehaviorProperty(property) {
|
|
8039
|
+
return BEHAVIOR_PROPERTIES.includes(property);
|
|
8040
|
+
}
|
|
8041
|
+
function isPresentationProperty(property) {
|
|
8042
|
+
return PRESENTATION_PROPERTIES.includes(property);
|
|
8043
|
+
}
|
|
8044
|
+
function getPropertyProtectionLevel(property) {
|
|
8045
|
+
if (isIdentityProperty(property)) return "identity";
|
|
8046
|
+
if (isBehaviorProperty(property)) return "behavior";
|
|
8047
|
+
if (isPresentationProperty(property)) return "presentation";
|
|
8048
|
+
return "unknown";
|
|
8049
|
+
}
|
|
8050
|
+
function filterPropertiesByCategory(properties, category) {
|
|
8051
|
+
switch (category) {
|
|
8052
|
+
case "identity":
|
|
8053
|
+
return properties.filter(isIdentityProperty);
|
|
8054
|
+
case "behavior":
|
|
8055
|
+
return properties.filter(isBehaviorProperty);
|
|
8056
|
+
case "presentation":
|
|
8057
|
+
return properties.filter(isPresentationProperty);
|
|
8058
|
+
}
|
|
8059
|
+
}
|
|
8060
|
+
|
|
7495
8061
|
// src/types/errors.ts
|
|
7496
8062
|
var RecordReferencedError = class extends Error {
|
|
7497
8063
|
constructor(recordId, references) {
|
|
@@ -8321,7 +8887,8 @@ var ObjectSchemaService = class extends BaseService {
|
|
|
8321
8887
|
}
|
|
8322
8888
|
/**
|
|
8323
8889
|
* Update an attribute.
|
|
8324
|
-
*
|
|
8890
|
+
* Custom attributes can be fully updated.
|
|
8891
|
+
* System attributes can only have presentation properties modified (label, description, placeholder, icon).
|
|
8325
8892
|
* Automatically uses tenant context from AsyncLocalStorage.
|
|
8326
8893
|
*
|
|
8327
8894
|
* @param attributeId - Attribute UUID
|
|
@@ -8333,20 +8900,34 @@ var ObjectSchemaService = class extends BaseService {
|
|
|
8333
8900
|
if (!dbAttr) {
|
|
8334
8901
|
throw new Error(`Attribute with id "${attributeId}" not found`);
|
|
8335
8902
|
}
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
);
|
|
8903
|
+
const requestedKeys = Object.keys(updates);
|
|
8904
|
+
const identityChanges = requestedKeys.filter(
|
|
8905
|
+
(k) => IDENTITY_PROPERTIES.includes(k)
|
|
8906
|
+
);
|
|
8907
|
+
if (identityChanges.length > 0) {
|
|
8908
|
+
const actualChanges = identityChanges.filter((k) => {
|
|
8909
|
+
const key = k;
|
|
8910
|
+
return updates[key] !== void 0 && updates[key] !== dbAttr[key];
|
|
8911
|
+
});
|
|
8912
|
+
if (actualChanges.length > 0) {
|
|
8913
|
+
throw new Error(
|
|
8914
|
+
`Cannot modify identity properties: ${actualChanges.join(", ")}. These properties cannot be changed after creation.`
|
|
8915
|
+
);
|
|
8916
|
+
}
|
|
8345
8917
|
}
|
|
8346
|
-
if (
|
|
8347
|
-
|
|
8348
|
-
|
|
8918
|
+
if (dbAttr.system) {
|
|
8919
|
+
const behaviorChanges = requestedKeys.filter(
|
|
8920
|
+
(k) => BEHAVIOR_PROPERTIES.includes(k)
|
|
8349
8921
|
);
|
|
8922
|
+
const actualBehaviorChanges = behaviorChanges.filter((k) => {
|
|
8923
|
+
const key = k;
|
|
8924
|
+
return updates[key] !== void 0;
|
|
8925
|
+
});
|
|
8926
|
+
if (actualBehaviorChanges.length > 0) {
|
|
8927
|
+
throw new Error(
|
|
8928
|
+
`Cannot modify behavior properties on system attribute "${dbAttr.name}": ${actualBehaviorChanges.join(", ")}. Only presentation properties (${PRESENTATION_PROPERTIES.join(", ")}) are editable.`
|
|
8929
|
+
);
|
|
8930
|
+
}
|
|
8350
8931
|
}
|
|
8351
8932
|
const mergedInput = {
|
|
8352
8933
|
name: dbAttr.name,
|
|
@@ -12231,7 +12812,6 @@ var WorkflowAccessGrantService = class extends BaseService {
|
|
|
12231
12812
|
};
|
|
12232
12813
|
|
|
12233
12814
|
// src/runtime/services/workflow/instance.service.ts
|
|
12234
|
-
import { randomUUID } from "crypto";
|
|
12235
12815
|
var WorkflowInstanceService = class extends BaseService {
|
|
12236
12816
|
constructor(adapter, workflowService, options) {
|
|
12237
12817
|
super(adapter);
|
|
@@ -12497,7 +13077,7 @@ var WorkflowInstanceService = class extends BaseService {
|
|
|
12497
13077
|
updatedAt: /* @__PURE__ */ new Date()
|
|
12498
13078
|
};
|
|
12499
13079
|
}
|
|
12500
|
-
const executionId =
|
|
13080
|
+
const executionId = generateId();
|
|
12501
13081
|
let current = {
|
|
12502
13082
|
...instance,
|
|
12503
13083
|
context: {
|
|
@@ -16172,124 +16752,340 @@ var PermissionService = class extends BaseService {
|
|
|
16172
16752
|
|
|
16173
16753
|
// src/runtime/services/view.service.ts
|
|
16174
16754
|
var ViewService = class extends BaseService {
|
|
16175
|
-
constructor(adapter
|
|
16755
|
+
constructor(adapter) {
|
|
16176
16756
|
super(adapter);
|
|
16177
|
-
this.nativeViews = nativeViews;
|
|
16178
16757
|
}
|
|
16179
16758
|
// ============================================================================
|
|
16180
16759
|
// CACHE MANAGEMENT
|
|
16181
16760
|
// ============================================================================
|
|
16182
|
-
/**
|
|
16183
|
-
* Invalidate cached views for an object.
|
|
16184
|
-
* Called automatically after view mutations.
|
|
16185
|
-
*/
|
|
16186
16761
|
async invalidateViewCache(objectName) {
|
|
16187
16762
|
await this.invalidateCache(cacheKeys.viewsByObject(this.tenantId, objectName));
|
|
16188
16763
|
}
|
|
16764
|
+
// ============================================================================
|
|
16765
|
+
// READ METHODS
|
|
16766
|
+
// ============================================================================
|
|
16189
16767
|
/**
|
|
16190
|
-
* Get all views for
|
|
16191
|
-
*
|
|
16768
|
+
* Get all views for the current tenant.
|
|
16769
|
+
* Optionally filter by view type.
|
|
16192
16770
|
*
|
|
16193
|
-
*
|
|
16771
|
+
* @param type - Optional view type filter
|
|
16772
|
+
* @returns All views for the tenant
|
|
16773
|
+
*/
|
|
16774
|
+
async getAllViews(type) {
|
|
16775
|
+
const dbViews = await this.adapter.views.findAllForTenant(type);
|
|
16776
|
+
return dbViews.map((v) => this.convertDBViewToDefinition(v));
|
|
16777
|
+
}
|
|
16778
|
+
/**
|
|
16779
|
+
* Get a specific view by its ID.
|
|
16780
|
+
* Returns null if not found.
|
|
16194
16781
|
*
|
|
16195
|
-
* @param
|
|
16196
|
-
* @returns
|
|
16782
|
+
* @param viewId - View ID (UUID)
|
|
16783
|
+
* @returns View definition or null
|
|
16197
16784
|
*/
|
|
16198
|
-
async
|
|
16199
|
-
|
|
16785
|
+
async getViewById(viewId) {
|
|
16786
|
+
const dbView = await this.adapter.views.findById(viewId);
|
|
16787
|
+
if (!dbView) {
|
|
16788
|
+
return null;
|
|
16789
|
+
}
|
|
16790
|
+
return this.convertDBViewToDefinition(dbView);
|
|
16200
16791
|
}
|
|
16201
16792
|
/**
|
|
16202
|
-
*
|
|
16793
|
+
* Get all views for an object from the database.
|
|
16794
|
+
* Optionally filter by type and merge with user overlays.
|
|
16795
|
+
*
|
|
16796
|
+
* @param objectName - Object name
|
|
16797
|
+
* @param options - Filter and overlay options
|
|
16798
|
+
* @returns Views from database
|
|
16203
16799
|
*/
|
|
16204
|
-
async
|
|
16205
|
-
const
|
|
16206
|
-
const dbViews = await this.adapter.views.findByObjectName(objectName);
|
|
16207
|
-
const
|
|
16208
|
-
|
|
16800
|
+
async getViews(objectName, options = {}) {
|
|
16801
|
+
const { type, userId } = options;
|
|
16802
|
+
const dbViews = await this.adapter.views.findByObjectName(objectName, type);
|
|
16803
|
+
const views = dbViews.map((v) => this.convertDBViewToDefinition(v));
|
|
16804
|
+
if (!userId) {
|
|
16805
|
+
return views;
|
|
16806
|
+
}
|
|
16807
|
+
const userOverlays = await this.adapter.viewOverlays.findByUser(userId);
|
|
16808
|
+
return views.map((view2) => {
|
|
16809
|
+
const overlay = userOverlays.find((o) => o.viewId === view2.id);
|
|
16810
|
+
return overlay ? this.applyOverlay(view2, overlay) : view2;
|
|
16811
|
+
});
|
|
16209
16812
|
}
|
|
16210
16813
|
/**
|
|
16211
16814
|
* Get a specific view by name.
|
|
16212
|
-
*
|
|
16815
|
+
* Returns null if not found (use getDefaultView for fallback behavior).
|
|
16213
16816
|
*
|
|
16214
16817
|
* @param objectName - Object name
|
|
16215
16818
|
* @param viewName - View name
|
|
16216
|
-
* @
|
|
16217
|
-
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
|
|
16819
|
+
* @param options - Type filter and overlay options
|
|
16820
|
+
* @returns View or null
|
|
16821
|
+
*/
|
|
16822
|
+
async getView(objectName, viewName, options = {}) {
|
|
16823
|
+
const { type, userId } = options;
|
|
16824
|
+
let dbView;
|
|
16825
|
+
if (type) {
|
|
16826
|
+
dbView = await this.adapter.views.findByNameAndType(objectName, viewName, type);
|
|
16827
|
+
} else {
|
|
16828
|
+
dbView = await this.adapter.views.findByName(objectName, viewName);
|
|
16222
16829
|
}
|
|
16223
|
-
|
|
16224
|
-
|
|
16225
|
-
return this.convertDBViewToDefinition(dbView);
|
|
16830
|
+
if (!dbView) {
|
|
16831
|
+
return null;
|
|
16226
16832
|
}
|
|
16227
|
-
|
|
16833
|
+
const view2 = this.convertDBViewToDefinition(dbView);
|
|
16834
|
+
if (!userId) {
|
|
16835
|
+
return view2;
|
|
16836
|
+
}
|
|
16837
|
+
const overlay = await this.adapter.viewOverlays.findByViewAndUser(dbView.id, userId);
|
|
16838
|
+
return overlay ? this.applyOverlay(view2, overlay) : view2;
|
|
16228
16839
|
}
|
|
16229
16840
|
/**
|
|
16230
|
-
* Get the default view for an object
|
|
16841
|
+
* Get the default view for an object and type.
|
|
16842
|
+
* If no view exists in DB, auto-creates and persists a default view.
|
|
16231
16843
|
*
|
|
16232
16844
|
* Priority:
|
|
16233
|
-
* 1.
|
|
16234
|
-
* 2.
|
|
16235
|
-
* 3. First available view (
|
|
16845
|
+
* 1. User's preferred view (from overlay with isUserDefault=true)
|
|
16846
|
+
* 2. View marked as default in DB (with matching layout if specified)
|
|
16847
|
+
* 3. First available view (with matching layout if specified)
|
|
16848
|
+
* 4. Auto-created default view (persisted to DB)
|
|
16236
16849
|
*
|
|
16237
16850
|
* @param objectName - Object name
|
|
16238
|
-
* @param
|
|
16239
|
-
* @
|
|
16851
|
+
* @param type - View type
|
|
16852
|
+
* @param objectDefinition - Object definition (for default generation)
|
|
16853
|
+
* @param options - Optional filters (userId, layout for detail views)
|
|
16854
|
+
* @returns View definition (existing or auto-created)
|
|
16855
|
+
*/
|
|
16856
|
+
async getDefaultView(objectName, type, objectDefinition, options) {
|
|
16857
|
+
const { userId, layout } = options ?? {};
|
|
16858
|
+
const matchesLayout = (view2) => {
|
|
16859
|
+
if (!layout || type !== "detail") return true;
|
|
16860
|
+
const detailView2 = view2;
|
|
16861
|
+
return detailView2.config.layout === layout;
|
|
16862
|
+
};
|
|
16863
|
+
const applyUserOverlay = async (view2, viewId) => {
|
|
16864
|
+
if (!userId) return view2;
|
|
16865
|
+
const overlay = await this.adapter.viewOverlays.findByViewAndUser(viewId, userId);
|
|
16866
|
+
return overlay ? this.applyOverlay(view2, overlay) : view2;
|
|
16867
|
+
};
|
|
16868
|
+
if (userId) {
|
|
16869
|
+
const userDefaultOverlay = await this.adapter.viewOverlays.findUserDefault(
|
|
16870
|
+
userId,
|
|
16871
|
+
objectName,
|
|
16872
|
+
type
|
|
16873
|
+
);
|
|
16874
|
+
if (userDefaultOverlay) {
|
|
16875
|
+
const dbView = await this.adapter.views.findById(userDefaultOverlay.viewId);
|
|
16876
|
+
if (dbView && dbView.type === type) {
|
|
16877
|
+
const view2 = this.convertDBViewToDefinition(dbView);
|
|
16878
|
+
if (matchesLayout(view2)) {
|
|
16879
|
+
return this.applyOverlay(view2, userDefaultOverlay);
|
|
16880
|
+
}
|
|
16881
|
+
}
|
|
16882
|
+
}
|
|
16883
|
+
}
|
|
16884
|
+
const defaultDbView = await this.adapter.views.findDefault(objectName, type);
|
|
16885
|
+
if (defaultDbView) {
|
|
16886
|
+
const view2 = this.convertDBViewToDefinition(defaultDbView);
|
|
16887
|
+
if (matchesLayout(view2)) {
|
|
16888
|
+
return await applyUserOverlay(view2, defaultDbView.id);
|
|
16889
|
+
}
|
|
16890
|
+
}
|
|
16891
|
+
const dbViews = await this.adapter.views.findByObjectName(objectName, type);
|
|
16892
|
+
for (const dbView of dbViews) {
|
|
16893
|
+
const view2 = this.convertDBViewToDefinition(dbView);
|
|
16894
|
+
if (matchesLayout(view2)) {
|
|
16895
|
+
return await applyUserOverlay(view2, dbView.id);
|
|
16896
|
+
}
|
|
16897
|
+
}
|
|
16898
|
+
if (dbViews.length > 0) {
|
|
16899
|
+
const firstView = dbViews[0];
|
|
16900
|
+
const view2 = this.convertDBViewToDefinition(firstView);
|
|
16901
|
+
return await applyUserOverlay(view2, firstView.id);
|
|
16902
|
+
}
|
|
16903
|
+
const created = await this.ensureDefaultView(objectName, type, objectDefinition, layout);
|
|
16904
|
+
if (userId) {
|
|
16905
|
+
if (!created.id) {
|
|
16906
|
+
throw new Error("Created view missing ID - this should never happen");
|
|
16907
|
+
}
|
|
16908
|
+
const overlay = await this.adapter.viewOverlays.findByViewAndUser(created.id, userId);
|
|
16909
|
+
return overlay ? this.applyOverlay(created, overlay) : created;
|
|
16910
|
+
}
|
|
16911
|
+
return created;
|
|
16912
|
+
}
|
|
16913
|
+
// ============================================================================
|
|
16914
|
+
// DEFAULT VIEW GENERATION
|
|
16915
|
+
// ============================================================================
|
|
16916
|
+
/**
|
|
16917
|
+
* Ensure a default view exists in DB for the given object and type.
|
|
16918
|
+
* If no view exists, generates and persists one.
|
|
16919
|
+
* Idempotent — safe to call concurrently (uses upsert).
|
|
16240
16920
|
*/
|
|
16241
|
-
async
|
|
16242
|
-
const
|
|
16243
|
-
const
|
|
16244
|
-
const
|
|
16245
|
-
|
|
16246
|
-
|
|
16247
|
-
|
|
16248
|
-
|
|
16921
|
+
async ensureDefaultView(objectName, type, objectDefinition, layout) {
|
|
16922
|
+
const generated = this.generateDefaultViewConfig(objectName, type, objectDefinition, layout);
|
|
16923
|
+
const viewName = type === "detail" && layout === "modal" ? "default-modal" : "default";
|
|
16924
|
+
const dbView = await this.adapter.views.upsert({
|
|
16925
|
+
objectName,
|
|
16926
|
+
type,
|
|
16927
|
+
name: viewName,
|
|
16928
|
+
label: generated.label,
|
|
16929
|
+
config: generated.config,
|
|
16930
|
+
default: true
|
|
16931
|
+
});
|
|
16932
|
+
const legacyFallbackId = `fallback:${objectName}:${type}`;
|
|
16933
|
+
await this.adapter.viewOverlays.migrateViewId(legacyFallbackId, dbView.id);
|
|
16934
|
+
await this.invalidateViewCache(objectName);
|
|
16935
|
+
return this.convertDBViewToDefinition(dbView);
|
|
16249
16936
|
}
|
|
16250
16937
|
/**
|
|
16251
|
-
*
|
|
16252
|
-
*
|
|
16938
|
+
* Generate default view config for an object.
|
|
16939
|
+
* Used by ensureDefaultView() and resetViewToDefault().
|
|
16940
|
+
*/
|
|
16941
|
+
generateDefaultViewConfig(objectName, type, objectDefinition, layout) {
|
|
16942
|
+
switch (type) {
|
|
16943
|
+
case "detail":
|
|
16944
|
+
return this.generateDefaultDetailConfig(objectName, objectDefinition, layout);
|
|
16945
|
+
case "list":
|
|
16946
|
+
return this.generateDefaultListConfig(objectName, objectDefinition);
|
|
16947
|
+
default:
|
|
16948
|
+
throw new SchemaError(
|
|
16949
|
+
`Default view generation not supported for type: ${type}`,
|
|
16950
|
+
SchemaErrorCode.VALIDATION_FAILED,
|
|
16951
|
+
{ type }
|
|
16952
|
+
);
|
|
16953
|
+
}
|
|
16954
|
+
}
|
|
16955
|
+
generateDefaultDetailConfig(objectName, object2, layout = "page") {
|
|
16956
|
+
const visibleAttrs = object2.attributes.filter(
|
|
16957
|
+
(attr) => !(attr.hidden || attr.archived || attr.system)
|
|
16958
|
+
);
|
|
16959
|
+
const fields = visibleAttrs.sort((a, b) => (a.order ?? 999) - (b.order ?? 999)).map((attr) => ({
|
|
16960
|
+
attribute: attr.name,
|
|
16961
|
+
span: this.getDefaultFieldSpan(attr.type)
|
|
16962
|
+
}));
|
|
16963
|
+
const formTab = {
|
|
16964
|
+
id: "form",
|
|
16965
|
+
name: "form",
|
|
16966
|
+
label: "Details",
|
|
16967
|
+
type: "form",
|
|
16968
|
+
groups: [
|
|
16969
|
+
{
|
|
16970
|
+
id: "general",
|
|
16971
|
+
label: "General",
|
|
16972
|
+
fields,
|
|
16973
|
+
order: 0
|
|
16974
|
+
}
|
|
16975
|
+
],
|
|
16976
|
+
order: 0
|
|
16977
|
+
};
|
|
16978
|
+
const tabs = [formTab];
|
|
16979
|
+
tabs.push({
|
|
16980
|
+
id: "activity",
|
|
16981
|
+
name: "activity",
|
|
16982
|
+
label: "Activity",
|
|
16983
|
+
type: "activity",
|
|
16984
|
+
order: 1
|
|
16985
|
+
});
|
|
16986
|
+
tabs.push({
|
|
16987
|
+
id: "notes",
|
|
16988
|
+
name: "notes",
|
|
16989
|
+
label: "Notes",
|
|
16990
|
+
type: "notes",
|
|
16991
|
+
order: 2,
|
|
16992
|
+
allowCreate: true
|
|
16993
|
+
});
|
|
16994
|
+
const hasDocuments = object2.attributes.some((attr) => attr.type === "document");
|
|
16995
|
+
if (hasDocuments) {
|
|
16996
|
+
tabs.push({
|
|
16997
|
+
id: "documents",
|
|
16998
|
+
name: "documents",
|
|
16999
|
+
label: "Documents",
|
|
17000
|
+
type: "documents",
|
|
17001
|
+
order: 3,
|
|
17002
|
+
allowUpload: true,
|
|
17003
|
+
allowRemove: true,
|
|
17004
|
+
showProcessing: true
|
|
17005
|
+
});
|
|
17006
|
+
}
|
|
17007
|
+
const config = {
|
|
17008
|
+
layout,
|
|
17009
|
+
tabs
|
|
17010
|
+
};
|
|
17011
|
+
return {
|
|
17012
|
+
name: "default",
|
|
17013
|
+
label: "Default View",
|
|
17014
|
+
object: objectName,
|
|
17015
|
+
type: "detail",
|
|
17016
|
+
config,
|
|
17017
|
+
default: true
|
|
17018
|
+
};
|
|
17019
|
+
}
|
|
17020
|
+
generateDefaultListConfig(objectName, object2) {
|
|
17021
|
+
const visibleAttrs = object2.attributes.filter(
|
|
17022
|
+
(attr) => !(attr.hidden || attr.archived || attr.system)
|
|
17023
|
+
);
|
|
17024
|
+
const columns = visibleAttrs.sort((a, b) => (a.order ?? 999) - (b.order ?? 999)).slice(0, 5).map((attr) => attr.name);
|
|
17025
|
+
const config = {
|
|
17026
|
+
layout: "table",
|
|
17027
|
+
columns
|
|
17028
|
+
};
|
|
17029
|
+
return {
|
|
17030
|
+
name: "default",
|
|
17031
|
+
label: "All Records",
|
|
17032
|
+
object: objectName,
|
|
17033
|
+
type: "list",
|
|
17034
|
+
config,
|
|
17035
|
+
default: true
|
|
17036
|
+
};
|
|
17037
|
+
}
|
|
17038
|
+
getDefaultFieldSpan(type) {
|
|
17039
|
+
switch (type) {
|
|
17040
|
+
case "textarea":
|
|
17041
|
+
case "richtext":
|
|
17042
|
+
case "location":
|
|
17043
|
+
return 12;
|
|
17044
|
+
case "checkbox":
|
|
17045
|
+
return 4;
|
|
17046
|
+
default:
|
|
17047
|
+
return 6;
|
|
17048
|
+
}
|
|
17049
|
+
}
|
|
17050
|
+
// ============================================================================
|
|
17051
|
+
// WRITE METHODS (Architect Mode)
|
|
17052
|
+
// ============================================================================
|
|
17053
|
+
/**
|
|
17054
|
+
* Create a new view (Architect Mode).
|
|
16253
17055
|
*
|
|
16254
17056
|
* @param input - View definition
|
|
16255
17057
|
* @returns Created view
|
|
16256
17058
|
*/
|
|
16257
17059
|
async createView(input) {
|
|
16258
17060
|
this.validateViewName(input.name);
|
|
16259
|
-
this.
|
|
16260
|
-
|
|
17061
|
+
const existing = await this.adapter.views.findByNameAndType(
|
|
17062
|
+
input.objectName,
|
|
17063
|
+
input.name,
|
|
17064
|
+
input.type
|
|
17065
|
+
);
|
|
16261
17066
|
if (existing) {
|
|
16262
17067
|
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`,
|
|
17068
|
+
`View "${input.name}" of type "${input.type}" already exists for object "${input.objectName}"`,
|
|
16271
17069
|
SchemaErrorCode.DUPLICATE_ATTRIBUTE,
|
|
16272
|
-
{ viewName: input.name,
|
|
17070
|
+
{ viewName: input.name, type: input.type, objectName: input.objectName }
|
|
16273
17071
|
);
|
|
16274
17072
|
}
|
|
16275
17073
|
const dbView = await this.adapter.views.create({
|
|
16276
17074
|
objectName: input.objectName,
|
|
17075
|
+
type: input.type,
|
|
16277
17076
|
name: input.name,
|
|
16278
17077
|
label: input.label,
|
|
16279
17078
|
description: input.description,
|
|
16280
17079
|
icon: input.icon,
|
|
16281
|
-
|
|
16282
|
-
|
|
16283
|
-
default: input.default ?? false,
|
|
16284
|
-
system: false,
|
|
16285
|
-
// Custom views are never system
|
|
17080
|
+
config: input.config,
|
|
17081
|
+
default: input.default,
|
|
16286
17082
|
metadata: input.metadata
|
|
16287
17083
|
});
|
|
16288
17084
|
await this.invalidateViewCache(input.objectName);
|
|
16289
17085
|
return this.convertDBViewToDefinition(dbView);
|
|
16290
17086
|
}
|
|
16291
17087
|
/**
|
|
16292
|
-
* Update
|
|
17088
|
+
* Update an existing view.
|
|
16293
17089
|
*
|
|
16294
17090
|
* @param viewId - View ID
|
|
16295
17091
|
* @param input - Update data
|
|
@@ -16300,18 +17096,11 @@ var ViewService = class extends BaseService {
|
|
|
16300
17096
|
if (!dbView) {
|
|
16301
17097
|
throw new NotFoundError("View", viewId);
|
|
16302
17098
|
}
|
|
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
17099
|
const updated = await this.adapter.views.update(viewId, {
|
|
16310
17100
|
label: input.label,
|
|
16311
17101
|
description: input.description,
|
|
16312
17102
|
icon: input.icon,
|
|
16313
|
-
|
|
16314
|
-
tabs: input.tabs,
|
|
17103
|
+
config: input.config,
|
|
16315
17104
|
default: input.default,
|
|
16316
17105
|
metadata: input.metadata
|
|
16317
17106
|
});
|
|
@@ -16319,7 +17108,8 @@ var ViewService = class extends BaseService {
|
|
|
16319
17108
|
return this.convertDBViewToDefinition(updated);
|
|
16320
17109
|
}
|
|
16321
17110
|
/**
|
|
16322
|
-
* Delete a
|
|
17111
|
+
* Delete a view.
|
|
17112
|
+
* Overlays are automatically deleted (cascade).
|
|
16323
17113
|
*
|
|
16324
17114
|
* @param viewId - View ID
|
|
16325
17115
|
*/
|
|
@@ -16328,16 +17118,12 @@ var ViewService = class extends BaseService {
|
|
|
16328
17118
|
if (!dbView) {
|
|
16329
17119
|
throw new NotFoundError("View", viewId);
|
|
16330
17120
|
}
|
|
16331
|
-
|
|
16332
|
-
throw new ProtectedResourceError("view", dbView.name, "delete");
|
|
16333
|
-
}
|
|
17121
|
+
await this.adapter.viewOverlays.deleteByView(viewId);
|
|
16334
17122
|
await this.adapter.views.delete(viewId);
|
|
16335
17123
|
await this.invalidateViewCache(dbView.objectName);
|
|
16336
17124
|
}
|
|
16337
17125
|
/**
|
|
16338
|
-
* Set a view as default for its object and
|
|
16339
|
-
* Only unsets other defaults for the same layout.
|
|
16340
|
-
* Automatically uses tenant context from AsyncLocalStorage.
|
|
17126
|
+
* Set a view as default for its object and type.
|
|
16341
17127
|
*
|
|
16342
17128
|
* @param viewId - View ID
|
|
16343
17129
|
* @returns Updated view
|
|
@@ -16347,11 +17133,9 @@ var ViewService = class extends BaseService {
|
|
|
16347
17133
|
if (!dbView) {
|
|
16348
17134
|
throw new NotFoundError("View", viewId);
|
|
16349
17135
|
}
|
|
16350
|
-
const
|
|
16351
|
-
const currentViews = await this.adapter.views.findByObjectName(dbView.objectName);
|
|
17136
|
+
const currentViews = await this.adapter.views.findByObjectName(dbView.objectName, dbView.type);
|
|
16352
17137
|
for (const v of currentViews) {
|
|
16353
|
-
|
|
16354
|
-
if (v.default && v.id !== viewId && !v.system && vLayout === viewLayout) {
|
|
17138
|
+
if (v.default && v.id !== viewId) {
|
|
16355
17139
|
await this.adapter.views.update(v.id, { default: false });
|
|
16356
17140
|
}
|
|
16357
17141
|
}
|
|
@@ -16359,12 +17143,140 @@ var ViewService = class extends BaseService {
|
|
|
16359
17143
|
await this.invalidateViewCache(dbView.objectName);
|
|
16360
17144
|
return this.convertDBViewToDefinition(updated);
|
|
16361
17145
|
}
|
|
17146
|
+
/**
|
|
17147
|
+
* Reset a view to its default (auto-generated) state.
|
|
17148
|
+
* Regenerates the view config based on the object definition.
|
|
17149
|
+
*
|
|
17150
|
+
* @param viewId - View ID
|
|
17151
|
+
* @param objectDefinition - Object definition for regeneration
|
|
17152
|
+
* @returns Updated view
|
|
17153
|
+
*/
|
|
17154
|
+
async resetViewToDefault(viewId, objectDefinition) {
|
|
17155
|
+
const dbView = await this.adapter.views.findById(viewId);
|
|
17156
|
+
if (!dbView) {
|
|
17157
|
+
throw new NotFoundError("View", viewId);
|
|
17158
|
+
}
|
|
17159
|
+
const generated = this.generateDefaultViewConfig(
|
|
17160
|
+
dbView.objectName,
|
|
17161
|
+
dbView.type,
|
|
17162
|
+
objectDefinition,
|
|
17163
|
+
dbView.type === "detail" ? dbView.config?.layout ?? "page" : void 0
|
|
17164
|
+
);
|
|
17165
|
+
const newConfig = generated.config;
|
|
17166
|
+
const updated = await this.adapter.views.update(viewId, { config: newConfig });
|
|
17167
|
+
await this.invalidateViewCache(dbView.objectName);
|
|
17168
|
+
return this.convertDBViewToDefinition(updated);
|
|
17169
|
+
}
|
|
16362
17170
|
// ============================================================================
|
|
16363
|
-
//
|
|
17171
|
+
// USER CUSTOMIZATION METHODS
|
|
16364
17172
|
// ============================================================================
|
|
16365
17173
|
/**
|
|
16366
|
-
*
|
|
17174
|
+
* Reset user customizations for a view.
|
|
17175
|
+
* Deletes the overlay, returning to source/fallback view.
|
|
17176
|
+
*
|
|
17177
|
+
* @param viewId - View ID (can be UUID or fallback ID)
|
|
17178
|
+
* @param userId - User ID
|
|
17179
|
+
*/
|
|
17180
|
+
async resetUserCustomizations(viewId, userId) {
|
|
17181
|
+
await this.adapter.viewOverlays.deleteByViewAndUser(viewId, userId);
|
|
17182
|
+
}
|
|
17183
|
+
/**
|
|
17184
|
+
* Set a view as the user's default for an object and type.
|
|
17185
|
+
*
|
|
17186
|
+
* @param viewId - View ID (can be UUID or fallback ID)
|
|
17187
|
+
* @param userId - User ID
|
|
17188
|
+
* @param objectName - Object name
|
|
17189
|
+
* @param type - View type
|
|
17190
|
+
*/
|
|
17191
|
+
async setUserDefaultView(viewId, userId, objectName, type) {
|
|
17192
|
+
await this.adapter.viewOverlays.clearUserDefault(userId, objectName, type);
|
|
17193
|
+
const existingOverlay = await this.adapter.viewOverlays.findByViewAndUser(viewId, userId);
|
|
17194
|
+
if (existingOverlay) {
|
|
17195
|
+
await this.adapter.viewOverlays.update(existingOverlay.id, { isUserDefault: true });
|
|
17196
|
+
} else {
|
|
17197
|
+
await this.adapter.viewOverlays.create({
|
|
17198
|
+
viewId,
|
|
17199
|
+
userId,
|
|
17200
|
+
configOverrides: {},
|
|
17201
|
+
isUserDefault: true
|
|
17202
|
+
});
|
|
17203
|
+
}
|
|
17204
|
+
}
|
|
17205
|
+
/**
|
|
17206
|
+
* Check if a user has customized a view.
|
|
17207
|
+
*
|
|
17208
|
+
* @param viewId - View ID
|
|
17209
|
+
* @param userId - User ID
|
|
17210
|
+
* @returns True if overlay exists
|
|
17211
|
+
*/
|
|
17212
|
+
async hasUserCustomizations(viewId, userId) {
|
|
17213
|
+
const overlay = await this.adapter.viewOverlays.findByViewAndUser(viewId, userId);
|
|
17214
|
+
return overlay !== null && Object.keys(overlay.configOverrides).length > 0;
|
|
17215
|
+
}
|
|
17216
|
+
// ============================================================================
|
|
17217
|
+
// OVERLAY MERGE LOGIC
|
|
17218
|
+
// ============================================================================
|
|
17219
|
+
/**
|
|
17220
|
+
* Apply an overlay to a view definition.
|
|
17221
|
+
* Implements merge semantics defined in the plan.
|
|
17222
|
+
*
|
|
17223
|
+
* @param view - Source view definition
|
|
17224
|
+
* @param overlay - User overlay
|
|
17225
|
+
* @returns Merged view definition
|
|
17226
|
+
*/
|
|
17227
|
+
applyOverlay(view2, overlay) {
|
|
17228
|
+
const overrides = overlay.configOverrides;
|
|
17229
|
+
switch (view2.type) {
|
|
17230
|
+
case "list":
|
|
17231
|
+
return this.applyListViewOverlay(view2, overrides);
|
|
17232
|
+
case "detail":
|
|
17233
|
+
return this.applyDetailViewOverlay(view2, overrides);
|
|
17234
|
+
default:
|
|
17235
|
+
return view2;
|
|
17236
|
+
}
|
|
17237
|
+
}
|
|
17238
|
+
applyListViewOverlay(view2, overrides) {
|
|
17239
|
+
const mergedConfig = {
|
|
17240
|
+
...view2.config,
|
|
17241
|
+
tabs: this.mergeViewTabs(view2.config.tabs, overrides.tabs, overrides.hiddenTabIds)
|
|
17242
|
+
};
|
|
17243
|
+
return { ...view2, config: mergedConfig };
|
|
17244
|
+
}
|
|
17245
|
+
applyDetailViewOverlay(view2, overrides) {
|
|
17246
|
+
const mergedConfig = {
|
|
17247
|
+
...view2.config,
|
|
17248
|
+
// Append for tabs (detail tabs)
|
|
17249
|
+
tabs: this.mergeDetailTabs(
|
|
17250
|
+
view2.config.tabs,
|
|
17251
|
+
overrides.detailTabs,
|
|
17252
|
+
overrides.hiddenDetailTabIds
|
|
17253
|
+
)
|
|
17254
|
+
};
|
|
17255
|
+
return {
|
|
17256
|
+
...view2,
|
|
17257
|
+
config: mergedConfig
|
|
17258
|
+
};
|
|
17259
|
+
}
|
|
17260
|
+
/**
|
|
17261
|
+
* Merge source tabs with overlay tabs.
|
|
17262
|
+
* - Source tabs are visible to all
|
|
17263
|
+
* - Overlay tabs are appended (user-private)
|
|
17264
|
+
* - hiddenTabIds allows hiding source tabs
|
|
17265
|
+
*/
|
|
17266
|
+
mergeViewTabs(sourceTabs = [], overlayTabs = [], hiddenTabIds = []) {
|
|
17267
|
+
const visibleSourceTabs = sourceTabs.filter((t) => !hiddenTabIds.includes(t.id));
|
|
17268
|
+
return [...visibleSourceTabs, ...overlayTabs];
|
|
17269
|
+
}
|
|
17270
|
+
/**
|
|
17271
|
+
* Merge detail tabs (form, activity, etc.)
|
|
16367
17272
|
*/
|
|
17273
|
+
mergeDetailTabs(sourceTabs = [], overlayTabs = [], hiddenTabIds = []) {
|
|
17274
|
+
const visibleSourceTabs = sourceTabs.filter((t) => !hiddenTabIds.includes(t.id));
|
|
17275
|
+
return [...visibleSourceTabs, ...overlayTabs];
|
|
17276
|
+
}
|
|
17277
|
+
// ============================================================================
|
|
17278
|
+
// PRIVATE HELPERS
|
|
17279
|
+
// ============================================================================
|
|
16368
17280
|
validateViewName(name) {
|
|
16369
17281
|
if (!name || name.length === 0) {
|
|
16370
17282
|
throw new ValidationError("View name cannot be empty", [
|
|
@@ -16386,66 +17298,72 @@ var ViewService = class extends BaseService {
|
|
|
16386
17298
|
]);
|
|
16387
17299
|
}
|
|
16388
17300
|
}
|
|
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
17301
|
/**
|
|
16412
17302
|
* Convert database view to ViewDefinition
|
|
16413
17303
|
*/
|
|
16414
17304
|
convertDBViewToDefinition(dbView) {
|
|
16415
|
-
|
|
17305
|
+
const base = {
|
|
16416
17306
|
id: dbView.id,
|
|
16417
17307
|
name: dbView.name,
|
|
16418
17308
|
label: dbView.label,
|
|
16419
17309
|
description: dbView.description,
|
|
16420
17310
|
icon: dbView.icon,
|
|
16421
17311
|
object: dbView.objectName,
|
|
16422
|
-
layout: dbView.layout,
|
|
16423
|
-
tabs: dbView.tabs,
|
|
16424
17312
|
default: dbView.default,
|
|
16425
|
-
system: dbView.system,
|
|
16426
17313
|
metadata: dbView.metadata
|
|
16427
17314
|
};
|
|
17315
|
+
switch (dbView.type) {
|
|
17316
|
+
case "detail": {
|
|
17317
|
+
const detailView2 = {
|
|
17318
|
+
...base,
|
|
17319
|
+
type: "detail",
|
|
17320
|
+
config: dbView.config
|
|
17321
|
+
};
|
|
17322
|
+
return detailView2;
|
|
17323
|
+
}
|
|
17324
|
+
case "list": {
|
|
17325
|
+
const listView2 = {
|
|
17326
|
+
...base,
|
|
17327
|
+
type: "list",
|
|
17328
|
+
config: dbView.config
|
|
17329
|
+
};
|
|
17330
|
+
return listView2;
|
|
17331
|
+
}
|
|
17332
|
+
// TODO: Add proper config types for calendar, timeline, gallery when implemented
|
|
17333
|
+
case "calendar":
|
|
17334
|
+
case "timeline":
|
|
17335
|
+
case "gallery":
|
|
17336
|
+
return { ...base, type: dbView.type, config: dbView.config };
|
|
17337
|
+
default:
|
|
17338
|
+
throw new SchemaError(
|
|
17339
|
+
`Unknown view type: ${dbView.type}`,
|
|
17340
|
+
SchemaErrorCode.VALIDATION_FAILED,
|
|
17341
|
+
{ type: dbView.type }
|
|
17342
|
+
);
|
|
17343
|
+
}
|
|
16428
17344
|
}
|
|
16429
17345
|
};
|
|
16430
17346
|
|
|
16431
17347
|
// src/runtime/view-sync.ts
|
|
16432
|
-
async function
|
|
17348
|
+
async function seedRegistryViews(adapter, registry2, options = {}) {
|
|
16433
17349
|
const result = {
|
|
16434
17350
|
success: true,
|
|
16435
17351
|
viewsSynced: 0,
|
|
16436
17352
|
viewsCreated: 0,
|
|
16437
|
-
|
|
17353
|
+
viewsSkipped: 0,
|
|
16438
17354
|
viewsDeleted: 0,
|
|
16439
17355
|
errors: []
|
|
16440
17356
|
};
|
|
16441
|
-
const
|
|
17357
|
+
const registryViews = registry2.getAll();
|
|
16442
17358
|
if (options.verbose && options.logger) {
|
|
16443
|
-
options.logger.info(`[ViewSync] Starting
|
|
17359
|
+
options.logger.info(`[ViewSync] Starting seed for ${registryViews.length} registry views...`);
|
|
16444
17360
|
}
|
|
16445
17361
|
try {
|
|
16446
17362
|
await adapter.transaction(async (tx) => {
|
|
16447
|
-
const
|
|
16448
|
-
|
|
17363
|
+
const viewsByObjectAndType = await seedAllViews(tx, registryViews, result, options);
|
|
17364
|
+
if (options.deleteOrphans) {
|
|
17365
|
+
await cleanupOrphanViews(tx, viewsByObjectAndType, result, options);
|
|
17366
|
+
}
|
|
16449
17367
|
});
|
|
16450
17368
|
} catch (error2) {
|
|
16451
17369
|
handleTransactionError(result, error2);
|
|
@@ -16453,38 +17371,78 @@ async function syncNativeViews(adapter, nativeViewRegistry, options = {}) {
|
|
|
16453
17371
|
logSyncComplete(result, options);
|
|
16454
17372
|
return result;
|
|
16455
17373
|
}
|
|
16456
|
-
|
|
16457
|
-
|
|
16458
|
-
|
|
17374
|
+
var syncNativeViews = seedRegistryViews;
|
|
17375
|
+
async function seedAllViews(tx, registryViews, result, options) {
|
|
17376
|
+
const viewsByObjectAndType = /* @__PURE__ */ new Map();
|
|
17377
|
+
for (const view2 of registryViews) {
|
|
16459
17378
|
try {
|
|
16460
|
-
await
|
|
16461
|
-
|
|
17379
|
+
await seedSingleView(tx, view2, result, options);
|
|
17380
|
+
trackViewByObjectAndType(viewsByObjectAndType, view2);
|
|
16462
17381
|
} catch (error2) {
|
|
16463
|
-
handleViewSyncError(result,
|
|
17382
|
+
handleViewSyncError(result, view2, error2);
|
|
17383
|
+
}
|
|
17384
|
+
}
|
|
17385
|
+
return viewsByObjectAndType;
|
|
17386
|
+
}
|
|
17387
|
+
async function seedSingleView(adapter, view2, result, options) {
|
|
17388
|
+
const exists = await adapter.views.exists(view2.object, view2.name, view2.type);
|
|
17389
|
+
if (exists) {
|
|
17390
|
+
result.viewsSkipped++;
|
|
17391
|
+
result.viewsSynced++;
|
|
17392
|
+
if (options.verbose && options.logger) {
|
|
17393
|
+
options.logger.info(`[ViewSync] Skipped (exists): ${view2.object}:${view2.name}:${view2.type}`);
|
|
17394
|
+
}
|
|
17395
|
+
return;
|
|
17396
|
+
}
|
|
17397
|
+
if (options.dryRun) {
|
|
17398
|
+
result.viewsCreated++;
|
|
17399
|
+
result.viewsSynced++;
|
|
17400
|
+
if (options.verbose && options.logger) {
|
|
17401
|
+
options.logger.info(`[ViewSync] Would create: ${view2.object}:${view2.name}:${view2.type}`);
|
|
16464
17402
|
}
|
|
17403
|
+
return;
|
|
17404
|
+
}
|
|
17405
|
+
await adapter.views.upsert({
|
|
17406
|
+
objectName: view2.object,
|
|
17407
|
+
type: view2.type,
|
|
17408
|
+
name: view2.name,
|
|
17409
|
+
label: view2.label,
|
|
17410
|
+
description: view2.description,
|
|
17411
|
+
icon: view2.icon,
|
|
17412
|
+
config: view2.config,
|
|
17413
|
+
default: view2.default ?? false,
|
|
17414
|
+
metadata: view2.metadata
|
|
17415
|
+
});
|
|
17416
|
+
result.viewsCreated++;
|
|
17417
|
+
result.viewsSynced++;
|
|
17418
|
+
if (options.verbose && options.logger) {
|
|
17419
|
+
options.logger.info(`[ViewSync] \u2713 Created: ${view2.object}:${view2.name}:${view2.type}`);
|
|
16465
17420
|
}
|
|
16466
|
-
return viewsByObject;
|
|
16467
17421
|
}
|
|
16468
|
-
function
|
|
16469
|
-
const
|
|
16470
|
-
|
|
16471
|
-
|
|
17422
|
+
function trackViewByObjectAndType(viewsByObjectAndType, view2) {
|
|
17423
|
+
const key = `${view2.object}:${view2.type}`;
|
|
17424
|
+
const viewNames = viewsByObjectAndType.get(key) ?? [];
|
|
17425
|
+
viewNames.push(view2.name);
|
|
17426
|
+
viewsByObjectAndType.set(key, viewNames);
|
|
16472
17427
|
}
|
|
16473
|
-
function handleViewSyncError(result,
|
|
17428
|
+
function handleViewSyncError(result, view2, error2) {
|
|
16474
17429
|
result.success = false;
|
|
16475
17430
|
result.errors.push({
|
|
16476
|
-
viewName:
|
|
16477
|
-
objectName:
|
|
17431
|
+
viewName: view2.name,
|
|
17432
|
+
objectName: view2.object,
|
|
16478
17433
|
error: error2 instanceof Error ? error2.message : String(error2)
|
|
16479
17434
|
});
|
|
16480
17435
|
}
|
|
16481
|
-
async function
|
|
17436
|
+
async function cleanupOrphanViews(tx, viewsByObjectAndType, result, options) {
|
|
16482
17437
|
if (options.dryRun) return;
|
|
16483
|
-
for (const [
|
|
16484
|
-
const
|
|
17438
|
+
for (const [key, viewNames] of viewsByObjectAndType) {
|
|
17439
|
+
const [objectName, type] = key.split(":");
|
|
17440
|
+
const deletedCount = await tx.views.deleteNotIn(objectName, type, viewNames);
|
|
16485
17441
|
result.viewsDeleted += deletedCount;
|
|
16486
17442
|
if (options.verbose && deletedCount > 0 && options.logger) {
|
|
16487
|
-
options.logger.info(
|
|
17443
|
+
options.logger.info(
|
|
17444
|
+
`[ViewSync] Deleted ${deletedCount} orphan ${type} views for ${objectName}`
|
|
17445
|
+
);
|
|
16488
17446
|
}
|
|
16489
17447
|
}
|
|
16490
17448
|
}
|
|
@@ -16499,63 +17457,27 @@ function handleTransactionError(result, error2) {
|
|
|
16499
17457
|
function logSyncComplete(result, options) {
|
|
16500
17458
|
if (options.verbose && options.logger) {
|
|
16501
17459
|
options.logger.info(
|
|
16502
|
-
`[ViewSync] ${result.success ? "\u2713" : "\u2717"}
|
|
16503
|
-
Views: ${result.viewsCreated} created, ${result.
|
|
17460
|
+
`[ViewSync] ${result.success ? "\u2713" : "\u2717"} Seed complete:
|
|
17461
|
+
Views: ${result.viewsCreated} created, ${result.viewsSkipped} skipped, ${result.viewsDeleted} deleted
|
|
16504
17462
|
Errors: ${result.errors.length}`
|
|
16505
17463
|
);
|
|
16506
17464
|
}
|
|
16507
17465
|
}
|
|
16508
|
-
async function
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
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) {
|
|
17466
|
+
async function verifyRegistryViewsSeeded(adapter, registry2) {
|
|
17467
|
+
const registryViews = registry2.getAll();
|
|
17468
|
+
for (const view2 of registryViews) {
|
|
17469
|
+
const exists = await adapter.views.exists(view2.object, view2.name, view2.type);
|
|
17470
|
+
if (!exists) {
|
|
16551
17471
|
return false;
|
|
16552
17472
|
}
|
|
16553
17473
|
}
|
|
16554
17474
|
return true;
|
|
16555
17475
|
}
|
|
16556
|
-
|
|
16557
|
-
|
|
17476
|
+
var verifyNativeViewsSync = verifyRegistryViewsSeeded;
|
|
17477
|
+
async function getViewSeedPreview(adapter, registry2) {
|
|
17478
|
+
return await seedRegistryViews(adapter, registry2, { dryRun: true });
|
|
16558
17479
|
}
|
|
17480
|
+
var getViewSyncPreview = getViewSeedPreview;
|
|
16559
17481
|
|
|
16560
17482
|
// src/runtime/sync.ts
|
|
16561
17483
|
var BASE_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -16755,7 +17677,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
16755
17677
|
console.info(
|
|
16756
17678
|
`[SyncAll] ${result.success ? "\u2713" : "\u2717"} Full sync complete:
|
|
16757
17679
|
Objects: ${objectsResult.objectsSynced} synced (${objectsResult.objectsCreated} created, ${objectsResult.objectsUpdated} updated)
|
|
16758
|
-
Views: ${viewsResult.viewsSynced} synced (${viewsResult.viewsCreated} created, ${viewsResult.
|
|
17680
|
+
Views: ${viewsResult.viewsSynced} synced (${viewsResult.viewsCreated} created, ${viewsResult.viewsSkipped} skipped)
|
|
16759
17681
|
Errors: ${objectsResult.errors.length + viewsResult.errors.length}`
|
|
16760
17682
|
);
|
|
16761
17683
|
}
|
|
@@ -16776,6 +17698,14 @@ var NoopGeocodingAdapter = class {
|
|
|
16776
17698
|
};
|
|
16777
17699
|
|
|
16778
17700
|
export {
|
|
17701
|
+
IDENTITY_PROPERTIES,
|
|
17702
|
+
BEHAVIOR_PROPERTIES,
|
|
17703
|
+
PRESENTATION_PROPERTIES,
|
|
17704
|
+
isIdentityProperty,
|
|
17705
|
+
isBehaviorProperty,
|
|
17706
|
+
isPresentationProperty,
|
|
17707
|
+
getPropertyProtectionLevel,
|
|
17708
|
+
filterPropertiesByCategory,
|
|
16779
17709
|
RELATION_TARGET_ANY,
|
|
16780
17710
|
isUniversalRelation,
|
|
16781
17711
|
RecordReferencedError,
|
|
@@ -16919,8 +17849,13 @@ export {
|
|
|
16919
17849
|
FlowsTabConfig,
|
|
16920
17850
|
DocumentsTabConfig,
|
|
16921
17851
|
TabBuilder,
|
|
17852
|
+
DetailViewBuilder,
|
|
16922
17853
|
ViewBuilder,
|
|
17854
|
+
detailView,
|
|
16923
17855
|
view,
|
|
17856
|
+
ListViewBuilder,
|
|
17857
|
+
ListViewTabConfigBuilder,
|
|
17858
|
+
listView,
|
|
16924
17859
|
group,
|
|
16925
17860
|
WorkflowFormRowBuilder,
|
|
16926
17861
|
WorkflowFormBuilder,
|
|
@@ -17011,6 +17946,14 @@ export {
|
|
|
17011
17946
|
QueryNoResultError,
|
|
17012
17947
|
QueryMultipleResultsError,
|
|
17013
17948
|
TenantContextError,
|
|
17949
|
+
FeatureFlagsContextError,
|
|
17950
|
+
isFeatureEnabled,
|
|
17951
|
+
getFeatureValue,
|
|
17952
|
+
getFeatureFlags,
|
|
17953
|
+
tryGetFeatureValue,
|
|
17954
|
+
hasFeatureFlagsContext,
|
|
17955
|
+
runWithFeatureFlags,
|
|
17956
|
+
withFeatureFlags,
|
|
17014
17957
|
getSchemaFromContext,
|
|
17015
17958
|
getSchemaByNameFromContext,
|
|
17016
17959
|
hasSchemaContext,
|
|
@@ -17018,7 +17961,7 @@ export {
|
|
|
17018
17961
|
addSchemaToContext,
|
|
17019
17962
|
runWithSchemaContext,
|
|
17020
17963
|
runWithMergedSchemaContext,
|
|
17021
|
-
getContext,
|
|
17964
|
+
getContext2 as getContext,
|
|
17022
17965
|
getTenantId,
|
|
17023
17966
|
getUserId,
|
|
17024
17967
|
hasContext,
|
|
@@ -17136,8 +18079,11 @@ export {
|
|
|
17136
18079
|
GlobalSearchService,
|
|
17137
18080
|
PermissionService,
|
|
17138
18081
|
ViewService,
|
|
18082
|
+
seedRegistryViews,
|
|
17139
18083
|
syncNativeViews,
|
|
18084
|
+
verifyRegistryViewsSeeded,
|
|
17140
18085
|
verifyNativeViewsSync,
|
|
18086
|
+
getViewSeedPreview,
|
|
17141
18087
|
getViewSyncPreview,
|
|
17142
18088
|
syncNativeObjects,
|
|
17143
18089
|
verifyNativeObjectsSync,
|