@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.
Files changed (63) hide show
  1. package/dist/chunk-7GLSGSUP.mjs +9456 -0
  2. package/dist/chunk-AZTBTKHP.mjs +9861 -0
  3. package/dist/chunk-B74KNXDP.mjs +9457 -0
  4. package/dist/chunk-CVFKFFCX.mjs +9912 -0
  5. package/dist/chunk-DXVIUNQT.mjs +9881 -0
  6. package/dist/chunk-EJB2V42F.mjs +9778 -0
  7. package/dist/chunk-GP2BONBI.mjs +9493 -0
  8. package/dist/chunk-H2KOC7F5.mjs +9549 -0
  9. package/dist/chunk-HXLOIN36.mjs +9893 -0
  10. package/dist/chunk-HYW67ZP6.mjs +9500 -0
  11. package/dist/chunk-IKUIWNHZ.mjs +9460 -0
  12. package/dist/chunk-JAWKNHY6.mjs +9553 -0
  13. package/dist/chunk-KT4UPBZR.mjs +9892 -0
  14. package/dist/chunk-NBCCRCJB.mjs +9547 -0
  15. package/dist/chunk-NN7Y3B3A.mjs +9859 -0
  16. package/dist/chunk-SBEZDUNH.mjs +9549 -0
  17. package/dist/chunk-TTZ3TWYP.mjs +9583 -0
  18. package/dist/chunk-UTZTDUIK.mjs +9818 -0
  19. package/dist/index.d.mts +172 -17
  20. package/dist/index.d.ts +172 -17
  21. package/dist/index.js +977 -512
  22. package/dist/index.mjs +71 -23
  23. package/dist/runtime-BFOdkIqK.d.mts +7644 -0
  24. package/dist/runtime-BFOdkIqK.d.ts +7644 -0
  25. package/dist/runtime-BI_wXiVl.d.mts +8043 -0
  26. package/dist/runtime-BI_wXiVl.d.ts +8043 -0
  27. package/dist/runtime-BZZRu7A1.d.mts +7083 -0
  28. package/dist/runtime-BZZRu7A1.d.ts +7083 -0
  29. package/dist/runtime-BlcFbbeG.d.mts +8035 -0
  30. package/dist/runtime-BlcFbbeG.d.ts +8035 -0
  31. package/dist/runtime-BleOubTG.d.mts +7236 -0
  32. package/dist/runtime-BleOubTG.d.ts +7236 -0
  33. package/dist/runtime-CC4VRtUK.d.mts +7422 -0
  34. package/dist/runtime-CC4VRtUK.d.ts +7422 -0
  35. package/dist/runtime-CKLGO3Jk.d.mts +7632 -0
  36. package/dist/runtime-CKLGO3Jk.d.ts +7632 -0
  37. package/dist/runtime-CjhnsVyg.d.mts +7668 -0
  38. package/dist/runtime-CjhnsVyg.d.ts +7668 -0
  39. package/dist/runtime-CmYcicVr.d.mts +7083 -0
  40. package/dist/runtime-CmYcicVr.d.ts +7083 -0
  41. package/dist/runtime-D01tNFxT.d.mts +7418 -0
  42. package/dist/runtime-D01tNFxT.d.ts +7418 -0
  43. package/dist/runtime-DWCd3xCL.d.mts +7632 -0
  44. package/dist/runtime-DWCd3xCL.d.ts +7632 -0
  45. package/dist/runtime-DbryZGqb.d.mts +8037 -0
  46. package/dist/runtime-DbryZGqb.d.ts +8037 -0
  47. package/dist/runtime-DdkNFkKd.d.mts +7185 -0
  48. package/dist/runtime-DdkNFkKd.d.ts +7185 -0
  49. package/dist/runtime-DnnW72ZF.d.mts +7451 -0
  50. package/dist/runtime-DnnW72ZF.d.ts +7451 -0
  51. package/dist/runtime-DrrCWvBr.d.mts +7451 -0
  52. package/dist/runtime-DrrCWvBr.d.ts +7451 -0
  53. package/dist/runtime-UYkiPdud.d.mts +8047 -0
  54. package/dist/runtime-UYkiPdud.d.ts +8047 -0
  55. package/dist/runtime-g7XZnqGw.d.mts +7452 -0
  56. package/dist/runtime-g7XZnqGw.d.ts +7452 -0
  57. package/dist/runtime-jnV5WCS6.d.mts +8047 -0
  58. package/dist/runtime-jnV5WCS6.d.ts +8047 -0
  59. package/dist/runtime.d.mts +1 -1
  60. package/dist/runtime.d.ts +1 -1
  61. package/dist/runtime.js +855 -466
  62. package/dist/runtime.mjs +29 -3
  63. 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 tenantId = this.recordService.tenantId;
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 (replaces existing 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(tenantId, name) {
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: data.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(tenantId) {
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: data.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
- for (const attr of stores.attributes.values()) {
1458
- if (attr.objectId === data.objectId && attr.name === data.name) {
1459
- const updated = {
1460
- ...attr,
1461
- label: data.label ?? attr.label,
1462
- type: data.type ?? attr.type,
1463
- description: data.description ?? attr.description,
1464
- icon: data.icon ?? attr.icon,
1465
- system: data.system ?? attr.system,
1466
- required: data.required ?? attr.required,
1467
- unique: data.unique ?? attr.unique,
1468
- order: data.order ?? attr.order,
1469
- hidden: data.hidden ?? attr.hidden,
1470
- archived: data.archived ?? attr.archived,
1471
- deprecated: data.deprecated ?? attr.deprecated,
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(tenantId, email) {
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: data.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(tenantId, options) {
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(tenantId, role) {
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: data.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(tenantId, options) {
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(tenantId, folderPath) {
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: data.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: _, ...record } = internal;
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(tenantId, objectId, options) {
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: _, ...r }) => r);
1777
+ const records = results.map(({ tenantId: _t, ...r }) => r);
1723
1778
  return Promise.resolve({ records, total });
1724
1779
  },
1725
- search(tenantId, objectId, query, options) {
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: _, ...r }) => r);
1793
+ const records = results.map(({ tenantId: _t, ...r }) => r);
1738
1794
  return Promise.resolve({ records, total });
1739
1795
  },
1740
- globalSearch(tenantId, query, options) {
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(tenantId, objectName, viewName) {
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(tenantId, objectName) {
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(tenantId) {
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: data.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.tenantId, data.objectName, data.name);
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
- getRolesByTenant(tenantId) {
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(tenantId, name) {
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: input.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, tenantId) {
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, tenantId) {
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
- getEffectivePermissions(userProfileId, tenantId) {
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, tenantId, options) {
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(this.tenantId, options);
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(this.tenantId, "record", recordId, {
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(this.tenantId, userId, options);
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(this.tenantId, resourceType, resourceId, {
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, tenantId, options) {
2733
+ var FileService = class extends TenantAwareService {
2734
+ constructor(adapter, options) {
2735
+ super();
2493
2736
  this.adapter = adapter;
2494
- this.tenantId = tenantId;
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(this.tenantId, options);
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(this.tenantId, folderPath);
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 a tenant (system + custom)
3291
+ * Get all flows for current tenant (system + custom)
3063
3292
  */
3064
- async getAllFlows(tenantId) {
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(tenantId);
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(tenantId) {
3077
- const allFlows = await this.getAllFlows(tenantId);
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, tenantId) {
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(tenantId, name);
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, tenantId) {
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(tenantId, input.name);
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
- const slotIds = new Set(input.slots.map((s) => s.id));
3303
- if (slotIds.size !== input.slots.length) {
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
- for (const page of input.pages) {
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
- for (const relation of input.relations) {
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, tenantId) {
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(this.tenantId, query.trim(), {
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
- "min",
3826
- "max",
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
- "percentFilled",
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 _ObjectSchemaService = class _ObjectSchemaService {
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, tenantId) {
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(tenantId, objectDef.name);
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, tenantId) {
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, tenantId);
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, tenantId) {
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, tenantId);
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, tenantId) {
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 effectiveTenantId = tenantId ?? dbObject.tenantId;
4543
- if (effectiveTenantId) {
4544
- const referencingObjects = await this.findObjectsWithRelationTo(
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 (_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.includes(
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: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
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 (system + custom attributes)
4594
- * If object is native, merges registry definition with DB custom attributes
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
- const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
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
- const dbAttributes = await this.adapter.attributes.findByObjectId(dbObject.id);
4629
- return this.mergeNativeObject(dbObject, dbAttributes);
4920
+ return this.buildObjectDefinition(dbObject);
4630
4921
  }
4631
4922
  /**
4632
- * Get object schema by name for a tenant (supports both native and custom objects)
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, tenantId) {
4635
- let dbObject = await this.adapter.objects.findByName(tenantId, name);
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
- if (dbObject.system) {
4644
- return this.mergeNativeObject(dbObject, dbAttributes);
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
- * List all object schemas for a tenant
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
- async listObjectSchemas(tenantId) {
4652
- const dbObjects = await this.adapter.objects.list(tenantId);
4653
- const schemas = await Promise.all(
4654
- dbObjects.map(async (dbObject) => {
4655
- const dbAttributes = await this.adapter.attributes.findByObjectId(dbObject.id);
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, tenantId) {
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, tenantId);
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, tenantId) {
4763
- if (tenantId) {
4764
- const obj = await this.adapter.objects.findByName(tenantId, name);
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, tenantId) {
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, tenantId);
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, tenantId) {
4803
- const allObjects = await this.adapter.objects.list(tenantId);
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, tenantId, options) {
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.getRolesByTenant(this.tenantId);
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, this.tenantId);
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, this.tenantId);
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, tenantId);
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 validObjectIds = await this.getValidObjectIds(attr.targets);
5486
- if (validObjectIds.size === 0) {
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, "tenant-1", {
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, tenantId, params = {}) {
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, tenantId);
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"], "tenant-1", "attr-123");
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, _tenantId, attributeId) {
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(tenantId, sourceObjectName);
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
- case "min":
5840
- return this.minNumbers(values);
5841
- case "max":
5842
- return this.maxNumbers(values);
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
- case "percentFilled": {
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 * 100;
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 * 100;
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 "percentFilled":
6163
+ case "percentNotEmpty":
5874
6164
  return 0;
5875
6165
  case "avg":
5876
- case "min":
5877
- case "max":
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
- * Minimum numeric value
6186
+ * Get earliest date from values
5899
6187
  */
5900
- minNumbers(values) {
5901
- const nums = values.filter((v) => typeof v === "number" && !Number.isNaN(v));
5902
- if (nums.length === 0) return null;
5903
- return Math.min(...nums);
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
- * Maximum numeric value
6195
+ * Get latest date from values
5907
6196
  */
5908
- maxNumbers(values) {
5909
- const nums = values.filter((v) => typeof v === "number" && !Number.isNaN(v));
5910
- if (nums.length === 0) return null;
5911
- return Math.max(...nums);
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 recordObject = await this.adapter.objects.findById(changedRecord.objectId);
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, tenantId) {
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
- var RecordService = class {
6141
- constructor(adapter, tenantId, options) {
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, tenantId);
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, tenantId) : void 0);
6155
- this.userId = options?.userId;
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, this.tenantId, attrId);
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, data, options?.hookMetadata);
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, data);
6640
+ validateDraftOrThrow(schema, dataWithDefaults);
6286
6641
  } else {
6287
- validateObjectOrThrow(schema, data);
6642
+ validateObjectOrThrow(schema, dataWithDefaults);
6288
6643
  }
6289
6644
  if (!options?.skipRelationValidation) {
6290
- await this.relationService.validateRelationsOrThrow(schema, data);
6645
+ await this.relationService.validateRelationsOrThrow(schema, dataWithDefaults);
6291
6646
  }
6292
6647
  if (!options?.skipUserValidation) {
6293
- await this.userService.validateUsersOrThrow(schema, data);
6648
+ await this.userService.validateUsersOrThrow(schema, dataWithDefaults);
6294
6649
  }
6295
6650
  }
6296
- const completionStatus = computeRecordStatus(schema, data);
6297
- const label = await this.computeLabel(schema, data);
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.getRecordOrThrow(recordId);
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
- updatePayload.__metadata = options.metadata;
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.getRecordOrThrow(recordId);
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 result = await this.adapter.objectRecords.list(this.tenantId, objectId, options);
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(result.records, schema),
6769
- total: result.total
7157
+ records: this.enrichRecordsWithFormulas(filteredRecords, schema),
7158
+ total: effectiveTotal
6770
7159
  };
6771
7160
  }
6772
- return result;
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(this.tenantId, objectId, query, options);
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, tenantId) {
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, tenantId, timeout });
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
- const tenantId = tenantMap.get(record.id);
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, tenantId, options) {
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, "tenant-123");
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(this.tenantId, data.email);
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(this.tenantId, "admin");
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(this.tenantId, options);
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(this.tenantId, email);
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, tenantId) {
7681
+ async getViewsForObject(objectName) {
7302
7682
  const nativeViewDefs = this.nativeViews.getByObjectName(objectName);
7303
- const dbViews = await this.adapter.views.findByObjectName(tenantId, objectName);
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, tenantId) {
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(tenantId, objectName, viewName);
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, tenantId) {
7339
- const views = await this.getViewsForObject(objectName, tenantId);
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, tenantId) {
7732
+ async createView(input) {
7354
7733
  this.validateViewName(input.name);
7355
- const existing = await this.adapter.views.findByName(tenantId, input.objectName, input.name);
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, tenantId) {
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(tenantId, dbView.objectName);
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, options) {
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
  });