@stndrds/schema 0.1.0-alpha.14 → 0.1.0-alpha.15

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 (52) hide show
  1. package/dist/chunk-2ASXJITY.mjs +5360 -0
  2. package/dist/chunk-5AWYVQKR.mjs +5310 -0
  3. package/dist/chunk-65HUQZHK.mjs +5374 -0
  4. package/dist/chunk-73P4F2OU.mjs +5809 -0
  5. package/dist/chunk-BHKKVBPP.mjs +5527 -0
  6. package/dist/chunk-BUWVNOSK.mjs +5457 -0
  7. package/dist/chunk-CCEHW4E7.mjs +6071 -0
  8. package/dist/chunk-CNR3BZLU.mjs +5163 -0
  9. package/dist/chunk-DLMVX6DN.mjs +6074 -0
  10. package/dist/chunk-DUUORFHI.mjs +5379 -0
  11. package/dist/chunk-DWTD7YPJ.mjs +74 -0
  12. package/dist/chunk-FAR5ENGR.mjs +78 -0
  13. package/dist/chunk-ITWOVZOW.mjs +5809 -0
  14. package/dist/chunk-KJLKY5X7.mjs +6314 -0
  15. package/dist/chunk-KTJXMGBR.mjs +5397 -0
  16. package/dist/chunk-NOVHQ7B2.mjs +6327 -0
  17. package/dist/chunk-PMFQ4ZL4.mjs +6230 -0
  18. package/dist/chunk-QC47SMCZ.mjs +5467 -0
  19. package/dist/chunk-Z6LIJ5PO.mjs +5362 -0
  20. package/dist/default-roles-G4TOWUVM.mjs +20 -0
  21. package/dist/default-roles-QEZPRSUL.mjs +20 -0
  22. package/dist/index.d.mts +193 -15
  23. package/dist/index.d.ts +193 -15
  24. package/dist/index.js +1657 -118
  25. package/dist/index.mjs +56 -163
  26. package/dist/runtime-B1_syHVj.d.mts +4787 -0
  27. package/dist/runtime-B1_syHVj.d.ts +4787 -0
  28. package/dist/runtime-B7N-oycb.d.mts +4854 -0
  29. package/dist/runtime-B7N-oycb.d.ts +4854 -0
  30. package/dist/runtime-BDoAIuTe.d.mts +4872 -0
  31. package/dist/runtime-BDoAIuTe.d.ts +4872 -0
  32. package/dist/runtime-BVRmzg3d.d.mts +4718 -0
  33. package/dist/runtime-BVRmzg3d.d.ts +4718 -0
  34. package/dist/runtime-BrXwIidf.d.mts +4794 -0
  35. package/dist/runtime-BrXwIidf.d.ts +4794 -0
  36. package/dist/runtime-CC_HZhVN.d.mts +5224 -0
  37. package/dist/runtime-CC_HZhVN.d.ts +5224 -0
  38. package/dist/runtime-CEx57sBZ.d.mts +4791 -0
  39. package/dist/runtime-CEx57sBZ.d.ts +4791 -0
  40. package/dist/runtime-COGvuXIq.d.mts +4790 -0
  41. package/dist/runtime-COGvuXIq.d.ts +4790 -0
  42. package/dist/runtime-CSGWjztI.d.mts +5166 -0
  43. package/dist/runtime-CSGWjztI.d.ts +5166 -0
  44. package/dist/runtime-DUxWyRcq.d.mts +5399 -0
  45. package/dist/runtime-DUxWyRcq.d.ts +5399 -0
  46. package/dist/runtime-LO17NDk1.d.mts +5292 -0
  47. package/dist/runtime-LO17NDk1.d.ts +5292 -0
  48. package/dist/runtime.d.mts +1 -1
  49. package/dist/runtime.d.ts +1 -1
  50. package/dist/runtime.js +1645 -97
  51. package/dist/runtime.mjs +7 -1
  52. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,15 +30,108 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/constants/default-roles.ts
34
+ var default_roles_exports = {};
35
+ __export(default_roles_exports, {
36
+ ALL_SYSTEM_RESOURCES: () => ALL_SYSTEM_RESOURCES,
37
+ DEFAULT_ROLES: () => DEFAULT_ROLES,
38
+ DEFAULT_ROLE_DESCRIPTIONS: () => DEFAULT_ROLE_DESCRIPTIONS,
39
+ DEFAULT_ROLE_LABELS: () => DEFAULT_ROLE_LABELS,
40
+ DEFAULT_ROLE_PERMISSIONS: () => DEFAULT_ROLE_PERMISSIONS,
41
+ SYSTEM_RESOURCES: () => SYSTEM_RESOURCES,
42
+ SYSTEM_RESOURCE_LABELS: () => SYSTEM_RESOURCE_LABELS,
43
+ isDefaultRole: () => isDefaultRole
44
+ });
45
+ function isDefaultRole(roleName) {
46
+ return Object.values(DEFAULT_ROLES).includes(roleName);
47
+ }
48
+ var SYSTEM_RESOURCES, ALL_SYSTEM_RESOURCES, SYSTEM_RESOURCE_LABELS, DEFAULT_ROLES, DEFAULT_ROLE_LABELS, DEFAULT_ROLE_DESCRIPTIONS, ALL_ACTIONS, MEMBER_ACTIONS, READ_ONLY, DEFAULT_ROLE_PERMISSIONS;
49
+ var init_default_roles = __esm({
50
+ "src/constants/default-roles.ts"() {
51
+ "use strict";
52
+ SYSTEM_RESOURCES = {
53
+ /** User profiles and invitations management */
54
+ USERS: "users",
55
+ /** Object definitions and attributes management */
56
+ SCHEMA: "schema",
57
+ /** Roles and permissions management */
58
+ ROLES: "roles",
59
+ /** Tenant settings and configuration */
60
+ SETTINGS: "settings",
61
+ /** Audit logs and activity trail */
62
+ AUDIT: "audit"
63
+ };
64
+ ALL_SYSTEM_RESOURCES = [
65
+ SYSTEM_RESOURCES.USERS,
66
+ SYSTEM_RESOURCES.SCHEMA,
67
+ SYSTEM_RESOURCES.ROLES,
68
+ SYSTEM_RESOURCES.SETTINGS,
69
+ SYSTEM_RESOURCES.AUDIT
70
+ ];
71
+ SYSTEM_RESOURCE_LABELS = {
72
+ users: "Users",
73
+ schema: "Schema",
74
+ roles: "Roles & Permissions",
75
+ settings: "Settings",
76
+ audit: "Audit Logs"
77
+ };
78
+ DEFAULT_ROLES = {
79
+ /** Full platform access - can manage everything */
80
+ ADMIN: "admin",
81
+ /** Standard user - can work with business data but not platform settings */
82
+ MEMBER: "member",
83
+ /** Read-only access - can view but not modify */
84
+ GUEST: "guest"
85
+ };
86
+ DEFAULT_ROLE_LABELS = {
87
+ admin: "Administrator",
88
+ member: "Member",
89
+ guest: "Guest"
90
+ };
91
+ DEFAULT_ROLE_DESCRIPTIONS = {
92
+ admin: "Full access to all platform features and data",
93
+ member: "Can view, create and edit business data",
94
+ guest: "Read-only access to business data"
95
+ };
96
+ ALL_ACTIONS = ["read", "create", "update", "delete"];
97
+ MEMBER_ACTIONS = ["read", "create", "update"];
98
+ READ_ONLY = ["read"];
99
+ DEFAULT_ROLE_PERMISSIONS = {
100
+ admin: {
101
+ system: [{ target: "*", actions: ALL_ACTIONS }],
102
+ object: [{ target: "*", actions: ALL_ACTIONS }]
103
+ },
104
+ member: {
105
+ system: [],
106
+ // No access to system resources
107
+ object: [{ target: "*", actions: MEMBER_ACTIONS }]
108
+ },
109
+ guest: {
110
+ system: [],
111
+ // No access to system resources
112
+ object: [{ target: "*", actions: READ_ONLY }]
113
+ }
114
+ };
115
+ }
116
+ });
117
+
30
118
  // src/index.ts
31
119
  var index_exports = {};
32
120
  __export(index_exports, {
121
+ ALL_SYSTEM_RESOURCES: () => ALL_SYSTEM_RESOURCES,
122
+ AttributeInUseError: () => AttributeInUseError,
33
123
  AttributeNotFoundError: () => AttributeNotFoundError,
124
+ AuditService: () => AuditService,
34
125
  CustomTabConfig: () => CustomTabConfig,
35
126
  DEFAULT_LABEL_FALLBACK: () => DEFAULT_LABEL_FALLBACK,
127
+ DEFAULT_ROLES: () => DEFAULT_ROLES,
128
+ DEFAULT_ROLE_DESCRIPTIONS: () => DEFAULT_ROLE_DESCRIPTIONS,
129
+ DEFAULT_ROLE_LABELS: () => DEFAULT_ROLE_LABELS,
130
+ DEFAULT_ROLE_PERMISSIONS: () => DEFAULT_ROLE_PERMISSIONS,
36
131
  DuplicateError: () => DuplicateError,
37
132
  FileNotFoundError: () => FileNotFoundError,
38
133
  FileService: () => FileService,
134
+ ForbiddenError: () => ForbiddenError,
39
135
  GeocodingService: () => GeocodingService,
40
136
  GlobalSearchService: () => GlobalSearchService,
41
137
  GroupBuilder: () => GroupBuilder,
@@ -47,11 +143,18 @@ __export(index_exports, {
47
143
  OPERATORS_BY_TYPE: () => OPERATORS_BY_TYPE,
48
144
  ObjectBuilder: () => ObjectBuilder,
49
145
  ObjectNotFoundError: () => ObjectNotFoundError,
146
+ ObjectReferencedError: () => ObjectReferencedError,
50
147
  ObjectSchemaService: () => ObjectSchemaService,
148
+ PermissionService: () => PermissionService,
51
149
  ProtectedResourceError: () => ProtectedResourceError,
150
+ ProtectedRoleError: () => ProtectedRoleError,
52
151
  RecordNotFoundError: () => RecordNotFoundError,
152
+ RecordReferencedError: () => RecordReferencedError,
53
153
  RecordService: () => RecordService,
54
154
  RelationService: () => RelationService,
155
+ RoleNotFoundError: () => RoleNotFoundError,
156
+ SYSTEM_RESOURCES: () => SYSTEM_RESOURCES,
157
+ SYSTEM_RESOURCE_LABELS: () => SYSTEM_RESOURCE_LABELS,
55
158
  SchemaError: () => SchemaError,
56
159
  SchemaErrorCode: () => SchemaErrorCode,
57
160
  SyncError: () => SyncError,
@@ -63,6 +166,7 @@ __export(index_exports, {
63
166
  ViewBuilder: () => ViewBuilder,
64
167
  ViewService: () => ViewService,
65
168
  attributeConfigSchemas: () => attributeConfigSchemas,
169
+ buildAuditChanges: () => buildAuditChanges,
66
170
  checkbox: () => checkbox,
67
171
  checkboxConfigSchema: () => checkboxConfigSchema,
68
172
  computeRecordStatus: () => computeRecordStatus,
@@ -103,6 +207,8 @@ __export(index_exports, {
103
207
  group: () => group,
104
208
  isAdvancedFilterState: () => isAdvancedFilterState,
105
209
  isCustomTab: () => isCustomTab,
210
+ isDefaultRole: () => isDefaultRole,
211
+ isForbiddenError: () => isForbiddenError,
106
212
  isFormTab: () => isFormTab,
107
213
  isLabelExpression: () => isLabelExpression,
108
214
  isNoValueOperator: () => isNoValueOperator,
@@ -159,6 +265,38 @@ __export(index_exports, {
159
265
  });
160
266
  module.exports = __toCommonJS(index_exports);
161
267
 
268
+ // src/types/errors.ts
269
+ var RecordReferencedError = class extends Error {
270
+ constructor(recordId, references) {
271
+ const total = references.reduce((sum, r) => sum + r.count, 0);
272
+ super(`Cannot delete record: referenced by ${total} record${total > 1 ? "s" : ""}`);
273
+ this.recordId = recordId;
274
+ this.references = references;
275
+ this.code = "RECORD_REFERENCED";
276
+ this.name = "RecordReferencedError";
277
+ }
278
+ };
279
+ var AttributeInUseError = class extends Error {
280
+ constructor(attributeName, usage) {
281
+ super(`Cannot delete attribute "${attributeName}": used in ${usage}`);
282
+ this.attributeName = attributeName;
283
+ this.usage = usage;
284
+ this.code = "ATTRIBUTE_IN_USE";
285
+ this.name = "AttributeInUseError";
286
+ }
287
+ };
288
+ var ObjectReferencedError = class extends Error {
289
+ constructor(objectName, referencingObjects) {
290
+ super(
291
+ `Cannot delete object "${objectName}": target of relations in ${referencingObjects.join(", ")}`
292
+ );
293
+ this.objectName = objectName;
294
+ this.referencingObjects = referencingObjects;
295
+ this.code = "OBJECT_REFERENCED";
296
+ this.name = "ObjectReferencedError";
297
+ }
298
+ };
299
+
162
300
  // src/types/filters.ts
163
301
  function isAdvancedFilterState(state) {
164
302
  return "groups" in state;
@@ -293,6 +431,9 @@ function generatePrefixedId(prefix) {
293
431
  return `${prefix}_${generateId()}`;
294
432
  }
295
433
 
434
+ // src/constants/index.ts
435
+ init_default_roles();
436
+
296
437
  // src/exceptions.ts
297
438
  var SchemaErrorCode = {
298
439
  // Generic
@@ -303,6 +444,7 @@ var SchemaErrorCode = {
303
444
  RECORD_NOT_FOUND: "SCHEMA_RECORD_NOT_FOUND",
304
445
  USER_PROFILE_NOT_FOUND: "SCHEMA_USER_PROFILE_NOT_FOUND",
305
446
  FILE_NOT_FOUND: "SCHEMA_FILE_NOT_FOUND",
447
+ ROLE_NOT_FOUND: "SCHEMA_ROLE_NOT_FOUND",
306
448
  // Validation
307
449
  VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED",
308
450
  INVALID_ATTRIBUTE_NAME: "SCHEMA_INVALID_ATTRIBUTE_NAME",
@@ -310,6 +452,9 @@ var SchemaErrorCode = {
310
452
  // Protected Resources
311
453
  PROTECTED_OBJECT: "SCHEMA_PROTECTED_OBJECT",
312
454
  PROTECTED_ATTRIBUTE: "SCHEMA_PROTECTED_ATTRIBUTE",
455
+ PROTECTED_ROLE: "SCHEMA_PROTECTED_ROLE",
456
+ // Permissions
457
+ FORBIDDEN: "SCHEMA_FORBIDDEN",
313
458
  // Sync
314
459
  SYNC_FAILED: "SCHEMA_SYNC_FAILED",
315
460
  NOT_SYSTEM_OBJECT: "SCHEMA_NOT_SYSTEM_OBJECT",
@@ -454,6 +599,39 @@ function isValidationError(error) {
454
599
  function isProtectedResourceError(error) {
455
600
  return error instanceof ProtectedResourceError;
456
601
  }
602
+ var ForbiddenError = class extends SchemaError {
603
+ constructor(objectName, action, userId) {
604
+ super(`No ${action} permission on object "${objectName}"`, SchemaErrorCode.FORBIDDEN, {
605
+ objectName,
606
+ action,
607
+ userId
608
+ });
609
+ this.name = "ForbiddenError";
610
+ this.objectName = objectName;
611
+ this.action = action;
612
+ this.userId = userId;
613
+ }
614
+ };
615
+ var ProtectedRoleError = class extends SchemaError {
616
+ constructor(roleName, operation) {
617
+ super(`Cannot ${operation} system role "${roleName}"`, SchemaErrorCode.PROTECTED_ROLE, {
618
+ roleName,
619
+ operation
620
+ });
621
+ this.name = "ProtectedRoleError";
622
+ this.roleName = roleName;
623
+ this.operation = operation;
624
+ }
625
+ };
626
+ var RoleNotFoundError = class extends NotFoundError {
627
+ constructor(roleId) {
628
+ super("Role", roleId, SchemaErrorCode.ROLE_NOT_FOUND);
629
+ this.name = "RoleNotFoundError";
630
+ }
631
+ };
632
+ function isForbiddenError(error) {
633
+ return error instanceof ForbiddenError;
634
+ }
457
635
 
458
636
  // src/builders/attribute-validators.ts
459
637
  var import_constants = require("@stndrds/constants");
@@ -1009,14 +1187,6 @@ var SingleRelationAttributeBuilder = class extends BaseAttributeBuilder {
1009
1187
  this.attr.targets?.push(target);
1010
1188
  return this;
1011
1189
  }
1012
- /**
1013
- * Set the deletion strategy when target record is deleted
1014
- * @param strategy - "restrict" (default), "cascade", or "set_null"
1015
- */
1016
- onDelete(strategy) {
1017
- this.attr.onDelete = strategy;
1018
- return this;
1019
- }
1020
1190
  /**
1021
1191
  * Convert to a multi-relation (cardinality: "many")
1022
1192
  */
@@ -1026,7 +1196,6 @@ var SingleRelationAttributeBuilder = class extends BaseAttributeBuilder {
1026
1196
  this.attr.label ?? "",
1027
1197
  {
1028
1198
  targets: this.attr.targets,
1029
- onDelete: this.attr.onDelete,
1030
1199
  isRequired: this.attr.required
1031
1200
  }
1032
1201
  );
@@ -1047,9 +1216,6 @@ var MultiRelationAttributeBuilder = class extends BaseAttributeBuilder {
1047
1216
  this.attr.cardinality = "many";
1048
1217
  this.attr.targets = initOptions?.targets ?? [];
1049
1218
  this.attr.defaultValue = [];
1050
- if (initOptions?.onDelete) {
1051
- this.attr.onDelete = initOptions.onDelete;
1052
- }
1053
1219
  if (initOptions?.isRequired) {
1054
1220
  this.setRequired(true);
1055
1221
  }
@@ -1067,14 +1233,6 @@ var MultiRelationAttributeBuilder = class extends BaseAttributeBuilder {
1067
1233
  this.attr.targets?.push(target);
1068
1234
  return this;
1069
1235
  }
1070
- /**
1071
- * Set the deletion strategy when target record is deleted
1072
- * @param strategy - "restrict" (default), "cascade", or "set_null"
1073
- */
1074
- onDelete(strategy) {
1075
- this.attr.onDelete = strategy;
1076
- return this;
1077
- }
1078
1236
  /**
1079
1237
  * Set minimum number of relations required
1080
1238
  */
@@ -2051,7 +2209,6 @@ var userConfigSchema = baseConfigSchema.extend({
2051
2209
  });
2052
2210
  var relationConfigSchema = baseConfigSchema.extend({
2053
2211
  targets: import_zod3.z.array(relationTargetSchema).min(1),
2054
- onDelete: import_zod3.z.enum(["restrict", "cascade", "set_null"]).optional(),
2055
2212
  cardinality: import_zod3.z.enum(["one", "many"]),
2056
2213
  minItems: import_zod3.z.number().int().min(0).optional(),
2057
2214
  maxItems: import_zod3.z.number().int().min(1).optional()
@@ -2701,6 +2858,12 @@ function createMockUserProfilesRepository(stores) {
2701
2858
  }
2702
2859
  return Promise.resolve(results);
2703
2860
  },
2861
+ countByRole(tenantId, role) {
2862
+ const count = Array.from(stores.userProfiles.values()).filter(
2863
+ (profile) => profile.tenantId === tenantId && profile.role === role
2864
+ ).length;
2865
+ return Promise.resolve(count);
2866
+ },
2704
2867
  updateLastLogin(id) {
2705
2868
  const profile = stores.userProfiles.get(id);
2706
2869
  if (profile) {
@@ -2794,12 +2957,36 @@ function createMockFilesRepository(stores) {
2794
2957
  }
2795
2958
  function createMockObjectRecordsRepository(stores) {
2796
2959
  return {
2960
+ hardDelete(id) {
2961
+ stores.objectRecords.delete(id);
2962
+ return Promise.resolve();
2963
+ },
2964
+ restore(id) {
2965
+ const existing = stores.objectRecords.get(id);
2966
+ if (!existing) {
2967
+ return Promise.reject(new Error(`ObjectRecord ${id} not found`));
2968
+ }
2969
+ existing.deletedAt = null;
2970
+ stores.objectRecords.set(id, existing);
2971
+ return Promise.resolve(existing);
2972
+ },
2797
2973
  findById(id) {
2798
2974
  const internal = stores.objectRecords.get(id);
2799
2975
  if (!internal) return Promise.resolve(null);
2800
2976
  const { tenantId: _, ...record } = internal;
2801
2977
  return Promise.resolve(record);
2802
2978
  },
2979
+ findByIds(ids) {
2980
+ const results = [];
2981
+ for (const id of ids) {
2982
+ const internal = stores.objectRecords.get(id);
2983
+ if (internal) {
2984
+ const { tenantId: _, ...record } = internal;
2985
+ results.push(record);
2986
+ }
2987
+ }
2988
+ return Promise.resolve(results);
2989
+ },
2803
2990
  create(data) {
2804
2991
  const internal = {
2805
2992
  id: generateId(),
@@ -2912,6 +3099,85 @@ function createMockObjectRecordsRepository(stores) {
2912
3099
  };
2913
3100
  });
2914
3101
  return Promise.resolve({ results, total });
3102
+ },
3103
+ // -------------------------------------------------------------------------
3104
+ // Schema Integrity Methods
3105
+ // -------------------------------------------------------------------------
3106
+ countRecordsReferencingId(targetId) {
3107
+ const relationAttrs = [];
3108
+ for (const attr of stores.attributes.values()) {
3109
+ if (attr.type === "relation") {
3110
+ relationAttrs.push({ objectId: attr.objectId, attrName: attr.name });
3111
+ }
3112
+ }
3113
+ const countsByObjectId = /* @__PURE__ */ new Map();
3114
+ for (const record of stores.objectRecords.values()) {
3115
+ for (const { objectId, attrName } of relationAttrs) {
3116
+ if (record.objectId !== objectId) continue;
3117
+ const value = record.values[attrName];
3118
+ let isReferencing = false;
3119
+ if (typeof value === "string" && value === targetId) {
3120
+ isReferencing = true;
3121
+ } else if (Array.isArray(value) && value.includes(targetId)) {
3122
+ isReferencing = true;
3123
+ }
3124
+ if (isReferencing) {
3125
+ const current = countsByObjectId.get(objectId) ?? 0;
3126
+ countsByObjectId.set(objectId, current + 1);
3127
+ }
3128
+ }
3129
+ }
3130
+ const results = [];
3131
+ for (const [objectId, count] of countsByObjectId) {
3132
+ const obj = stores.objects.get(objectId);
3133
+ if (obj) {
3134
+ results.push({
3135
+ objectName: obj.name,
3136
+ objectLabel: obj.label,
3137
+ count
3138
+ });
3139
+ }
3140
+ }
3141
+ return Promise.resolve(results);
3142
+ },
3143
+ removeAttributeData(objectId, attributeName) {
3144
+ let updated = 0;
3145
+ for (const record of stores.objectRecords.values()) {
3146
+ if (record.objectId === objectId && attributeName in record.values) {
3147
+ delete record.values[attributeName];
3148
+ record.updatedAt = /* @__PURE__ */ new Date();
3149
+ updated++;
3150
+ }
3151
+ }
3152
+ return Promise.resolve(updated);
3153
+ },
3154
+ batchRefreshLabels(objectId, computeLabel) {
3155
+ let updated = 0;
3156
+ for (const record of stores.objectRecords.values()) {
3157
+ if (record.objectId === objectId) {
3158
+ const newLabel = computeLabel(record.values);
3159
+ if (newLabel !== record.label) {
3160
+ record.label = newLabel;
3161
+ record.updatedAt = /* @__PURE__ */ new Date();
3162
+ updated++;
3163
+ }
3164
+ }
3165
+ }
3166
+ return Promise.resolve(updated);
3167
+ },
3168
+ batchRefreshStatus(objectId, computeStatus) {
3169
+ let updated = 0;
3170
+ for (const record of stores.objectRecords.values()) {
3171
+ if (record.objectId === objectId) {
3172
+ const newStatus = computeStatus(record.values);
3173
+ if (newStatus !== record.completionStatus) {
3174
+ record.completionStatus = newStatus;
3175
+ record.updatedAt = /* @__PURE__ */ new Date();
3176
+ updated++;
3177
+ }
3178
+ }
3179
+ }
3180
+ return Promise.resolve(updated);
2915
3181
  }
2916
3182
  };
2917
3183
  }
@@ -3015,6 +3281,157 @@ function createMockViewsRepository(stores) {
3015
3281
  }
3016
3282
  };
3017
3283
  }
3284
+ function createMockPermissionsRepository(stores) {
3285
+ return {
3286
+ getRolesByTenant(tenantId) {
3287
+ const results = Array.from(stores.roles.values()).filter((r) => r.tenantId === tenantId);
3288
+ return Promise.resolve(results);
3289
+ },
3290
+ getRoleById(roleId) {
3291
+ return Promise.resolve(stores.roles.get(roleId) ?? null);
3292
+ },
3293
+ getRoleByName(tenantId, name) {
3294
+ for (const role of stores.roles.values()) {
3295
+ if (role.tenantId === tenantId && role.name === name) {
3296
+ return Promise.resolve(role);
3297
+ }
3298
+ }
3299
+ return Promise.resolve(null);
3300
+ },
3301
+ createRole(input) {
3302
+ const role = {
3303
+ id: generateId(),
3304
+ tenantId: input.tenantId,
3305
+ name: input.name,
3306
+ label: input.label,
3307
+ description: input.description,
3308
+ system: false,
3309
+ createdAt: /* @__PURE__ */ new Date(),
3310
+ updatedAt: /* @__PURE__ */ new Date()
3311
+ };
3312
+ stores.roles.set(role.id, role);
3313
+ return Promise.resolve(role);
3314
+ },
3315
+ updateRole(roleId, updates) {
3316
+ const existing = stores.roles.get(roleId);
3317
+ if (!existing) {
3318
+ return Promise.reject(new Error(`Role ${roleId} not found`));
3319
+ }
3320
+ const updated = {
3321
+ ...existing,
3322
+ ...updates,
3323
+ updatedAt: /* @__PURE__ */ new Date()
3324
+ };
3325
+ stores.roles.set(roleId, updated);
3326
+ return Promise.resolve(updated);
3327
+ },
3328
+ deleteRole(roleId) {
3329
+ const role = stores.roles.get(roleId);
3330
+ if (role?.system) {
3331
+ return Promise.reject(new Error(`Cannot delete system role ${roleId}`));
3332
+ }
3333
+ stores.roles.delete(roleId);
3334
+ for (const [id, perm] of stores.permissions.entries()) {
3335
+ if (perm.roleId === roleId) {
3336
+ stores.permissions.delete(id);
3337
+ }
3338
+ }
3339
+ for (const [id, ur] of stores.userRoles.entries()) {
3340
+ if (ur.roleId === roleId) {
3341
+ stores.userRoles.delete(id);
3342
+ }
3343
+ }
3344
+ return Promise.resolve();
3345
+ },
3346
+ getPermissionsByRole(roleId) {
3347
+ const results = Array.from(stores.permissions.values()).filter((p) => p.roleId === roleId);
3348
+ return Promise.resolve(results);
3349
+ },
3350
+ setPermissions(roleId, permissions) {
3351
+ for (const [id, perm] of stores.permissions.entries()) {
3352
+ if (perm.roleId === roleId) {
3353
+ stores.permissions.delete(id);
3354
+ }
3355
+ }
3356
+ for (const input of permissions) {
3357
+ const perm = {
3358
+ id: generateId(),
3359
+ roleId,
3360
+ scope: input.scope,
3361
+ target: input.target,
3362
+ actions: input.actions,
3363
+ filter: input.filter,
3364
+ createdAt: /* @__PURE__ */ new Date()
3365
+ };
3366
+ stores.permissions.set(perm.id, perm);
3367
+ }
3368
+ return Promise.resolve();
3369
+ },
3370
+ getUserRoles(userProfileId, tenantId) {
3371
+ const roleIds = Array.from(stores.userRoles.values()).filter((ur) => ur.userProfileId === userProfileId && ur.tenantId === tenantId).map((ur) => ur.roleId);
3372
+ const roles = Array.from(stores.roles.values()).filter((r) => roleIds.includes(r.id));
3373
+ return Promise.resolve(roles);
3374
+ },
3375
+ assignRole(input) {
3376
+ for (const ur of stores.userRoles.values()) {
3377
+ if (ur.userProfileId === input.userProfileId && ur.roleId === input.roleId && ur.tenantId === input.tenantId) {
3378
+ return Promise.resolve(ur);
3379
+ }
3380
+ }
3381
+ const userRole = {
3382
+ id: generateId(),
3383
+ userProfileId: input.userProfileId,
3384
+ roleId: input.roleId,
3385
+ tenantId: input.tenantId,
3386
+ assignedAt: /* @__PURE__ */ new Date(),
3387
+ assignedBy: input.assignedBy
3388
+ };
3389
+ stores.userRoles.set(userRole.id, userRole);
3390
+ return Promise.resolve(userRole);
3391
+ },
3392
+ revokeRole(userProfileId, roleId, tenantId) {
3393
+ for (const [id, ur] of stores.userRoles.entries()) {
3394
+ if (ur.userProfileId === userProfileId && ur.roleId === roleId && ur.tenantId === tenantId) {
3395
+ stores.userRoles.delete(id);
3396
+ break;
3397
+ }
3398
+ }
3399
+ return Promise.resolve();
3400
+ },
3401
+ getEffectivePermissions(userProfileId, tenantId) {
3402
+ const userRoleIds = Array.from(stores.userRoles.values()).filter((ur) => ur.userProfileId === userProfileId && ur.tenantId === tenantId).map((ur) => ur.roleId);
3403
+ const userRoles = Array.from(stores.roles.values()).filter((r) => userRoleIds.includes(r.id));
3404
+ const isAdmin = userRoles.some((r) => r.name === "admin");
3405
+ const userPermissions = Array.from(stores.permissions.values()).filter(
3406
+ (p) => userRoleIds.includes(p.roleId)
3407
+ );
3408
+ const objectPermissions = {};
3409
+ const systemPermissions = {};
3410
+ for (const perm of userPermissions) {
3411
+ if (perm.scope === "object") {
3412
+ if (!objectPermissions[perm.target]) {
3413
+ objectPermissions[perm.target] = [];
3414
+ }
3415
+ for (const action of perm.actions) {
3416
+ if (!objectPermissions[perm.target].includes(action)) {
3417
+ objectPermissions[perm.target].push(action);
3418
+ }
3419
+ }
3420
+ } else if (perm.scope === "system") {
3421
+ if (!systemPermissions[perm.target]) {
3422
+ systemPermissions[perm.target] = [];
3423
+ }
3424
+ for (const action of perm.actions) {
3425
+ if (!systemPermissions[perm.target].includes(action)) {
3426
+ systemPermissions[perm.target].push(action);
3427
+ }
3428
+ }
3429
+ }
3430
+ }
3431
+ return Promise.resolve({ isAdmin, objectPermissions, systemPermissions });
3432
+ }
3433
+ };
3434
+ }
3018
3435
  function createMockAdapter() {
3019
3436
  const stores = {
3020
3437
  objects: /* @__PURE__ */ new Map(),
@@ -3022,7 +3439,10 @@ function createMockAdapter() {
3022
3439
  userProfiles: /* @__PURE__ */ new Map(),
3023
3440
  files: /* @__PURE__ */ new Map(),
3024
3441
  objectRecords: /* @__PURE__ */ new Map(),
3025
- views: /* @__PURE__ */ new Map()
3442
+ views: /* @__PURE__ */ new Map(),
3443
+ roles: /* @__PURE__ */ new Map(),
3444
+ permissions: /* @__PURE__ */ new Map(),
3445
+ userRoles: /* @__PURE__ */ new Map()
3026
3446
  };
3027
3447
  const adapter = {
3028
3448
  objects: createMockObjectsRepository(stores),
@@ -3031,6 +3451,7 @@ function createMockAdapter() {
3031
3451
  files: createMockFilesRepository(stores),
3032
3452
  objectRecords: createMockObjectRecordsRepository(stores),
3033
3453
  views: createMockViewsRepository(stores),
3454
+ permissions: createMockPermissionsRepository(stores),
3034
3455
  async transaction(callback) {
3035
3456
  return await callback(adapter);
3036
3457
  },
@@ -3044,11 +3465,266 @@ function createMockAdapter() {
3044
3465
  stores.files.clear();
3045
3466
  stores.objectRecords.clear();
3046
3467
  stores.views.clear();
3468
+ stores.roles.clear();
3469
+ stores.permissions.clear();
3470
+ stores.userRoles.clear();
3047
3471
  }
3048
3472
  };
3049
3473
  return adapter;
3050
3474
  }
3051
3475
 
3476
+ // src/runtime/services/audit.helpers.ts
3477
+ function buildAuditChanges(oldValues, newValues, fieldsToCheck) {
3478
+ const changes = [];
3479
+ for (const field of fieldsToCheck) {
3480
+ if (newValues[field] !== void 0 && newValues[field] !== oldValues[field]) {
3481
+ changes.push({
3482
+ field: String(field),
3483
+ oldValue: oldValues[field],
3484
+ newValue: newValues[field]
3485
+ });
3486
+ }
3487
+ }
3488
+ return changes;
3489
+ }
3490
+
3491
+ // src/runtime/services/audit.service.ts
3492
+ var SENSITIVE_PATTERNS = [
3493
+ /password/i,
3494
+ /token/i,
3495
+ /secret/i,
3496
+ /apiKey/i,
3497
+ /api_key/i,
3498
+ /creditCard/i,
3499
+ /credit_card/i,
3500
+ /ssn/i,
3501
+ /cvv/i
3502
+ ];
3503
+ var AuditService = class {
3504
+ constructor(adapter, tenantId, options) {
3505
+ this.adapter = adapter;
3506
+ this.tenantId = tenantId;
3507
+ this.options = options;
3508
+ this.buffer = [];
3509
+ this.flushTimer = null;
3510
+ if (options?.async && options.flushIntervalMs) {
3511
+ this.startFlushTimer();
3512
+ }
3513
+ }
3514
+ // ============================================================================
3515
+ // PUBLIC LOGGING METHODS
3516
+ // ============================================================================
3517
+ /**
3518
+ * Log a record action (create, update, delete, restore)
3519
+ */
3520
+ async logRecordAction(params) {
3521
+ const entry = {
3522
+ tenantId: this.tenantId,
3523
+ actorId: params.actorId,
3524
+ actorEmail: params.actorEmail,
3525
+ actorType: "user",
3526
+ action: params.action,
3527
+ resourceType: "record",
3528
+ resourceId: params.recordId,
3529
+ resourceLabel: params.recordLabel,
3530
+ objectName: params.objectName,
3531
+ objectId: params.objectId,
3532
+ changes: params.changes ? this.redactSensitiveFields(params.changes) : void 0,
3533
+ metadata: params.metadata
3534
+ };
3535
+ await this.log(entry);
3536
+ }
3537
+ /**
3538
+ * Log a schema action (object/attribute changes)
3539
+ */
3540
+ async logSchemaAction(params) {
3541
+ const entry = {
3542
+ tenantId: this.tenantId,
3543
+ actorId: params.actorId,
3544
+ actorEmail: params.actorEmail,
3545
+ actorType: "user",
3546
+ action: params.action,
3547
+ resourceType: params.resourceType,
3548
+ resourceId: params.resourceId,
3549
+ resourceLabel: params.resourceLabel,
3550
+ objectName: params.objectName,
3551
+ objectId: params.objectId,
3552
+ changes: params.changes ? this.redactSensitiveFields(params.changes) : void 0,
3553
+ metadata: params.metadata
3554
+ };
3555
+ await this.log(entry);
3556
+ }
3557
+ /**
3558
+ * Log a user action
3559
+ */
3560
+ async logUserAction(params) {
3561
+ const entry = {
3562
+ tenantId: this.tenantId,
3563
+ actorId: params.actorId,
3564
+ actorEmail: params.actorEmail,
3565
+ actorType: "user",
3566
+ action: params.action,
3567
+ resourceType: "user",
3568
+ resourceId: params.targetUserId,
3569
+ resourceLabel: params.targetUserEmail,
3570
+ changes: params.changes ? this.redactSensitiveFields(params.changes) : void 0,
3571
+ metadata: params.metadata
3572
+ };
3573
+ await this.log(entry);
3574
+ }
3575
+ /**
3576
+ * Log a role action
3577
+ */
3578
+ async logRoleAction(params) {
3579
+ const entry = {
3580
+ tenantId: this.tenantId,
3581
+ actorId: params.actorId,
3582
+ actorEmail: params.actorEmail,
3583
+ actorType: "user",
3584
+ action: params.action,
3585
+ resourceType: "role",
3586
+ resourceId: params.roleId,
3587
+ resourceLabel: params.roleLabel,
3588
+ changes: params.changes ? this.redactSensitiveFields(params.changes) : void 0,
3589
+ metadata: {
3590
+ ...params.metadata,
3591
+ targetUserId: params.targetUserId
3592
+ }
3593
+ };
3594
+ await this.log(entry);
3595
+ }
3596
+ /**
3597
+ * Log a system action (no actor)
3598
+ */
3599
+ async logSystemAction(params) {
3600
+ const entry = {
3601
+ tenantId: this.tenantId,
3602
+ actorType: "system",
3603
+ action: params.action,
3604
+ resourceType: params.resourceType,
3605
+ resourceId: params.resourceId,
3606
+ resourceLabel: params.resourceLabel,
3607
+ objectName: params.objectName,
3608
+ objectId: params.objectId,
3609
+ changes: params.changes ? this.redactSensitiveFields(params.changes) : void 0,
3610
+ metadata: params.metadata
3611
+ };
3612
+ await this.log(entry);
3613
+ }
3614
+ // ============================================================================
3615
+ // QUERY METHODS
3616
+ // ============================================================================
3617
+ /**
3618
+ * List audit logs with filtering and pagination
3619
+ */
3620
+ async list(options) {
3621
+ if (!this.adapter.audit) {
3622
+ return { entries: [], total: 0 };
3623
+ }
3624
+ return await this.adapter.audit.list(this.tenantId, options);
3625
+ }
3626
+ /**
3627
+ * Get audit history for a specific record
3628
+ */
3629
+ async getRecordHistory(recordId) {
3630
+ if (!this.adapter.audit) {
3631
+ return [];
3632
+ }
3633
+ return await this.adapter.audit.getByResource(this.tenantId, "record", recordId, {
3634
+ orderDirection: "desc"
3635
+ });
3636
+ }
3637
+ /**
3638
+ * Get activity for a specific user
3639
+ */
3640
+ async getUserActivity(userId, options) {
3641
+ if (!this.adapter.audit) {
3642
+ return [];
3643
+ }
3644
+ return await this.adapter.audit.getByActor(this.tenantId, userId, options);
3645
+ }
3646
+ /**
3647
+ * Get audit logs for a specific resource
3648
+ */
3649
+ async getResourceHistory(resourceType, resourceId) {
3650
+ if (!this.adapter.audit) {
3651
+ return [];
3652
+ }
3653
+ return await this.adapter.audit.getByResource(this.tenantId, resourceType, resourceId, {
3654
+ orderDirection: "desc"
3655
+ });
3656
+ }
3657
+ // ============================================================================
3658
+ // BUFFER MANAGEMENT (ASYNC MODE)
3659
+ // ============================================================================
3660
+ /**
3661
+ * Flush buffered logs to the database
3662
+ */
3663
+ async flush() {
3664
+ if (this.buffer.length === 0 || !this.adapter.audit) {
3665
+ return;
3666
+ }
3667
+ const entries = [...this.buffer];
3668
+ this.buffer = [];
3669
+ await this.adapter.audit.createMany(entries);
3670
+ }
3671
+ /**
3672
+ * Clean up resources (stop timer, flush remaining logs)
3673
+ */
3674
+ async dispose() {
3675
+ if (this.flushTimer) {
3676
+ clearInterval(this.flushTimer);
3677
+ this.flushTimer = null;
3678
+ }
3679
+ await this.flush();
3680
+ }
3681
+ // ============================================================================
3682
+ // PRIVATE METHODS
3683
+ // ============================================================================
3684
+ /**
3685
+ * Log an entry (sync or async based on options)
3686
+ */
3687
+ async log(entry) {
3688
+ if (!this.adapter.audit) {
3689
+ return;
3690
+ }
3691
+ if (this.options?.async) {
3692
+ this.buffer.push(entry);
3693
+ const batchSize = this.options.batchSize ?? 10;
3694
+ if (this.buffer.length >= batchSize) {
3695
+ await this.flush();
3696
+ }
3697
+ } else {
3698
+ await this.adapter.audit.create(entry);
3699
+ }
3700
+ }
3701
+ /**
3702
+ * Start the flush timer for async mode
3703
+ */
3704
+ startFlushTimer() {
3705
+ const intervalMs = this.options?.flushIntervalMs ?? 1e3;
3706
+ this.flushTimer = setInterval(() => {
3707
+ this.flush().catch(console.error);
3708
+ }, intervalMs);
3709
+ }
3710
+ /**
3711
+ * Redact sensitive fields from changes
3712
+ */
3713
+ redactSensitiveFields(changes) {
3714
+ return changes.map((change) => {
3715
+ const isSensitive = SENSITIVE_PATTERNS.some((pattern) => pattern.test(change.field));
3716
+ if (isSensitive) {
3717
+ return {
3718
+ ...change,
3719
+ oldValue: "[REDACTED]",
3720
+ newValue: "[REDACTED]"
3721
+ };
3722
+ }
3723
+ return change;
3724
+ });
3725
+ }
3726
+ };
3727
+
3052
3728
  // src/runtime/services/file.service.ts
3053
3729
  var FileService = class {
3054
3730
  constructor(adapter, tenantId) {
@@ -3366,9 +4042,12 @@ var GlobalSearchService = class {
3366
4042
 
3367
4043
  // src/runtime/services/object-schema.service.ts
3368
4044
  var ObjectSchemaService = class {
3369
- constructor(adapter, nativeRegistry) {
4045
+ constructor(adapter, nativeRegistry, options) {
3370
4046
  this.adapter = adapter;
3371
4047
  this.nativeRegistry = nativeRegistry;
4048
+ this.auditService = options?.auditService;
4049
+ this.userId = options?.userId;
4050
+ this.userEmail = options?.userEmail;
3372
4051
  }
3373
4052
  /**
3374
4053
  * Create a new custom object
@@ -3410,41 +4089,57 @@ var ObjectSchemaService = class {
3410
4089
  ...objectDef.metadata,
3411
4090
  icon: objectDef.icon
3412
4091
  };
3413
- const dbObject = await this.adapter.objects.create({
3414
- tenantId,
3415
- name: objectDef.name,
3416
- label: objectDef.label,
3417
- pluralLabel: objectDef.pluralLabel,
3418
- description: objectDef.description,
3419
- labelExpression: objectDef.labelExpression,
3420
- system: false,
3421
- // Always false for custom objects
3422
- metadata
3423
- });
3424
- const createdAttributes = [];
3425
- if (objectDef.attributes.length > 0) {
3426
- for (let i = 0; i < objectDef.attributes.length; i++) {
3427
- const attr = objectDef.attributes[i];
3428
- const config = validatedConfigs[i];
3429
- const dbAttr = await this.adapter.attributes.create({
3430
- objectId: dbObject.id,
3431
- name: attr.name,
3432
- label: attr.label,
3433
- type: attr.type,
3434
- required: attr.required ?? false,
3435
- unique: false,
3436
- system: false,
3437
- config
3438
- });
3439
- createdAttributes.push(this.convertDBAttributeToAttribute(dbAttr));
4092
+ const { dbObject, createdAttributes } = await this.adapter.transaction(async (tx) => {
4093
+ const dbObj = await tx.objects.create({
4094
+ tenantId,
4095
+ name: objectDef.name,
4096
+ label: objectDef.label,
4097
+ pluralLabel: objectDef.pluralLabel,
4098
+ description: objectDef.description,
4099
+ labelExpression: objectDef.labelExpression,
4100
+ system: false,
4101
+ // Always false for custom objects
4102
+ metadata
4103
+ });
4104
+ const attrs = [];
4105
+ if (objectDef.attributes.length > 0) {
4106
+ for (let i = 0; i < objectDef.attributes.length; i++) {
4107
+ const attr = objectDef.attributes[i];
4108
+ const config = validatedConfigs[i];
4109
+ const dbAttr = await tx.attributes.create({
4110
+ objectId: dbObj.id,
4111
+ name: attr.name,
4112
+ label: attr.label,
4113
+ type: attr.type,
4114
+ required: attr.required ?? false,
4115
+ unique: false,
4116
+ system: false,
4117
+ config
4118
+ });
4119
+ attrs.push(this.convertDBAttributeToAttribute(dbAttr));
4120
+ }
3440
4121
  }
3441
- }
3442
- return {
4122
+ return { dbObject: dbObj, createdAttributes: attrs };
4123
+ });
4124
+ const result = {
3443
4125
  ...objectDef,
3444
4126
  id: dbObject.id,
3445
4127
  attributes: createdAttributes,
3446
4128
  system: false
3447
4129
  };
4130
+ if (this.auditService && this.userId) {
4131
+ await this.auditService.logSchemaAction({
4132
+ action: "object.created",
4133
+ actorId: this.userId,
4134
+ actorEmail: this.userEmail,
4135
+ resourceType: "object",
4136
+ resourceId: dbObject.id,
4137
+ resourceLabel: dbObject.label,
4138
+ objectName: dbObject.name,
4139
+ objectId: dbObject.id
4140
+ });
4141
+ }
4142
+ return result;
3448
4143
  }
3449
4144
  /**
3450
4145
  * Add a custom attribute to an existing object
@@ -3480,6 +4175,18 @@ var ObjectSchemaService = class {
3480
4175
  // Always false for attributes added via API
3481
4176
  config
3482
4177
  });
4178
+ if (this.auditService && this.userId) {
4179
+ await this.auditService.logSchemaAction({
4180
+ action: "attribute.created",
4181
+ actorId: this.userId,
4182
+ actorEmail: this.userEmail,
4183
+ resourceType: "attribute",
4184
+ resourceId: dbAttr.id,
4185
+ resourceLabel: dbAttr.label,
4186
+ objectName: dbObject.name,
4187
+ objectId: dbObject.id
4188
+ });
4189
+ }
3483
4190
  return this.convertDBAttributeToAttribute(dbAttr);
3484
4191
  }
3485
4192
  /**
@@ -3523,11 +4230,39 @@ var ObjectSchemaService = class {
3523
4230
  if (dbAttr.type === "relation" && updates.targets) {
3524
4231
  await this.validateRelationTargets(mergedInput, tenantId);
3525
4232
  }
3526
- const updatedDbAttr = await this.adapter.attributes.update(attributeId, {
4233
+ const oldValues = {
4234
+ label: dbAttr.label,
4235
+ required: dbAttr.required
4236
+ };
4237
+ const updatedDbAttr = await this.adapter.attributes.update(attributeId, {
3527
4238
  label: updates.label,
3528
4239
  required: updates.required,
3529
4240
  config
3530
4241
  });
4242
+ if (this.auditService && this.userId) {
4243
+ const changes = buildAuditChanges(oldValues, updates, ["label", "required"]);
4244
+ if (changes.length > 0) {
4245
+ const dbObject = await this.adapter.objects.findById(dbAttr.objectId);
4246
+ await this.auditService.logSchemaAction({
4247
+ action: "attribute.updated",
4248
+ actorId: this.userId,
4249
+ actorEmail: this.userEmail,
4250
+ resourceType: "attribute",
4251
+ resourceId: attributeId,
4252
+ resourceLabel: updatedDbAttr.label,
4253
+ objectName: dbObject?.name,
4254
+ objectId: dbAttr.objectId,
4255
+ changes
4256
+ });
4257
+ }
4258
+ }
4259
+ if (updates.required !== void 0 && updates.required !== oldValues.required) {
4260
+ const schema = await this.getObjectSchema(dbAttr.objectId);
4261
+ await this.adapter.objectRecords.batchRefreshStatus(
4262
+ dbAttr.objectId,
4263
+ (values) => computeRecordStatus(schema, values)
4264
+ );
4265
+ }
3531
4266
  return this.convertDBAttributeToAttribute(updatedDbAttr);
3532
4267
  }
3533
4268
  /**
@@ -3546,7 +4281,27 @@ var ObjectSchemaService = class {
3546
4281
  `Cannot delete system attribute "${dbAttr.name}". System attributes are protected.`
3547
4282
  );
3548
4283
  }
4284
+ const dbObject = await this.adapter.objects.findById(dbAttr.objectId);
4285
+ if (dbObject?.labelExpression) {
4286
+ const usedAttributes = extractAttributeNames(dbObject.labelExpression);
4287
+ if (usedAttributes.includes(dbAttr.name)) {
4288
+ throw new AttributeInUseError(dbAttr.name, "labelExpression");
4289
+ }
4290
+ }
3549
4291
  await this.adapter.attributes.delete(attributeId);
4292
+ await this.adapter.objectRecords.removeAttributeData(dbAttr.objectId, dbAttr.name);
4293
+ if (this.auditService && this.userId) {
4294
+ await this.auditService.logSchemaAction({
4295
+ action: "attribute.deleted",
4296
+ actorId: this.userId,
4297
+ actorEmail: this.userEmail,
4298
+ resourceType: "attribute",
4299
+ resourceId: attributeId,
4300
+ resourceLabel: dbAttr.label,
4301
+ objectName: dbObject?.name,
4302
+ objectId: dbAttr.objectId
4303
+ });
4304
+ }
3550
4305
  }
3551
4306
  /**
3552
4307
  * List attributes for an object
@@ -3591,6 +4346,13 @@ var ObjectSchemaService = class {
3591
4346
  `Cannot modify system object "${dbObject.name}". System objects are protected.`
3592
4347
  );
3593
4348
  }
4349
+ const oldValues = {
4350
+ label: dbObject.label,
4351
+ pluralLabel: dbObject.pluralLabel,
4352
+ description: dbObject.description,
4353
+ labelExpression: dbObject.labelExpression,
4354
+ icon: dbObject.metadata?.icon
4355
+ };
3594
4356
  let metadata = dbObject.metadata;
3595
4357
  if (updates.icon !== void 0 || updates.metadata !== void 0) {
3596
4358
  metadata = {
@@ -3606,6 +4368,35 @@ var ObjectSchemaService = class {
3606
4368
  labelExpression: updates.labelExpression,
3607
4369
  metadata
3608
4370
  });
4371
+ if (this.auditService && this.userId) {
4372
+ const changes = buildAuditChanges(oldValues, { ...updates, icon: updates.icon }, [
4373
+ "label",
4374
+ "pluralLabel",
4375
+ "description",
4376
+ "labelExpression",
4377
+ "icon"
4378
+ ]);
4379
+ if (changes.length > 0) {
4380
+ await this.auditService.logSchemaAction({
4381
+ action: "object.updated",
4382
+ actorId: this.userId,
4383
+ actorEmail: this.userEmail,
4384
+ resourceType: "object",
4385
+ resourceId: objectId,
4386
+ resourceLabel: updatedDbObject.label,
4387
+ objectName: updatedDbObject.name,
4388
+ objectId,
4389
+ changes
4390
+ });
4391
+ }
4392
+ }
4393
+ if (updates.labelExpression !== void 0 && updates.labelExpression !== oldValues.labelExpression) {
4394
+ const newExpression = updates.labelExpression;
4395
+ await this.adapter.objectRecords.batchRefreshLabels(
4396
+ objectId,
4397
+ (values) => renderLabelExpression(newExpression, values)
4398
+ );
4399
+ }
3609
4400
  const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
3610
4401
  return this.convertDBObjectToDefinition(updatedDbObject, dbAttributes);
3611
4402
  }
@@ -3621,7 +4412,7 @@ var ObjectSchemaService = class {
3621
4412
  * await service.deleteObject("obj-123");
3622
4413
  * ```
3623
4414
  */
3624
- async deleteObject(objectId) {
4415
+ async deleteObject(objectId, tenantId) {
3625
4416
  const dbObject = await this.adapter.objects.findById(objectId);
3626
4417
  if (!dbObject) {
3627
4418
  throw new Error(`Object with id "${objectId}" not found`);
@@ -3631,7 +4422,29 @@ var ObjectSchemaService = class {
3631
4422
  `Cannot delete system object "${dbObject.name}". System objects are protected.`
3632
4423
  );
3633
4424
  }
4425
+ const effectiveTenantId = tenantId ?? dbObject.tenantId;
4426
+ if (effectiveTenantId) {
4427
+ const referencingObjects = await this.findObjectsWithRelationTo(
4428
+ dbObject.name,
4429
+ effectiveTenantId
4430
+ );
4431
+ if (referencingObjects.length > 0) {
4432
+ throw new ObjectReferencedError(dbObject.name, referencingObjects);
4433
+ }
4434
+ }
3634
4435
  await this.adapter.objects.delete(objectId);
4436
+ if (this.auditService && this.userId) {
4437
+ await this.auditService.logSchemaAction({
4438
+ action: "object.deleted",
4439
+ actorId: this.userId,
4440
+ actorEmail: this.userEmail,
4441
+ resourceType: "object",
4442
+ resourceId: objectId,
4443
+ resourceLabel: dbObject.label,
4444
+ objectName: dbObject.name,
4445
+ objectId
4446
+ });
4447
+ }
3635
4448
  }
3636
4449
  /**
3637
4450
  * Validate attribute name format (variable identifier)
@@ -3801,14 +4614,13 @@ var ObjectSchemaService = class {
3801
4614
  * @internal
3802
4615
  */
3803
4616
  buildAttributeConfig(attribute) {
3804
- const {
3805
- name: _,
3806
- label: __,
3807
- type: ___,
3808
- required: ____,
3809
- unique: _____,
3810
- ...configInput
3811
- } = attribute;
4617
+ const DB_COLUMN_FIELDS = ["name", "label", "type", "required", "unique"];
4618
+ const configInput = {};
4619
+ for (const [key, value] of Object.entries(attribute)) {
4620
+ if (!DB_COLUMN_FIELDS.includes(key)) {
4621
+ configInput[key] = value;
4622
+ }
4623
+ }
3812
4624
  try {
3813
4625
  return parseAttributeConfig(attribute.type, configInput);
3814
4626
  } catch (error) {
@@ -3818,6 +4630,19 @@ var ObjectSchemaService = class {
3818
4630
  throw error;
3819
4631
  }
3820
4632
  }
4633
+ /**
4634
+ * Check if an object exists (in tenant, system, or native registry)
4635
+ * @internal
4636
+ */
4637
+ async objectExists(name, tenantId) {
4638
+ if (tenantId) {
4639
+ const obj = await this.adapter.objects.findByName(tenantId, name);
4640
+ if (obj) return true;
4641
+ }
4642
+ const systemObj = await this.adapter.objects.findSystemByName(name);
4643
+ if (systemObj) return true;
4644
+ return this.nativeRegistry.getByName(name) !== void 0;
4645
+ }
3821
4646
  /**
3822
4647
  * Validate that relation target objects exist
3823
4648
  * @internal
@@ -3828,26 +4653,43 @@ var ObjectSchemaService = class {
3828
4653
  throw new Error("Relation attribute must have at least one target object");
3829
4654
  }
3830
4655
  const cardinality = attribute.cardinality;
3831
- if (!(cardinality && ["one", "many"].includes(cardinality))) {
4656
+ const isValidCardinality = cardinality === "one" || cardinality === "many";
4657
+ if (!isValidCardinality) {
3832
4658
  throw new Error("Relation attribute must specify cardinality: 'one' or 'many'");
3833
4659
  }
3834
4660
  for (const target of targets) {
3835
4661
  if (!target.object) {
3836
4662
  throw new Error("Each relation target must specify an object name");
3837
4663
  }
3838
- let targetObject = tenantId ? await this.adapter.objects.findByName(tenantId, target.object) : null;
3839
- if (!targetObject) {
3840
- targetObject = await this.adapter.objects.findSystemByName(target.object);
4664
+ const exists = await this.objectExists(target.object, tenantId);
4665
+ if (!exists) {
4666
+ throw new Error(
4667
+ `Relation target object "${target.object}" does not exist. Make sure the object is created before adding a relation to it.`
4668
+ );
3841
4669
  }
3842
- if (!targetObject) {
3843
- const nativeObject = this.nativeRegistry.getByName(target.object);
3844
- if (!nativeObject) {
3845
- throw new Error(
3846
- `Relation target object "${target.object}" does not exist. Make sure the object is created before adding a relation to it.`
3847
- );
3848
- }
4670
+ }
4671
+ }
4672
+ /**
4673
+ * Find objects that have a relation attribute targeting the given object.
4674
+ * Used to prevent deletion of objects that are relation targets.
4675
+ * @internal
4676
+ */
4677
+ async findObjectsWithRelationTo(targetObjectName, tenantId) {
4678
+ const allObjects = await this.adapter.objects.list(tenantId);
4679
+ const referencing = [];
4680
+ for (const obj of allObjects) {
4681
+ if (obj.name === targetObjectName) continue;
4682
+ const attrs = await this.adapter.attributes.findByObjectId(obj.id);
4683
+ const hasRelationToTarget = attrs.some((attr) => {
4684
+ if (attr.type !== "relation") return false;
4685
+ const config = attr.config;
4686
+ return config?.targets?.some((t) => t.object === targetObjectName) ?? false;
4687
+ });
4688
+ if (hasRelationToTarget) {
4689
+ referencing.push(obj.name);
3849
4690
  }
3850
4691
  }
4692
+ return referencing;
3851
4693
  }
3852
4694
  /**
3853
4695
  * Convert DB attribute to Attribute type
@@ -3868,6 +4710,424 @@ var ObjectSchemaService = class {
3868
4710
  }
3869
4711
  };
3870
4712
 
4713
+ // src/runtime/services/permission.service.ts
4714
+ var PermissionService = class {
4715
+ constructor(adapter, tenantId, options) {
4716
+ this.adapter = adapter;
4717
+ this.tenantId = tenantId;
4718
+ this.cache = /* @__PURE__ */ new Map();
4719
+ if (!adapter.permissions) {
4720
+ throw new Error(
4721
+ "PermissionService requires a DatabaseAdapter with permissions repository. Make sure your adapter implements the permissions property."
4722
+ );
4723
+ }
4724
+ this.permissionsRepo = adapter.permissions;
4725
+ this.cacheTtlMs = options?.cacheTtlMs ?? 6e4;
4726
+ this.maxCacheSize = options?.maxCacheSize ?? 1e4;
4727
+ this.auditService = options?.auditService;
4728
+ this.userId = options?.userId;
4729
+ this.userEmail = options?.userEmail;
4730
+ }
4731
+ // ============================================================================
4732
+ // PERMISSION CHECKS
4733
+ // ============================================================================
4734
+ /**
4735
+ * Check if user can perform an action on an object.
4736
+ *
4737
+ * @param userProfileId - User profile ID to check
4738
+ * @param objectName - Object name to check access for
4739
+ * @param action - Action to check (read, create, update, delete)
4740
+ * @returns true if user has permission, false otherwise
4741
+ */
4742
+ async canAccessObject(userProfileId, objectName, action) {
4743
+ const permissions = await this.getEffectivePermissions(userProfileId);
4744
+ if (permissions.isAdmin) {
4745
+ return true;
4746
+ }
4747
+ const wildcardPerms = permissions.objectPermissions["*"];
4748
+ if (wildcardPerms?.includes(action)) {
4749
+ return true;
4750
+ }
4751
+ const objectPerms = permissions.objectPermissions[objectName];
4752
+ return objectPerms?.includes(action) ?? false;
4753
+ }
4754
+ /**
4755
+ * Check if user can access an object, throw ForbiddenError if not.
4756
+ *
4757
+ * @param userProfileId - User profile ID to check
4758
+ * @param objectName - Object name to check access for
4759
+ * @param action - Action to check
4760
+ * @throws ForbiddenError if user lacks permission
4761
+ */
4762
+ async checkObjectAccess(userProfileId, objectName, action) {
4763
+ const canAccess = await this.canAccessObject(userProfileId, objectName, action);
4764
+ if (!canAccess) {
4765
+ throw new ForbiddenError(objectName, action, userProfileId);
4766
+ }
4767
+ }
4768
+ /**
4769
+ * Get permissions for a specific object as boolean flags.
4770
+ * Optimized to fetch permissions once and compute all flags.
4771
+ *
4772
+ * @param userProfileId - User profile ID to check
4773
+ * @param objectName - Object name to check
4774
+ * @returns Object with canRead, canCreate, canUpdate, canDelete flags
4775
+ */
4776
+ async getObjectPermissions(userProfileId, objectName) {
4777
+ const permissions = await this.getEffectivePermissions(userProfileId);
4778
+ if (permissions.isAdmin) {
4779
+ return { canRead: true, canCreate: true, canUpdate: true, canDelete: true };
4780
+ }
4781
+ const wildcardPerms = permissions.objectPermissions["*"] ?? [];
4782
+ const objectPerms = permissions.objectPermissions[objectName] ?? [];
4783
+ const allPerms = /* @__PURE__ */ new Set([...wildcardPerms, ...objectPerms]);
4784
+ return {
4785
+ canRead: allPerms.has("read"),
4786
+ canCreate: allPerms.has("create"),
4787
+ canUpdate: allPerms.has("update"),
4788
+ canDelete: allPerms.has("delete")
4789
+ };
4790
+ }
4791
+ // ============================================================================
4792
+ // SYSTEM PERMISSION CHECKS
4793
+ // ============================================================================
4794
+ /**
4795
+ * Check if user can perform an action on a system resource.
4796
+ *
4797
+ * @param userProfileId - User profile ID to check
4798
+ * @param resource - System resource (users, schema, roles, settings)
4799
+ * @param action - Action to check (read, create, update, delete)
4800
+ * @returns true if user has permission, false otherwise
4801
+ */
4802
+ async canAccessSystem(userProfileId, resource, action) {
4803
+ const permissions = await this.getEffectivePermissions(userProfileId);
4804
+ if (permissions.isAdmin) {
4805
+ return true;
4806
+ }
4807
+ const wildcardPerms = permissions.systemPermissions?.["*"];
4808
+ if (wildcardPerms?.includes(action)) {
4809
+ return true;
4810
+ }
4811
+ const resourcePerms = permissions.systemPermissions?.[resource];
4812
+ return resourcePerms?.includes(action) ?? false;
4813
+ }
4814
+ /**
4815
+ * Check if user can access a system resource, throw ForbiddenError if not.
4816
+ *
4817
+ * @param userProfileId - User profile ID to check
4818
+ * @param resource - System resource to check access for
4819
+ * @param action - Action to check
4820
+ * @throws ForbiddenError if user lacks permission
4821
+ */
4822
+ async checkSystemAccess(userProfileId, resource, action) {
4823
+ const canAccess = await this.canAccessSystem(userProfileId, resource, action);
4824
+ if (!canAccess) {
4825
+ throw new ForbiddenError(`_${resource}`, action, userProfileId);
4826
+ }
4827
+ }
4828
+ /**
4829
+ * Get permissions for a specific system resource as boolean flags.
4830
+ * Optimized to fetch permissions once and compute all flags.
4831
+ *
4832
+ * @param userProfileId - User profile ID to check
4833
+ * @param resource - System resource to check
4834
+ * @returns Object with canRead, canCreate, canUpdate, canDelete flags
4835
+ */
4836
+ async getSystemPermissions(userProfileId, resource) {
4837
+ const permissions = await this.getEffectivePermissions(userProfileId);
4838
+ if (permissions.isAdmin) {
4839
+ return { canRead: true, canCreate: true, canUpdate: true, canDelete: true };
4840
+ }
4841
+ const wildcardPerms = permissions.systemPermissions?.["*"] ?? [];
4842
+ const resourcePerms = permissions.systemPermissions?.[resource] ?? [];
4843
+ const allPerms = /* @__PURE__ */ new Set([...wildcardPerms, ...resourcePerms]);
4844
+ return {
4845
+ canRead: allPerms.has("read"),
4846
+ canCreate: allPerms.has("create"),
4847
+ canUpdate: allPerms.has("update"),
4848
+ canDelete: allPerms.has("delete")
4849
+ };
4850
+ }
4851
+ // ============================================================================
4852
+ // EFFECTIVE PERMISSIONS
4853
+ // ============================================================================
4854
+ /**
4855
+ * Get effective permissions for a user.
4856
+ * Results are cached with TTL and size limit.
4857
+ *
4858
+ * @param userProfileId - User profile ID
4859
+ * @returns Merged permissions from all user's roles
4860
+ */
4861
+ async getEffectivePermissions(userProfileId) {
4862
+ const cacheKey = this.getCacheKey(userProfileId);
4863
+ const cached = this.cache.get(cacheKey);
4864
+ if (cached && cached.expiresAt > Date.now()) {
4865
+ return cached.permissions;
4866
+ }
4867
+ const permissions = await this.permissionsRepo.getEffectivePermissions(
4868
+ userProfileId,
4869
+ this.tenantId
4870
+ );
4871
+ if (this.cache.size >= this.maxCacheSize) {
4872
+ const firstKey = this.cache.keys().next().value;
4873
+ if (firstKey) {
4874
+ this.cache.delete(firstKey);
4875
+ }
4876
+ }
4877
+ this.cache.set(cacheKey, {
4878
+ permissions,
4879
+ expiresAt: Date.now() + this.cacheTtlMs
4880
+ });
4881
+ return permissions;
4882
+ }
4883
+ // ============================================================================
4884
+ // CACHE MANAGEMENT
4885
+ // ============================================================================
4886
+ /**
4887
+ * Invalidate cached permissions for a specific user.
4888
+ * Call this after role/permission changes.
4889
+ */
4890
+ invalidateCache(userProfileId) {
4891
+ this.cache.delete(this.getCacheKey(userProfileId));
4892
+ }
4893
+ /**
4894
+ * Invalidate all cached permissions.
4895
+ * Call this after bulk role/permission changes.
4896
+ */
4897
+ invalidateAllCache() {
4898
+ this.cache.clear();
4899
+ }
4900
+ /**
4901
+ * Get current cache size (for monitoring).
4902
+ */
4903
+ getCacheSize() {
4904
+ return this.cache.size;
4905
+ }
4906
+ getCacheKey(userProfileId) {
4907
+ return `${this.tenantId}:${userProfileId}`;
4908
+ }
4909
+ // ============================================================================
4910
+ // ROLE MANAGEMENT
4911
+ // ============================================================================
4912
+ /**
4913
+ * Get all roles for the current tenant.
4914
+ */
4915
+ async getRoles() {
4916
+ return await this.permissionsRepo.getRolesByTenant(this.tenantId);
4917
+ }
4918
+ /**
4919
+ * Get a role by ID.
4920
+ */
4921
+ async getRoleById(roleId) {
4922
+ return await this.permissionsRepo.getRoleById(roleId);
4923
+ }
4924
+ /**
4925
+ * Create a new role.
4926
+ */
4927
+ async createRole(input) {
4928
+ const role = await this.permissionsRepo.createRole({
4929
+ ...input,
4930
+ tenantId: this.tenantId
4931
+ });
4932
+ if (this.auditService && this.userId) {
4933
+ await this.auditService.logRoleAction({
4934
+ action: "role.created",
4935
+ actorId: this.userId,
4936
+ actorEmail: this.userEmail,
4937
+ roleId: role.id,
4938
+ roleLabel: role.label
4939
+ });
4940
+ }
4941
+ return role;
4942
+ }
4943
+ /**
4944
+ * Update a role.
4945
+ */
4946
+ async updateRole(roleId, updates) {
4947
+ const oldRole = await this.permissionsRepo.getRoleById(roleId);
4948
+ const role = await this.permissionsRepo.updateRole(roleId, updates);
4949
+ if (this.auditService && this.userId && oldRole) {
4950
+ const changes = [];
4951
+ if (updates.label !== void 0 && updates.label !== oldRole.label) {
4952
+ changes.push({ field: "label", oldValue: oldRole.label, newValue: updates.label });
4953
+ }
4954
+ if (updates.description !== void 0 && updates.description !== oldRole.description) {
4955
+ changes.push({
4956
+ field: "description",
4957
+ oldValue: oldRole.description,
4958
+ newValue: updates.description
4959
+ });
4960
+ }
4961
+ if (changes.length > 0) {
4962
+ await this.auditService.logRoleAction({
4963
+ action: "role.updated",
4964
+ actorId: this.userId,
4965
+ actorEmail: this.userEmail,
4966
+ roleId: role.id,
4967
+ roleLabel: role.label,
4968
+ changes
4969
+ });
4970
+ }
4971
+ }
4972
+ return role;
4973
+ }
4974
+ /**
4975
+ * Delete a role.
4976
+ */
4977
+ async deleteRole(roleId) {
4978
+ const role = await this.permissionsRepo.getRoleById(roleId);
4979
+ await this.permissionsRepo.deleteRole(roleId);
4980
+ this.invalidateAllCache();
4981
+ if (this.auditService && this.userId && role) {
4982
+ await this.auditService.logRoleAction({
4983
+ action: "role.deleted",
4984
+ actorId: this.userId,
4985
+ actorEmail: this.userEmail,
4986
+ roleId,
4987
+ roleLabel: role.label
4988
+ });
4989
+ }
4990
+ }
4991
+ // ============================================================================
4992
+ // PERMISSION MANAGEMENT
4993
+ // ============================================================================
4994
+ /**
4995
+ * Get all permissions for a role.
4996
+ */
4997
+ async getPermissionsByRole(roleId) {
4998
+ return await this.permissionsRepo.getPermissionsByRole(roleId);
4999
+ }
5000
+ /**
5001
+ * Set permissions for a role (replaces existing).
5002
+ */
5003
+ async setPermissions(roleId, permissions) {
5004
+ await this.permissionsRepo.setPermissions(roleId, permissions);
5005
+ this.invalidateAllCache();
5006
+ if (this.auditService && this.userId) {
5007
+ const role = await this.permissionsRepo.getRoleById(roleId);
5008
+ await this.auditService.logRoleAction({
5009
+ action: "role.updated",
5010
+ actorId: this.userId,
5011
+ actorEmail: this.userEmail,
5012
+ roleId,
5013
+ roleLabel: role?.label ?? roleId,
5014
+ metadata: { permissionsUpdated: true, permissionCount: permissions.length }
5015
+ });
5016
+ }
5017
+ }
5018
+ // ============================================================================
5019
+ // USER ROLE MANAGEMENT
5020
+ // ============================================================================
5021
+ /**
5022
+ * Get all roles assigned to a user.
5023
+ */
5024
+ async getUserRoles(userProfileId) {
5025
+ return await this.permissionsRepo.getUserRoles(userProfileId, this.tenantId);
5026
+ }
5027
+ /**
5028
+ * Assign a role to a user.
5029
+ */
5030
+ async assignRole(userProfileId, roleId, assignedBy) {
5031
+ const result = await this.permissionsRepo.assignRole({
5032
+ userProfileId,
5033
+ roleId,
5034
+ tenantId: this.tenantId,
5035
+ assignedBy
5036
+ });
5037
+ this.invalidateCache(userProfileId);
5038
+ if (this.auditService && this.userId) {
5039
+ const role = await this.permissionsRepo.getRoleById(roleId);
5040
+ await this.auditService.logRoleAction({
5041
+ action: "role.assigned",
5042
+ actorId: this.userId,
5043
+ actorEmail: this.userEmail,
5044
+ roleId,
5045
+ roleLabel: role?.label ?? roleId,
5046
+ targetUserId: userProfileId
5047
+ });
5048
+ }
5049
+ return result;
5050
+ }
5051
+ /**
5052
+ * Revoke a role from a user.
5053
+ */
5054
+ async revokeRole(userProfileId, roleId) {
5055
+ const role = await this.permissionsRepo.getRoleById(roleId);
5056
+ await this.permissionsRepo.revokeRole(userProfileId, roleId, this.tenantId);
5057
+ this.invalidateCache(userProfileId);
5058
+ if (this.auditService && this.userId) {
5059
+ await this.auditService.logRoleAction({
5060
+ action: "role.revoked",
5061
+ actorId: this.userId,
5062
+ actorEmail: this.userEmail,
5063
+ roleId,
5064
+ roleLabel: role?.label ?? roleId,
5065
+ targetUserId: userProfileId
5066
+ });
5067
+ }
5068
+ }
5069
+ // ============================================================================
5070
+ // INITIALIZATION
5071
+ // ============================================================================
5072
+ /**
5073
+ * Initialize default roles for the tenant if they don't exist.
5074
+ *
5075
+ * Creates the following roles with their default permissions:
5076
+ * - **admin**: Full access to all system resources and objects
5077
+ * - **member**: No system access, can read/create/update objects (no delete)
5078
+ * - **guest**: No system access, read-only access to objects
5079
+ *
5080
+ * This method is idempotent - it only creates roles that don't already exist.
5081
+ * Should be called during tenant bootstrap or on first admin login.
5082
+ *
5083
+ * @example
5084
+ * ```typescript
5085
+ * const service = new PermissionService(adapter, tenantId);
5086
+ * await service.initializeDefaultRoles();
5087
+ * ```
5088
+ */
5089
+ async initializeDefaultRoles() {
5090
+ const {
5091
+ DEFAULT_ROLES: DEFAULT_ROLES2,
5092
+ DEFAULT_ROLE_LABELS: DEFAULT_ROLE_LABELS2,
5093
+ DEFAULT_ROLE_DESCRIPTIONS: DEFAULT_ROLE_DESCRIPTIONS2,
5094
+ DEFAULT_ROLE_PERMISSIONS: DEFAULT_ROLE_PERMISSIONS2
5095
+ } = await Promise.resolve().then(() => (init_default_roles(), default_roles_exports));
5096
+ const existingRoles = await this.getRoles();
5097
+ const existingRoleNames = new Set(existingRoles.map((r) => r.name));
5098
+ for (const roleName of Object.values(DEFAULT_ROLES2)) {
5099
+ if (existingRoleNames.has(roleName)) {
5100
+ continue;
5101
+ }
5102
+ const role = await this.permissionsRepo.createRole({
5103
+ tenantId: this.tenantId,
5104
+ name: roleName,
5105
+ label: DEFAULT_ROLE_LABELS2[roleName],
5106
+ description: DEFAULT_ROLE_DESCRIPTIONS2[roleName]
5107
+ });
5108
+ const permConfig = DEFAULT_ROLE_PERMISSIONS2[roleName];
5109
+ const permissionInputs = [];
5110
+ for (const perm of permConfig.system) {
5111
+ permissionInputs.push({
5112
+ scope: "system",
5113
+ target: perm.target,
5114
+ actions: perm.actions
5115
+ });
5116
+ }
5117
+ for (const perm of permConfig.object) {
5118
+ permissionInputs.push({
5119
+ scope: "object",
5120
+ target: perm.target,
5121
+ actions: perm.actions
5122
+ });
5123
+ }
5124
+ if (permissionInputs.length > 0) {
5125
+ await this.setPermissions(role.id, permissionInputs);
5126
+ }
5127
+ }
5128
+ }
5129
+ };
5130
+
3871
5131
  // src/runtime/services/relation.service.ts
3872
5132
  var RelationService = class {
3873
5133
  constructor(adapter, nativeRegistry) {
@@ -4042,8 +5302,7 @@ var RelationService = class {
4042
5302
  objectName: objectSchema.name,
4043
5303
  objectLabel: objectSchema.label,
4044
5304
  objectIcon: objectSchema.icon,
4045
- label,
4046
- data: record.values
5305
+ label
4047
5306
  });
4048
5307
  }
4049
5308
  }
@@ -4058,40 +5317,58 @@ var RelationService = class {
4058
5317
  * Resolve record IDs to their display labels
4059
5318
  * Useful for displaying current values in the UI
4060
5319
  *
5320
+ * Uses batch fetching for performance - fetches all records in one query,
5321
+ * then groups by objectId to minimize schema lookups.
5322
+ *
4061
5323
  * @param ids - Record IDs to resolve
4062
- * @param tenantId - Tenant ID for multi-tenant isolation
5324
+ * @param _tenantId - Tenant ID for multi-tenant isolation (unused but kept for API compatibility)
5325
+ * @param attributeId - Relation attribute ID to use its displayTemplate for label rendering
4063
5326
  *
4064
5327
  * @example
4065
5328
  * ```typescript
4066
- * const resolved = await relationService.resolveIds(["rec-1", "rec-2"], "tenant-1");
5329
+ * const resolved = await relationService.resolveIds(["rec-1", "rec-2"], "tenant-1", "attr-123");
4067
5330
  * // [{ id: "rec-1", label: "Nike Air Max", objectName: "products", ... }]
4068
5331
  * ```
4069
5332
  */
4070
- async resolveIds(ids, tenantId) {
5333
+ async resolveIds(ids, _tenantId, attributeId) {
4071
5334
  if (!ids || ids.length === 0) {
4072
5335
  return [];
4073
5336
  }
4074
- const recordService = new RecordService(this.adapter, tenantId);
5337
+ const attribute = await this.findAttributeById(attributeId);
5338
+ const records = await this.adapter.objectRecords.findByIds(ids);
5339
+ if (records.length === 0) {
5340
+ return [];
5341
+ }
5342
+ const recordsByObjectId = /* @__PURE__ */ new Map();
5343
+ for (const record of records) {
5344
+ const existing = recordsByObjectId.get(record.objectId) ?? [];
5345
+ existing.push(record);
5346
+ recordsByObjectId.set(record.objectId, existing);
5347
+ }
4075
5348
  const resolved = [];
4076
- for (const id of ids) {
4077
- const record = await recordService.getRecord(id);
4078
- if (!record) {
4079
- continue;
4080
- }
4081
- const objectSchema = await this.schemaService.getObjectSchema(record.objectId);
5349
+ for (const [objectId, objectRecords] of recordsByObjectId) {
5350
+ const objectSchema = await this.schemaService.getObjectSchema(objectId);
4082
5351
  if (!objectSchema) {
4083
5352
  continue;
4084
5353
  }
4085
- const label = renderLabelExpression(objectSchema.labelExpression, record.values);
4086
- resolved.push({
4087
- id: record.id,
4088
- objectId: record.objectId,
4089
- objectName: objectSchema.name,
4090
- objectLabel: objectSchema.label,
4091
- objectIcon: objectSchema.icon,
4092
- label,
4093
- data: record.values
4094
- });
5354
+ let template = objectSchema.labelExpression;
5355
+ if (attribute?.targets) {
5356
+ const targetConfig = attribute.targets.find((t) => t.object === objectSchema.name);
5357
+ if (targetConfig?.displayTemplate) {
5358
+ template = targetConfig.displayTemplate;
5359
+ }
5360
+ }
5361
+ for (const record of objectRecords) {
5362
+ const label = renderLabelExpression(template, record.values);
5363
+ resolved.push({
5364
+ id: record.id,
5365
+ objectId: record.objectId,
5366
+ objectName: objectSchema.name,
5367
+ objectLabel: objectSchema.label,
5368
+ objectIcon: objectSchema.icon,
5369
+ label
5370
+ });
5371
+ }
4095
5372
  }
4096
5373
  return resolved;
4097
5374
  }
@@ -4118,6 +5395,70 @@ var RecordService = class {
4118
5395
  this.schemaService = new ObjectSchemaService(adapter, registry);
4119
5396
  this.relationService = new RelationService(adapter, registry);
4120
5397
  this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
5398
+ this.permissionService = options?.permissionService;
5399
+ this.auditService = options?.auditService;
5400
+ this.userId = options?.userId;
5401
+ this.userEmail = options?.userEmail;
5402
+ }
5403
+ /**
5404
+ * Check permission for an action on an object.
5405
+ * Only checks if permissionService and userId are configured.
5406
+ * @internal
5407
+ */
5408
+ async checkPermission(objectName, action) {
5409
+ if (this.permissionService && this.userId) {
5410
+ await this.permissionService.checkObjectAccess(this.userId, objectName, action);
5411
+ }
5412
+ }
5413
+ /**
5414
+ * Resolve relation IDs to their display labels
5415
+ * @internal
5416
+ */
5417
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: complexity is ok here
5418
+ async resolveRelationLabels(relationAttrs, values) {
5419
+ const resolvedMap = /* @__PURE__ */ new Map();
5420
+ const idsWithAttrId = [];
5421
+ const idsWithoutAttrId = [];
5422
+ for (const attr of relationAttrs) {
5423
+ const val = values[attr.name];
5424
+ const ids = this.extractRelationIds(val);
5425
+ if (ids.length > 0) {
5426
+ if (attr.id) {
5427
+ idsWithAttrId.push({ attrId: attr.id, ids });
5428
+ } else {
5429
+ idsWithoutAttrId.push(...ids);
5430
+ }
5431
+ }
5432
+ }
5433
+ await Promise.all(
5434
+ idsWithAttrId.map(async ({ attrId, ids }) => {
5435
+ const resolved = await this.relationService.resolveIds(ids, this.tenantId, attrId);
5436
+ for (const r of resolved) {
5437
+ resolvedMap.set(r.id, r.label);
5438
+ }
5439
+ })
5440
+ );
5441
+ if (idsWithoutAttrId.length > 0) {
5442
+ const uniqueIds = [...new Set(idsWithoutAttrId)].filter((id) => !resolvedMap.has(id));
5443
+ if (uniqueIds.length > 0) {
5444
+ const records = await this.adapter.objectRecords.findByIds(uniqueIds);
5445
+ for (const record of records) {
5446
+ if (record.label) {
5447
+ resolvedMap.set(record.id, record.label);
5448
+ }
5449
+ }
5450
+ }
5451
+ }
5452
+ return resolvedMap;
5453
+ }
5454
+ /**
5455
+ * Extract relation IDs from a value (string or array)
5456
+ * @internal
5457
+ */
5458
+ extractRelationIds(val) {
5459
+ if (typeof val === "string") return [val];
5460
+ if (Array.isArray(val) && typeof val[0] === "string") return [val[0]];
5461
+ return [];
4121
5462
  }
4122
5463
  /**
4123
5464
  * Compute display label from schema expression
@@ -4132,27 +5473,16 @@ var RecordService = class {
4132
5473
  if (relationAttrs.length === 0) {
4133
5474
  return renderLabelExpression(schema.labelExpression, values);
4134
5475
  }
4135
- const idsToResolve = [];
4136
- for (const attr of relationAttrs) {
4137
- const val = values[attr.name];
4138
- if (typeof val === "string") {
4139
- idsToResolve.push(val);
4140
- } else if (Array.isArray(val) && typeof val[0] === "string") {
4141
- idsToResolve.push(val[0]);
4142
- }
4143
- }
4144
- if (idsToResolve.length === 0) {
5476
+ const resolvedMap = await this.resolveRelationLabels(relationAttrs, values);
5477
+ if (resolvedMap.size === 0) {
4145
5478
  return renderLabelExpression(schema.labelExpression, values);
4146
5479
  }
4147
- const resolved = await this.relationService.resolveIds(idsToResolve, this.tenantId);
4148
- const resolvedMap = new Map(resolved.map((r) => [r.id, r.label]));
4149
5480
  const enrichedValues = { ...values };
4150
5481
  for (const attr of relationAttrs) {
4151
5482
  const val = values[attr.name];
4152
- if (typeof val === "string" && resolvedMap.has(val)) {
4153
- enrichedValues[attr.name] = resolvedMap.get(val);
4154
- } else if (Array.isArray(val) && typeof val[0] === "string" && resolvedMap.has(val[0])) {
4155
- enrichedValues[attr.name] = resolvedMap.get(val[0]);
5483
+ const ids = this.extractRelationIds(val);
5484
+ if (ids.length > 0 && resolvedMap.has(ids[0])) {
5485
+ enrichedValues[attr.name] = resolvedMap.get(ids[0]);
4156
5486
  }
4157
5487
  }
4158
5488
  return renderLabelExpression(schema.labelExpression, enrichedValues);
@@ -4188,6 +5518,7 @@ var RecordService = class {
4188
5518
  */
4189
5519
  async createRecord(objectId, data, options) {
4190
5520
  const schema = await this.schemaService.getObjectSchema(objectId);
5521
+ await this.checkPermission(schema.name, "create");
4191
5522
  const hookCtx = this.buildCreateHookContext(schema, data, options?.hookMetadata);
4192
5523
  if (!options?.skipHooks) {
4193
5524
  await this.hookRegistry.execute("beforeCreate", schema.name, hookCtx);
@@ -4219,6 +5550,18 @@ var RecordService = class {
4219
5550
  };
4220
5551
  await this.hookRegistry.execute("afterCreate", schema.name, afterCtx);
4221
5552
  }
5553
+ if (this.auditService && this.userId) {
5554
+ await this.auditService.logRecordAction({
5555
+ action: "record.created",
5556
+ actorId: this.userId,
5557
+ actorEmail: this.userEmail,
5558
+ objectName: schema.name,
5559
+ objectId: schema.id,
5560
+ recordId: record.id,
5561
+ recordLabel: record.label,
5562
+ metadata: options?.hookMetadata
5563
+ });
5564
+ }
4222
5565
  return record;
4223
5566
  }
4224
5567
  /**
@@ -4277,6 +5620,7 @@ var RecordService = class {
4277
5620
  async updateRecord(recordId, data, options) {
4278
5621
  const existing = await this.getRecordOrThrow(recordId);
4279
5622
  const schema = await this.schemaService.getObjectSchema(existing.objectId);
5623
+ await this.checkPermission(schema.name, "update");
4280
5624
  const mergedData = { ...existing.values, ...data };
4281
5625
  const changedAttributes = Object.keys(data).filter((key) => existing.values[key] !== data[key]);
4282
5626
  const hookCtx = this.buildHookContext(
@@ -4314,6 +5658,24 @@ var RecordService = class {
4314
5658
  };
4315
5659
  await this.hookRegistry.execute("afterUpdate", schema.name, afterCtx);
4316
5660
  }
5661
+ if (this.auditService && this.userId && changedAttributes.length > 0) {
5662
+ const changes = changedAttributes.map((attr) => ({
5663
+ field: attr,
5664
+ oldValue: hookCtx.oldValues?.[attr],
5665
+ newValue: data[attr]
5666
+ }));
5667
+ await this.auditService.logRecordAction({
5668
+ action: "record.updated",
5669
+ actorId: this.userId,
5670
+ actorEmail: this.userEmail,
5671
+ objectName: schema.name,
5672
+ objectId: schema.id,
5673
+ recordId: updated.id,
5674
+ recordLabel: updated.label,
5675
+ changes,
5676
+ metadata: options?.hookMetadata
5677
+ });
5678
+ }
4317
5679
  return updated;
4318
5680
  }
4319
5681
  /**
@@ -4403,11 +5765,18 @@ var RecordService = class {
4403
5765
  async deleteRecord(recordId, options) {
4404
5766
  const record = await this.getRecordOrThrow(recordId);
4405
5767
  const schema = await this.schemaService.getObjectSchema(record.objectId);
5768
+ await this.checkPermission(schema.name, "delete");
4406
5769
  if (options?.checkSystem) {
4407
5770
  if (schema.system) {
4408
5771
  throw new Error(`Cannot delete record of system object "${schema.label}"`);
4409
5772
  }
4410
5773
  }
5774
+ if (!options?.skipReferenceCheck) {
5775
+ const references = await this.adapter.objectRecords.countRecordsReferencingId(recordId);
5776
+ if (references.length > 0) {
5777
+ throw new RecordReferencedError(recordId, references);
5778
+ }
5779
+ }
4411
5780
  const hookCtx = this.buildDeleteHookContext(schema, record, options?.hookMetadata);
4412
5781
  if (!options?.skipHooks) {
4413
5782
  await this.hookRegistry.execute("beforeDelete", schema.name, hookCtx);
@@ -4416,6 +5785,110 @@ var RecordService = class {
4416
5785
  if (!options?.skipHooks) {
4417
5786
  await this.hookRegistry.execute("afterDelete", schema.name, hookCtx);
4418
5787
  }
5788
+ if (this.auditService && this.userId) {
5789
+ await this.auditService.logRecordAction({
5790
+ action: "record.deleted",
5791
+ actorId: this.userId,
5792
+ actorEmail: this.userEmail,
5793
+ objectName: schema.name,
5794
+ objectId: schema.id,
5795
+ recordId: record.id,
5796
+ recordLabel: record.label,
5797
+ metadata: options?.hookMetadata
5798
+ });
5799
+ }
5800
+ }
5801
+ /**
5802
+ * Restore a soft-deleted record
5803
+ *
5804
+ * Triggers beforeRestore and afterRestore hooks if a HookRegistry is configured.
5805
+ *
5806
+ * @param recordId - Record UUID
5807
+ * @param options - Restore options
5808
+ * @returns Restored record
5809
+ *
5810
+ * @example
5811
+ * ```typescript
5812
+ * // Restore a deleted record
5813
+ * const restored = await service.restoreRecord("rec-123");
5814
+ * console.log(restored.deletedAt); // null
5815
+ * ```
5816
+ */
5817
+ async restoreRecord(recordId, options) {
5818
+ const record = await this.getRecordOrThrow(recordId);
5819
+ if (!record.deletedAt) {
5820
+ throw new Error(`Record "${recordId}" is not deleted`);
5821
+ }
5822
+ const schema = await this.schemaService.getObjectSchema(record.objectId);
5823
+ await this.checkPermission(schema.name, "update");
5824
+ const hookCtx = this.buildRestoreHookContext(schema, record, options?.hookMetadata);
5825
+ if (!options?.skipHooks) {
5826
+ await this.hookRegistry.execute("beforeRestore", schema.name, hookCtx);
5827
+ }
5828
+ const restored = await this.adapter.objectRecords.restore(recordId);
5829
+ if (!options?.skipHooks) {
5830
+ const afterCtx = {
5831
+ ...hookCtx,
5832
+ record: restored
5833
+ };
5834
+ await this.hookRegistry.execute("afterRestore", schema.name, afterCtx);
5835
+ }
5836
+ if (this.auditService && this.userId) {
5837
+ await this.auditService.logRecordAction({
5838
+ action: "record.restored",
5839
+ actorId: this.userId,
5840
+ actorEmail: this.userEmail,
5841
+ objectName: schema.name,
5842
+ objectId: schema.id,
5843
+ recordId: restored.id,
5844
+ recordLabel: restored.label,
5845
+ metadata: options?.hookMetadata
5846
+ });
5847
+ }
5848
+ return restored;
5849
+ }
5850
+ /**
5851
+ * Build hook context for restore operations
5852
+ * @internal
5853
+ */
5854
+ buildRestoreHookContext(schema, record, metadata) {
5855
+ return {
5856
+ objectId: schema.id,
5857
+ objectName: schema.name,
5858
+ recordId: record.id,
5859
+ tenantId: this.tenantId,
5860
+ record,
5861
+ oldValues: record.values,
5862
+ newValues: record.values,
5863
+ changedAttributes: [],
5864
+ getChange: (attr) => ({
5865
+ oldValue: record.values[attr],
5866
+ newValue: record.values[attr],
5867
+ changed: false
5868
+ }),
5869
+ metadata: metadata ?? {},
5870
+ timestamp: /* @__PURE__ */ new Date()
5871
+ };
5872
+ }
5873
+ /**
5874
+ * Permanently delete a record (hard delete)
5875
+ *
5876
+ * This cannot be undone. Use with caution - prefer soft delete for data safety.
5877
+ * Does NOT trigger delete hooks (already triggered on soft delete).
5878
+ *
5879
+ * @param recordId - Record UUID
5880
+ *
5881
+ * @example
5882
+ * ```typescript
5883
+ * // Permanently delete a record
5884
+ * await service.hardDeleteRecord("rec-123");
5885
+ * ```
5886
+ */
5887
+ async hardDeleteRecord(recordId) {
5888
+ const record = await this.getRecordOrThrow(recordId);
5889
+ const schema = await this.schemaService.getObjectSchema(record.objectId);
5890
+ await this.checkPermission(schema.name, "delete");
5891
+ await this.adapter.objectRecords.hardDelete(recordId);
4419
5892
  }
4420
5893
  /**
4421
5894
  * List records for an object with pagination
@@ -4425,6 +5898,10 @@ var RecordService = class {
4425
5898
  * @returns Records and total count
4426
5899
  */
4427
5900
  async listRecords(objectId, options) {
5901
+ if (this.permissionService && this.userId) {
5902
+ const schema = await this.schemaService.getObjectSchema(objectId);
5903
+ await this.checkPermission(schema.name, "read");
5904
+ }
4428
5905
  return await this.adapter.objectRecords.list(this.tenantId, objectId, options);
4429
5906
  }
4430
5907
  /**
@@ -4436,6 +5913,10 @@ var RecordService = class {
4436
5913
  * @returns Matching records and total count
4437
5914
  */
4438
5915
  async searchRecords(objectId, query, options) {
5916
+ if (this.permissionService && this.userId) {
5917
+ const schema = await this.schemaService.getObjectSchema(objectId);
5918
+ await this.checkPermission(schema.name, "read");
5919
+ }
4439
5920
  return await this.adapter.objectRecords.search(this.tenantId, objectId, query, options);
4440
5921
  }
4441
5922
  /**
@@ -4483,9 +5964,12 @@ var RecordService = class {
4483
5964
 
4484
5965
  // src/runtime/services/user-profile.service.ts
4485
5966
  var UserProfileService = class {
4486
- constructor(adapter, tenantId) {
5967
+ constructor(adapter, tenantId, options) {
4487
5968
  this.adapter = adapter;
4488
5969
  this.tenantId = tenantId;
5970
+ this.auditService = options?.auditService;
5971
+ this.userId = options?.userId;
5972
+ this.userEmail = options?.userEmail;
4489
5973
  }
4490
5974
  /**
4491
5975
  * Create a new user profile (typically after first auth)
@@ -4523,7 +6007,17 @@ var UserProfileService = class {
4523
6007
  if (existingEmail) {
4524
6008
  throw new Error(`User profile with email "${data.email}" already exists in this tenant`);
4525
6009
  }
4526
- return await this.adapter.userProfiles.create(data);
6010
+ const profile = await this.adapter.userProfiles.create(data);
6011
+ if (this.auditService && this.userId) {
6012
+ await this.auditService.logUserAction({
6013
+ action: "user.created",
6014
+ actorId: this.userId,
6015
+ actorEmail: this.userEmail,
6016
+ targetUserId: profile.id,
6017
+ targetUserEmail: profile.email
6018
+ });
6019
+ }
6020
+ return profile;
4527
6021
  }
4528
6022
  /**
4529
6023
  * Get user profile by ID
@@ -4584,8 +6078,27 @@ var UserProfileService = class {
4584
6078
  * Update user profile
4585
6079
  */
4586
6080
  async updateProfile(profileId, data) {
4587
- await this.getProfileOrThrow(profileId);
4588
- return await this.adapter.userProfiles.update(profileId, data);
6081
+ const existing = await this.getProfileOrThrow(profileId);
6082
+ const updated = await this.adapter.userProfiles.update(profileId, data);
6083
+ if (this.auditService && this.userId) {
6084
+ const changes = buildAuditChanges(existing, data, [
6085
+ "firstName",
6086
+ "lastName",
6087
+ "role",
6088
+ "status"
6089
+ ]);
6090
+ if (changes.length > 0) {
6091
+ await this.auditService.logUserAction({
6092
+ action: "user.updated",
6093
+ actorId: this.userId,
6094
+ actorEmail: this.userEmail,
6095
+ targetUserId: profileId,
6096
+ targetUserEmail: updated.email,
6097
+ changes
6098
+ });
6099
+ }
6100
+ }
6101
+ return updated;
4589
6102
  }
4590
6103
  /**
4591
6104
  * Delete user profile
@@ -4594,17 +6107,25 @@ var UserProfileService = class {
4594
6107
  * @param options - Delete options
4595
6108
  */
4596
6109
  async deleteProfile(profileId, options) {
6110
+ const profile = await this.getProfileOrThrow(profileId);
4597
6111
  if (options?.checkAdmin) {
4598
- const profile = await this.getProfileOrThrow(profileId);
4599
6112
  if (profile.role === "admin") {
4600
- const allProfiles = await this.listProfiles();
4601
- const adminCount = allProfiles.filter((p) => p.role === "admin").length;
6113
+ const adminCount = await this.adapter.userProfiles.countByRole(this.tenantId, "admin");
4602
6114
  if (adminCount <= 1) {
4603
6115
  throw new Error("Cannot delete the last admin user");
4604
6116
  }
4605
6117
  }
4606
6118
  }
4607
6119
  await this.adapter.userProfiles.delete(profileId);
6120
+ if (this.auditService && this.userId) {
6121
+ await this.auditService.logUserAction({
6122
+ action: "user.deleted",
6123
+ actorId: this.userId,
6124
+ actorEmail: this.userEmail,
6125
+ targetUserId: profileId,
6126
+ targetUserEmail: profile.email
6127
+ });
6128
+ }
4608
6129
  }
4609
6130
  /**
4610
6131
  * List all user profiles for the tenant
@@ -5191,12 +6712,20 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
5191
6712
  }
5192
6713
  // Annotate the CommonJS export names for ESM import in node:
5193
6714
  0 && (module.exports = {
6715
+ ALL_SYSTEM_RESOURCES,
6716
+ AttributeInUseError,
5194
6717
  AttributeNotFoundError,
6718
+ AuditService,
5195
6719
  CustomTabConfig,
5196
6720
  DEFAULT_LABEL_FALLBACK,
6721
+ DEFAULT_ROLES,
6722
+ DEFAULT_ROLE_DESCRIPTIONS,
6723
+ DEFAULT_ROLE_LABELS,
6724
+ DEFAULT_ROLE_PERMISSIONS,
5197
6725
  DuplicateError,
5198
6726
  FileNotFoundError,
5199
6727
  FileService,
6728
+ ForbiddenError,
5200
6729
  GeocodingService,
5201
6730
  GlobalSearchService,
5202
6731
  GroupBuilder,
@@ -5208,11 +6737,18 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
5208
6737
  OPERATORS_BY_TYPE,
5209
6738
  ObjectBuilder,
5210
6739
  ObjectNotFoundError,
6740
+ ObjectReferencedError,
5211
6741
  ObjectSchemaService,
6742
+ PermissionService,
5212
6743
  ProtectedResourceError,
6744
+ ProtectedRoleError,
5213
6745
  RecordNotFoundError,
6746
+ RecordReferencedError,
5214
6747
  RecordService,
5215
6748
  RelationService,
6749
+ RoleNotFoundError,
6750
+ SYSTEM_RESOURCES,
6751
+ SYSTEM_RESOURCE_LABELS,
5216
6752
  SchemaError,
5217
6753
  SchemaErrorCode,
5218
6754
  SyncError,
@@ -5224,6 +6760,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
5224
6760
  ViewBuilder,
5225
6761
  ViewService,
5226
6762
  attributeConfigSchemas,
6763
+ buildAuditChanges,
5227
6764
  checkbox,
5228
6765
  checkboxConfigSchema,
5229
6766
  computeRecordStatus,
@@ -5264,6 +6801,8 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
5264
6801
  group,
5265
6802
  isAdvancedFilterState,
5266
6803
  isCustomTab,
6804
+ isDefaultRole,
6805
+ isForbiddenError,
5267
6806
  isFormTab,
5268
6807
  isLabelExpression,
5269
6808
  isNoValueOperator,