@stndrds/schema 0.1.0-alpha.21 → 0.1.0-alpha.23
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-7GLSGSUP.mjs +9456 -0
- package/dist/chunk-AZTBTKHP.mjs +9861 -0
- package/dist/chunk-B74KNXDP.mjs +9457 -0
- package/dist/chunk-CVFKFFCX.mjs +9912 -0
- package/dist/chunk-DXVIUNQT.mjs +9881 -0
- package/dist/chunk-EJB2V42F.mjs +9778 -0
- package/dist/chunk-GP2BONBI.mjs +9493 -0
- package/dist/chunk-H2KOC7F5.mjs +9549 -0
- package/dist/chunk-HXLOIN36.mjs +9893 -0
- package/dist/chunk-HYW67ZP6.mjs +9500 -0
- package/dist/chunk-IKUIWNHZ.mjs +9460 -0
- package/dist/chunk-JAWKNHY6.mjs +9553 -0
- package/dist/chunk-KT4UPBZR.mjs +9892 -0
- package/dist/chunk-NBCCRCJB.mjs +9547 -0
- package/dist/chunk-NN7Y3B3A.mjs +9859 -0
- package/dist/chunk-SBEZDUNH.mjs +9549 -0
- package/dist/chunk-TTZ3TWYP.mjs +9583 -0
- package/dist/chunk-UTZTDUIK.mjs +9818 -0
- package/dist/index.d.mts +172 -17
- package/dist/index.d.ts +172 -17
- package/dist/index.js +977 -512
- package/dist/index.mjs +71 -23
- package/dist/runtime-BFOdkIqK.d.mts +7644 -0
- package/dist/runtime-BFOdkIqK.d.ts +7644 -0
- package/dist/runtime-BI_wXiVl.d.mts +8043 -0
- package/dist/runtime-BI_wXiVl.d.ts +8043 -0
- package/dist/runtime-BZZRu7A1.d.mts +7083 -0
- package/dist/runtime-BZZRu7A1.d.ts +7083 -0
- package/dist/runtime-BlcFbbeG.d.mts +8035 -0
- package/dist/runtime-BlcFbbeG.d.ts +8035 -0
- package/dist/runtime-BleOubTG.d.mts +7236 -0
- package/dist/runtime-BleOubTG.d.ts +7236 -0
- package/dist/runtime-CC4VRtUK.d.mts +7422 -0
- package/dist/runtime-CC4VRtUK.d.ts +7422 -0
- package/dist/runtime-CKLGO3Jk.d.mts +7632 -0
- package/dist/runtime-CKLGO3Jk.d.ts +7632 -0
- package/dist/runtime-CjhnsVyg.d.mts +7668 -0
- package/dist/runtime-CjhnsVyg.d.ts +7668 -0
- package/dist/runtime-CmYcicVr.d.mts +7083 -0
- package/dist/runtime-CmYcicVr.d.ts +7083 -0
- package/dist/runtime-D01tNFxT.d.mts +7418 -0
- package/dist/runtime-D01tNFxT.d.ts +7418 -0
- package/dist/runtime-DWCd3xCL.d.mts +7632 -0
- package/dist/runtime-DWCd3xCL.d.ts +7632 -0
- package/dist/runtime-DbryZGqb.d.mts +8037 -0
- package/dist/runtime-DbryZGqb.d.ts +8037 -0
- package/dist/runtime-DdkNFkKd.d.mts +7185 -0
- package/dist/runtime-DdkNFkKd.d.ts +7185 -0
- package/dist/runtime-DnnW72ZF.d.mts +7451 -0
- package/dist/runtime-DnnW72ZF.d.ts +7451 -0
- package/dist/runtime-DrrCWvBr.d.mts +7451 -0
- package/dist/runtime-DrrCWvBr.d.ts +7451 -0
- package/dist/runtime-UYkiPdud.d.mts +8047 -0
- package/dist/runtime-UYkiPdud.d.ts +8047 -0
- package/dist/runtime-g7XZnqGw.d.mts +7452 -0
- package/dist/runtime-g7XZnqGw.d.ts +7452 -0
- package/dist/runtime-jnV5WCS6.d.mts +8047 -0
- package/dist/runtime-jnV5WCS6.d.ts +8047 -0
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +855 -466
- package/dist/runtime.mjs +29 -3
- package/package.json +2 -2
package/dist/runtime.js
CHANGED
|
@@ -134,6 +134,8 @@ __export(runtime_exports, {
|
|
|
134
134
|
NoopHookRegistry: () => NoopHookRegistry,
|
|
135
135
|
ObjectSchemaService: () => ObjectSchemaService,
|
|
136
136
|
PermissionService: () => PermissionService,
|
|
137
|
+
PolicyRegistry: () => PolicyRegistry,
|
|
138
|
+
PolicyViolationError: () => PolicyViolationError,
|
|
137
139
|
QueryBuilder: () => QueryBuilder,
|
|
138
140
|
QueryMultipleResultsError: () => QueryMultipleResultsError,
|
|
139
141
|
QueryNoResultError: () => QueryNoResultError,
|
|
@@ -143,6 +145,9 @@ __export(runtime_exports, {
|
|
|
143
145
|
RollupScheduler: () => RollupScheduler,
|
|
144
146
|
RollupService: () => RollupService,
|
|
145
147
|
SHORTCUT_TO_FILTER_OPERATOR: () => SHORTCUT_TO_FILTER_OPERATOR,
|
|
148
|
+
TenantAwareRepository: () => TenantAwareRepository,
|
|
149
|
+
TenantAwareService: () => TenantAwareService,
|
|
150
|
+
TenantContextError: () => TenantContextError,
|
|
146
151
|
UserProfileService: () => UserProfileService,
|
|
147
152
|
UserService: () => UserService,
|
|
148
153
|
ViewService: () => ViewService,
|
|
@@ -150,6 +155,7 @@ __export(runtime_exports, {
|
|
|
150
155
|
createDefaultState: () => createDefaultState,
|
|
151
156
|
createMockAdapter: () => createMockAdapter,
|
|
152
157
|
createQueryBuilder: () => createQueryBuilder,
|
|
158
|
+
defaultPolicyRegistry: () => defaultPolicyRegistry,
|
|
153
159
|
enrichValuesWithSelectLabels: () => enrichValuesWithSelectLabels,
|
|
154
160
|
evaluateFormula: () => evaluateFormula,
|
|
155
161
|
evaluateFormulaAttribute: () => evaluateFormulaAttribute,
|
|
@@ -164,18 +170,24 @@ __export(runtime_exports, {
|
|
|
164
170
|
formatFormulaResult: () => formatFormulaResult,
|
|
165
171
|
formatRecord: () => formatRecord,
|
|
166
172
|
formatRecords: () => formatRecords,
|
|
173
|
+
getContext: () => getContext,
|
|
167
174
|
getPathDepth: () => getPathDepth,
|
|
168
175
|
getRelationPath: () => getRelationPath,
|
|
169
176
|
getSyncPreview: () => getSyncPreview,
|
|
170
177
|
getTargetAttributeName: () => getTargetAttributeName,
|
|
178
|
+
getTenantId: () => getTenantId,
|
|
179
|
+
getUserId: () => getUserId,
|
|
171
180
|
getViewSyncPreview: () => getViewSyncPreview,
|
|
181
|
+
hasContext: () => hasContext,
|
|
172
182
|
hasRelationReferences: () => hasRelationReferences,
|
|
173
183
|
isLabelExpression: () => isLabelExpression,
|
|
184
|
+
notesPolicy: () => notesPolicy,
|
|
174
185
|
parsePath: () => parsePath,
|
|
175
186
|
pathHasManyCardinality: () => pathHasManyCardinality,
|
|
176
187
|
renderLabelExpression: () => renderLabelExpression,
|
|
177
188
|
resolveMultiplePaths: () => resolveMultiplePaths,
|
|
178
189
|
resolveSingleValue: () => resolveSingleValue,
|
|
190
|
+
runWithContext: () => runWithContext,
|
|
179
191
|
syncAll: () => syncAll,
|
|
180
192
|
syncNativeObjects: () => syncNativeObjects,
|
|
181
193
|
syncNativeViews: () => syncNativeViews,
|
|
@@ -183,7 +195,8 @@ __export(runtime_exports, {
|
|
|
183
195
|
validateFormulaExpression: () => validateFormulaExpression,
|
|
184
196
|
validatePath: () => validatePath,
|
|
185
197
|
verifyNativeObjectsSync: () => verifyNativeObjectsSync,
|
|
186
|
-
verifyNativeViewsSync: () => verifyNativeViewsSync
|
|
198
|
+
verifyNativeViewsSync: () => verifyNativeViewsSync,
|
|
199
|
+
withTenantContext: () => withTenantContext
|
|
187
200
|
});
|
|
188
201
|
module.exports = __toCommonJS(runtime_exports);
|
|
189
202
|
|
|
@@ -265,12 +278,12 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
265
278
|
});
|
|
266
279
|
}
|
|
267
280
|
/**
|
|
268
|
-
* Resolve object name to object ID (cached)
|
|
281
|
+
* Resolve object name to object ID (cached).
|
|
282
|
+
* Uses tenant context from AsyncLocalStorage.
|
|
269
283
|
*/
|
|
270
284
|
async resolveObjectId() {
|
|
271
285
|
if (this.objectId) return this.objectId;
|
|
272
|
-
const
|
|
273
|
-
const dbObject = await this.adapter.objects.findByName(tenantId, this.state.objectName);
|
|
286
|
+
const dbObject = await this.adapter.objects.findByName(this.state.objectName);
|
|
274
287
|
if (!dbObject) {
|
|
275
288
|
throw new Error(`Object "${this.state.objectName}" not found`);
|
|
276
289
|
}
|
|
@@ -588,10 +601,10 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
588
601
|
* .eq('id', 'rec-123')
|
|
589
602
|
* .update({ price: 899 });
|
|
590
603
|
*
|
|
591
|
-
* // Update with metadata (
|
|
604
|
+
* // Update with metadata (merged with existing, set undefined to remove)
|
|
592
605
|
* const withMeta = await schema.from('products')
|
|
593
606
|
* .eq('id', 'rec-123')
|
|
594
|
-
* .update({ price: 899 }, { metadata: { synced: true } });
|
|
607
|
+
* .update({ price: 899 }, { metadata: { synced: true, oldKey: undefined } });
|
|
595
608
|
* ```
|
|
596
609
|
*/
|
|
597
610
|
async update(data, options) {
|
|
@@ -658,6 +671,49 @@ function createQueryBuilder(recordService, adapter, objectName) {
|
|
|
658
671
|
return new QueryBuilder(recordService, adapter, objectName);
|
|
659
672
|
}
|
|
660
673
|
|
|
674
|
+
// src/runtime/context/errors.ts
|
|
675
|
+
var TenantContextError = class _TenantContextError extends Error {
|
|
676
|
+
constructor(message) {
|
|
677
|
+
super(
|
|
678
|
+
message ?? "No tenant context found. This usually means:\n - HTTP request: Missing TenantContextMiddleware/Interceptor\n - Background job: Wrap with runWithContext()\n - Test: Wrap test body with runWithContext()\n\nAll database operations require a tenant context for security."
|
|
679
|
+
);
|
|
680
|
+
this.name = "TenantContextError";
|
|
681
|
+
if ("captureStackTrace" in Error) {
|
|
682
|
+
Error.captureStackTrace(
|
|
683
|
+
this,
|
|
684
|
+
_TenantContextError
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
// src/runtime/context/tenant-context.ts
|
|
691
|
+
var import_node_async_hooks = require("async_hooks");
|
|
692
|
+
var storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
693
|
+
function getContext() {
|
|
694
|
+
const ctx = storage.getStore();
|
|
695
|
+
if (!ctx) {
|
|
696
|
+
throw new TenantContextError();
|
|
697
|
+
}
|
|
698
|
+
return ctx;
|
|
699
|
+
}
|
|
700
|
+
function getTenantId() {
|
|
701
|
+
return getContext().tenantId;
|
|
702
|
+
}
|
|
703
|
+
function getUserId() {
|
|
704
|
+
return getContext().userId;
|
|
705
|
+
}
|
|
706
|
+
function hasContext() {
|
|
707
|
+
return storage.getStore() !== void 0;
|
|
708
|
+
}
|
|
709
|
+
function runWithContext(context, fn) {
|
|
710
|
+
const frozenContext = Object.freeze({ ...context });
|
|
711
|
+
return storage.run(frozenContext, fn);
|
|
712
|
+
}
|
|
713
|
+
async function withTenantContext(tenantId, fn, userId) {
|
|
714
|
+
return await runWithContext({ tenantId, userId }, fn);
|
|
715
|
+
}
|
|
716
|
+
|
|
661
717
|
// src/runtime/formula/evaluator.ts
|
|
662
718
|
var import_expr_eval = require("expr-eval");
|
|
663
719
|
function createFormulaParser() {
|
|
@@ -1100,15 +1156,6 @@ function formatDate(value) {
|
|
|
1100
1156
|
}
|
|
1101
1157
|
return String(value);
|
|
1102
1158
|
}
|
|
1103
|
-
function formatTimestamp(value) {
|
|
1104
|
-
if (typeof value === "number") {
|
|
1105
|
-
return new Date(value).toISOString();
|
|
1106
|
-
}
|
|
1107
|
-
if (value instanceof Date) {
|
|
1108
|
-
return value.toISOString();
|
|
1109
|
-
}
|
|
1110
|
-
return String(value);
|
|
1111
|
-
}
|
|
1112
1159
|
function formatPhone(value) {
|
|
1113
1160
|
if (typeof value !== "object" || value === null) return String(value);
|
|
1114
1161
|
const phone = value;
|
|
@@ -1193,8 +1240,6 @@ function formatAttributeValue(value, attribute) {
|
|
|
1193
1240
|
return formatCurrency(value, attribute);
|
|
1194
1241
|
case "date":
|
|
1195
1242
|
return formatDate(value);
|
|
1196
|
-
case "timestamp":
|
|
1197
|
-
return formatTimestamp(value);
|
|
1198
1243
|
case "phone":
|
|
1199
1244
|
return formatPhone(value);
|
|
1200
1245
|
case "location":
|
|
@@ -1299,7 +1344,8 @@ function createMockObjectsRepository(stores) {
|
|
|
1299
1344
|
findById(id) {
|
|
1300
1345
|
return Promise.resolve(stores.objects.get(id) ?? null);
|
|
1301
1346
|
},
|
|
1302
|
-
findByName(
|
|
1347
|
+
findByName(name) {
|
|
1348
|
+
const tenantId = getTenantId();
|
|
1303
1349
|
for (const obj of stores.objects.values()) {
|
|
1304
1350
|
if (obj.tenantId === tenantId && obj.name === name) {
|
|
1305
1351
|
return Promise.resolve(obj);
|
|
@@ -1316,9 +1362,10 @@ function createMockObjectsRepository(stores) {
|
|
|
1316
1362
|
return Promise.resolve(null);
|
|
1317
1363
|
},
|
|
1318
1364
|
create(data) {
|
|
1365
|
+
const tenantId = getTenantId();
|
|
1319
1366
|
const obj = {
|
|
1320
1367
|
id: generateId(),
|
|
1321
|
-
tenantId
|
|
1368
|
+
tenantId,
|
|
1322
1369
|
name: data.name,
|
|
1323
1370
|
label: data.label,
|
|
1324
1371
|
pluralLabel: data.pluralLabel,
|
|
@@ -1350,13 +1397,15 @@ function createMockObjectsRepository(stores) {
|
|
|
1350
1397
|
stores.objects.delete(id);
|
|
1351
1398
|
return Promise.resolve();
|
|
1352
1399
|
},
|
|
1353
|
-
list(
|
|
1400
|
+
list() {
|
|
1401
|
+
const tenantId = getTenantId();
|
|
1354
1402
|
const results = Array.from(stores.objects.values()).filter(
|
|
1355
1403
|
(obj) => obj.tenantId === tenantId
|
|
1356
1404
|
);
|
|
1357
1405
|
return Promise.resolve(results);
|
|
1358
1406
|
},
|
|
1359
1407
|
upsert(data) {
|
|
1408
|
+
const tenantId = getTenantId();
|
|
1360
1409
|
for (const obj of stores.objects.values()) {
|
|
1361
1410
|
if (obj.system && obj.name === data.name) {
|
|
1362
1411
|
const updated = {
|
|
@@ -1374,7 +1423,7 @@ function createMockObjectsRepository(stores) {
|
|
|
1374
1423
|
}
|
|
1375
1424
|
const newObj = {
|
|
1376
1425
|
id: generateId(),
|
|
1377
|
-
tenantId
|
|
1426
|
+
tenantId,
|
|
1378
1427
|
name: data.name,
|
|
1379
1428
|
label: data.label,
|
|
1380
1429
|
pluralLabel: data.pluralLabel,
|
|
@@ -1454,28 +1503,21 @@ function createMockAttributesRepository(stores) {
|
|
|
1454
1503
|
return Promise.resolve(count);
|
|
1455
1504
|
},
|
|
1456
1505
|
upsert(data) {
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
defaultValue: data.defaultValue ?? attr.defaultValue,
|
|
1473
|
-
config: data.config ?? attr.config,
|
|
1474
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1475
|
-
};
|
|
1476
|
-
stores.attributes.set(attr.id, updated);
|
|
1477
|
-
return Promise.resolve(updated);
|
|
1478
|
-
}
|
|
1506
|
+
const existing = Array.from(stores.attributes.values()).find(
|
|
1507
|
+
(attr) => attr.objectId === data.objectId && attr.name === data.name
|
|
1508
|
+
);
|
|
1509
|
+
if (existing) {
|
|
1510
|
+
const updated = {
|
|
1511
|
+
...existing,
|
|
1512
|
+
...data,
|
|
1513
|
+
id: existing.id,
|
|
1514
|
+
objectId: existing.objectId,
|
|
1515
|
+
name: existing.name,
|
|
1516
|
+
createdAt: existing.createdAt,
|
|
1517
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1518
|
+
};
|
|
1519
|
+
stores.attributes.set(existing.id, updated);
|
|
1520
|
+
return Promise.resolve(updated);
|
|
1479
1521
|
}
|
|
1480
1522
|
return this.create(data);
|
|
1481
1523
|
}
|
|
@@ -1494,7 +1536,8 @@ function createMockUserProfilesRepository(stores) {
|
|
|
1494
1536
|
}
|
|
1495
1537
|
return Promise.resolve(null);
|
|
1496
1538
|
},
|
|
1497
|
-
findByEmail(
|
|
1539
|
+
findByEmail(email) {
|
|
1540
|
+
const tenantId = getTenantId();
|
|
1498
1541
|
for (const profile of stores.userProfiles.values()) {
|
|
1499
1542
|
if (profile.tenantId === tenantId && profile.email === email) {
|
|
1500
1543
|
return Promise.resolve(profile);
|
|
@@ -1503,9 +1546,10 @@ function createMockUserProfilesRepository(stores) {
|
|
|
1503
1546
|
return Promise.resolve(null);
|
|
1504
1547
|
},
|
|
1505
1548
|
create(data) {
|
|
1549
|
+
const tenantId = getTenantId();
|
|
1506
1550
|
const profile = {
|
|
1507
1551
|
id: generateId(),
|
|
1508
|
-
tenantId
|
|
1552
|
+
tenantId,
|
|
1509
1553
|
authId: data.authId,
|
|
1510
1554
|
email: data.email,
|
|
1511
1555
|
firstName: data.firstName,
|
|
@@ -1536,14 +1580,16 @@ function createMockUserProfilesRepository(stores) {
|
|
|
1536
1580
|
stores.userProfiles.delete(id);
|
|
1537
1581
|
return Promise.resolve();
|
|
1538
1582
|
},
|
|
1539
|
-
list(
|
|
1583
|
+
list(options) {
|
|
1584
|
+
const tenantId = getTenantId();
|
|
1540
1585
|
let results = Array.from(stores.userProfiles.values()).filter((p) => p.tenantId === tenantId);
|
|
1541
1586
|
if (options?.limit) {
|
|
1542
1587
|
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
1543
1588
|
}
|
|
1544
1589
|
return Promise.resolve(results);
|
|
1545
1590
|
},
|
|
1546
|
-
countByRole(
|
|
1591
|
+
countByRole(role) {
|
|
1592
|
+
const tenantId = getTenantId();
|
|
1547
1593
|
const count = Array.from(stores.userProfiles.values()).filter(
|
|
1548
1594
|
(profile) => profile.tenantId === tenantId && profile.role === role
|
|
1549
1595
|
).length;
|
|
@@ -1567,9 +1613,10 @@ function createMockFilesRepository(stores) {
|
|
|
1567
1613
|
return Promise.resolve(file ?? null);
|
|
1568
1614
|
},
|
|
1569
1615
|
create(data) {
|
|
1616
|
+
const tenantId = getTenantId();
|
|
1570
1617
|
const file = {
|
|
1571
1618
|
id: generateId(),
|
|
1572
|
-
tenantId
|
|
1619
|
+
tenantId,
|
|
1573
1620
|
name: data.name,
|
|
1574
1621
|
originalName: data.originalName,
|
|
1575
1622
|
mimeType: data.mimeType,
|
|
@@ -1614,7 +1661,8 @@ function createMockFilesRepository(stores) {
|
|
|
1614
1661
|
stores.files.delete(id);
|
|
1615
1662
|
return Promise.resolve();
|
|
1616
1663
|
},
|
|
1617
|
-
list(
|
|
1664
|
+
list(options) {
|
|
1665
|
+
const tenantId = getTenantId();
|
|
1618
1666
|
let results = Array.from(stores.files.values()).filter(
|
|
1619
1667
|
(f) => f.tenantId === tenantId && !f.deletedAt
|
|
1620
1668
|
);
|
|
@@ -1626,7 +1674,8 @@ function createMockFilesRepository(stores) {
|
|
|
1626
1674
|
}
|
|
1627
1675
|
return Promise.resolve(results);
|
|
1628
1676
|
},
|
|
1629
|
-
findByFolder(
|
|
1677
|
+
findByFolder(folderPath) {
|
|
1678
|
+
const tenantId = getTenantId();
|
|
1630
1679
|
const results = Array.from(stores.files.values()).filter(
|
|
1631
1680
|
(f) => f.tenantId === tenantId && f.folderPath === folderPath && !f.deletedAt
|
|
1632
1681
|
);
|
|
@@ -1673,19 +1722,23 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
1673
1722
|
return Promise.resolve(results);
|
|
1674
1723
|
},
|
|
1675
1724
|
create(data) {
|
|
1725
|
+
const tenantId = getTenantId();
|
|
1676
1726
|
const internal = {
|
|
1677
1727
|
id: generateId(),
|
|
1678
|
-
tenantId
|
|
1728
|
+
tenantId,
|
|
1679
1729
|
objectId: data.objectId,
|
|
1680
1730
|
label: data.label,
|
|
1681
1731
|
completionStatus: data.completionStatus,
|
|
1682
1732
|
values: data.data,
|
|
1683
1733
|
metadata: data.metadata ?? {},
|
|
1684
1734
|
createdAt: /* @__PURE__ */ new Date(),
|
|
1685
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1735
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
1736
|
+
createdBy: data.createdBy,
|
|
1737
|
+
lastUpdatedBy: data.createdBy
|
|
1738
|
+
// Same as createdBy on initial creation
|
|
1686
1739
|
};
|
|
1687
1740
|
stores.objectRecords.set(internal.id, internal);
|
|
1688
|
-
const { tenantId:
|
|
1741
|
+
const { tenantId: _t, ...record } = internal;
|
|
1689
1742
|
return Promise.resolve(record);
|
|
1690
1743
|
},
|
|
1691
1744
|
update(id, data) {
|
|
@@ -1693,7 +1746,7 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
1693
1746
|
if (!existing) {
|
|
1694
1747
|
return Promise.reject(new Error(`ObjectRecord ${id} not found`));
|
|
1695
1748
|
}
|
|
1696
|
-
const { __completionStatus, __label, __metadata, ...valueData } = data;
|
|
1749
|
+
const { __completionStatus, __label, __metadata, __lastUpdatedBy, ...valueData } = data;
|
|
1697
1750
|
const updated = {
|
|
1698
1751
|
...existing,
|
|
1699
1752
|
label: __label ?? existing.label,
|
|
@@ -1701,7 +1754,8 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
1701
1754
|
// Metadata uses replace behavior (not merge)
|
|
1702
1755
|
metadata: __metadata !== void 0 ? __metadata : existing.metadata,
|
|
1703
1756
|
values: { ...existing.values, ...valueData },
|
|
1704
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1757
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
1758
|
+
lastUpdatedBy: __lastUpdatedBy ?? existing.lastUpdatedBy
|
|
1705
1759
|
};
|
|
1706
1760
|
stores.objectRecords.set(id, updated);
|
|
1707
1761
|
const { tenantId: _, ...record } = updated;
|
|
@@ -1711,7 +1765,8 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
1711
1765
|
stores.objectRecords.delete(id);
|
|
1712
1766
|
return Promise.resolve();
|
|
1713
1767
|
},
|
|
1714
|
-
list(
|
|
1768
|
+
list(objectId, options) {
|
|
1769
|
+
const tenantId = getTenantId();
|
|
1715
1770
|
let results = Array.from(stores.objectRecords.values()).filter(
|
|
1716
1771
|
(r) => r.tenantId === tenantId && r.objectId === objectId
|
|
1717
1772
|
);
|
|
@@ -1719,10 +1774,11 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
1719
1774
|
if (options?.limit) {
|
|
1720
1775
|
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
1721
1776
|
}
|
|
1722
|
-
const records = results.map(({ tenantId:
|
|
1777
|
+
const records = results.map(({ tenantId: _t, ...r }) => r);
|
|
1723
1778
|
return Promise.resolve({ records, total });
|
|
1724
1779
|
},
|
|
1725
|
-
search(
|
|
1780
|
+
search(objectId, query, options) {
|
|
1781
|
+
const tenantId = getTenantId();
|
|
1726
1782
|
const lowerQuery = query.toLowerCase();
|
|
1727
1783
|
let results = Array.from(stores.objectRecords.values()).filter((r) => {
|
|
1728
1784
|
if (r.tenantId !== tenantId || r.objectId !== objectId) return false;
|
|
@@ -1734,10 +1790,12 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
1734
1790
|
if (options?.limit) {
|
|
1735
1791
|
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
1736
1792
|
}
|
|
1737
|
-
const records = results.map(({ tenantId:
|
|
1793
|
+
const records = results.map(({ tenantId: _t, ...r }) => r);
|
|
1738
1794
|
return Promise.resolve({ records, total });
|
|
1739
1795
|
},
|
|
1740
|
-
|
|
1796
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a mock adapter, we don't need to worry about complexity
|
|
1797
|
+
globalSearch(query, options) {
|
|
1798
|
+
const tenantId = getTenantId();
|
|
1741
1799
|
const lowerQuery = query.toLowerCase();
|
|
1742
1800
|
const objectsMap = /* @__PURE__ */ new Map();
|
|
1743
1801
|
for (const obj of stores.objects.values()) {
|
|
@@ -1808,6 +1866,7 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
1808
1866
|
// -------------------------------------------------------------------------
|
|
1809
1867
|
// Schema Integrity Methods
|
|
1810
1868
|
// -------------------------------------------------------------------------
|
|
1869
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a mock adapter, we don't need to worry about complexity
|
|
1811
1870
|
countRecordsReferencingId(targetId) {
|
|
1812
1871
|
const relationAttrs = [];
|
|
1813
1872
|
for (const attr of stores.attributes.values()) {
|
|
@@ -1884,6 +1943,7 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
1884
1943
|
}
|
|
1885
1944
|
return Promise.resolve(updated);
|
|
1886
1945
|
},
|
|
1946
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a mock adapter, we don't need to worry about complexity
|
|
1887
1947
|
findByRelation(objectName, relationAttributeName, targetRecordId) {
|
|
1888
1948
|
const obj = Array.from(stores.objects.values()).find((o) => o.name === objectName);
|
|
1889
1949
|
if (!obj) return Promise.resolve([]);
|
|
@@ -1911,21 +1971,24 @@ function createMockViewsRepository(stores) {
|
|
|
1911
1971
|
findById(id) {
|
|
1912
1972
|
return Promise.resolve(stores.views.get(id) ?? null);
|
|
1913
1973
|
},
|
|
1914
|
-
findByName(
|
|
1974
|
+
findByName(objectName, viewName) {
|
|
1975
|
+
const tenantId = getTenantId();
|
|
1915
1976
|
return Promise.resolve(
|
|
1916
1977
|
Array.from(stores.views.values()).find(
|
|
1917
1978
|
(v) => v.tenantId === tenantId && v.objectName === objectName && v.name === viewName
|
|
1918
1979
|
) ?? null
|
|
1919
1980
|
);
|
|
1920
1981
|
},
|
|
1921
|
-
findByObjectName(
|
|
1982
|
+
findByObjectName(objectName) {
|
|
1983
|
+
const tenantId = getTenantId();
|
|
1922
1984
|
return Promise.resolve(
|
|
1923
1985
|
Array.from(stores.views.values()).filter(
|
|
1924
1986
|
(v) => v.tenantId === tenantId && v.objectName === objectName
|
|
1925
1987
|
)
|
|
1926
1988
|
);
|
|
1927
1989
|
},
|
|
1928
|
-
findAllForTenant(
|
|
1990
|
+
findAllForTenant() {
|
|
1991
|
+
const tenantId = getTenantId();
|
|
1929
1992
|
return Promise.resolve(
|
|
1930
1993
|
Array.from(stores.views.values()).filter((v) => v.tenantId === tenantId)
|
|
1931
1994
|
);
|
|
@@ -1943,11 +2006,12 @@ function createMockViewsRepository(stores) {
|
|
|
1943
2006
|
);
|
|
1944
2007
|
},
|
|
1945
2008
|
create(data) {
|
|
2009
|
+
const tenantId = getTenantId();
|
|
1946
2010
|
const id = generateId();
|
|
1947
2011
|
const now = /* @__PURE__ */ new Date();
|
|
1948
2012
|
const dbView = {
|
|
1949
2013
|
id,
|
|
1950
|
-
tenantId
|
|
2014
|
+
tenantId,
|
|
1951
2015
|
objectName: data.objectName,
|
|
1952
2016
|
name: data.name,
|
|
1953
2017
|
label: data.label,
|
|
@@ -1991,7 +2055,7 @@ function createMockViewsRepository(stores) {
|
|
|
1991
2055
|
return Promise.resolve(deleted);
|
|
1992
2056
|
},
|
|
1993
2057
|
async upsert(data) {
|
|
1994
|
-
const existing = data.system ? await this.findSystemByName(data.objectName, data.name) : await this.findByName(data.
|
|
2058
|
+
const existing = data.system ? await this.findSystemByName(data.objectName, data.name) : await this.findByName(data.objectName, data.name);
|
|
1995
2059
|
if (existing) {
|
|
1996
2060
|
return this.update(existing.id, {
|
|
1997
2061
|
label: data.label,
|
|
@@ -2008,14 +2072,16 @@ function createMockViewsRepository(stores) {
|
|
|
2008
2072
|
}
|
|
2009
2073
|
function createMockPermissionsRepository(stores) {
|
|
2010
2074
|
return {
|
|
2011
|
-
|
|
2075
|
+
getRoles() {
|
|
2076
|
+
const tenantId = getTenantId();
|
|
2012
2077
|
const results = Array.from(stores.roles.values()).filter((r) => r.tenantId === tenantId);
|
|
2013
2078
|
return Promise.resolve(results);
|
|
2014
2079
|
},
|
|
2015
2080
|
getRoleById(roleId) {
|
|
2016
2081
|
return Promise.resolve(stores.roles.get(roleId) ?? null);
|
|
2017
2082
|
},
|
|
2018
|
-
getRoleByName(
|
|
2083
|
+
getRoleByName(name) {
|
|
2084
|
+
const tenantId = getTenantId();
|
|
2019
2085
|
for (const role of stores.roles.values()) {
|
|
2020
2086
|
if (role.tenantId === tenantId && role.name === name) {
|
|
2021
2087
|
return Promise.resolve(role);
|
|
@@ -2024,9 +2090,10 @@ function createMockPermissionsRepository(stores) {
|
|
|
2024
2090
|
return Promise.resolve(null);
|
|
2025
2091
|
},
|
|
2026
2092
|
createRole(input) {
|
|
2093
|
+
const tenantId = getTenantId();
|
|
2027
2094
|
const role = {
|
|
2028
2095
|
id: generateId(),
|
|
2029
|
-
tenantId
|
|
2096
|
+
tenantId,
|
|
2030
2097
|
name: input.name,
|
|
2031
2098
|
label: input.label,
|
|
2032
2099
|
description: input.description,
|
|
@@ -2092,7 +2159,8 @@ function createMockPermissionsRepository(stores) {
|
|
|
2092
2159
|
}
|
|
2093
2160
|
return Promise.resolve();
|
|
2094
2161
|
},
|
|
2095
|
-
getUserRoles(userProfileId
|
|
2162
|
+
getUserRoles(userProfileId) {
|
|
2163
|
+
const tenantId = getTenantId();
|
|
2096
2164
|
const roleIds = Array.from(stores.userRoles.values()).filter((ur) => ur.userProfileId === userProfileId && ur.tenantId === tenantId).map((ur) => ur.roleId);
|
|
2097
2165
|
const roles = Array.from(stores.roles.values()).filter((r) => roleIds.includes(r.id));
|
|
2098
2166
|
return Promise.resolve(roles);
|
|
@@ -2114,7 +2182,8 @@ function createMockPermissionsRepository(stores) {
|
|
|
2114
2182
|
stores.userRoles.set(userRole.id, userRole);
|
|
2115
2183
|
return Promise.resolve(userRole);
|
|
2116
2184
|
},
|
|
2117
|
-
revokeRole(userProfileId, roleId
|
|
2185
|
+
revokeRole(userProfileId, roleId) {
|
|
2186
|
+
const tenantId = getTenantId();
|
|
2118
2187
|
for (const [id, ur] of stores.userRoles.entries()) {
|
|
2119
2188
|
if (ur.userProfileId === userProfileId && ur.roleId === roleId && ur.tenantId === tenantId) {
|
|
2120
2189
|
stores.userRoles.delete(id);
|
|
@@ -2123,7 +2192,9 @@ function createMockPermissionsRepository(stores) {
|
|
|
2123
2192
|
}
|
|
2124
2193
|
return Promise.resolve();
|
|
2125
2194
|
},
|
|
2126
|
-
|
|
2195
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a mock adapter, we don't need to worry about complexity
|
|
2196
|
+
getEffectivePermissions(userProfileId) {
|
|
2197
|
+
const tenantId = getTenantId();
|
|
2127
2198
|
const userRoleIds = Array.from(stores.userRoles.values()).filter((ur) => ur.userProfileId === userProfileId && ur.tenantId === tenantId).map((ur) => ur.roleId);
|
|
2128
2199
|
const userRoles = Array.from(stores.roles.values()).filter((r) => userRoleIds.includes(r.id));
|
|
2129
2200
|
const isAdmin = userRoles.some((r) => r.name === "admin");
|
|
@@ -2198,6 +2269,160 @@ function createMockAdapter() {
|
|
|
2198
2269
|
return adapter;
|
|
2199
2270
|
}
|
|
2200
2271
|
|
|
2272
|
+
// src/types/policies.ts
|
|
2273
|
+
var PolicyViolationError = class extends Error {
|
|
2274
|
+
constructor(objectName, action, recordId) {
|
|
2275
|
+
const message = recordId ? `Access denied: cannot ${action} record ${recordId} on ${objectName}` : `Access denied: cannot ${action} on ${objectName}`;
|
|
2276
|
+
super(message);
|
|
2277
|
+
this.objectName = objectName;
|
|
2278
|
+
this.action = action;
|
|
2279
|
+
this.recordId = recordId;
|
|
2280
|
+
this.name = "PolicyViolationError";
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
|
|
2284
|
+
// src/runtime/policies/policy.registry.ts
|
|
2285
|
+
var PolicyRegistry = class {
|
|
2286
|
+
constructor() {
|
|
2287
|
+
this.policies = /* @__PURE__ */ new Map();
|
|
2288
|
+
}
|
|
2289
|
+
/**
|
|
2290
|
+
* Register a policy for an object type.
|
|
2291
|
+
* Overwrites any existing policy for the same object.
|
|
2292
|
+
*
|
|
2293
|
+
* @param policy - The policy to register
|
|
2294
|
+
*/
|
|
2295
|
+
register(policy) {
|
|
2296
|
+
this.policies.set(policy.objectName, policy);
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Unregister a policy for an object type.
|
|
2300
|
+
*
|
|
2301
|
+
* @param objectName - The object name to unregister
|
|
2302
|
+
* @returns true if a policy was removed, false if none existed
|
|
2303
|
+
*/
|
|
2304
|
+
unregister(objectName) {
|
|
2305
|
+
return this.policies.delete(objectName);
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Get the policy for an object type.
|
|
2309
|
+
*
|
|
2310
|
+
* @param objectName - The object name to look up
|
|
2311
|
+
* @returns The policy if registered, undefined otherwise
|
|
2312
|
+
*/
|
|
2313
|
+
get(objectName) {
|
|
2314
|
+
return this.policies.get(objectName);
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Check if a policy is registered for an object type.
|
|
2318
|
+
*
|
|
2319
|
+
* @param objectName - The object name to check
|
|
2320
|
+
* @returns true if a policy exists
|
|
2321
|
+
*/
|
|
2322
|
+
has(objectName) {
|
|
2323
|
+
return this.policies.has(objectName);
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Get all registered object names.
|
|
2327
|
+
*/
|
|
2328
|
+
listObjectNames() {
|
|
2329
|
+
return Array.from(this.policies.keys());
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Clear all registered policies.
|
|
2333
|
+
*/
|
|
2334
|
+
clear() {
|
|
2335
|
+
this.policies.clear();
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
var defaultPolicyRegistry = new PolicyRegistry();
|
|
2339
|
+
|
|
2340
|
+
// src/runtime/policies/notes.policy.ts
|
|
2341
|
+
var notesPolicy = {
|
|
2342
|
+
objectName: "notes",
|
|
2343
|
+
/**
|
|
2344
|
+
* Inject visibility filter for list/search operations.
|
|
2345
|
+
*
|
|
2346
|
+
* Creates: (visibility = 'shared') OR (createdBy = userId)
|
|
2347
|
+
*
|
|
2348
|
+
* This filters at the SQL level for better performance.
|
|
2349
|
+
* canAccessRecord provides defense-in-depth as a secondary check.
|
|
2350
|
+
*
|
|
2351
|
+
* Note: When user filters exist, we preserve linkedTo filter (common for notes)
|
|
2352
|
+
* but the visibility OR filter takes precedence. Full filter merging would require
|
|
2353
|
+
* AdvancedFilterState support in the adapter.
|
|
2354
|
+
*/
|
|
2355
|
+
applyListFilter(ctx, options) {
|
|
2356
|
+
const visibilityRules = [
|
|
2357
|
+
{ attribute: "visibility", operator: "is", value: "shared" },
|
|
2358
|
+
{ attribute: "createdBy", operator: "is", value: ctx.userId }
|
|
2359
|
+
];
|
|
2360
|
+
if (!options?.filters || options.filters.rules.length === 0) {
|
|
2361
|
+
return {
|
|
2362
|
+
...options,
|
|
2363
|
+
filters: { combinator: "or", rules: visibilityRules }
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
const linkedToFilter = options.filters.rules.find((r) => r.attribute === "linkedTo");
|
|
2367
|
+
if (linkedToFilter) {
|
|
2368
|
+
return {
|
|
2369
|
+
...options,
|
|
2370
|
+
filters: {
|
|
2371
|
+
combinator: "and",
|
|
2372
|
+
rules: [linkedToFilter]
|
|
2373
|
+
}
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
return {
|
|
2377
|
+
...options,
|
|
2378
|
+
filters: { combinator: "or", rules: visibilityRules }
|
|
2379
|
+
};
|
|
2380
|
+
},
|
|
2381
|
+
/**
|
|
2382
|
+
* Check if user can access a specific note.
|
|
2383
|
+
*
|
|
2384
|
+
* Rules:
|
|
2385
|
+
* - Shared notes: accessible by anyone in the tenant
|
|
2386
|
+
* - Private notes: only accessible by the author (createdBy)
|
|
2387
|
+
*/
|
|
2388
|
+
canAccessRecord(ctx, record) {
|
|
2389
|
+
const visibility = record.values.visibility;
|
|
2390
|
+
if (visibility === "shared") {
|
|
2391
|
+
return true;
|
|
2392
|
+
}
|
|
2393
|
+
if (visibility === "private") {
|
|
2394
|
+
return record.createdBy === ctx.userId;
|
|
2395
|
+
}
|
|
2396
|
+
return false;
|
|
2397
|
+
},
|
|
2398
|
+
/**
|
|
2399
|
+
* Check if user can modify a note.
|
|
2400
|
+
*
|
|
2401
|
+
* Rules:
|
|
2402
|
+
* - Private notes: only the author can modify
|
|
2403
|
+
* - Shared notes: anyone can modify
|
|
2404
|
+
*/
|
|
2405
|
+
canModifyRecord(ctx, record) {
|
|
2406
|
+
const visibility = record.values.visibility;
|
|
2407
|
+
if (visibility === "private") {
|
|
2408
|
+
return record.createdBy === ctx.userId;
|
|
2409
|
+
}
|
|
2410
|
+
return true;
|
|
2411
|
+
},
|
|
2412
|
+
/**
|
|
2413
|
+
* Check if user can delete a note.
|
|
2414
|
+
*
|
|
2415
|
+
* Rules:
|
|
2416
|
+
* - Only the author (createdBy) can delete a note, regardless of visibility
|
|
2417
|
+
*/
|
|
2418
|
+
canDeleteRecord(ctx, record) {
|
|
2419
|
+
return record.createdBy === ctx.userId;
|
|
2420
|
+
}
|
|
2421
|
+
};
|
|
2422
|
+
|
|
2423
|
+
// src/runtime/policies/index.ts
|
|
2424
|
+
defaultPolicyRegistry.register(notesPolicy);
|
|
2425
|
+
|
|
2201
2426
|
// src/runtime/services/audit.helpers.ts
|
|
2202
2427
|
function buildAuditChanges(oldValues, newValues, fieldsToCheck) {
|
|
2203
2428
|
const changes = [];
|
|
@@ -2213,6 +2438,40 @@ function buildAuditChanges(oldValues, newValues, fieldsToCheck) {
|
|
|
2213
2438
|
return changes;
|
|
2214
2439
|
}
|
|
2215
2440
|
|
|
2441
|
+
// src/runtime/services/base.service.ts
|
|
2442
|
+
var TenantAwareService = class {
|
|
2443
|
+
/**
|
|
2444
|
+
* Get the current tenant ID from execution context.
|
|
2445
|
+
* @throws TenantContextError if no context is set
|
|
2446
|
+
*/
|
|
2447
|
+
get tenantId() {
|
|
2448
|
+
return getTenantId();
|
|
2449
|
+
}
|
|
2450
|
+
/**
|
|
2451
|
+
* Get the current user ID from execution context, if set.
|
|
2452
|
+
* Returns undefined if no user is associated with the current context.
|
|
2453
|
+
*/
|
|
2454
|
+
get userId() {
|
|
2455
|
+
return getUserId();
|
|
2456
|
+
}
|
|
2457
|
+
};
|
|
2458
|
+
var TenantAwareRepository = class {
|
|
2459
|
+
/**
|
|
2460
|
+
* Get the current tenant ID from execution context.
|
|
2461
|
+
* @throws TenantContextError if no context is set
|
|
2462
|
+
*/
|
|
2463
|
+
get tenantId() {
|
|
2464
|
+
return getTenantId();
|
|
2465
|
+
}
|
|
2466
|
+
/**
|
|
2467
|
+
* Get the current user ID from execution context, if set.
|
|
2468
|
+
* Returns undefined if no user is associated with the current context.
|
|
2469
|
+
*/
|
|
2470
|
+
get userId() {
|
|
2471
|
+
return getUserId();
|
|
2472
|
+
}
|
|
2473
|
+
};
|
|
2474
|
+
|
|
2216
2475
|
// src/runtime/services/audit.service.ts
|
|
2217
2476
|
var SENSITIVE_PATTERNS = [
|
|
2218
2477
|
/password/i,
|
|
@@ -2225,10 +2484,10 @@ var SENSITIVE_PATTERNS = [
|
|
|
2225
2484
|
/ssn/i,
|
|
2226
2485
|
/cvv/i
|
|
2227
2486
|
];
|
|
2228
|
-
var AuditService = class {
|
|
2229
|
-
constructor(adapter,
|
|
2487
|
+
var AuditService = class extends TenantAwareService {
|
|
2488
|
+
constructor(adapter, options) {
|
|
2489
|
+
super();
|
|
2230
2490
|
this.adapter = adapter;
|
|
2231
|
-
this.tenantId = tenantId;
|
|
2232
2491
|
this.options = options;
|
|
2233
2492
|
this.buffer = [];
|
|
2234
2493
|
this.flushTimer = null;
|
|
@@ -2248,9 +2507,7 @@ var AuditService = class {
|
|
|
2248
2507
|
*/
|
|
2249
2508
|
async logRecordAction(params) {
|
|
2250
2509
|
const entry = {
|
|
2251
|
-
tenantId: this.tenantId,
|
|
2252
2510
|
actorId: params.actorId,
|
|
2253
|
-
actorEmail: params.actorEmail,
|
|
2254
2511
|
actorType: "user",
|
|
2255
2512
|
action: params.action,
|
|
2256
2513
|
resourceType: "record",
|
|
@@ -2268,9 +2525,7 @@ var AuditService = class {
|
|
|
2268
2525
|
*/
|
|
2269
2526
|
async logSchemaAction(params) {
|
|
2270
2527
|
const entry = {
|
|
2271
|
-
tenantId: this.tenantId,
|
|
2272
2528
|
actorId: params.actorId,
|
|
2273
|
-
actorEmail: params.actorEmail,
|
|
2274
2529
|
actorType: "user",
|
|
2275
2530
|
action: params.action,
|
|
2276
2531
|
resourceType: params.resourceType,
|
|
@@ -2288,9 +2543,7 @@ var AuditService = class {
|
|
|
2288
2543
|
*/
|
|
2289
2544
|
async logUserAction(params) {
|
|
2290
2545
|
const entry = {
|
|
2291
|
-
tenantId: this.tenantId,
|
|
2292
2546
|
actorId: params.actorId,
|
|
2293
|
-
actorEmail: params.actorEmail,
|
|
2294
2547
|
actorType: "user",
|
|
2295
2548
|
action: params.action,
|
|
2296
2549
|
resourceType: "user",
|
|
@@ -2306,9 +2559,7 @@ var AuditService = class {
|
|
|
2306
2559
|
*/
|
|
2307
2560
|
async logRoleAction(params) {
|
|
2308
2561
|
const entry = {
|
|
2309
|
-
tenantId: this.tenantId,
|
|
2310
2562
|
actorId: params.actorId,
|
|
2311
|
-
actorEmail: params.actorEmail,
|
|
2312
2563
|
actorType: "user",
|
|
2313
2564
|
action: params.action,
|
|
2314
2565
|
resourceType: "role",
|
|
@@ -2327,9 +2578,7 @@ var AuditService = class {
|
|
|
2327
2578
|
*/
|
|
2328
2579
|
async logFileAction(params) {
|
|
2329
2580
|
const entry = {
|
|
2330
|
-
tenantId: this.tenantId,
|
|
2331
2581
|
actorId: params.actorId,
|
|
2332
|
-
actorEmail: params.actorEmail,
|
|
2333
2582
|
actorType: "user",
|
|
2334
2583
|
action: params.action,
|
|
2335
2584
|
resourceType: "file",
|
|
@@ -2345,7 +2594,6 @@ var AuditService = class {
|
|
|
2345
2594
|
*/
|
|
2346
2595
|
async logSystemAction(params) {
|
|
2347
2596
|
const entry = {
|
|
2348
|
-
tenantId: this.tenantId,
|
|
2349
2597
|
actorType: "system",
|
|
2350
2598
|
action: params.action,
|
|
2351
2599
|
resourceType: params.resourceType,
|
|
@@ -2368,7 +2616,7 @@ var AuditService = class {
|
|
|
2368
2616
|
if (!this.adapter.audit) {
|
|
2369
2617
|
return { entries: [], total: 0 };
|
|
2370
2618
|
}
|
|
2371
|
-
return await this.adapter.audit.list(
|
|
2619
|
+
return await this.adapter.audit.list(options);
|
|
2372
2620
|
}
|
|
2373
2621
|
/**
|
|
2374
2622
|
* Get audit history for a specific record
|
|
@@ -2377,7 +2625,7 @@ var AuditService = class {
|
|
|
2377
2625
|
if (!this.adapter.audit) {
|
|
2378
2626
|
return [];
|
|
2379
2627
|
}
|
|
2380
|
-
return await this.adapter.audit.getByResource(
|
|
2628
|
+
return await this.adapter.audit.getByResource("record", recordId, {
|
|
2381
2629
|
orderDirection: "desc"
|
|
2382
2630
|
});
|
|
2383
2631
|
}
|
|
@@ -2388,7 +2636,7 @@ var AuditService = class {
|
|
|
2388
2636
|
if (!this.adapter.audit) {
|
|
2389
2637
|
return [];
|
|
2390
2638
|
}
|
|
2391
|
-
return await this.adapter.audit.getByActor(
|
|
2639
|
+
return await this.adapter.audit.getByActor(userId, options);
|
|
2392
2640
|
}
|
|
2393
2641
|
/**
|
|
2394
2642
|
* Get audit logs for a specific resource
|
|
@@ -2397,7 +2645,7 @@ var AuditService = class {
|
|
|
2397
2645
|
if (!this.adapter.audit) {
|
|
2398
2646
|
return [];
|
|
2399
2647
|
}
|
|
2400
|
-
return await this.adapter.audit.getByResource(
|
|
2648
|
+
return await this.adapter.audit.getByResource(resourceType, resourceId, {
|
|
2401
2649
|
orderDirection: "desc"
|
|
2402
2650
|
});
|
|
2403
2651
|
}
|
|
@@ -2444,12 +2692,6 @@ var AuditService = class {
|
|
|
2444
2692
|
if (!this.adapter.audit) {
|
|
2445
2693
|
return;
|
|
2446
2694
|
}
|
|
2447
|
-
if (entry.actorId && !entry.actorEmail && entry.actorType === "user") {
|
|
2448
|
-
const profile = await this.adapter.userProfiles.findById(entry.actorId);
|
|
2449
|
-
if (profile) {
|
|
2450
|
-
entry.actorEmail = profile.email;
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
2695
|
if (this.options?.async) {
|
|
2454
2696
|
this.buffer.push(entry);
|
|
2455
2697
|
const batchSize = this.options.batchSize ?? 10;
|
|
@@ -2488,13 +2730,11 @@ var AuditService = class {
|
|
|
2488
2730
|
};
|
|
2489
2731
|
|
|
2490
2732
|
// src/runtime/services/file.service.ts
|
|
2491
|
-
var FileService = class {
|
|
2492
|
-
constructor(adapter,
|
|
2733
|
+
var FileService = class extends TenantAwareService {
|
|
2734
|
+
constructor(adapter, options) {
|
|
2735
|
+
super();
|
|
2493
2736
|
this.adapter = adapter;
|
|
2494
|
-
this.
|
|
2495
|
-
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter, tenantId) : void 0);
|
|
2496
|
-
this.userId = options?.userId;
|
|
2497
|
-
this.userEmail = options?.userEmail;
|
|
2737
|
+
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter) : void 0);
|
|
2498
2738
|
}
|
|
2499
2739
|
// ============================================================================
|
|
2500
2740
|
// UPLOAD (requires StorageAdapter)
|
|
@@ -2542,7 +2782,6 @@ var FileService = class {
|
|
|
2542
2782
|
folderPath: input.folderPath
|
|
2543
2783
|
});
|
|
2544
2784
|
const file = await this.adapter.files.create({
|
|
2545
|
-
tenantId: this.tenantId,
|
|
2546
2785
|
name: input.fileName,
|
|
2547
2786
|
originalName: input.fileName,
|
|
2548
2787
|
mimeType: input.mimeType,
|
|
@@ -2561,7 +2800,6 @@ var FileService = class {
|
|
|
2561
2800
|
await this.auditService.logFileAction({
|
|
2562
2801
|
action: "file.uploaded",
|
|
2563
2802
|
actorId: this.userId,
|
|
2564
|
-
actorEmail: this.userEmail,
|
|
2565
2803
|
fileId: file.id,
|
|
2566
2804
|
fileName: file.name,
|
|
2567
2805
|
metadata: {
|
|
@@ -2604,17 +2842,11 @@ var FileService = class {
|
|
|
2604
2842
|
* ```
|
|
2605
2843
|
*/
|
|
2606
2844
|
async createFile(data) {
|
|
2607
|
-
if (data.tenantId !== this.tenantId) {
|
|
2608
|
-
throw new Error(
|
|
2609
|
-
`Tenant mismatch: service initialized with "${this.tenantId}" but data has "${data.tenantId}"`
|
|
2610
|
-
);
|
|
2611
|
-
}
|
|
2612
2845
|
const file = await this.adapter.files.create(data);
|
|
2613
2846
|
if (this.auditService && this.userId) {
|
|
2614
2847
|
await this.auditService.logFileAction({
|
|
2615
2848
|
action: "file.uploaded",
|
|
2616
2849
|
actorId: this.userId,
|
|
2617
|
-
actorEmail: this.userEmail,
|
|
2618
2850
|
fileId: file.id,
|
|
2619
2851
|
fileName: file.name,
|
|
2620
2852
|
metadata: {
|
|
@@ -2677,7 +2909,6 @@ var FileService = class {
|
|
|
2677
2909
|
await this.auditService.logFileAction({
|
|
2678
2910
|
action: "file.updated",
|
|
2679
2911
|
actorId: this.userId,
|
|
2680
|
-
actorEmail: this.userEmail,
|
|
2681
2912
|
fileId,
|
|
2682
2913
|
fileName: updatedFile.name,
|
|
2683
2914
|
changes
|
|
@@ -2711,7 +2942,6 @@ var FileService = class {
|
|
|
2711
2942
|
await this.auditService.logFileAction({
|
|
2712
2943
|
action: "file.deleted",
|
|
2713
2944
|
actorId: this.userId,
|
|
2714
|
-
actorEmail: this.userEmail,
|
|
2715
2945
|
fileId,
|
|
2716
2946
|
fileName: file.name
|
|
2717
2947
|
});
|
|
@@ -2743,7 +2973,6 @@ var FileService = class {
|
|
|
2743
2973
|
await this.auditService.logFileAction({
|
|
2744
2974
|
action: "file.deleted",
|
|
2745
2975
|
actorId: this.userId,
|
|
2746
|
-
actorEmail: this.userEmail,
|
|
2747
2976
|
fileId,
|
|
2748
2977
|
fileName: file.name,
|
|
2749
2978
|
metadata: { deletedFromStorage: true }
|
|
@@ -2774,7 +3003,6 @@ var FileService = class {
|
|
|
2774
3003
|
await this.auditService.logFileAction({
|
|
2775
3004
|
action: "file.deleted",
|
|
2776
3005
|
actorId: this.userId,
|
|
2777
|
-
actorEmail: this.userEmail,
|
|
2778
3006
|
fileId,
|
|
2779
3007
|
fileName: file.name,
|
|
2780
3008
|
metadata: { deletedFromStorage: options?.deleteFromStorage ?? false }
|
|
@@ -2789,13 +3017,13 @@ var FileService = class {
|
|
|
2789
3017
|
* List files for the tenant
|
|
2790
3018
|
*/
|
|
2791
3019
|
async listFiles(options) {
|
|
2792
|
-
return await this.adapter.files.list(
|
|
3020
|
+
return await this.adapter.files.list(options);
|
|
2793
3021
|
}
|
|
2794
3022
|
/**
|
|
2795
3023
|
* List files by folder
|
|
2796
3024
|
*/
|
|
2797
3025
|
async listFilesByFolder(folderPath) {
|
|
2798
|
-
return await this.adapter.files.findByFolder(
|
|
3026
|
+
return await this.adapter.files.findByFolder(folderPath);
|
|
2799
3027
|
}
|
|
2800
3028
|
/**
|
|
2801
3029
|
* List files uploaded by a specific user
|
|
@@ -3053,34 +3281,35 @@ var ForbiddenError = class extends SchemaError {
|
|
|
3053
3281
|
};
|
|
3054
3282
|
|
|
3055
3283
|
// src/runtime/services/flow.service.ts
|
|
3056
|
-
var FlowService = class {
|
|
3284
|
+
var FlowService = class extends TenantAwareService {
|
|
3057
3285
|
constructor(adapter, systemFlows = []) {
|
|
3286
|
+
super();
|
|
3058
3287
|
this.adapter = adapter;
|
|
3059
3288
|
this.systemFlows = new Map(systemFlows.map((f) => [f.name, f]));
|
|
3060
3289
|
}
|
|
3061
3290
|
/**
|
|
3062
|
-
* Get all flows for
|
|
3291
|
+
* Get all flows for current tenant (system + custom)
|
|
3063
3292
|
*/
|
|
3064
|
-
async getAllFlows(
|
|
3293
|
+
async getAllFlows() {
|
|
3065
3294
|
const systemFlowsList = Array.from(this.systemFlows.values());
|
|
3066
3295
|
if (!this.adapter.flows) {
|
|
3067
3296
|
return systemFlowsList;
|
|
3068
3297
|
}
|
|
3069
|
-
const dbFlows = await this.adapter.flows.findAllForTenant(
|
|
3298
|
+
const dbFlows = await this.adapter.flows.findAllForTenant();
|
|
3070
3299
|
const customFlows = dbFlows.filter((f) => !f.system).map(this.convertDBFlowToDefinition);
|
|
3071
3300
|
return [...systemFlowsList, ...customFlows];
|
|
3072
3301
|
}
|
|
3073
3302
|
/**
|
|
3074
3303
|
* Get published flows only
|
|
3075
3304
|
*/
|
|
3076
|
-
async getPublishedFlows(
|
|
3077
|
-
const allFlows = await this.getAllFlows(
|
|
3305
|
+
async getPublishedFlows() {
|
|
3306
|
+
const allFlows = await this.getAllFlows();
|
|
3078
3307
|
return allFlows.filter((f) => f.status === "published");
|
|
3079
3308
|
}
|
|
3080
3309
|
/**
|
|
3081
3310
|
* Get a specific flow by name
|
|
3082
3311
|
*/
|
|
3083
|
-
async getFlow(name
|
|
3312
|
+
async getFlow(name) {
|
|
3084
3313
|
const systemFlow = this.systemFlows.get(name);
|
|
3085
3314
|
if (systemFlow) {
|
|
3086
3315
|
return systemFlow;
|
|
@@ -3088,7 +3317,7 @@ var FlowService = class {
|
|
|
3088
3317
|
if (!this.adapter.flows) {
|
|
3089
3318
|
return null;
|
|
3090
3319
|
}
|
|
3091
|
-
const dbFlow = await this.adapter.flows.findByName(
|
|
3320
|
+
const dbFlow = await this.adapter.flows.findByName(name);
|
|
3092
3321
|
if (dbFlow) {
|
|
3093
3322
|
return this.convertDBFlowToDefinition(dbFlow);
|
|
3094
3323
|
}
|
|
@@ -3110,7 +3339,7 @@ var FlowService = class {
|
|
|
3110
3339
|
/**
|
|
3111
3340
|
* Create a new custom flow (as draft)
|
|
3112
3341
|
*/
|
|
3113
|
-
async createFlow(input
|
|
3342
|
+
async createFlow(input) {
|
|
3114
3343
|
if (!this.adapter.flows) {
|
|
3115
3344
|
throw new SchemaError(
|
|
3116
3345
|
"Flows feature is not enabled. Database adapter does not support flows.",
|
|
@@ -3119,7 +3348,7 @@ var FlowService = class {
|
|
|
3119
3348
|
);
|
|
3120
3349
|
}
|
|
3121
3350
|
this.validateFlowName(input.name);
|
|
3122
|
-
const existing = await this.adapter.flows.findByName(
|
|
3351
|
+
const existing = await this.adapter.flows.findByName(input.name);
|
|
3123
3352
|
if (existing) {
|
|
3124
3353
|
throw new SchemaError(
|
|
3125
3354
|
`Flow "${input.name}" already exists`,
|
|
@@ -3136,7 +3365,6 @@ var FlowService = class {
|
|
|
3136
3365
|
}
|
|
3137
3366
|
this.validateFlowStructure(input);
|
|
3138
3367
|
const dbFlow = await this.adapter.flows.create({
|
|
3139
|
-
tenantId,
|
|
3140
3368
|
name: input.name,
|
|
3141
3369
|
label: input.label,
|
|
3142
3370
|
description: input.description,
|
|
@@ -3289,6 +3517,12 @@ var FlowService = class {
|
|
|
3289
3517
|
* Validate flow structure (slots, pages, relations)
|
|
3290
3518
|
*/
|
|
3291
3519
|
validateFlowStructure(input) {
|
|
3520
|
+
this.validateBasicFlowRequirements(input);
|
|
3521
|
+
const slotIds = this.validateSlotIds(input.slots);
|
|
3522
|
+
this.validateFieldReferences(input.pages, slotIds);
|
|
3523
|
+
this.validateRelationReferences(input.relations, slotIds);
|
|
3524
|
+
}
|
|
3525
|
+
validateBasicFlowRequirements(input) {
|
|
3292
3526
|
if (!input.slots || input.slots.length === 0) {
|
|
3293
3527
|
throw new ValidationError("Flow must have at least one slot", [
|
|
3294
3528
|
{ path: ["slots"], message: "Flow must have at least one slot" }
|
|
@@ -3299,13 +3533,18 @@ var FlowService = class {
|
|
|
3299
3533
|
{ path: ["pages"], message: "Flow must have at least one page" }
|
|
3300
3534
|
]);
|
|
3301
3535
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3536
|
+
}
|
|
3537
|
+
validateSlotIds(slots) {
|
|
3538
|
+
const slotIds = new Set(slots.map((s) => s.id));
|
|
3539
|
+
if (slotIds.size !== slots.length) {
|
|
3304
3540
|
throw new ValidationError("Duplicate slot IDs detected", [
|
|
3305
3541
|
{ path: ["slots"], message: "Duplicate slot IDs detected" }
|
|
3306
3542
|
]);
|
|
3307
3543
|
}
|
|
3308
|
-
|
|
3544
|
+
return slotIds;
|
|
3545
|
+
}
|
|
3546
|
+
validateFieldReferences(pages, slotIds) {
|
|
3547
|
+
for (const page of pages) {
|
|
3309
3548
|
for (const row of page.rows) {
|
|
3310
3549
|
for (const field of row.fields) {
|
|
3311
3550
|
if (!slotIds.has(field.slotId)) {
|
|
@@ -3319,7 +3558,9 @@ var FlowService = class {
|
|
|
3319
3558
|
}
|
|
3320
3559
|
}
|
|
3321
3560
|
}
|
|
3322
|
-
|
|
3561
|
+
}
|
|
3562
|
+
validateRelationReferences(relations, slotIds) {
|
|
3563
|
+
for (const relation of relations) {
|
|
3323
3564
|
if (!slotIds.has(relation.sourceSlotId)) {
|
|
3324
3565
|
throw new ValidationError("Relation references unknown source slot", [
|
|
3325
3566
|
{
|
|
@@ -3409,10 +3650,10 @@ var GeocodingService = class {
|
|
|
3409
3650
|
};
|
|
3410
3651
|
|
|
3411
3652
|
// src/runtime/services/global-search.service.ts
|
|
3412
|
-
var GlobalSearchService = class {
|
|
3413
|
-
constructor(adapter
|
|
3653
|
+
var GlobalSearchService = class extends TenantAwareService {
|
|
3654
|
+
constructor(adapter) {
|
|
3655
|
+
super();
|
|
3414
3656
|
this.adapter = adapter;
|
|
3415
|
-
this.tenantId = tenantId;
|
|
3416
3657
|
}
|
|
3417
3658
|
/**
|
|
3418
3659
|
* Search across all object records for the tenant
|
|
@@ -3442,7 +3683,7 @@ var GlobalSearchService = class {
|
|
|
3442
3683
|
if (!query || query.trim().length === 0) {
|
|
3443
3684
|
return { results: [], total: 0 };
|
|
3444
3685
|
}
|
|
3445
|
-
return await this.adapter.objectRecords.globalSearch(
|
|
3686
|
+
return await this.adapter.objectRecords.globalSearch(query.trim(), {
|
|
3446
3687
|
limit: options?.limit ?? 20,
|
|
3447
3688
|
offset: options?.offset ?? 0,
|
|
3448
3689
|
objectNames: options?.objectNames,
|
|
@@ -3485,6 +3726,9 @@ var GlobalSearchService = class {
|
|
|
3485
3726
|
}
|
|
3486
3727
|
};
|
|
3487
3728
|
|
|
3729
|
+
// src/types/attributes.ts
|
|
3730
|
+
var RELATION_TARGET_ANY = "*";
|
|
3731
|
+
|
|
3488
3732
|
// src/builders/attribute-validators.ts
|
|
3489
3733
|
var import_constants2 = require("@stndrds/constants");
|
|
3490
3734
|
var VALID_ICONS = new Set(import_constants2.ICONS);
|
|
@@ -3678,6 +3922,79 @@ var ObjectReferencedError = class extends Error {
|
|
|
3678
3922
|
}
|
|
3679
3923
|
};
|
|
3680
3924
|
|
|
3925
|
+
// src/types/objects.ts
|
|
3926
|
+
var SYSTEM_FIELD_NAMES = [
|
|
3927
|
+
"id",
|
|
3928
|
+
"createdAt",
|
|
3929
|
+
"updatedAt",
|
|
3930
|
+
"createdBy",
|
|
3931
|
+
"lastUpdatedBy"
|
|
3932
|
+
];
|
|
3933
|
+
var RESERVED_ATTRIBUTE_NAMES = [
|
|
3934
|
+
// System fields (auto-managed)
|
|
3935
|
+
...SYSTEM_FIELD_NAMES,
|
|
3936
|
+
// Other ObjectRecord properties
|
|
3937
|
+
"objectId",
|
|
3938
|
+
"label",
|
|
3939
|
+
"completionStatus",
|
|
3940
|
+
"values",
|
|
3941
|
+
"metadata",
|
|
3942
|
+
"deletedAt"
|
|
3943
|
+
];
|
|
3944
|
+
|
|
3945
|
+
// src/types/system-attributes.ts
|
|
3946
|
+
var SYSTEM_ATTRIBUTES = {
|
|
3947
|
+
createdAt: {
|
|
3948
|
+
id: "system:createdAt",
|
|
3949
|
+
name: "createdAt",
|
|
3950
|
+
label: "Created time",
|
|
3951
|
+
type: "date",
|
|
3952
|
+
required: false,
|
|
3953
|
+
disabled: true,
|
|
3954
|
+
system: true,
|
|
3955
|
+
icon: "clock-circle",
|
|
3956
|
+
description: "When this record was created"
|
|
3957
|
+
},
|
|
3958
|
+
updatedAt: {
|
|
3959
|
+
id: "system:updatedAt",
|
|
3960
|
+
name: "updatedAt",
|
|
3961
|
+
label: "Last edited time",
|
|
3962
|
+
type: "date",
|
|
3963
|
+
required: false,
|
|
3964
|
+
disabled: true,
|
|
3965
|
+
system: true,
|
|
3966
|
+
icon: "clock-circle",
|
|
3967
|
+
description: "When this record was last modified"
|
|
3968
|
+
},
|
|
3969
|
+
createdBy: {
|
|
3970
|
+
id: "system:createdBy",
|
|
3971
|
+
name: "createdBy",
|
|
3972
|
+
label: "Created by",
|
|
3973
|
+
type: "user",
|
|
3974
|
+
required: false,
|
|
3975
|
+
disabled: true,
|
|
3976
|
+
system: true,
|
|
3977
|
+
multiple: false,
|
|
3978
|
+
icon: "user",
|
|
3979
|
+
description: "User who created this record"
|
|
3980
|
+
},
|
|
3981
|
+
lastUpdatedBy: {
|
|
3982
|
+
id: "system:lastUpdatedBy",
|
|
3983
|
+
name: "lastUpdatedBy",
|
|
3984
|
+
label: "Last edited by",
|
|
3985
|
+
type: "user",
|
|
3986
|
+
required: false,
|
|
3987
|
+
disabled: true,
|
|
3988
|
+
system: true,
|
|
3989
|
+
multiple: false,
|
|
3990
|
+
icon: "user",
|
|
3991
|
+
description: "User who last modified this record"
|
|
3992
|
+
}
|
|
3993
|
+
};
|
|
3994
|
+
function getSystemAttributeList() {
|
|
3995
|
+
return Object.values(SYSTEM_ATTRIBUTES);
|
|
3996
|
+
}
|
|
3997
|
+
|
|
3681
3998
|
// src/validation/validators.ts
|
|
3682
3999
|
var import_zod4 = require("zod");
|
|
3683
4000
|
var baseConfigSchema = import_zod4.z.object({
|
|
@@ -3713,41 +4030,17 @@ var relationTargetSchema = import_zod4.z.object({
|
|
|
3713
4030
|
displayTemplate: import_zod4.z.string().optional(),
|
|
3714
4031
|
filter: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
|
|
3715
4032
|
});
|
|
3716
|
-
var documentTypeConfigSchema = import_zod4.z.object({
|
|
3717
|
-
type: import_zod4.z.enum([
|
|
3718
|
-
"id_card",
|
|
3719
|
-
"passport",
|
|
3720
|
-
"incorporation_certificate",
|
|
3721
|
-
"driver_license",
|
|
3722
|
-
"birth_certificate",
|
|
3723
|
-
"residence_permit",
|
|
3724
|
-
"bank_statement",
|
|
3725
|
-
"proof_of_address",
|
|
3726
|
-
"custom"
|
|
3727
|
-
]),
|
|
3728
|
-
label: import_zod4.z.string(),
|
|
3729
|
-
faces: import_zod4.z.array(import_zod4.z.enum(["front", "back", "single"])),
|
|
3730
|
-
attributeMapping: import_zod4.z.array(
|
|
3731
|
-
import_zod4.z.object({
|
|
3732
|
-
attributeId: import_zod4.z.string(),
|
|
3733
|
-
extractedKey: import_zod4.z.string(),
|
|
3734
|
-
face: import_zod4.z.enum(["front", "back", "single"]).optional(),
|
|
3735
|
-
required: import_zod4.z.boolean().optional()
|
|
3736
|
-
})
|
|
3737
|
-
).optional()
|
|
3738
|
-
});
|
|
3739
|
-
var fileVerificationConfigSchema = import_zod4.z.object({
|
|
3740
|
-
enabled: import_zod4.z.boolean(),
|
|
3741
|
-
documentTypes: import_zod4.z.array(documentTypeConfigSchema),
|
|
3742
|
-
autoExtract: import_zod4.z.boolean().optional(),
|
|
3743
|
-
autoValidate: import_zod4.z.boolean().optional()
|
|
3744
|
-
});
|
|
3745
4033
|
var textConfigSchema = baseConfigSchema.extend({
|
|
3746
4034
|
minLength: import_zod4.z.number().int().min(0).optional(),
|
|
3747
4035
|
maxLength: import_zod4.z.number().int().min(1).optional(),
|
|
3748
4036
|
pattern: import_zod4.z.string().optional()
|
|
3749
4037
|
});
|
|
3750
4038
|
var textareaConfigSchema = baseConfigSchema;
|
|
4039
|
+
var richtextConfigSchema = baseConfigSchema.extend({
|
|
4040
|
+
features: import_zod4.z.array(
|
|
4041
|
+
import_zod4.z.enum(["headings", "bold", "italic", "lists", "links", "images", "codeBlocks", "tables"])
|
|
4042
|
+
).optional()
|
|
4043
|
+
});
|
|
3751
4044
|
var numberConfigSchema = baseConfigSchema.extend({
|
|
3752
4045
|
min: import_zod4.z.number().optional(),
|
|
3753
4046
|
max: import_zod4.z.number().optional(),
|
|
@@ -3778,9 +4071,6 @@ var locationConfigSchema = baseConfigSchema.extend({
|
|
|
3778
4071
|
allowedCountries: import_zod4.z.array(import_zod4.z.string().length(3)).optional(),
|
|
3779
4072
|
displayFormat: import_zod4.z.enum(["single_line", "multi_line", "compact"]).optional()
|
|
3780
4073
|
});
|
|
3781
|
-
var timestampConfigSchema = baseConfigSchema.extend({
|
|
3782
|
-
autoUpdate: import_zod4.z.boolean().optional()
|
|
3783
|
-
});
|
|
3784
4074
|
var selectConfigSchema = baseConfigSchema.extend({
|
|
3785
4075
|
options: optionsArraySchema
|
|
3786
4076
|
});
|
|
@@ -3791,7 +4081,6 @@ var fileConfigSchema = baseConfigSchema.extend({
|
|
|
3791
4081
|
maxFiles: import_zod4.z.number().int().min(1).optional(),
|
|
3792
4082
|
maxSize: import_zod4.z.number().int().min(1).optional(),
|
|
3793
4083
|
allowedTypes: import_zod4.z.array(import_zod4.z.string()).optional(),
|
|
3794
|
-
verification: fileVerificationConfigSchema.optional(),
|
|
3795
4084
|
multiple: import_zod4.z.boolean().optional()
|
|
3796
4085
|
});
|
|
3797
4086
|
var userConfigSchema = baseConfigSchema.extend({
|
|
@@ -3819,24 +4108,40 @@ var rollupConfigSchema = baseConfigSchema.extend({
|
|
|
3819
4108
|
relationPath: import_zod4.z.string().optional(),
|
|
3820
4109
|
targetAttribute: import_zod4.z.string().min(1),
|
|
3821
4110
|
function: import_zod4.z.enum([
|
|
4111
|
+
// Numeric aggregates
|
|
3822
4112
|
"sum",
|
|
3823
|
-
"count",
|
|
3824
4113
|
"avg",
|
|
3825
|
-
|
|
3826
|
-
"
|
|
4114
|
+
// Date aggregates
|
|
4115
|
+
"earliest",
|
|
4116
|
+
"latest",
|
|
4117
|
+
// Count (universal)
|
|
4118
|
+
"count",
|
|
3827
4119
|
"countValues",
|
|
4120
|
+
"countUniqueValues",
|
|
3828
4121
|
"countEmpty",
|
|
4122
|
+
// Percent (universal)
|
|
3829
4123
|
"percentEmpty",
|
|
3830
|
-
"
|
|
3831
|
-
"collect"
|
|
4124
|
+
"percentNotEmpty"
|
|
3832
4125
|
]),
|
|
3833
4126
|
decimals: import_zod4.z.number().int().min(0).max(10).optional(),
|
|
3834
4127
|
showAsOriginal: import_zod4.z.boolean().optional(),
|
|
3835
|
-
targetAttributeType: import_zod4.z.string().optional()
|
|
4128
|
+
targetAttributeType: import_zod4.z.string().optional(),
|
|
4129
|
+
targetAttributeOptions: import_zod4.z.array(
|
|
4130
|
+
import_zod4.z.object({
|
|
4131
|
+
id: import_zod4.z.string(),
|
|
4132
|
+
label: import_zod4.z.string(),
|
|
4133
|
+
value: import_zod4.z.string(),
|
|
4134
|
+
color: import_zod4.z.string().optional(),
|
|
4135
|
+
icon: import_zod4.z.string().optional(),
|
|
4136
|
+
description: import_zod4.z.string().optional(),
|
|
4137
|
+
group: import_zod4.z.enum(["idle", "in_progress", "finished"]).optional()
|
|
4138
|
+
})
|
|
4139
|
+
).optional()
|
|
3836
4140
|
});
|
|
3837
4141
|
var attributeConfigSchemas = {
|
|
3838
4142
|
text: textConfigSchema,
|
|
3839
4143
|
textarea: textareaConfigSchema,
|
|
4144
|
+
richtext: richtextConfigSchema,
|
|
3840
4145
|
number: numberConfigSchema,
|
|
3841
4146
|
checkbox: checkboxConfigSchema,
|
|
3842
4147
|
date: dateConfigSchema,
|
|
@@ -3844,7 +4149,6 @@ var attributeConfigSchemas = {
|
|
|
3844
4149
|
currency: currencyConfigSchema,
|
|
3845
4150
|
status: statusConfigSchema,
|
|
3846
4151
|
location: locationConfigSchema,
|
|
3847
|
-
timestamp: timestampConfigSchema,
|
|
3848
4152
|
select: selectConfigSchema,
|
|
3849
4153
|
multiselect: multiselectConfigSchema,
|
|
3850
4154
|
file: fileConfigSchema,
|
|
@@ -3943,9 +4247,6 @@ function createLocationValidator(_attr) {
|
|
|
3943
4247
|
longitude: import_zod4.z.number().optional()
|
|
3944
4248
|
});
|
|
3945
4249
|
}
|
|
3946
|
-
function createTimestampValidator(_attr) {
|
|
3947
|
-
return import_zod4.z.number().int().positive();
|
|
3948
|
-
}
|
|
3949
4250
|
function createFileValidator(attr) {
|
|
3950
4251
|
const uuidSchema = import_zod4.z.uuid({
|
|
3951
4252
|
message: `${attr.label} must be a valid file ID`
|
|
@@ -4015,10 +4316,31 @@ function createFormulaValidator(_attr) {
|
|
|
4015
4316
|
function createRollupValidator(_attr) {
|
|
4016
4317
|
return import_zod4.z.unknown();
|
|
4017
4318
|
}
|
|
4319
|
+
function createTextAreaValidator(_attr) {
|
|
4320
|
+
return import_zod4.z.string();
|
|
4321
|
+
}
|
|
4322
|
+
var blockNoteBlockSchema = import_zod4.z.lazy(
|
|
4323
|
+
() => import_zod4.z.object({
|
|
4324
|
+
id: import_zod4.z.string().min(1, "Block must have an id"),
|
|
4325
|
+
type: import_zod4.z.string().min(1, "Block must have a type"),
|
|
4326
|
+
props: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.union([import_zod4.z.boolean(), import_zod4.z.number(), import_zod4.z.string()])),
|
|
4327
|
+
content: import_zod4.z.any().optional(),
|
|
4328
|
+
children: import_zod4.z.array(import_zod4.z.any())
|
|
4329
|
+
})
|
|
4330
|
+
);
|
|
4331
|
+
function createRichtextValidator(attr) {
|
|
4332
|
+
return import_zod4.z.array(blockNoteBlockSchema, {
|
|
4333
|
+
message: `${attr.label} must be a valid BlockNote content array`
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4018
4336
|
function createAttributeValidator(attr) {
|
|
4019
4337
|
switch (attr.type) {
|
|
4020
4338
|
case "text":
|
|
4021
4339
|
return createTextValidator(attr);
|
|
4340
|
+
case "textarea":
|
|
4341
|
+
return createTextAreaValidator(attr);
|
|
4342
|
+
case "richtext":
|
|
4343
|
+
return createRichtextValidator(attr);
|
|
4022
4344
|
case "number":
|
|
4023
4345
|
return createNumberValidator(attr);
|
|
4024
4346
|
case "checkbox":
|
|
@@ -4033,8 +4355,6 @@ function createAttributeValidator(attr) {
|
|
|
4033
4355
|
return createStatusValidator(attr);
|
|
4034
4356
|
case "location":
|
|
4035
4357
|
return createLocationValidator(attr);
|
|
4036
|
-
case "timestamp":
|
|
4037
|
-
return createTimestampValidator(attr);
|
|
4038
4358
|
case "select":
|
|
4039
4359
|
return createSelectValidator(attr);
|
|
4040
4360
|
case "multiselect":
|
|
@@ -4157,23 +4477,22 @@ function computeRecordStatus(objectDef, data) {
|
|
|
4157
4477
|
}
|
|
4158
4478
|
|
|
4159
4479
|
// src/runtime/services/object-schema.service.ts
|
|
4160
|
-
var
|
|
4480
|
+
var ObjectSchemaService = class extends TenantAwareService {
|
|
4161
4481
|
constructor(adapter, nativeRegistry, options) {
|
|
4482
|
+
super();
|
|
4162
4483
|
this.adapter = adapter;
|
|
4163
4484
|
this.nativeRegistry = nativeRegistry;
|
|
4164
4485
|
this.auditService = options?.auditService;
|
|
4165
|
-
this.userId = options?.userId;
|
|
4166
|
-
this.userEmail = options?.userEmail;
|
|
4167
4486
|
}
|
|
4168
4487
|
/**
|
|
4169
|
-
* Create a new custom object
|
|
4170
|
-
* Validates name format and ensures system=false
|
|
4488
|
+
* Create a new custom object.
|
|
4489
|
+
* Validates name format and ensures system=false.
|
|
4490
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4171
4491
|
*
|
|
4172
4492
|
* @param definition - Object definition (attributes can be raw definitions or builders)
|
|
4173
|
-
* @param tenantId - Tenant ID owning the object
|
|
4174
4493
|
* @returns Created ObjectDefinition
|
|
4175
4494
|
*/
|
|
4176
|
-
async createCustomObject(definition
|
|
4495
|
+
async createCustomObject(definition) {
|
|
4177
4496
|
const builder = object({
|
|
4178
4497
|
name: definition.name,
|
|
4179
4498
|
label: definition.label
|
|
@@ -4187,17 +4506,14 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4187
4506
|
builder.attributes(definition.attributes);
|
|
4188
4507
|
}
|
|
4189
4508
|
const objectDef = builder.build();
|
|
4190
|
-
const existing = await this.adapter.objects.findByName(
|
|
4509
|
+
const existing = await this.adapter.objects.findByName(objectDef.name);
|
|
4191
4510
|
if (existing) {
|
|
4192
4511
|
throw new Error(`Object with name "${objectDef.name}" already exists`);
|
|
4193
4512
|
}
|
|
4194
4513
|
const validatedConfigs = [];
|
|
4195
4514
|
if (objectDef.attributes.length > 0) {
|
|
4196
4515
|
for (const attr of objectDef.attributes) {
|
|
4197
|
-
const config = await this.validateAttributeInput(
|
|
4198
|
-
attr,
|
|
4199
|
-
tenantId
|
|
4200
|
-
);
|
|
4516
|
+
const config = await this.validateAttributeInput(attr);
|
|
4201
4517
|
validatedConfigs.push(config);
|
|
4202
4518
|
}
|
|
4203
4519
|
}
|
|
@@ -4207,7 +4523,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4207
4523
|
};
|
|
4208
4524
|
const { dbObject, createdAttributes } = await this.adapter.transaction(async (tx) => {
|
|
4209
4525
|
const dbObj = await tx.objects.create({
|
|
4210
|
-
tenantId,
|
|
4211
4526
|
name: objectDef.name,
|
|
4212
4527
|
label: objectDef.label,
|
|
4213
4528
|
pluralLabel: objectDef.pluralLabel,
|
|
@@ -4247,7 +4562,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4247
4562
|
await this.auditService.logSchemaAction({
|
|
4248
4563
|
action: "object.created",
|
|
4249
4564
|
actorId: this.userId,
|
|
4250
|
-
actorEmail: this.userEmail,
|
|
4251
4565
|
resourceType: "object",
|
|
4252
4566
|
resourceId: dbObject.id,
|
|
4253
4567
|
resourceLabel: dbObject.label,
|
|
@@ -4258,16 +4572,16 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4258
4572
|
return result;
|
|
4259
4573
|
}
|
|
4260
4574
|
/**
|
|
4261
|
-
* Add a custom attribute to an existing object
|
|
4262
|
-
* Can be used on both custom and native objects
|
|
4263
|
-
* Attributes added via API are always custom (system=false)
|
|
4575
|
+
* Add a custom attribute to an existing object.
|
|
4576
|
+
* Can be used on both custom and native objects.
|
|
4577
|
+
* Attributes added via API are always custom (system=false).
|
|
4578
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4264
4579
|
*
|
|
4265
4580
|
* @param objectId - Object UUID from database
|
|
4266
4581
|
* @param attribute - Attribute definition
|
|
4267
|
-
* @param tenantId - Tenant ID (required for relation validation)
|
|
4268
4582
|
* @returns Created Attribute
|
|
4269
4583
|
*/
|
|
4270
|
-
async addAttributeToObject(objectId, attribute
|
|
4584
|
+
async addAttributeToObject(objectId, attribute) {
|
|
4271
4585
|
const dbObject = await this.adapter.objects.findById(objectId);
|
|
4272
4586
|
if (!dbObject) {
|
|
4273
4587
|
throw new Error(`Object with id "${objectId}" not found`);
|
|
@@ -4279,7 +4593,7 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4279
4593
|
`Attribute with name "${attribute.name}" already exists on object "${dbObject.name}"`
|
|
4280
4594
|
);
|
|
4281
4595
|
}
|
|
4282
|
-
const config = await this.validateAttributeInput(attribute
|
|
4596
|
+
const config = await this.validateAttributeInput(attribute);
|
|
4283
4597
|
const dbAttr = await this.adapter.attributes.create({
|
|
4284
4598
|
objectId,
|
|
4285
4599
|
name: attribute.name,
|
|
@@ -4295,7 +4609,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4295
4609
|
await this.auditService.logSchemaAction({
|
|
4296
4610
|
action: "attribute.created",
|
|
4297
4611
|
actorId: this.userId,
|
|
4298
|
-
actorEmail: this.userEmail,
|
|
4299
4612
|
resourceType: "attribute",
|
|
4300
4613
|
resourceId: dbAttr.id,
|
|
4301
4614
|
resourceLabel: dbAttr.label,
|
|
@@ -4306,15 +4619,15 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4306
4619
|
return this.convertDBAttributeToAttribute(dbAttr);
|
|
4307
4620
|
}
|
|
4308
4621
|
/**
|
|
4309
|
-
* Update an attribute
|
|
4310
|
-
* Can only update custom attributes (system=false)
|
|
4622
|
+
* Update an attribute.
|
|
4623
|
+
* Can only update custom attributes (system=false).
|
|
4624
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4311
4625
|
*
|
|
4312
4626
|
* @param attributeId - Attribute UUID
|
|
4313
4627
|
* @param updates - Partial attribute updates
|
|
4314
|
-
* @param tenantId - Tenant ID (required for relation validation)
|
|
4315
4628
|
* @returns Updated Attribute
|
|
4316
4629
|
*/
|
|
4317
|
-
async updateAttribute(attributeId, updates
|
|
4630
|
+
async updateAttribute(attributeId, updates) {
|
|
4318
4631
|
const dbAttr = await this.adapter.attributes.findById(attributeId);
|
|
4319
4632
|
if (!dbAttr) {
|
|
4320
4633
|
throw new Error(`Attribute with id "${attributeId}" not found`);
|
|
@@ -4344,7 +4657,7 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4344
4657
|
};
|
|
4345
4658
|
const config = this.buildAttributeConfig(mergedInput);
|
|
4346
4659
|
if (dbAttr.type === "relation" && updates.targets) {
|
|
4347
|
-
await this.validateRelationTargets(mergedInput
|
|
4660
|
+
await this.validateRelationTargets(mergedInput);
|
|
4348
4661
|
}
|
|
4349
4662
|
const oldValues = {
|
|
4350
4663
|
label: dbAttr.label,
|
|
@@ -4362,7 +4675,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4362
4675
|
await this.auditService.logSchemaAction({
|
|
4363
4676
|
action: "attribute.updated",
|
|
4364
4677
|
actorId: this.userId,
|
|
4365
|
-
actorEmail: this.userEmail,
|
|
4366
4678
|
resourceType: "attribute",
|
|
4367
4679
|
resourceId: attributeId,
|
|
4368
4680
|
resourceLabel: updatedDbAttr.label,
|
|
@@ -4410,7 +4722,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4410
4722
|
await this.auditService.logSchemaAction({
|
|
4411
4723
|
action: "attribute.deleted",
|
|
4412
4724
|
actorId: this.userId,
|
|
4413
|
-
actorEmail: this.userEmail,
|
|
4414
4725
|
resourceType: "attribute",
|
|
4415
4726
|
resourceId: attributeId,
|
|
4416
4727
|
resourceLabel: dbAttr.label,
|
|
@@ -4496,7 +4807,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4496
4807
|
await this.auditService.logSchemaAction({
|
|
4497
4808
|
action: "object.updated",
|
|
4498
4809
|
actorId: this.userId,
|
|
4499
|
-
actorEmail: this.userEmail,
|
|
4500
4810
|
resourceType: "object",
|
|
4501
4811
|
resourceId: objectId,
|
|
4502
4812
|
resourceLabel: updatedDbObject.label,
|
|
@@ -4518,9 +4828,10 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4518
4828
|
return this.convertDBObjectToDefinition(updatedDbObject, dbAttributes);
|
|
4519
4829
|
}
|
|
4520
4830
|
/**
|
|
4521
|
-
* Delete a custom object
|
|
4522
|
-
* Can only delete custom objects (system=false)
|
|
4523
|
-
* This will also delete all associated attributes and records
|
|
4831
|
+
* Delete a custom object.
|
|
4832
|
+
* Can only delete custom objects (system=false).
|
|
4833
|
+
* This will also delete all associated attributes and records.
|
|
4834
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4524
4835
|
*
|
|
4525
4836
|
* @param objectId - Object UUID
|
|
4526
4837
|
*
|
|
@@ -4529,7 +4840,7 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4529
4840
|
* await service.deleteObject("obj-123");
|
|
4530
4841
|
* ```
|
|
4531
4842
|
*/
|
|
4532
|
-
async deleteObject(objectId
|
|
4843
|
+
async deleteObject(objectId) {
|
|
4533
4844
|
const dbObject = await this.adapter.objects.findById(objectId);
|
|
4534
4845
|
if (!dbObject) {
|
|
4535
4846
|
throw new Error(`Object with id "${objectId}" not found`);
|
|
@@ -4539,22 +4850,15 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4539
4850
|
`Cannot delete system object "${dbObject.name}". System objects are protected.`
|
|
4540
4851
|
);
|
|
4541
4852
|
}
|
|
4542
|
-
const
|
|
4543
|
-
if (
|
|
4544
|
-
|
|
4545
|
-
dbObject.name,
|
|
4546
|
-
effectiveTenantId
|
|
4547
|
-
);
|
|
4548
|
-
if (referencingObjects.length > 0) {
|
|
4549
|
-
throw new ObjectReferencedError(dbObject.name, referencingObjects);
|
|
4550
|
-
}
|
|
4853
|
+
const referencingObjects = await this.findObjectsWithRelationTo(dbObject.name);
|
|
4854
|
+
if (referencingObjects.length > 0) {
|
|
4855
|
+
throw new ObjectReferencedError(dbObject.name, referencingObjects);
|
|
4551
4856
|
}
|
|
4552
4857
|
await this.adapter.objects.delete(objectId);
|
|
4553
4858
|
if (this.auditService && this.userId) {
|
|
4554
4859
|
await this.auditService.logSchemaAction({
|
|
4555
4860
|
action: "object.deleted",
|
|
4556
4861
|
actorId: this.userId,
|
|
4557
|
-
actorEmail: this.userEmail,
|
|
4558
4862
|
resourceType: "object",
|
|
4559
4863
|
resourceId: objectId,
|
|
4560
4864
|
resourceLabel: dbObject.label,
|
|
@@ -4580,41 +4884,29 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
4580
4884
|
"Invalid attribute name format.\nName must be a valid variable identifier:\n \u2705 Valid: 'firstName', 'first_name', 'FirstName', 'FIRST_NAME'\n \u274C Invalid: 'first-name', 'first name', '123name', 'first.name'"
|
|
4581
4885
|
);
|
|
4582
4886
|
}
|
|
4583
|
-
if (
|
|
4584
|
-
name
|
|
4585
|
-
)) {
|
|
4887
|
+
if (RESERVED_ATTRIBUTE_NAMES.includes(name)) {
|
|
4586
4888
|
throw new Error(
|
|
4587
4889
|
`"${name}" is a reserved name and cannot be used as an attribute name.
|
|
4588
|
-
Reserved names: ${
|
|
4890
|
+
Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
4589
4891
|
);
|
|
4590
4892
|
}
|
|
4591
4893
|
}
|
|
4894
|
+
// ============================================================================
|
|
4895
|
+
// SCHEMA RETRIEVAL (DRY - all methods use buildObjectDefinition)
|
|
4896
|
+
// ============================================================================
|
|
4592
4897
|
/**
|
|
4593
|
-
* Get complete object schema (
|
|
4594
|
-
*
|
|
4595
|
-
* If object is custom, returns DB definition only
|
|
4898
|
+
* Get complete object schema (object attributes + system attributes)
|
|
4899
|
+
* Includes system attributes (createdAt, updatedAt, createdBy, lastUpdatedBy)
|
|
4596
4900
|
*
|
|
4597
4901
|
* @param objectId - Object UUID from database
|
|
4598
|
-
* @returns Complete ObjectDefinition with all attributes
|
|
4599
|
-
*
|
|
4600
|
-
* @example
|
|
4601
|
-
* ```typescript
|
|
4602
|
-
* const service = new ObjectSchemaService(adapter, registry);
|
|
4603
|
-
* const productSchema = await service.getObjectSchema("obj-123");
|
|
4604
|
-
*
|
|
4605
|
-
* console.log(productSchema.attributes); // System + custom attributes
|
|
4606
|
-
* ```
|
|
4902
|
+
* @returns Complete ObjectDefinition with all attributes including system attributes
|
|
4607
4903
|
*/
|
|
4608
4904
|
async getObjectSchema(objectId) {
|
|
4609
4905
|
const dbObject = await this.adapter.objects.findById(objectId);
|
|
4610
4906
|
if (!dbObject) {
|
|
4611
4907
|
throw new Error(`Object with id "${objectId}" not found`);
|
|
4612
4908
|
}
|
|
4613
|
-
|
|
4614
|
-
if (dbObject.system) {
|
|
4615
|
-
return this.mergeNativeObject(dbObject, dbAttributes);
|
|
4616
|
-
}
|
|
4617
|
-
return this.convertDBObjectToDefinition(dbObject, dbAttributes);
|
|
4909
|
+
return this.buildObjectDefinition(dbObject);
|
|
4618
4910
|
}
|
|
4619
4911
|
/**
|
|
4620
4912
|
* Get object schema by name (for system/native objects)
|
|
@@ -4625,41 +4917,54 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
4625
4917
|
if (!dbObject) {
|
|
4626
4918
|
throw new Error(`Native object "${name}" not found in database`);
|
|
4627
4919
|
}
|
|
4628
|
-
|
|
4629
|
-
return this.mergeNativeObject(dbObject, dbAttributes);
|
|
4920
|
+
return this.buildObjectDefinition(dbObject);
|
|
4630
4921
|
}
|
|
4631
4922
|
/**
|
|
4632
|
-
* Get object schema by name
|
|
4923
|
+
* Get object schema by name (supports both native and custom objects).
|
|
4924
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4633
4925
|
*/
|
|
4634
|
-
async getObjectSchemaByNameForTenant(name
|
|
4635
|
-
let dbObject = await this.adapter.objects.findByName(
|
|
4926
|
+
async getObjectSchemaByNameForTenant(name) {
|
|
4927
|
+
let dbObject = await this.adapter.objects.findByName(name);
|
|
4636
4928
|
if (!dbObject) {
|
|
4637
4929
|
dbObject = await this.adapter.objects.findSystemByName(name);
|
|
4638
4930
|
}
|
|
4639
4931
|
if (!dbObject) {
|
|
4640
4932
|
throw new Error(`Object "${name}" not found`);
|
|
4641
4933
|
}
|
|
4934
|
+
return this.buildObjectDefinition(dbObject);
|
|
4935
|
+
}
|
|
4936
|
+
/**
|
|
4937
|
+
* List all object schemas.
|
|
4938
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4939
|
+
*/
|
|
4940
|
+
async listObjectSchemas() {
|
|
4941
|
+
const dbObjects = await this.adapter.objects.list();
|
|
4942
|
+
return Promise.all(dbObjects.map((dbObject) => this.buildObjectDefinition(dbObject)));
|
|
4943
|
+
}
|
|
4944
|
+
// ============================================================================
|
|
4945
|
+
// PRIVATE HELPERS - SCHEMA BUILDING
|
|
4946
|
+
// ============================================================================
|
|
4947
|
+
/**
|
|
4948
|
+
* Build complete ObjectDefinition from DB object
|
|
4949
|
+
* Handles both native (merged with registry) and custom objects
|
|
4950
|
+
* Always appends system attributes at the end
|
|
4951
|
+
* @internal
|
|
4952
|
+
*/
|
|
4953
|
+
async buildObjectDefinition(dbObject) {
|
|
4642
4954
|
const dbAttributes = await this.adapter.attributes.findByObjectId(dbObject.id);
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
}
|
|
4646
|
-
return this.convertDBObjectToDefinition(dbObject, dbAttributes);
|
|
4955
|
+
const baseDef = dbObject.system ? this.mergeNativeObject(dbObject, dbAttributes) : this.convertDBObjectToDefinition(dbObject, dbAttributes);
|
|
4956
|
+
return this.withSystemAttributes(baseDef);
|
|
4647
4957
|
}
|
|
4648
4958
|
/**
|
|
4649
|
-
*
|
|
4959
|
+
* Append system attributes to an ObjectDefinition
|
|
4960
|
+
* System attributes are always available on all records (createdAt, updatedAt, createdBy, lastUpdatedBy)
|
|
4961
|
+
* @internal
|
|
4650
4962
|
*/
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
if (dbObject.system) {
|
|
4657
|
-
return this.mergeNativeObject(dbObject, dbAttributes);
|
|
4658
|
-
}
|
|
4659
|
-
return this.convertDBObjectToDefinition(dbObject, dbAttributes);
|
|
4660
|
-
})
|
|
4661
|
-
);
|
|
4662
|
-
return schemas;
|
|
4963
|
+
withSystemAttributes(def) {
|
|
4964
|
+
return {
|
|
4965
|
+
...def,
|
|
4966
|
+
attributes: [...def.attributes, ...getSystemAttributeList()]
|
|
4967
|
+
};
|
|
4663
4968
|
}
|
|
4664
4969
|
/**
|
|
4665
4970
|
* Merge native object from registry with custom attributes from DB
|
|
@@ -4721,15 +5026,15 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
4721
5026
|
};
|
|
4722
5027
|
}
|
|
4723
5028
|
/**
|
|
4724
|
-
* Validate attribute input and return parsed config
|
|
4725
|
-
* Validates: name format, config schema, relation targets
|
|
5029
|
+
* Validate attribute input and return parsed config.
|
|
5030
|
+
* Validates: name format, config schema, relation targets.
|
|
4726
5031
|
* @internal
|
|
4727
5032
|
*/
|
|
4728
|
-
async validateAttributeInput(attribute
|
|
5033
|
+
async validateAttributeInput(attribute) {
|
|
4729
5034
|
this.validateAttributeName(attribute.name);
|
|
4730
5035
|
const config = this.buildAttributeConfig(attribute);
|
|
4731
5036
|
if (attribute.type === "relation") {
|
|
4732
|
-
await this.validateRelationTargets(attribute
|
|
5037
|
+
await this.validateRelationTargets(attribute);
|
|
4733
5038
|
}
|
|
4734
5039
|
return config;
|
|
4735
5040
|
}
|
|
@@ -4756,23 +5061,24 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
4756
5061
|
}
|
|
4757
5062
|
}
|
|
4758
5063
|
/**
|
|
4759
|
-
* Check if an object exists (in tenant, system, or native registry)
|
|
5064
|
+
* Check if an object exists (in tenant, system, or native registry).
|
|
5065
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4760
5066
|
* @internal
|
|
4761
5067
|
*/
|
|
4762
|
-
async objectExists(name
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
if (obj) return true;
|
|
4766
|
-
}
|
|
5068
|
+
async objectExists(name) {
|
|
5069
|
+
const obj = await this.adapter.objects.findByName(name);
|
|
5070
|
+
if (obj) return true;
|
|
4767
5071
|
const systemObj = await this.adapter.objects.findSystemByName(name);
|
|
4768
5072
|
if (systemObj) return true;
|
|
4769
5073
|
return this.nativeRegistry.getByName(name) !== void 0;
|
|
4770
5074
|
}
|
|
4771
5075
|
/**
|
|
4772
|
-
* Validate that relation target objects exist
|
|
5076
|
+
* Validate that relation target objects exist.
|
|
5077
|
+
* Skip validation for universal relations (toAny with target "*").
|
|
5078
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4773
5079
|
* @internal
|
|
4774
5080
|
*/
|
|
4775
|
-
async validateRelationTargets(attribute
|
|
5081
|
+
async validateRelationTargets(attribute) {
|
|
4776
5082
|
const targets = attribute.targets;
|
|
4777
5083
|
if (!targets || targets.length === 0) {
|
|
4778
5084
|
throw new Error("Relation attribute must have at least one target object");
|
|
@@ -4782,11 +5088,14 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
4782
5088
|
if (!isValidCardinality) {
|
|
4783
5089
|
throw new Error("Relation attribute must specify cardinality: 'one' or 'many'");
|
|
4784
5090
|
}
|
|
5091
|
+
if (targets.length === 1 && targets[0].object === RELATION_TARGET_ANY) {
|
|
5092
|
+
return;
|
|
5093
|
+
}
|
|
4785
5094
|
for (const target of targets) {
|
|
4786
5095
|
if (!target.object) {
|
|
4787
5096
|
throw new Error("Each relation target must specify an object name");
|
|
4788
5097
|
}
|
|
4789
|
-
const exists = await this.objectExists(target.object
|
|
5098
|
+
const exists = await this.objectExists(target.object);
|
|
4790
5099
|
if (!exists) {
|
|
4791
5100
|
throw new Error(
|
|
4792
5101
|
`Relation target object "${target.object}" does not exist. Make sure the object is created before adding a relation to it.`
|
|
@@ -4797,10 +5106,11 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
4797
5106
|
/**
|
|
4798
5107
|
* Find objects that have a relation attribute targeting the given object.
|
|
4799
5108
|
* Used to prevent deletion of objects that are relation targets.
|
|
5109
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
4800
5110
|
* @internal
|
|
4801
5111
|
*/
|
|
4802
|
-
async findObjectsWithRelationTo(targetObjectName
|
|
4803
|
-
const allObjects = await this.adapter.objects.list(
|
|
5112
|
+
async findObjectsWithRelationTo(targetObjectName) {
|
|
5113
|
+
const allObjects = await this.adapter.objects.list();
|
|
4804
5114
|
const referencing = [];
|
|
4805
5115
|
for (const obj of allObjects) {
|
|
4806
5116
|
if (obj.name === targetObjectName) continue;
|
|
@@ -4834,23 +5144,12 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
4834
5144
|
return baseAttr;
|
|
4835
5145
|
}
|
|
4836
5146
|
};
|
|
4837
|
-
/**
|
|
4838
|
-
* Reserved attribute names that cannot be used.
|
|
4839
|
-
* These names conflict with system fields on ObjectRecord.
|
|
4840
|
-
*/
|
|
4841
|
-
_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES = [
|
|
4842
|
-
"id",
|
|
4843
|
-
"createdAt",
|
|
4844
|
-
"updatedAt",
|
|
4845
|
-
"metadata"
|
|
4846
|
-
];
|
|
4847
|
-
var ObjectSchemaService = _ObjectSchemaService;
|
|
4848
5147
|
|
|
4849
5148
|
// src/runtime/services/permission.service.ts
|
|
4850
|
-
var PermissionService = class {
|
|
4851
|
-
constructor(adapter,
|
|
5149
|
+
var PermissionService = class extends TenantAwareService {
|
|
5150
|
+
constructor(adapter, options) {
|
|
5151
|
+
super();
|
|
4852
5152
|
this.adapter = adapter;
|
|
4853
|
-
this.tenantId = tenantId;
|
|
4854
5153
|
this.cache = /* @__PURE__ */ new Map();
|
|
4855
5154
|
/** Track pending permission fetches to prevent duplicate concurrent requests */
|
|
4856
5155
|
this.pendingFetches = /* @__PURE__ */ new Map();
|
|
@@ -4863,8 +5162,6 @@ var PermissionService = class {
|
|
|
4863
5162
|
this.cacheTtlMs = options?.cacheTtlMs ?? 6e4;
|
|
4864
5163
|
this.maxCacheSize = options?.maxCacheSize ?? 1e4;
|
|
4865
5164
|
this.auditService = options?.auditService;
|
|
4866
|
-
this.userId = options?.userId;
|
|
4867
|
-
this.userEmail = options?.userEmail;
|
|
4868
5165
|
}
|
|
4869
5166
|
// ============================================================================
|
|
4870
5167
|
// PERMISSION CHECKS
|
|
@@ -5019,10 +5316,7 @@ var PermissionService = class {
|
|
|
5019
5316
|
* @internal
|
|
5020
5317
|
*/
|
|
5021
5318
|
async fetchAndCachePermissions(userProfileId, cacheKey) {
|
|
5022
|
-
const permissions = await this.permissionsRepo.getEffectivePermissions(
|
|
5023
|
-
userProfileId,
|
|
5024
|
-
this.tenantId
|
|
5025
|
-
);
|
|
5319
|
+
const permissions = await this.permissionsRepo.getEffectivePermissions(userProfileId);
|
|
5026
5320
|
if (this.cache.size >= this.maxCacheSize) {
|
|
5027
5321
|
const firstKey = this.cache.keys().next().value;
|
|
5028
5322
|
if (firstKey) {
|
|
@@ -5066,9 +5360,10 @@ var PermissionService = class {
|
|
|
5066
5360
|
// ============================================================================
|
|
5067
5361
|
/**
|
|
5068
5362
|
* Get all roles for the current tenant.
|
|
5363
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
5069
5364
|
*/
|
|
5070
5365
|
async getRoles() {
|
|
5071
|
-
return await this.permissionsRepo.
|
|
5366
|
+
return await this.permissionsRepo.getRoles();
|
|
5072
5367
|
}
|
|
5073
5368
|
/**
|
|
5074
5369
|
* Get a role by ID.
|
|
@@ -5080,15 +5375,11 @@ var PermissionService = class {
|
|
|
5080
5375
|
* Create a new role.
|
|
5081
5376
|
*/
|
|
5082
5377
|
async createRole(input) {
|
|
5083
|
-
const role = await this.permissionsRepo.createRole(
|
|
5084
|
-
...input,
|
|
5085
|
-
tenantId: this.tenantId
|
|
5086
|
-
});
|
|
5378
|
+
const role = await this.permissionsRepo.createRole(input);
|
|
5087
5379
|
if (this.auditService && this.userId) {
|
|
5088
5380
|
await this.auditService.logRoleAction({
|
|
5089
5381
|
action: "role.created",
|
|
5090
5382
|
actorId: this.userId,
|
|
5091
|
-
actorEmail: this.userEmail,
|
|
5092
5383
|
roleId: role.id,
|
|
5093
5384
|
roleLabel: role.label
|
|
5094
5385
|
});
|
|
@@ -5117,7 +5408,6 @@ var PermissionService = class {
|
|
|
5117
5408
|
await this.auditService.logRoleAction({
|
|
5118
5409
|
action: "role.updated",
|
|
5119
5410
|
actorId: this.userId,
|
|
5120
|
-
actorEmail: this.userEmail,
|
|
5121
5411
|
roleId: role.id,
|
|
5122
5412
|
roleLabel: role.label,
|
|
5123
5413
|
changes
|
|
@@ -5137,7 +5427,6 @@ var PermissionService = class {
|
|
|
5137
5427
|
await this.auditService.logRoleAction({
|
|
5138
5428
|
action: "role.deleted",
|
|
5139
5429
|
actorId: this.userId,
|
|
5140
|
-
actorEmail: this.userEmail,
|
|
5141
5430
|
roleId,
|
|
5142
5431
|
roleLabel: role.label
|
|
5143
5432
|
});
|
|
@@ -5163,7 +5452,6 @@ var PermissionService = class {
|
|
|
5163
5452
|
await this.auditService.logRoleAction({
|
|
5164
5453
|
action: "role.updated",
|
|
5165
5454
|
actorId: this.userId,
|
|
5166
|
-
actorEmail: this.userEmail,
|
|
5167
5455
|
roleId,
|
|
5168
5456
|
roleLabel: role?.label ?? roleId,
|
|
5169
5457
|
metadata: { permissionsUpdated: true, permissionCount: permissions.length }
|
|
@@ -5177,7 +5465,7 @@ var PermissionService = class {
|
|
|
5177
5465
|
* Get all roles assigned to a user.
|
|
5178
5466
|
*/
|
|
5179
5467
|
async getUserRoles(userProfileId) {
|
|
5180
|
-
return await this.permissionsRepo.getUserRoles(userProfileId
|
|
5468
|
+
return await this.permissionsRepo.getUserRoles(userProfileId);
|
|
5181
5469
|
}
|
|
5182
5470
|
/**
|
|
5183
5471
|
* Assign a role to a user.
|
|
@@ -5186,7 +5474,6 @@ var PermissionService = class {
|
|
|
5186
5474
|
const result = await this.permissionsRepo.assignRole({
|
|
5187
5475
|
userProfileId,
|
|
5188
5476
|
roleId,
|
|
5189
|
-
tenantId: this.tenantId,
|
|
5190
5477
|
assignedBy
|
|
5191
5478
|
});
|
|
5192
5479
|
this.invalidateCache(userProfileId);
|
|
@@ -5195,7 +5482,6 @@ var PermissionService = class {
|
|
|
5195
5482
|
await this.auditService.logRoleAction({
|
|
5196
5483
|
action: "role.assigned",
|
|
5197
5484
|
actorId: this.userId,
|
|
5198
|
-
actorEmail: this.userEmail,
|
|
5199
5485
|
roleId,
|
|
5200
5486
|
roleLabel: role?.label ?? roleId,
|
|
5201
5487
|
targetUserId: userProfileId
|
|
@@ -5208,13 +5494,12 @@ var PermissionService = class {
|
|
|
5208
5494
|
*/
|
|
5209
5495
|
async revokeRole(userProfileId, roleId) {
|
|
5210
5496
|
const role = await this.permissionsRepo.getRoleById(roleId);
|
|
5211
|
-
await this.permissionsRepo.revokeRole(userProfileId, roleId
|
|
5497
|
+
await this.permissionsRepo.revokeRole(userProfileId, roleId);
|
|
5212
5498
|
this.invalidateCache(userProfileId);
|
|
5213
5499
|
if (this.auditService && this.userId) {
|
|
5214
5500
|
await this.auditService.logRoleAction({
|
|
5215
5501
|
action: "role.revoked",
|
|
5216
5502
|
actorId: this.userId,
|
|
5217
|
-
actorEmail: this.userEmail,
|
|
5218
5503
|
roleId,
|
|
5219
5504
|
roleLabel: role?.label ?? roleId,
|
|
5220
5505
|
targetUserId: userProfileId
|
|
@@ -5237,7 +5522,7 @@ var PermissionService = class {
|
|
|
5237
5522
|
*
|
|
5238
5523
|
* @example
|
|
5239
5524
|
* ```typescript
|
|
5240
|
-
* const service = new PermissionService(adapter
|
|
5525
|
+
* const service = new PermissionService(adapter);
|
|
5241
5526
|
* await service.initializeDefaultRoles();
|
|
5242
5527
|
* ```
|
|
5243
5528
|
*/
|
|
@@ -5255,7 +5540,6 @@ var PermissionService = class {
|
|
|
5255
5540
|
continue;
|
|
5256
5541
|
}
|
|
5257
5542
|
const role = await this.permissionsRepo.createRole({
|
|
5258
|
-
tenantId: this.tenantId,
|
|
5259
5543
|
name: roleName,
|
|
5260
5544
|
label: DEFAULT_ROLE_LABELS2[roleName],
|
|
5261
5545
|
description: DEFAULT_ROLE_DESCRIPTIONS2[roleName]
|
|
@@ -5430,8 +5714,9 @@ Available objects: ${this.listNames().join(", ")}`
|
|
|
5430
5714
|
var registry = new NativeObjectRegistryClass();
|
|
5431
5715
|
|
|
5432
5716
|
// src/runtime/services/relation.service.ts
|
|
5433
|
-
var RelationService = class {
|
|
5717
|
+
var RelationService = class extends TenantAwareService {
|
|
5434
5718
|
constructor(adapter, nativeRegistry) {
|
|
5719
|
+
super();
|
|
5435
5720
|
this.adapter = adapter;
|
|
5436
5721
|
this.schemaService = new ObjectSchemaService(adapter, nativeRegistry);
|
|
5437
5722
|
}
|
|
@@ -5482,8 +5767,9 @@ var RelationService = class {
|
|
|
5482
5767
|
if (ids.length === 0) {
|
|
5483
5768
|
return errors;
|
|
5484
5769
|
}
|
|
5485
|
-
const
|
|
5486
|
-
|
|
5770
|
+
const isUniversal = attr.targets.length === 1 && attr.targets[0].object === RELATION_TARGET_ANY;
|
|
5771
|
+
const validObjectIds = isUniversal ? null : await this.getValidObjectIds(attr.targets);
|
|
5772
|
+
if (!isUniversal && validObjectIds?.size === 0) {
|
|
5487
5773
|
errors.push({
|
|
5488
5774
|
attribute: attr.name,
|
|
5489
5775
|
message: `No valid target objects found for ${attr.label}`
|
|
@@ -5497,7 +5783,7 @@ var RelationService = class {
|
|
|
5497
5783
|
invalidIds.push(id);
|
|
5498
5784
|
continue;
|
|
5499
5785
|
}
|
|
5500
|
-
if (!validObjectIds.has(record.objectId)) {
|
|
5786
|
+
if (!isUniversal && validObjectIds && !validObjectIds.has(record.objectId)) {
|
|
5501
5787
|
invalidIds.push(id);
|
|
5502
5788
|
}
|
|
5503
5789
|
}
|
|
@@ -5527,6 +5813,7 @@ var RelationService = class {
|
|
|
5527
5813
|
}
|
|
5528
5814
|
/**
|
|
5529
5815
|
* Get valid object IDs from relation targets
|
|
5816
|
+
* Note: Universal relations (toAny) are handled earlier in validateRelationAttribute
|
|
5530
5817
|
*/
|
|
5531
5818
|
async getValidObjectIds(targets) {
|
|
5532
5819
|
const objectIds = /* @__PURE__ */ new Set();
|
|
@@ -5558,23 +5845,23 @@ var RelationService = class {
|
|
|
5558
5845
|
// OPTIONS & RESOLUTION
|
|
5559
5846
|
// ============================================================================
|
|
5560
5847
|
/**
|
|
5561
|
-
* Get available options for a relation attribute
|
|
5562
|
-
* Searches across all target objects defined in the relation
|
|
5848
|
+
* Get available options for a relation attribute.
|
|
5849
|
+
* Searches across all target objects defined in the relation.
|
|
5850
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
5563
5851
|
*
|
|
5564
5852
|
* @param attribute - Relation attribute definition
|
|
5565
|
-
* @param tenantId - Tenant ID for multi-tenant isolation
|
|
5566
5853
|
* @param params - Query parameters
|
|
5567
5854
|
*
|
|
5568
5855
|
* @example
|
|
5569
5856
|
* ```typescript
|
|
5570
|
-
* const options = await relationService.getOptions(attribute,
|
|
5857
|
+
* const options = await relationService.getOptions(attribute, {
|
|
5571
5858
|
* query: "nike",
|
|
5572
5859
|
* page: 1,
|
|
5573
5860
|
* pageSize: 20
|
|
5574
5861
|
* });
|
|
5575
5862
|
* ```
|
|
5576
5863
|
*/
|
|
5577
|
-
async getOptions(attribute,
|
|
5864
|
+
async getOptions(attribute, params = {}) {
|
|
5578
5865
|
const { query = "", page = 1, pageSize = 20, targetObject } = params;
|
|
5579
5866
|
const targets = attribute.targets;
|
|
5580
5867
|
const filteredTargets = targetObject ? targets.filter((t) => t.object === targetObject) : targets;
|
|
@@ -5583,7 +5870,7 @@ var RelationService = class {
|
|
|
5583
5870
|
}
|
|
5584
5871
|
const allOptions = [];
|
|
5585
5872
|
let totalCount = 0;
|
|
5586
|
-
const recordService = new RecordService(this.adapter
|
|
5873
|
+
const recordService = new RecordService(this.adapter);
|
|
5587
5874
|
for (const target of filteredTargets) {
|
|
5588
5875
|
const objectSchema = await this.schemaService.getObjectSchemaByName(target.object);
|
|
5589
5876
|
if (!objectSchema?.id) {
|
|
@@ -5619,23 +5906,22 @@ var RelationService = class {
|
|
|
5619
5906
|
};
|
|
5620
5907
|
}
|
|
5621
5908
|
/**
|
|
5622
|
-
* Resolve record IDs to their display labels
|
|
5623
|
-
* Useful for displaying current values in the UI
|
|
5909
|
+
* Resolve record IDs to their display labels.
|
|
5910
|
+
* Useful for displaying current values in the UI.
|
|
5624
5911
|
*
|
|
5625
5912
|
* Uses batch fetching for performance - fetches all records in one query,
|
|
5626
5913
|
* then groups by objectId to minimize schema lookups.
|
|
5627
5914
|
*
|
|
5628
5915
|
* @param ids - Record IDs to resolve
|
|
5629
|
-
* @param _tenantId - Tenant ID for multi-tenant isolation (unused but kept for API compatibility)
|
|
5630
5916
|
* @param attributeId - Relation attribute ID to use its displayTemplate for label rendering
|
|
5631
5917
|
*
|
|
5632
5918
|
* @example
|
|
5633
5919
|
* ```typescript
|
|
5634
|
-
* const resolved = await relationService.resolveIds(["rec-1", "rec-2"], "
|
|
5920
|
+
* const resolved = await relationService.resolveIds(["rec-1", "rec-2"], "attr-123");
|
|
5635
5921
|
* // [{ id: "rec-1", label: "Nike Air Max", objectName: "products", ... }]
|
|
5636
5922
|
* ```
|
|
5637
5923
|
*/
|
|
5638
|
-
async resolveIds(ids,
|
|
5924
|
+
async resolveIds(ids, attributeId) {
|
|
5639
5925
|
if (!ids || ids.length === 0) {
|
|
5640
5926
|
return [];
|
|
5641
5927
|
}
|
|
@@ -5770,13 +6056,8 @@ var RollupService = class {
|
|
|
5770
6056
|
if (!record) {
|
|
5771
6057
|
return { value: null, recordCount: 0 };
|
|
5772
6058
|
}
|
|
5773
|
-
const parentObject = await this.adapter.objects.findById(record.objectId);
|
|
5774
|
-
if (!parentObject) {
|
|
5775
|
-
return { value: null, recordCount: 0 };
|
|
5776
|
-
}
|
|
5777
|
-
const tenantId = parentObject.tenantId;
|
|
5778
6059
|
const sourceObjectName = rollupAttr.relationAttribute;
|
|
5779
|
-
const sourceObject = await this.adapter.objects.findByName(
|
|
6060
|
+
const sourceObject = await this.adapter.objects.findByName(sourceObjectName);
|
|
5780
6061
|
if (!sourceObject) {
|
|
5781
6062
|
return { value: null, recordCount: 0 };
|
|
5782
6063
|
}
|
|
@@ -5790,7 +6071,6 @@ var RollupService = class {
|
|
|
5790
6071
|
return { value: null, recordCount: 0 };
|
|
5791
6072
|
}
|
|
5792
6073
|
const relatedRecords = await this.adapter.objectRecords.findByRelation(
|
|
5793
|
-
tenantId,
|
|
5794
6074
|
sourceObject.id,
|
|
5795
6075
|
reverseRelationAttr.name,
|
|
5796
6076
|
recordId
|
|
@@ -5804,6 +6084,9 @@ var RollupService = class {
|
|
|
5804
6084
|
* Extract and aggregate values from related records
|
|
5805
6085
|
*/
|
|
5806
6086
|
aggregateValues(relatedRecords, rollupAttr) {
|
|
6087
|
+
if (rollupAttr.function === "count") {
|
|
6088
|
+
return { value: relatedRecords.length, recordCount: relatedRecords.length };
|
|
6089
|
+
}
|
|
5807
6090
|
const values = relatedRecords.map((r) => r.values[rollupAttr.targetAttribute]).filter((v) => v !== void 0 && v !== null);
|
|
5808
6091
|
const aggregated = this.aggregate(values, rollupAttr.function);
|
|
5809
6092
|
const result = typeof aggregated === "number" && rollupAttr.decimals !== void 0 ? Number(aggregated.toFixed(rollupAttr.decimals)) : aggregated;
|
|
@@ -5832,32 +6115,38 @@ var RollupService = class {
|
|
|
5832
6115
|
*/
|
|
5833
6116
|
aggregate(values, fn) {
|
|
5834
6117
|
switch (fn) {
|
|
6118
|
+
// Numeric aggregates
|
|
5835
6119
|
case "sum":
|
|
5836
6120
|
return this.sumNumbers(values);
|
|
5837
6121
|
case "avg":
|
|
5838
6122
|
return this.averageNumbers(values);
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
6123
|
+
// Date aggregates
|
|
6124
|
+
case "earliest":
|
|
6125
|
+
return this.earliestDate(values);
|
|
6126
|
+
case "latest":
|
|
6127
|
+
return this.latestDate(values);
|
|
6128
|
+
// Count functions
|
|
5843
6129
|
case "count":
|
|
5844
6130
|
return values.length;
|
|
5845
6131
|
case "countValues":
|
|
5846
6132
|
return values.filter((v) => v != null && v !== "").length;
|
|
6133
|
+
case "countUniqueValues": {
|
|
6134
|
+
const nonEmpty = values.filter((v) => v != null && v !== "");
|
|
6135
|
+
return new Set(nonEmpty.map((v) => JSON.stringify(v))).size;
|
|
6136
|
+
}
|
|
5847
6137
|
case "countEmpty":
|
|
5848
6138
|
return values.filter((v) => v == null || v === "").length;
|
|
5849
|
-
|
|
6139
|
+
// Percent functions
|
|
6140
|
+
case "percentNotEmpty": {
|
|
5850
6141
|
if (values.length === 0) return 0;
|
|
5851
6142
|
const filled = values.filter((v) => v != null && v !== "").length;
|
|
5852
|
-
return filled / values.length
|
|
6143
|
+
return filled / values.length;
|
|
5853
6144
|
}
|
|
5854
6145
|
case "percentEmpty": {
|
|
5855
6146
|
if (values.length === 0) return 0;
|
|
5856
6147
|
const empty = values.filter((v) => v == null || v === "").length;
|
|
5857
|
-
return empty / values.length
|
|
6148
|
+
return empty / values.length;
|
|
5858
6149
|
}
|
|
5859
|
-
case "collect":
|
|
5860
|
-
return values;
|
|
5861
6150
|
}
|
|
5862
6151
|
}
|
|
5863
6152
|
/**
|
|
@@ -5868,16 +6157,15 @@ var RollupService = class {
|
|
|
5868
6157
|
case "sum":
|
|
5869
6158
|
case "count":
|
|
5870
6159
|
case "countValues":
|
|
6160
|
+
case "countUniqueValues":
|
|
5871
6161
|
case "countEmpty":
|
|
5872
6162
|
case "percentEmpty":
|
|
5873
|
-
case "
|
|
6163
|
+
case "percentNotEmpty":
|
|
5874
6164
|
return 0;
|
|
5875
6165
|
case "avg":
|
|
5876
|
-
case "
|
|
5877
|
-
case "
|
|
6166
|
+
case "earliest":
|
|
6167
|
+
case "latest":
|
|
5878
6168
|
return null;
|
|
5879
|
-
case "collect":
|
|
5880
|
-
return [];
|
|
5881
6169
|
}
|
|
5882
6170
|
}
|
|
5883
6171
|
/**
|
|
@@ -5895,20 +6183,22 @@ var RollupService = class {
|
|
|
5895
6183
|
return nums.reduce((sum, n) => sum + n, 0) / nums.length;
|
|
5896
6184
|
}
|
|
5897
6185
|
/**
|
|
5898
|
-
*
|
|
6186
|
+
* Get earliest date from values
|
|
5899
6187
|
*/
|
|
5900
|
-
|
|
5901
|
-
const
|
|
5902
|
-
if (
|
|
5903
|
-
|
|
6188
|
+
earliestDate(values) {
|
|
6189
|
+
const dates = values.filter((v) => v instanceof Date || typeof v === "string").map((v) => v instanceof Date ? v : new Date(v)).filter((d) => !Number.isNaN(d.getTime()));
|
|
6190
|
+
if (dates.length === 0) return null;
|
|
6191
|
+
const earliest = new Date(Math.min(...dates.map((d) => d.getTime())));
|
|
6192
|
+
return earliest.toISOString();
|
|
5904
6193
|
}
|
|
5905
6194
|
/**
|
|
5906
|
-
*
|
|
6195
|
+
* Get latest date from values
|
|
5907
6196
|
*/
|
|
5908
|
-
|
|
5909
|
-
const
|
|
5910
|
-
if (
|
|
5911
|
-
|
|
6197
|
+
latestDate(values) {
|
|
6198
|
+
const dates = values.filter((v) => v instanceof Date || typeof v === "string").map((v) => v instanceof Date ? v : new Date(v)).filter((d) => !Number.isNaN(d.getTime()));
|
|
6199
|
+
if (dates.length === 0) return null;
|
|
6200
|
+
const latest = new Date(Math.max(...dates.map((d) => d.getTime())));
|
|
6201
|
+
return latest.toISOString();
|
|
5912
6202
|
}
|
|
5913
6203
|
/**
|
|
5914
6204
|
* Recalculate all rollup attributes for a record and update it
|
|
@@ -5969,12 +6259,7 @@ var RollupService = class {
|
|
|
5969
6259
|
*/
|
|
5970
6260
|
async findRecordsWithForwardRollup(changedRecord, changedSchema) {
|
|
5971
6261
|
const affectedRecords = [];
|
|
5972
|
-
const
|
|
5973
|
-
if (!recordObject) {
|
|
5974
|
-
return affectedRecords;
|
|
5975
|
-
}
|
|
5976
|
-
const tenantId = recordObject.tenantId;
|
|
5977
|
-
const allObjects = await this.adapter.objects.list(tenantId);
|
|
6262
|
+
const allObjects = await this.adapter.objects.list();
|
|
5978
6263
|
for (const obj of allObjects) {
|
|
5979
6264
|
if (obj.id === changedRecord.objectId) {
|
|
5980
6265
|
continue;
|
|
@@ -6003,7 +6288,6 @@ var RollupService = class {
|
|
|
6003
6288
|
continue;
|
|
6004
6289
|
}
|
|
6005
6290
|
const recordsPointingToChanged = await this.adapter.objectRecords.findByRelation(
|
|
6006
|
-
tenantId,
|
|
6007
6291
|
obj.id,
|
|
6008
6292
|
relationAttr.name,
|
|
6009
6293
|
changedRecord.id
|
|
@@ -6020,10 +6304,10 @@ var RollupService = class {
|
|
|
6020
6304
|
};
|
|
6021
6305
|
|
|
6022
6306
|
// src/runtime/services/user.service.ts
|
|
6023
|
-
var UserService = class {
|
|
6024
|
-
constructor(adapter
|
|
6307
|
+
var UserService = class extends TenantAwareService {
|
|
6308
|
+
constructor(adapter) {
|
|
6309
|
+
super();
|
|
6025
6310
|
this.adapter = adapter;
|
|
6026
|
-
this.tenantId = tenantId;
|
|
6027
6311
|
}
|
|
6028
6312
|
/**
|
|
6029
6313
|
* Validate all user attributes in the data
|
|
@@ -6137,23 +6421,32 @@ var UserService = class {
|
|
|
6137
6421
|
};
|
|
6138
6422
|
|
|
6139
6423
|
// src/runtime/services/record.service.ts
|
|
6140
|
-
|
|
6141
|
-
|
|
6424
|
+
function applyDefaultValues(schema, data) {
|
|
6425
|
+
const result = { ...data };
|
|
6426
|
+
for (const attr of schema.attributes) {
|
|
6427
|
+
if (attr.name in result && result[attr.name] !== void 0) {
|
|
6428
|
+
continue;
|
|
6429
|
+
}
|
|
6430
|
+
if ("defaultValue" in attr && attr.defaultValue !== void 0 && attr.defaultValue !== null) {
|
|
6431
|
+
result[attr.name] = attr.defaultValue;
|
|
6432
|
+
}
|
|
6433
|
+
}
|
|
6434
|
+
return result;
|
|
6435
|
+
}
|
|
6436
|
+
var RecordService = class extends TenantAwareService {
|
|
6437
|
+
constructor(adapter, options) {
|
|
6438
|
+
super();
|
|
6142
6439
|
this.adapter = adapter;
|
|
6143
|
-
this.tenantId = tenantId;
|
|
6144
6440
|
this.schemaService = new ObjectSchemaService(adapter, registry, {
|
|
6145
|
-
userId: options?.userId,
|
|
6146
|
-
userEmail: options?.userEmail,
|
|
6147
6441
|
auditService: options?.auditService
|
|
6148
6442
|
});
|
|
6149
6443
|
this.relationService = new RelationService(adapter, registry);
|
|
6150
|
-
this.userService = new UserService(adapter
|
|
6444
|
+
this.userService = new UserService(adapter);
|
|
6151
6445
|
this.rollupService = new RollupService(adapter);
|
|
6152
6446
|
this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
|
|
6153
6447
|
this.permissionService = options?.permissionService;
|
|
6154
|
-
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter
|
|
6155
|
-
this.
|
|
6156
|
-
this.userEmail = options?.userEmail;
|
|
6448
|
+
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter) : void 0);
|
|
6449
|
+
this.policyRegistry = options?.policyRegistry === null ? null : options?.policyRegistry ?? defaultPolicyRegistry;
|
|
6157
6450
|
}
|
|
6158
6451
|
/**
|
|
6159
6452
|
* Check permission for an action on an object.
|
|
@@ -6165,6 +6458,67 @@ var RecordService = class {
|
|
|
6165
6458
|
await this.permissionService.checkObjectAccess(this.userId, objectName, action);
|
|
6166
6459
|
}
|
|
6167
6460
|
}
|
|
6461
|
+
/**
|
|
6462
|
+
* Get policy for an object if one exists and userId is configured.
|
|
6463
|
+
* @internal
|
|
6464
|
+
*/
|
|
6465
|
+
getPolicy(objectName) {
|
|
6466
|
+
if (!(this.policyRegistry && this.userId)) {
|
|
6467
|
+
return void 0;
|
|
6468
|
+
}
|
|
6469
|
+
return this.policyRegistry.get(objectName);
|
|
6470
|
+
}
|
|
6471
|
+
/**
|
|
6472
|
+
* Build policy context for the current request.
|
|
6473
|
+
* @internal
|
|
6474
|
+
*/
|
|
6475
|
+
buildPolicyContext(objectName) {
|
|
6476
|
+
return {
|
|
6477
|
+
// biome-ignore lint/style/noNonNullAssertion: userId is guaranteed to be set
|
|
6478
|
+
userId: this.userId,
|
|
6479
|
+
tenantId: this.tenantId,
|
|
6480
|
+
objectName
|
|
6481
|
+
};
|
|
6482
|
+
}
|
|
6483
|
+
/**
|
|
6484
|
+
* Check if user can access a record based on policy.
|
|
6485
|
+
* Returns true if no policy exists or user can access.
|
|
6486
|
+
* @internal
|
|
6487
|
+
*/
|
|
6488
|
+
checkRecordAccess(policy, record) {
|
|
6489
|
+
if (!policy.canAccessRecord) {
|
|
6490
|
+
return true;
|
|
6491
|
+
}
|
|
6492
|
+
return policy.canAccessRecord(this.buildPolicyContext(policy.objectName), record);
|
|
6493
|
+
}
|
|
6494
|
+
/**
|
|
6495
|
+
* Check if user can modify a record based on policy.
|
|
6496
|
+
* Throws PolicyViolationError if denied.
|
|
6497
|
+
* @internal
|
|
6498
|
+
*/
|
|
6499
|
+
checkRecordModify(policy, record) {
|
|
6500
|
+
if (!policy.canModifyRecord) {
|
|
6501
|
+
return;
|
|
6502
|
+
}
|
|
6503
|
+
const canModify = policy.canModifyRecord(this.buildPolicyContext(policy.objectName), record);
|
|
6504
|
+
if (!canModify) {
|
|
6505
|
+
throw new PolicyViolationError(policy.objectName, "update", record.id);
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
/**
|
|
6509
|
+
* Check if user can delete a record based on policy.
|
|
6510
|
+
* Throws PolicyViolationError if denied.
|
|
6511
|
+
* @internal
|
|
6512
|
+
*/
|
|
6513
|
+
checkRecordDelete(policy, record) {
|
|
6514
|
+
if (!policy.canDeleteRecord) {
|
|
6515
|
+
return;
|
|
6516
|
+
}
|
|
6517
|
+
const canDelete = policy.canDeleteRecord(this.buildPolicyContext(policy.objectName), record);
|
|
6518
|
+
if (!canDelete) {
|
|
6519
|
+
throw new PolicyViolationError(policy.objectName, "delete", record.id);
|
|
6520
|
+
}
|
|
6521
|
+
}
|
|
6168
6522
|
/**
|
|
6169
6523
|
* Resolve relation IDs to their display labels
|
|
6170
6524
|
* @internal
|
|
@@ -6187,7 +6541,7 @@ var RecordService = class {
|
|
|
6187
6541
|
}
|
|
6188
6542
|
await Promise.all(
|
|
6189
6543
|
idsWithAttrId.map(async ({ attrId, ids }) => {
|
|
6190
|
-
const resolved = await this.relationService.resolveIds(ids,
|
|
6544
|
+
const resolved = await this.relationService.resolveIds(ids, attrId);
|
|
6191
6545
|
for (const r of resolved) {
|
|
6192
6546
|
resolvedMap.set(r.id, r.label);
|
|
6193
6547
|
}
|
|
@@ -6275,33 +6629,34 @@ var RecordService = class {
|
|
|
6275
6629
|
*/
|
|
6276
6630
|
async createRecord(objectId, data, options) {
|
|
6277
6631
|
const schema = await this.schemaService.getObjectSchema(objectId);
|
|
6632
|
+
const dataWithDefaults = applyDefaultValues(schema, data);
|
|
6278
6633
|
await this.checkPermission(schema.name, "create");
|
|
6279
|
-
const hookCtx = this.buildCreateHookContext(schema,
|
|
6634
|
+
const hookCtx = this.buildCreateHookContext(schema, dataWithDefaults, options?.hookMetadata);
|
|
6280
6635
|
if (!options?.skipHooks) {
|
|
6281
6636
|
await this.hookRegistry.execute("beforeCreate", schema.name, hookCtx);
|
|
6282
6637
|
}
|
|
6283
6638
|
if (options?.validate !== false) {
|
|
6284
6639
|
if (options?.allowDraft) {
|
|
6285
|
-
validateDraftOrThrow(schema,
|
|
6640
|
+
validateDraftOrThrow(schema, dataWithDefaults);
|
|
6286
6641
|
} else {
|
|
6287
|
-
validateObjectOrThrow(schema,
|
|
6642
|
+
validateObjectOrThrow(schema, dataWithDefaults);
|
|
6288
6643
|
}
|
|
6289
6644
|
if (!options?.skipRelationValidation) {
|
|
6290
|
-
await this.relationService.validateRelationsOrThrow(schema,
|
|
6645
|
+
await this.relationService.validateRelationsOrThrow(schema, dataWithDefaults);
|
|
6291
6646
|
}
|
|
6292
6647
|
if (!options?.skipUserValidation) {
|
|
6293
|
-
await this.userService.validateUsersOrThrow(schema,
|
|
6648
|
+
await this.userService.validateUsersOrThrow(schema, dataWithDefaults);
|
|
6294
6649
|
}
|
|
6295
6650
|
}
|
|
6296
|
-
const completionStatus = computeRecordStatus(schema,
|
|
6297
|
-
const label = await this.computeLabel(schema,
|
|
6651
|
+
const completionStatus = computeRecordStatus(schema, dataWithDefaults);
|
|
6652
|
+
const label = await this.computeLabel(schema, dataWithDefaults);
|
|
6298
6653
|
const record = await this.adapter.objectRecords.create({
|
|
6299
|
-
tenantId: this.tenantId,
|
|
6300
6654
|
objectId,
|
|
6301
|
-
data,
|
|
6655
|
+
data: dataWithDefaults,
|
|
6302
6656
|
label,
|
|
6303
6657
|
completionStatus,
|
|
6304
|
-
metadata: options?.metadata
|
|
6658
|
+
metadata: options?.metadata,
|
|
6659
|
+
createdBy: this.userId
|
|
6305
6660
|
});
|
|
6306
6661
|
if (!options?.skipHooks) {
|
|
6307
6662
|
const afterCtx = {
|
|
@@ -6316,7 +6671,6 @@ var RecordService = class {
|
|
|
6316
6671
|
await this.auditService.logRecordAction({
|
|
6317
6672
|
action: "record.created",
|
|
6318
6673
|
actorId: this.userId,
|
|
6319
|
-
actorEmail: this.userEmail,
|
|
6320
6674
|
objectName: schema.name,
|
|
6321
6675
|
objectId: schema.id,
|
|
6322
6676
|
recordId: record.id,
|
|
@@ -6339,6 +6693,12 @@ var RecordService = class {
|
|
|
6339
6693
|
return null;
|
|
6340
6694
|
}
|
|
6341
6695
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
6696
|
+
if (!options?.skipPolicyCheck) {
|
|
6697
|
+
const policy = this.getPolicy(schema.name);
|
|
6698
|
+
if (policy && !this.checkRecordAccess(policy, record)) {
|
|
6699
|
+
return null;
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6342
6702
|
let enrichedRecord = record;
|
|
6343
6703
|
if (!options?.skipFormulas) {
|
|
6344
6704
|
enrichedRecord = this.enrichWithFormulas(record, schema);
|
|
@@ -6384,9 +6744,16 @@ var RecordService = class {
|
|
|
6384
6744
|
* ```
|
|
6385
6745
|
*/
|
|
6386
6746
|
async updateRecord(recordId, data, options) {
|
|
6387
|
-
const existing = await this.
|
|
6747
|
+
const existing = await this.adapter.objectRecords.findById(recordId);
|
|
6748
|
+
if (!existing) {
|
|
6749
|
+
throw new RecordNotFoundError(recordId);
|
|
6750
|
+
}
|
|
6388
6751
|
const schema = await this.schemaService.getObjectSchema(existing.objectId);
|
|
6389
6752
|
await this.checkPermission(schema.name, "update");
|
|
6753
|
+
const policy = this.getPolicy(schema.name);
|
|
6754
|
+
if (policy) {
|
|
6755
|
+
this.checkRecordModify(policy, existing);
|
|
6756
|
+
}
|
|
6390
6757
|
const mergedData = { ...existing.values, ...data };
|
|
6391
6758
|
const changedAttributes = Object.keys(data).filter((key) => existing.values[key] !== data[key]);
|
|
6392
6759
|
const hookCtx = this.buildHookContext(
|
|
@@ -6418,10 +6785,16 @@ var RecordService = class {
|
|
|
6418
6785
|
const updatePayload = {
|
|
6419
6786
|
...data,
|
|
6420
6787
|
__completionStatus: completionStatus,
|
|
6421
|
-
__label: label
|
|
6788
|
+
__label: label,
|
|
6789
|
+
__lastUpdatedBy: this.userId
|
|
6422
6790
|
};
|
|
6423
6791
|
if (options?.metadata !== void 0) {
|
|
6424
|
-
|
|
6792
|
+
const existingMetadata = existing.metadata ?? {};
|
|
6793
|
+
const mergedMetadata = { ...existingMetadata, ...options.metadata };
|
|
6794
|
+
const cleanedMetadata = Object.fromEntries(
|
|
6795
|
+
Object.entries(mergedMetadata).filter(([, value]) => value !== void 0)
|
|
6796
|
+
);
|
|
6797
|
+
updatePayload.__metadata = cleanedMetadata;
|
|
6425
6798
|
}
|
|
6426
6799
|
const updated = await this.adapter.objectRecords.update(recordId, updatePayload);
|
|
6427
6800
|
if (!options?.skipHooks) {
|
|
@@ -6441,7 +6814,6 @@ var RecordService = class {
|
|
|
6441
6814
|
await this.auditService.logRecordAction({
|
|
6442
6815
|
action: "record.updated",
|
|
6443
6816
|
actorId: this.userId,
|
|
6444
|
-
actorEmail: this.userEmail,
|
|
6445
6817
|
objectName: schema.name,
|
|
6446
6818
|
objectId: schema.id,
|
|
6447
6819
|
recordId: updated.id,
|
|
@@ -6537,9 +6909,16 @@ var RecordService = class {
|
|
|
6537
6909
|
* @param options - Delete options
|
|
6538
6910
|
*/
|
|
6539
6911
|
async deleteRecord(recordId, options) {
|
|
6540
|
-
const record = await this.
|
|
6912
|
+
const record = await this.adapter.objectRecords.findById(recordId);
|
|
6913
|
+
if (!record) {
|
|
6914
|
+
throw new RecordNotFoundError(recordId);
|
|
6915
|
+
}
|
|
6541
6916
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
6542
6917
|
await this.checkPermission(schema.name, "delete");
|
|
6918
|
+
const policy = this.getPolicy(schema.name);
|
|
6919
|
+
if (policy) {
|
|
6920
|
+
this.checkRecordDelete(policy, record);
|
|
6921
|
+
}
|
|
6543
6922
|
if (options?.checkSystem) {
|
|
6544
6923
|
if (schema.system) {
|
|
6545
6924
|
throw new ProtectedResourceError("object", schema.name, "delete");
|
|
@@ -6564,7 +6943,6 @@ var RecordService = class {
|
|
|
6564
6943
|
await this.auditService.logRecordAction({
|
|
6565
6944
|
action: "record.deleted",
|
|
6566
6945
|
actorId: this.userId,
|
|
6567
|
-
actorEmail: this.userEmail,
|
|
6568
6946
|
objectName: schema.name,
|
|
6569
6947
|
objectId: schema.id,
|
|
6570
6948
|
recordId: record.id,
|
|
@@ -6616,7 +6994,6 @@ var RecordService = class {
|
|
|
6616
6994
|
await this.auditService.logRecordAction({
|
|
6617
6995
|
action: "record.restored",
|
|
6618
6996
|
actorId: this.userId,
|
|
6619
|
-
actorEmail: this.userEmail,
|
|
6620
6997
|
objectName: schema.name,
|
|
6621
6998
|
objectId: schema.id,
|
|
6622
6999
|
recordId: restored.id,
|
|
@@ -6762,14 +7139,29 @@ var RecordService = class {
|
|
|
6762
7139
|
if (this.permissionService && this.userId) {
|
|
6763
7140
|
await this.checkPermission(schema.name, "read");
|
|
6764
7141
|
}
|
|
6765
|
-
const
|
|
7142
|
+
const policy = options?.skipPolicyFilter ? void 0 : this.getPolicy(schema.name);
|
|
7143
|
+
let effectiveOptions = options;
|
|
7144
|
+
if (policy?.applyListFilter) {
|
|
7145
|
+
effectiveOptions = policy.applyListFilter(this.buildPolicyContext(schema.name), options);
|
|
7146
|
+
}
|
|
7147
|
+
const result = await this.adapter.objectRecords.list(objectId, effectiveOptions);
|
|
7148
|
+
let filteredRecords = result.records;
|
|
7149
|
+
let effectiveTotal = result.total;
|
|
7150
|
+
if (policy?.canAccessRecord) {
|
|
7151
|
+
const ctx = this.buildPolicyContext(schema.name);
|
|
7152
|
+
filteredRecords = result.records.filter((record) => policy.canAccessRecord?.(ctx, record));
|
|
7153
|
+
effectiveTotal = filteredRecords.length;
|
|
7154
|
+
}
|
|
6766
7155
|
if (!options?.skipFormulas) {
|
|
6767
7156
|
return {
|
|
6768
|
-
records: this.enrichRecordsWithFormulas(
|
|
6769
|
-
total:
|
|
7157
|
+
records: this.enrichRecordsWithFormulas(filteredRecords, schema),
|
|
7158
|
+
total: effectiveTotal
|
|
6770
7159
|
};
|
|
6771
7160
|
}
|
|
6772
|
-
return
|
|
7161
|
+
return {
|
|
7162
|
+
records: filteredRecords,
|
|
7163
|
+
total: effectiveTotal
|
|
7164
|
+
};
|
|
6773
7165
|
}
|
|
6774
7166
|
/**
|
|
6775
7167
|
* Search records using full-text search
|
|
@@ -6784,7 +7176,7 @@ var RecordService = class {
|
|
|
6784
7176
|
if (this.permissionService && this.userId) {
|
|
6785
7177
|
await this.checkPermission(schema.name, "read");
|
|
6786
7178
|
}
|
|
6787
|
-
const result = await this.adapter.objectRecords.search(
|
|
7179
|
+
const result = await this.adapter.objectRecords.search(objectId, query, options);
|
|
6788
7180
|
if (!options?.skipFormulas) {
|
|
6789
7181
|
return {
|
|
6790
7182
|
records: this.enrichRecordsWithFormulas(result.records, schema),
|
|
@@ -6987,16 +7379,15 @@ var RollupScheduler = class {
|
|
|
6987
7379
|
this.maxPending = options?.maxPending ?? 100;
|
|
6988
7380
|
}
|
|
6989
7381
|
/**
|
|
6990
|
-
* Schedule a rollup recalculation for a parent record
|
|
7382
|
+
* Schedule a rollup recalculation for a parent record.
|
|
6991
7383
|
*
|
|
6992
7384
|
* If the same record is already pending, the timer is reset.
|
|
6993
7385
|
* If too many recalculations are pending, triggers an immediate flush.
|
|
6994
7386
|
*
|
|
6995
7387
|
* @param parentId - ID of the parent record to recalculate
|
|
6996
7388
|
* @param parentObjectId - Object ID of the parent
|
|
6997
|
-
* @param tenantId - Tenant ID
|
|
6998
7389
|
*/
|
|
6999
|
-
scheduleRecalculation(parentId, parentObjectId
|
|
7390
|
+
scheduleRecalculation(parentId, parentObjectId) {
|
|
7000
7391
|
const key = `${parentId}:${parentObjectId}`;
|
|
7001
7392
|
const existing = this.pending.get(key);
|
|
7002
7393
|
if (existing) {
|
|
@@ -7006,7 +7397,7 @@ var RollupScheduler = class {
|
|
|
7006
7397
|
await this.executeRecalculation(parentId, parentObjectId);
|
|
7007
7398
|
this.pending.delete(key);
|
|
7008
7399
|
}, this.debounceMs);
|
|
7009
|
-
this.pending.set(key, { parentId, parentObjectId,
|
|
7400
|
+
this.pending.set(key, { parentId, parentObjectId, timeout });
|
|
7010
7401
|
if (this.pending.size >= this.maxPending) {
|
|
7011
7402
|
this.flush();
|
|
7012
7403
|
}
|
|
@@ -7020,16 +7411,11 @@ var RollupScheduler = class {
|
|
|
7020
7411
|
clearTimeout(entry.timeout);
|
|
7021
7412
|
}
|
|
7022
7413
|
this.pending.clear();
|
|
7023
|
-
const tenantMap = /* @__PURE__ */ new Map();
|
|
7024
|
-
for (const [, entry] of entries) {
|
|
7025
|
-
tenantMap.set(entry.parentId, entry.tenantId);
|
|
7026
|
-
}
|
|
7027
7414
|
const allIds = entries.map(([, entry]) => entry.parentId);
|
|
7028
7415
|
const records = await this.adapter.objectRecords.findByIds(allIds);
|
|
7029
7416
|
for (const record of records) {
|
|
7030
7417
|
const schema = await this.getSchemaById(record.objectId);
|
|
7031
|
-
|
|
7032
|
-
if (schema && tenantId) {
|
|
7418
|
+
if (schema) {
|
|
7033
7419
|
await this.rollupService.recalculateAndUpdate(record, schema);
|
|
7034
7420
|
}
|
|
7035
7421
|
}
|
|
@@ -7062,27 +7448,25 @@ var RollupScheduler = class {
|
|
|
7062
7448
|
};
|
|
7063
7449
|
|
|
7064
7450
|
// src/runtime/services/user-profile.service.ts
|
|
7065
|
-
var UserProfileService = class {
|
|
7066
|
-
constructor(adapter,
|
|
7451
|
+
var UserProfileService = class extends TenantAwareService {
|
|
7452
|
+
constructor(adapter, options) {
|
|
7453
|
+
super();
|
|
7067
7454
|
this.adapter = adapter;
|
|
7068
|
-
this.tenantId = tenantId;
|
|
7069
7455
|
this.auditService = options?.auditService;
|
|
7070
|
-
this.userId = options?.userId;
|
|
7071
|
-
this.userEmail = options?.userEmail;
|
|
7072
7456
|
}
|
|
7073
7457
|
/**
|
|
7074
|
-
* Create a new user profile (typically after first auth)
|
|
7458
|
+
* Create a new user profile (typically after first auth).
|
|
7459
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7075
7460
|
*
|
|
7076
|
-
* @param data - User profile creation data
|
|
7461
|
+
* @param data - User profile creation data (without tenantId)
|
|
7077
7462
|
* @returns Created user profile
|
|
7078
7463
|
*
|
|
7079
7464
|
* @example
|
|
7080
7465
|
* ```typescript
|
|
7081
|
-
* const service = new UserProfileService(adapter
|
|
7466
|
+
* const service = new UserProfileService(adapter);
|
|
7082
7467
|
*
|
|
7083
7468
|
* // After Supabase auth
|
|
7084
7469
|
* const profile = await service.createProfile({
|
|
7085
|
-
* tenantId: "tenant-123",
|
|
7086
7470
|
* authId: authUser.id,
|
|
7087
7471
|
* email: authUser.email,
|
|
7088
7472
|
* firstName: authUser.user_metadata.first_name,
|
|
@@ -7093,16 +7477,11 @@ var UserProfileService = class {
|
|
|
7093
7477
|
* ```
|
|
7094
7478
|
*/
|
|
7095
7479
|
async createProfile(data) {
|
|
7096
|
-
if (data.tenantId !== this.tenantId) {
|
|
7097
|
-
throw new Error(
|
|
7098
|
-
`Tenant mismatch: service initialized with "${this.tenantId}" but data has "${data.tenantId}"`
|
|
7099
|
-
);
|
|
7100
|
-
}
|
|
7101
7480
|
const existing = await this.adapter.userProfiles.findByAuthId(data.authId);
|
|
7102
7481
|
if (existing) {
|
|
7103
7482
|
throw new Error(`User profile with authId "${data.authId}" already exists`);
|
|
7104
7483
|
}
|
|
7105
|
-
const existingEmail = await this.adapter.userProfiles.findByEmail(
|
|
7484
|
+
const existingEmail = await this.adapter.userProfiles.findByEmail(data.email);
|
|
7106
7485
|
if (existingEmail) {
|
|
7107
7486
|
throw new Error(`User profile with email "${data.email}" already exists in this tenant`);
|
|
7108
7487
|
}
|
|
@@ -7111,7 +7490,6 @@ var UserProfileService = class {
|
|
|
7111
7490
|
await this.auditService.logUserAction({
|
|
7112
7491
|
action: "user.created",
|
|
7113
7492
|
actorId: this.userId,
|
|
7114
|
-
actorEmail: this.userEmail,
|
|
7115
7493
|
targetUserId: profile.id,
|
|
7116
7494
|
targetUserEmail: profile.email
|
|
7117
7495
|
});
|
|
@@ -7157,7 +7535,6 @@ var UserProfileService = class {
|
|
|
7157
7535
|
* const profile = await service.getOrCreateProfile(
|
|
7158
7536
|
* authUser.id,
|
|
7159
7537
|
* {
|
|
7160
|
-
* tenantId: "tenant-123",
|
|
7161
7538
|
* authId: authUser.id,
|
|
7162
7539
|
* email: authUser.email,
|
|
7163
7540
|
* role: "member",
|
|
@@ -7174,7 +7551,8 @@ var UserProfileService = class {
|
|
|
7174
7551
|
return await this.createProfile(data);
|
|
7175
7552
|
}
|
|
7176
7553
|
/**
|
|
7177
|
-
* Update user profile
|
|
7554
|
+
* Update user profile.
|
|
7555
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7178
7556
|
*/
|
|
7179
7557
|
async updateProfile(profileId, data) {
|
|
7180
7558
|
const existing = await this.getProfileOrThrow(profileId);
|
|
@@ -7190,7 +7568,6 @@ var UserProfileService = class {
|
|
|
7190
7568
|
await this.auditService.logUserAction({
|
|
7191
7569
|
action: "user.updated",
|
|
7192
7570
|
actorId: this.userId,
|
|
7193
|
-
actorEmail: this.userEmail,
|
|
7194
7571
|
targetUserId: profileId,
|
|
7195
7572
|
targetUserEmail: updated.email,
|
|
7196
7573
|
changes
|
|
@@ -7200,7 +7577,8 @@ var UserProfileService = class {
|
|
|
7200
7577
|
return updated;
|
|
7201
7578
|
}
|
|
7202
7579
|
/**
|
|
7203
|
-
* Delete user profile
|
|
7580
|
+
* Delete user profile.
|
|
7581
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7204
7582
|
*
|
|
7205
7583
|
* @param profileId - Profile UUID
|
|
7206
7584
|
* @param options - Delete options
|
|
@@ -7209,7 +7587,7 @@ var UserProfileService = class {
|
|
|
7209
7587
|
const profile = await this.getProfileOrThrow(profileId);
|
|
7210
7588
|
if (options?.checkAdmin) {
|
|
7211
7589
|
if (profile.role === "admin") {
|
|
7212
|
-
const adminCount = await this.adapter.userProfiles.countByRole(
|
|
7590
|
+
const adminCount = await this.adapter.userProfiles.countByRole("admin");
|
|
7213
7591
|
if (adminCount <= 1) {
|
|
7214
7592
|
throw new Error("Cannot delete the last admin user");
|
|
7215
7593
|
}
|
|
@@ -7220,17 +7598,17 @@ var UserProfileService = class {
|
|
|
7220
7598
|
await this.auditService.logUserAction({
|
|
7221
7599
|
action: "user.deleted",
|
|
7222
7600
|
actorId: this.userId,
|
|
7223
|
-
actorEmail: this.userEmail,
|
|
7224
7601
|
targetUserId: profileId,
|
|
7225
7602
|
targetUserEmail: profile.email
|
|
7226
7603
|
});
|
|
7227
7604
|
}
|
|
7228
7605
|
}
|
|
7229
7606
|
/**
|
|
7230
|
-
* List all user profiles for the tenant
|
|
7607
|
+
* List all user profiles for the tenant.
|
|
7608
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7231
7609
|
*/
|
|
7232
7610
|
async listProfiles(options) {
|
|
7233
|
-
return await this.adapter.userProfiles.list(
|
|
7611
|
+
return await this.adapter.userProfiles.list(options);
|
|
7234
7612
|
}
|
|
7235
7613
|
/**
|
|
7236
7614
|
* Update last login timestamp
|
|
@@ -7265,10 +7643,11 @@ var UserProfileService = class {
|
|
|
7265
7643
|
return await this.updateProfile(profileId, { status: newStatus });
|
|
7266
7644
|
}
|
|
7267
7645
|
/**
|
|
7268
|
-
* Get user by email
|
|
7646
|
+
* Get user by email.
|
|
7647
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7269
7648
|
*/
|
|
7270
7649
|
async getProfileByEmail(email) {
|
|
7271
|
-
return await this.adapter.userProfiles.findByEmail(
|
|
7650
|
+
return await this.adapter.userProfiles.findByEmail(email);
|
|
7272
7651
|
}
|
|
7273
7652
|
/**
|
|
7274
7653
|
* Check if user has role
|
|
@@ -7286,38 +7665,39 @@ var UserProfileService = class {
|
|
|
7286
7665
|
};
|
|
7287
7666
|
|
|
7288
7667
|
// src/runtime/services/view.service.ts
|
|
7289
|
-
var ViewService = class {
|
|
7668
|
+
var ViewService = class extends TenantAwareService {
|
|
7290
7669
|
constructor(adapter, nativeViews) {
|
|
7670
|
+
super();
|
|
7291
7671
|
this.adapter = adapter;
|
|
7292
7672
|
this.nativeViews = nativeViews;
|
|
7293
7673
|
}
|
|
7294
7674
|
/**
|
|
7295
|
-
* Get all views for an object (native + custom)
|
|
7675
|
+
* Get all views for an object (native + custom).
|
|
7676
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7296
7677
|
*
|
|
7297
7678
|
* @param objectName - Object name
|
|
7298
|
-
* @param tenantId - Tenant ID
|
|
7299
7679
|
* @returns All views for the object
|
|
7300
7680
|
*/
|
|
7301
|
-
async getViewsForObject(objectName
|
|
7681
|
+
async getViewsForObject(objectName) {
|
|
7302
7682
|
const nativeViewDefs = this.nativeViews.getByObjectName(objectName);
|
|
7303
|
-
const dbViews = await this.adapter.views.findByObjectName(
|
|
7683
|
+
const dbViews = await this.adapter.views.findByObjectName(objectName);
|
|
7304
7684
|
const customViews = dbViews.filter((v) => !v.system).map(this.convertDBViewToDefinition);
|
|
7305
7685
|
return [...nativeViewDefs, ...customViews];
|
|
7306
7686
|
}
|
|
7307
7687
|
/**
|
|
7308
|
-
* Get a specific view by name
|
|
7688
|
+
* Get a specific view by name.
|
|
7689
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7309
7690
|
*
|
|
7310
7691
|
* @param objectName - Object name
|
|
7311
7692
|
* @param viewName - View name
|
|
7312
|
-
* @param tenantId - Tenant ID
|
|
7313
7693
|
* @returns View definition or null
|
|
7314
7694
|
*/
|
|
7315
|
-
async getView(objectName, viewName
|
|
7695
|
+
async getView(objectName, viewName) {
|
|
7316
7696
|
const nativeView = this.nativeViews.get(objectName, viewName);
|
|
7317
7697
|
if (nativeView) {
|
|
7318
7698
|
return nativeView;
|
|
7319
7699
|
}
|
|
7320
|
-
const dbView = await this.adapter.views.findByName(
|
|
7700
|
+
const dbView = await this.adapter.views.findByName(objectName, viewName);
|
|
7321
7701
|
if (dbView) {
|
|
7322
7702
|
return this.convertDBViewToDefinition(dbView);
|
|
7323
7703
|
}
|
|
@@ -7332,11 +7712,10 @@ var ViewService = class {
|
|
|
7332
7712
|
* 3. First available view
|
|
7333
7713
|
*
|
|
7334
7714
|
* @param objectName - Object name
|
|
7335
|
-
* @param tenantId - Tenant ID
|
|
7336
7715
|
* @returns Default view or null
|
|
7337
7716
|
*/
|
|
7338
|
-
async getDefaultView(objectName
|
|
7339
|
-
const views = await this.getViewsForObject(objectName
|
|
7717
|
+
async getDefaultView(objectName) {
|
|
7718
|
+
const views = await this.getViewsForObject(objectName);
|
|
7340
7719
|
const customDefault = views.find((v) => v.default && !v.system);
|
|
7341
7720
|
if (customDefault) return customDefault;
|
|
7342
7721
|
const nativeDefault = views.find((v) => v.default && v.system);
|
|
@@ -7344,15 +7723,15 @@ var ViewService = class {
|
|
|
7344
7723
|
return views[0] ?? null;
|
|
7345
7724
|
}
|
|
7346
7725
|
/**
|
|
7347
|
-
* Create a custom view
|
|
7726
|
+
* Create a custom view.
|
|
7727
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7348
7728
|
*
|
|
7349
7729
|
* @param input - View definition
|
|
7350
|
-
* @param tenantId - Tenant ID
|
|
7351
7730
|
* @returns Created view
|
|
7352
7731
|
*/
|
|
7353
|
-
async createView(input
|
|
7732
|
+
async createView(input) {
|
|
7354
7733
|
this.validateViewName(input.name);
|
|
7355
|
-
const existing = await this.adapter.views.findByName(
|
|
7734
|
+
const existing = await this.adapter.views.findByName(input.objectName, input.name);
|
|
7356
7735
|
if (existing) {
|
|
7357
7736
|
throw new SchemaError(
|
|
7358
7737
|
`View "${input.name}" already exists for object "${input.objectName}"`,
|
|
@@ -7368,7 +7747,6 @@ var ViewService = class {
|
|
|
7368
7747
|
);
|
|
7369
7748
|
}
|
|
7370
7749
|
const dbView = await this.adapter.views.create({
|
|
7371
|
-
tenantId,
|
|
7372
7750
|
objectName: input.objectName,
|
|
7373
7751
|
name: input.name,
|
|
7374
7752
|
label: input.label,
|
|
@@ -7423,18 +7801,18 @@ var ViewService = class {
|
|
|
7423
7801
|
await this.adapter.views.delete(viewId);
|
|
7424
7802
|
}
|
|
7425
7803
|
/**
|
|
7426
|
-
* Set a view as default for its object
|
|
7804
|
+
* Set a view as default for its object.
|
|
7805
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7427
7806
|
*
|
|
7428
7807
|
* @param viewId - View ID
|
|
7429
|
-
* @param tenantId - Tenant ID
|
|
7430
7808
|
* @returns Updated view
|
|
7431
7809
|
*/
|
|
7432
|
-
async setDefaultView(viewId
|
|
7810
|
+
async setDefaultView(viewId) {
|
|
7433
7811
|
const dbView = await this.adapter.views.findById(viewId);
|
|
7434
7812
|
if (!dbView) {
|
|
7435
7813
|
throw new NotFoundError("View", viewId);
|
|
7436
7814
|
}
|
|
7437
|
-
const currentViews = await this.adapter.views.findByObjectName(
|
|
7815
|
+
const currentViews = await this.adapter.views.findByObjectName(dbView.objectName);
|
|
7438
7816
|
for (const v of currentViews) {
|
|
7439
7817
|
if (v.default && v.id !== viewId && !v.system) {
|
|
7440
7818
|
await this.adapter.views.update(v.id, { default: false });
|
|
@@ -7590,7 +7968,6 @@ async function syncSingleView(adapter, nativeView, result, options) {
|
|
|
7590
7968
|
return;
|
|
7591
7969
|
}
|
|
7592
7970
|
await adapter.views.upsert({
|
|
7593
|
-
tenantId: options.tenantId || "default",
|
|
7594
7971
|
objectName: nativeView.object,
|
|
7595
7972
|
name: nativeView.name,
|
|
7596
7973
|
label: nativeView.label,
|
|
@@ -7730,9 +8107,8 @@ async function handleDryRun(adapter, nativeObject, existingObject, result, optio
|
|
|
7730
8107
|
result.attributesSynced++;
|
|
7731
8108
|
}
|
|
7732
8109
|
}
|
|
7733
|
-
async function upsertObject(adapter, nativeObject,
|
|
8110
|
+
async function upsertObject(adapter, nativeObject, _options) {
|
|
7734
8111
|
return await adapter.objects.upsert({
|
|
7735
|
-
tenantId: options.tenantId || "default",
|
|
7736
8112
|
system: true,
|
|
7737
8113
|
name: nativeObject.name,
|
|
7738
8114
|
label: nativeObject.label,
|
|
@@ -7844,6 +8220,8 @@ var NoopGeocodingAdapter = class {
|
|
|
7844
8220
|
NoopHookRegistry,
|
|
7845
8221
|
ObjectSchemaService,
|
|
7846
8222
|
PermissionService,
|
|
8223
|
+
PolicyRegistry,
|
|
8224
|
+
PolicyViolationError,
|
|
7847
8225
|
QueryBuilder,
|
|
7848
8226
|
QueryMultipleResultsError,
|
|
7849
8227
|
QueryNoResultError,
|
|
@@ -7853,6 +8231,9 @@ var NoopGeocodingAdapter = class {
|
|
|
7853
8231
|
RollupScheduler,
|
|
7854
8232
|
RollupService,
|
|
7855
8233
|
SHORTCUT_TO_FILTER_OPERATOR,
|
|
8234
|
+
TenantAwareRepository,
|
|
8235
|
+
TenantAwareService,
|
|
8236
|
+
TenantContextError,
|
|
7856
8237
|
UserProfileService,
|
|
7857
8238
|
UserService,
|
|
7858
8239
|
ViewService,
|
|
@@ -7860,6 +8241,7 @@ var NoopGeocodingAdapter = class {
|
|
|
7860
8241
|
createDefaultState,
|
|
7861
8242
|
createMockAdapter,
|
|
7862
8243
|
createQueryBuilder,
|
|
8244
|
+
defaultPolicyRegistry,
|
|
7863
8245
|
enrichValuesWithSelectLabels,
|
|
7864
8246
|
evaluateFormula,
|
|
7865
8247
|
evaluateFormulaAttribute,
|
|
@@ -7874,18 +8256,24 @@ var NoopGeocodingAdapter = class {
|
|
|
7874
8256
|
formatFormulaResult,
|
|
7875
8257
|
formatRecord,
|
|
7876
8258
|
formatRecords,
|
|
8259
|
+
getContext,
|
|
7877
8260
|
getPathDepth,
|
|
7878
8261
|
getRelationPath,
|
|
7879
8262
|
getSyncPreview,
|
|
7880
8263
|
getTargetAttributeName,
|
|
8264
|
+
getTenantId,
|
|
8265
|
+
getUserId,
|
|
7881
8266
|
getViewSyncPreview,
|
|
8267
|
+
hasContext,
|
|
7882
8268
|
hasRelationReferences,
|
|
7883
8269
|
isLabelExpression,
|
|
8270
|
+
notesPolicy,
|
|
7884
8271
|
parsePath,
|
|
7885
8272
|
pathHasManyCardinality,
|
|
7886
8273
|
renderLabelExpression,
|
|
7887
8274
|
resolveMultiplePaths,
|
|
7888
8275
|
resolveSingleValue,
|
|
8276
|
+
runWithContext,
|
|
7889
8277
|
syncAll,
|
|
7890
8278
|
syncNativeObjects,
|
|
7891
8279
|
syncNativeViews,
|
|
@@ -7893,5 +8281,6 @@ var NoopGeocodingAdapter = class {
|
|
|
7893
8281
|
validateFormulaExpression,
|
|
7894
8282
|
validatePath,
|
|
7895
8283
|
verifyNativeObjectsSync,
|
|
7896
|
-
verifyNativeViewsSync
|
|
8284
|
+
verifyNativeViewsSync,
|
|
8285
|
+
withTenantContext
|
|
7897
8286
|
});
|