@stndrds/schema 0.1.0-alpha.14

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 (128) hide show
  1. package/dist/chunk-3ABIOLKO.mjs +4624 -0
  2. package/dist/chunk-3IRX2BDY.mjs +3970 -0
  3. package/dist/chunk-4OTLPWSP.mjs +4448 -0
  4. package/dist/chunk-54ODQBUN.mjs +3867 -0
  5. package/dist/chunk-6HIA3FR2.mjs +4230 -0
  6. package/dist/chunk-6P3XHNLD.mjs +4171 -0
  7. package/dist/chunk-7LJECNAS.mjs +4167 -0
  8. package/dist/chunk-A2WNLUKV.mjs +4475 -0
  9. package/dist/chunk-AA3234RM.mjs +4475 -0
  10. package/dist/chunk-ADNXDLLD.mjs +3136 -0
  11. package/dist/chunk-B4DCDQUI.mjs +4695 -0
  12. package/dist/chunk-B4PSXY6I.mjs +4527 -0
  13. package/dist/chunk-BJHJBZCM.mjs +4228 -0
  14. package/dist/chunk-C3ZWTO6G.mjs +4474 -0
  15. package/dist/chunk-CLOGNKDD.mjs +4040 -0
  16. package/dist/chunk-EW4V63PA.mjs +3053 -0
  17. package/dist/chunk-FR2LCO6R.mjs +2485 -0
  18. package/dist/chunk-GJSF2RKL.mjs +2786 -0
  19. package/dist/chunk-GO5NPP2X.mjs +4726 -0
  20. package/dist/chunk-H3ZYJUDS.mjs +3050 -0
  21. package/dist/chunk-IDDLSFSK.mjs +4230 -0
  22. package/dist/chunk-JRADUJQ7.mjs +4726 -0
  23. package/dist/chunk-NEDR6N7R.mjs +3118 -0
  24. package/dist/chunk-NESPRMDF.mjs +2482 -0
  25. package/dist/chunk-NWFGRJBW.mjs +4612 -0
  26. package/dist/chunk-NXJ57GSN.mjs +2918 -0
  27. package/dist/chunk-O4C5FCTS.mjs +3961 -0
  28. package/dist/chunk-O4PRB27Q.mjs +2513 -0
  29. package/dist/chunk-OJDFNUCR.mjs +4463 -0
  30. package/dist/chunk-OSEKVBHC.mjs +4627 -0
  31. package/dist/chunk-QX4U6U6K.mjs +2484 -0
  32. package/dist/chunk-RMDQ6LKV.mjs +3046 -0
  33. package/dist/chunk-SBXKDATH.mjs +2673 -0
  34. package/dist/chunk-SKMP3AP3.mjs +2918 -0
  35. package/dist/chunk-STMS7WZD.mjs +2623 -0
  36. package/dist/chunk-T3T6PTFG.mjs +3057 -0
  37. package/dist/chunk-TPW72RAH.mjs +4624 -0
  38. package/dist/chunk-TUQEGNGV.mjs +2534 -0
  39. package/dist/chunk-TVKFS3YN.mjs +2449 -0
  40. package/dist/chunk-TZTH3BHG.mjs +3110 -0
  41. package/dist/chunk-W6MU3XPG.mjs +4612 -0
  42. package/dist/chunk-WL7Z3YFR.mjs +4603 -0
  43. package/dist/chunk-XNVVYHH3.mjs +4602 -0
  44. package/dist/chunk-XTA3WY64.mjs +2918 -0
  45. package/dist/chunk-YV7DEZFZ.mjs +2528 -0
  46. package/dist/chunk-ZLJPJND6.mjs +4474 -0
  47. package/dist/chunk-ZNZXTCEX.mjs +4234 -0
  48. package/dist/index.d.mts +1222 -0
  49. package/dist/index.d.ts +1222 -0
  50. package/dist/index.js +5320 -0
  51. package/dist/index.mjs +635 -0
  52. package/dist/runtime-2iKarXtl.d.mts +1924 -0
  53. package/dist/runtime-2iKarXtl.d.ts +1924 -0
  54. package/dist/runtime-4l2ddHZ7.d.mts +2242 -0
  55. package/dist/runtime-4l2ddHZ7.d.ts +2243 -0
  56. package/dist/runtime-8hKS6zAv.d.mts +2226 -0
  57. package/dist/runtime-8hKS6zAv.d.ts +2226 -0
  58. package/dist/runtime-B6yjtoR3.d.mts +4142 -0
  59. package/dist/runtime-B6yjtoR3.d.ts +4142 -0
  60. package/dist/runtime-BGCShrZB.d.mts +2001 -0
  61. package/dist/runtime-BGCShrZB.d.ts +2001 -0
  62. package/dist/runtime-BRW4NGAk.d.mts +1668 -0
  63. package/dist/runtime-BRW4NGAk.d.ts +1668 -0
  64. package/dist/runtime-BnfBr314.d.mts +1683 -0
  65. package/dist/runtime-BnfBr314.d.ts +1683 -0
  66. package/dist/runtime-BrqhiJUF.d.mts +4151 -0
  67. package/dist/runtime-BrqhiJUF.d.ts +4151 -0
  68. package/dist/runtime-Bvj8c-BE.d.mts +4277 -0
  69. package/dist/runtime-Bvj8c-BE.d.ts +4277 -0
  70. package/dist/runtime-C03qT4qU.d.mts +4267 -0
  71. package/dist/runtime-C03qT4qU.d.ts +4267 -0
  72. package/dist/runtime-CaCZ7mSf.d.mts +2974 -0
  73. package/dist/runtime-CaCZ7mSf.d.ts +2974 -0
  74. package/dist/runtime-CaIzNX0Z.d.mts +4091 -0
  75. package/dist/runtime-CaIzNX0Z.d.ts +4091 -0
  76. package/dist/runtime-ChPTgmLP.d.mts +4320 -0
  77. package/dist/runtime-ChPTgmLP.d.ts +4320 -0
  78. package/dist/runtime-CnImZ1Vv.d.mts +4267 -0
  79. package/dist/runtime-CnImZ1Vv.d.ts +4267 -0
  80. package/dist/runtime-Cp-B26Nj.d.mts +2890 -0
  81. package/dist/runtime-Cp-B26Nj.d.ts +2890 -0
  82. package/dist/runtime-Cq8jfk3c.d.mts +4277 -0
  83. package/dist/runtime-Cq8jfk3c.d.ts +4277 -0
  84. package/dist/runtime-CqDFXhhP.d.mts +1685 -0
  85. package/dist/runtime-CqDFXhhP.d.ts +1685 -0
  86. package/dist/runtime-CqtpZLdL.d.mts +4280 -0
  87. package/dist/runtime-CqtpZLdL.d.ts +4280 -0
  88. package/dist/runtime-D-4DblaZ.d.mts +4147 -0
  89. package/dist/runtime-D-4DblaZ.d.ts +4147 -0
  90. package/dist/runtime-D59sSqNl.d.mts +4146 -0
  91. package/dist/runtime-D59sSqNl.d.ts +4146 -0
  92. package/dist/runtime-DEfPk3wT.d.mts +1840 -0
  93. package/dist/runtime-DEfPk3wT.d.ts +1840 -0
  94. package/dist/runtime-DGV-7vES.d.mts +1685 -0
  95. package/dist/runtime-DGV-7vES.d.ts +1685 -0
  96. package/dist/runtime-DNacghqc.d.mts +3971 -0
  97. package/dist/runtime-DNacghqc.d.ts +3971 -0
  98. package/dist/runtime-DPLsUHYK.d.mts +3977 -0
  99. package/dist/runtime-DPLsUHYK.d.ts +3977 -0
  100. package/dist/runtime-DTxCw60F.d.mts +2973 -0
  101. package/dist/runtime-DTxCw60F.d.ts +2973 -0
  102. package/dist/runtime-DUXJ-zNS.d.mts +2788 -0
  103. package/dist/runtime-DUXJ-zNS.d.ts +2788 -0
  104. package/dist/runtime-DbljInhV.d.mts +2237 -0
  105. package/dist/runtime-DbljInhV.d.ts +2237 -0
  106. package/dist/runtime-DcH3y6Yu.d.mts +4272 -0
  107. package/dist/runtime-DcH3y6Yu.d.ts +4272 -0
  108. package/dist/runtime-DdBFz85g.d.mts +4229 -0
  109. package/dist/runtime-DdBFz85g.d.ts +4229 -0
  110. package/dist/runtime-DjMWhjkm.d.mts +4321 -0
  111. package/dist/runtime-DjMWhjkm.d.ts +4321 -0
  112. package/dist/runtime-Djz3fOBE.d.mts +2078 -0
  113. package/dist/runtime-Djz3fOBE.d.ts +2078 -0
  114. package/dist/runtime-Dli8g3Aq.d.mts +2082 -0
  115. package/dist/runtime-Dli8g3Aq.d.ts +2082 -0
  116. package/dist/runtime-a8J378im.d.mts +4123 -0
  117. package/dist/runtime-a8J378im.d.ts +4123 -0
  118. package/dist/runtime-dp3rb3Lf.d.mts +2968 -0
  119. package/dist/runtime-dp3rb3Lf.d.ts +2968 -0
  120. package/dist/runtime-iWXlSN_H.d.mts +1683 -0
  121. package/dist/runtime-iWXlSN_H.d.ts +1683 -0
  122. package/dist/runtime-yMql0npY.d.mts +4321 -0
  123. package/dist/runtime-yMql0npY.d.ts +4321 -0
  124. package/dist/runtime.d.mts +3 -0
  125. package/dist/runtime.d.ts +3 -0
  126. package/dist/runtime.js +3626 -0
  127. package/dist/runtime.mjs +48 -0
  128. package/package.json +58 -0
@@ -0,0 +1,3626 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/runtime.ts
31
+ var runtime_exports = {};
32
+ __export(runtime_exports, {
33
+ DEFAULT_LABEL_FALLBACK: () => DEFAULT_LABEL_FALLBACK,
34
+ FileService: () => FileService,
35
+ GeocodingService: () => GeocodingService,
36
+ GlobalSearchService: () => GlobalSearchService,
37
+ NoopGeocodingAdapter: () => NoopGeocodingAdapter,
38
+ NoopHookRegistry: () => NoopHookRegistry,
39
+ ObjectSchemaService: () => ObjectSchemaService,
40
+ RecordService: () => RecordService,
41
+ RelationService: () => RelationService,
42
+ UserProfileService: () => UserProfileService,
43
+ ViewService: () => ViewService,
44
+ createMockAdapter: () => createMockAdapter,
45
+ extractAttributeNames: () => extractAttributeNames,
46
+ getSyncPreview: () => getSyncPreview,
47
+ getViewSyncPreview: () => getViewSyncPreview,
48
+ isLabelExpression: () => isLabelExpression,
49
+ renderLabelExpression: () => renderLabelExpression,
50
+ syncAll: () => syncAll,
51
+ syncNativeObjects: () => syncNativeObjects,
52
+ syncNativeViews: () => syncNativeViews,
53
+ verifyNativeObjectsSync: () => verifyNativeObjectsSync,
54
+ verifyNativeViewsSync: () => verifyNativeViewsSync
55
+ });
56
+ module.exports = __toCommonJS(runtime_exports);
57
+
58
+ // src/runtime/hooks/hook.registry.ts
59
+ var NoopHookRegistry = class {
60
+ async execute() {
61
+ }
62
+ };
63
+
64
+ // src/utils.ts
65
+ function generateId() {
66
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
67
+ return crypto.randomUUID();
68
+ }
69
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
70
+ const r = Math.random() * 16 | 0;
71
+ const v = c === "x" ? r : r & 3 | 8;
72
+ return v.toString(16);
73
+ });
74
+ }
75
+
76
+ // src/runtime/template.ts
77
+ var pipes = {
78
+ /** Convert to uppercase */
79
+ UPPER: (v) => String(v).toUpperCase(),
80
+ /** Convert to lowercase */
81
+ LOWER: (v) => String(v).toLowerCase(),
82
+ /** Capitalize first letter of each word */
83
+ capitalize: (v) => String(v).replace(/\b\w/g, (c) => c.toUpperCase()),
84
+ /** Trim whitespace from both ends */
85
+ trim: (v) => String(v).trim()
86
+ };
87
+ function getValue(obj, path) {
88
+ return path.split(".").reduce((acc, key) => {
89
+ if (acc == null || typeof acc !== "object") return void 0;
90
+ return acc[key];
91
+ }, obj);
92
+ }
93
+ var DEFAULT_LABEL_FALLBACK = "(Untitled)";
94
+ function renderLabelExpression(template, values, fallback = DEFAULT_LABEL_FALLBACK) {
95
+ const result = template.replace(/\{\{\s*([^}]+)\s*\}\}/g, (_, expr) => {
96
+ const parts = expr.split("|").map((s) => s.trim());
97
+ const path = parts[0];
98
+ let value = getValue(values, path);
99
+ if (value == null || value === "") return "";
100
+ for (let i = 1; i < parts.length; i++) {
101
+ const pipeName = parts[i].trim();
102
+ const fn = pipes[pipeName];
103
+ if (fn) {
104
+ value = fn(String(value));
105
+ }
106
+ }
107
+ return String(value);
108
+ }).trim();
109
+ return result || fallback;
110
+ }
111
+ function isLabelExpression(value) {
112
+ return /\{\{\s*\S+.*\}\}/.test(value);
113
+ }
114
+ function extractAttributeNames(template) {
115
+ const names = [];
116
+ const regex = /\{\{\s*([^|}]+)/g;
117
+ let match;
118
+ while ((match = regex.exec(template)) !== null) {
119
+ const path = match[1].trim();
120
+ const rootName = path.split(".")[0];
121
+ if (rootName && !names.includes(rootName)) {
122
+ names.push(rootName);
123
+ }
124
+ }
125
+ return names;
126
+ }
127
+
128
+ // src/runtime/mock-adapter.ts
129
+ function createMockObjectsRepository(stores) {
130
+ return {
131
+ findById(id) {
132
+ return Promise.resolve(stores.objects.get(id) ?? null);
133
+ },
134
+ findByName(tenantId, name) {
135
+ for (const obj of stores.objects.values()) {
136
+ if (obj.tenantId === tenantId && obj.name === name) {
137
+ return Promise.resolve(obj);
138
+ }
139
+ }
140
+ return Promise.resolve(null);
141
+ },
142
+ findSystemByName(name) {
143
+ for (const obj of stores.objects.values()) {
144
+ if (obj.system && obj.name === name) {
145
+ return Promise.resolve(obj);
146
+ }
147
+ }
148
+ return Promise.resolve(null);
149
+ },
150
+ create(data) {
151
+ const obj = {
152
+ id: generateId(),
153
+ tenantId: data.tenantId,
154
+ name: data.name,
155
+ label: data.label,
156
+ pluralLabel: data.pluralLabel,
157
+ description: data.description,
158
+ icon: data.icon,
159
+ labelExpression: data.labelExpression,
160
+ system: data.system ?? false,
161
+ metadata: data.metadata,
162
+ createdAt: /* @__PURE__ */ new Date(),
163
+ updatedAt: /* @__PURE__ */ new Date()
164
+ };
165
+ stores.objects.set(obj.id, obj);
166
+ return Promise.resolve(obj);
167
+ },
168
+ update(id, data) {
169
+ const existing = stores.objects.get(id);
170
+ if (!existing) {
171
+ return Promise.reject(new Error(`Object ${id} not found`));
172
+ }
173
+ const updated = {
174
+ ...existing,
175
+ ...data,
176
+ updatedAt: /* @__PURE__ */ new Date()
177
+ };
178
+ stores.objects.set(id, updated);
179
+ return Promise.resolve(updated);
180
+ },
181
+ delete(id) {
182
+ stores.objects.delete(id);
183
+ return Promise.resolve();
184
+ },
185
+ list(tenantId) {
186
+ const results = Array.from(stores.objects.values()).filter(
187
+ (obj) => obj.tenantId === tenantId
188
+ );
189
+ return Promise.resolve(results);
190
+ },
191
+ upsert(data) {
192
+ for (const obj of stores.objects.values()) {
193
+ if (obj.system && obj.name === data.name) {
194
+ const updated = {
195
+ ...obj,
196
+ label: data.label,
197
+ pluralLabel: data.pluralLabel,
198
+ description: data.description,
199
+ icon: data.icon,
200
+ metadata: data.metadata,
201
+ updatedAt: /* @__PURE__ */ new Date()
202
+ };
203
+ stores.objects.set(obj.id, updated);
204
+ return Promise.resolve(updated);
205
+ }
206
+ }
207
+ const newObj = {
208
+ id: generateId(),
209
+ tenantId: data.tenantId,
210
+ name: data.name,
211
+ label: data.label,
212
+ pluralLabel: data.pluralLabel,
213
+ description: data.description,
214
+ icon: data.icon,
215
+ labelExpression: data.labelExpression,
216
+ system: data.system,
217
+ metadata: data.metadata,
218
+ createdAt: /* @__PURE__ */ new Date(),
219
+ updatedAt: /* @__PURE__ */ new Date()
220
+ };
221
+ stores.objects.set(newObj.id, newObj);
222
+ return Promise.resolve(newObj);
223
+ }
224
+ };
225
+ }
226
+ function createMockAttributesRepository(stores) {
227
+ return {
228
+ findById(id) {
229
+ return Promise.resolve(stores.attributes.get(id) ?? null);
230
+ },
231
+ findByObjectId(objectId) {
232
+ const results = Array.from(stores.attributes.values()).filter(
233
+ (attr) => attr.objectId === objectId
234
+ );
235
+ return Promise.resolve(results);
236
+ },
237
+ create(data) {
238
+ const attr = {
239
+ id: generateId(),
240
+ objectId: data.objectId,
241
+ name: data.name,
242
+ label: data.label,
243
+ type: data.type,
244
+ description: data.description,
245
+ icon: data.icon,
246
+ system: data.system ?? false,
247
+ required: data.required ?? false,
248
+ unique: data.unique ?? false,
249
+ order: data.order ?? 0,
250
+ hidden: data.hidden,
251
+ archived: data.archived,
252
+ deprecated: data.deprecated,
253
+ defaultValue: data.defaultValue,
254
+ config: data.config ?? {},
255
+ createdAt: /* @__PURE__ */ new Date(),
256
+ updatedAt: /* @__PURE__ */ new Date()
257
+ };
258
+ stores.attributes.set(attr.id, attr);
259
+ return Promise.resolve(attr);
260
+ },
261
+ update(id, data) {
262
+ const existing = stores.attributes.get(id);
263
+ if (!existing) {
264
+ return Promise.reject(new Error(`Attribute ${id} not found`));
265
+ }
266
+ const updated = {
267
+ ...existing,
268
+ ...data,
269
+ updatedAt: /* @__PURE__ */ new Date()
270
+ };
271
+ stores.attributes.set(id, updated);
272
+ return Promise.resolve(updated);
273
+ },
274
+ delete(id) {
275
+ stores.attributes.delete(id);
276
+ return Promise.resolve();
277
+ },
278
+ deleteByNames(objectId, excludeNames) {
279
+ let count = 0;
280
+ for (const [id, attr] of stores.attributes.entries()) {
281
+ if (attr.objectId === objectId && !excludeNames.includes(attr.name)) {
282
+ stores.attributes.delete(id);
283
+ count++;
284
+ }
285
+ }
286
+ return Promise.resolve(count);
287
+ },
288
+ upsert(data) {
289
+ for (const attr of stores.attributes.values()) {
290
+ if (attr.objectId === data.objectId && attr.name === data.name) {
291
+ const updated = {
292
+ ...attr,
293
+ label: data.label ?? attr.label,
294
+ type: data.type ?? attr.type,
295
+ description: data.description ?? attr.description,
296
+ icon: data.icon ?? attr.icon,
297
+ system: data.system ?? attr.system,
298
+ required: data.required ?? attr.required,
299
+ unique: data.unique ?? attr.unique,
300
+ order: data.order ?? attr.order,
301
+ hidden: data.hidden ?? attr.hidden,
302
+ archived: data.archived ?? attr.archived,
303
+ deprecated: data.deprecated ?? attr.deprecated,
304
+ defaultValue: data.defaultValue ?? attr.defaultValue,
305
+ config: data.config ?? attr.config,
306
+ updatedAt: /* @__PURE__ */ new Date()
307
+ };
308
+ stores.attributes.set(attr.id, updated);
309
+ return Promise.resolve(updated);
310
+ }
311
+ }
312
+ return this.create(data);
313
+ }
314
+ };
315
+ }
316
+ function createMockUserProfilesRepository(stores) {
317
+ return {
318
+ findById(id) {
319
+ return Promise.resolve(stores.userProfiles.get(id) ?? null);
320
+ },
321
+ findByAuthId(authId) {
322
+ for (const profile of stores.userProfiles.values()) {
323
+ if (profile.authId === authId) {
324
+ return Promise.resolve(profile);
325
+ }
326
+ }
327
+ return Promise.resolve(null);
328
+ },
329
+ findByEmail(tenantId, email) {
330
+ for (const profile of stores.userProfiles.values()) {
331
+ if (profile.tenantId === tenantId && profile.email === email) {
332
+ return Promise.resolve(profile);
333
+ }
334
+ }
335
+ return Promise.resolve(null);
336
+ },
337
+ create(data) {
338
+ const profile = {
339
+ id: generateId(),
340
+ tenantId: data.tenantId,
341
+ authId: data.authId,
342
+ email: data.email,
343
+ firstName: data.firstName,
344
+ lastName: data.lastName,
345
+ avatarUrl: data.avatarUrl,
346
+ role: data.role ?? "member",
347
+ status: data.status ?? "active",
348
+ createdAt: /* @__PURE__ */ new Date(),
349
+ updatedAt: /* @__PURE__ */ new Date()
350
+ };
351
+ stores.userProfiles.set(profile.id, profile);
352
+ return Promise.resolve(profile);
353
+ },
354
+ update(id, data) {
355
+ const existing = stores.userProfiles.get(id);
356
+ if (!existing) {
357
+ return Promise.reject(new Error(`UserProfile ${id} not found`));
358
+ }
359
+ const updated = {
360
+ ...existing,
361
+ ...data,
362
+ updatedAt: /* @__PURE__ */ new Date()
363
+ };
364
+ stores.userProfiles.set(id, updated);
365
+ return Promise.resolve(updated);
366
+ },
367
+ delete(id) {
368
+ stores.userProfiles.delete(id);
369
+ return Promise.resolve();
370
+ },
371
+ list(tenantId, options) {
372
+ let results = Array.from(stores.userProfiles.values()).filter((p) => p.tenantId === tenantId);
373
+ if (options?.limit) {
374
+ results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
375
+ }
376
+ return Promise.resolve(results);
377
+ },
378
+ updateLastLogin(id) {
379
+ const profile = stores.userProfiles.get(id);
380
+ if (profile) {
381
+ profile.lastLoginAt = /* @__PURE__ */ new Date();
382
+ stores.userProfiles.set(id, profile);
383
+ }
384
+ return Promise.resolve();
385
+ }
386
+ };
387
+ }
388
+ function createMockFilesRepository(stores) {
389
+ return {
390
+ findById(id) {
391
+ const file = stores.files.get(id);
392
+ if (file?.deletedAt) return Promise.resolve(null);
393
+ return Promise.resolve(file ?? null);
394
+ },
395
+ create(data) {
396
+ const file = {
397
+ id: generateId(),
398
+ tenantId: data.tenantId,
399
+ name: data.name,
400
+ originalName: data.originalName,
401
+ mimeType: data.mimeType,
402
+ size: data.size,
403
+ storageProvider: data.storageProvider,
404
+ storagePath: data.storagePath,
405
+ storageBucket: data.storageBucket,
406
+ url: data.url,
407
+ uploadedBy: data.uploadedBy,
408
+ folderPath: data.folderPath,
409
+ tags: data.tags,
410
+ visibility: data.visibility ?? "private",
411
+ allowedUsers: data.allowedUsers,
412
+ createdAt: /* @__PURE__ */ new Date(),
413
+ updatedAt: /* @__PURE__ */ new Date()
414
+ };
415
+ stores.files.set(file.id, file);
416
+ return Promise.resolve(file);
417
+ },
418
+ update(id, data) {
419
+ const existing = stores.files.get(id);
420
+ if (!existing) {
421
+ return Promise.reject(new Error(`File ${id} not found`));
422
+ }
423
+ const updated = {
424
+ ...existing,
425
+ ...data,
426
+ updatedAt: /* @__PURE__ */ new Date()
427
+ };
428
+ stores.files.set(id, updated);
429
+ return Promise.resolve(updated);
430
+ },
431
+ delete(id) {
432
+ const file = stores.files.get(id);
433
+ if (file) {
434
+ file.deletedAt = /* @__PURE__ */ new Date();
435
+ stores.files.set(id, file);
436
+ }
437
+ return Promise.resolve();
438
+ },
439
+ hardDelete(id) {
440
+ stores.files.delete(id);
441
+ return Promise.resolve();
442
+ },
443
+ list(tenantId, options) {
444
+ let results = Array.from(stores.files.values()).filter(
445
+ (f) => f.tenantId === tenantId && !f.deletedAt
446
+ );
447
+ if (options?.mimeType) {
448
+ results = results.filter((f) => f.mimeType === options.mimeType);
449
+ }
450
+ if (options?.limit) {
451
+ results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
452
+ }
453
+ return Promise.resolve(results);
454
+ },
455
+ findByFolder(tenantId, folderPath) {
456
+ const results = Array.from(stores.files.values()).filter(
457
+ (f) => f.tenantId === tenantId && f.folderPath === folderPath && !f.deletedAt
458
+ );
459
+ return Promise.resolve(results);
460
+ },
461
+ findByUploader(uploadedBy) {
462
+ const results = Array.from(stores.files.values()).filter(
463
+ (f) => f.uploadedBy === uploadedBy && !f.deletedAt
464
+ );
465
+ return Promise.resolve(results);
466
+ }
467
+ };
468
+ }
469
+ function createMockObjectRecordsRepository(stores) {
470
+ return {
471
+ findById(id) {
472
+ const internal = stores.objectRecords.get(id);
473
+ if (!internal) return Promise.resolve(null);
474
+ const { tenantId: _, ...record } = internal;
475
+ return Promise.resolve(record);
476
+ },
477
+ create(data) {
478
+ const internal = {
479
+ id: generateId(),
480
+ tenantId: data.tenantId,
481
+ objectId: data.objectId,
482
+ label: data.label,
483
+ completionStatus: data.completionStatus,
484
+ values: data.data,
485
+ createdAt: /* @__PURE__ */ new Date(),
486
+ updatedAt: /* @__PURE__ */ new Date()
487
+ };
488
+ stores.objectRecords.set(internal.id, internal);
489
+ const { tenantId: _, ...record } = internal;
490
+ return Promise.resolve(record);
491
+ },
492
+ update(id, data) {
493
+ const existing = stores.objectRecords.get(id);
494
+ if (!existing) {
495
+ return Promise.reject(new Error(`ObjectRecord ${id} not found`));
496
+ }
497
+ const { __completionStatus, __label, ...valueData } = data;
498
+ const updated = {
499
+ ...existing,
500
+ label: __label ?? existing.label,
501
+ completionStatus: __completionStatus ?? existing.completionStatus,
502
+ values: { ...existing.values, ...valueData },
503
+ updatedAt: /* @__PURE__ */ new Date()
504
+ };
505
+ stores.objectRecords.set(id, updated);
506
+ const { tenantId: _, ...record } = updated;
507
+ return Promise.resolve(record);
508
+ },
509
+ delete(id) {
510
+ stores.objectRecords.delete(id);
511
+ return Promise.resolve();
512
+ },
513
+ list(tenantId, objectId, options) {
514
+ let results = Array.from(stores.objectRecords.values()).filter(
515
+ (r) => r.tenantId === tenantId && r.objectId === objectId
516
+ );
517
+ const total = results.length;
518
+ if (options?.limit) {
519
+ results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
520
+ }
521
+ const records = results.map(({ tenantId: _, ...r }) => r);
522
+ return Promise.resolve({ records, total });
523
+ },
524
+ search(tenantId, objectId, query, options) {
525
+ const lowerQuery = query.toLowerCase();
526
+ let results = Array.from(stores.objectRecords.values()).filter((r) => {
527
+ if (r.tenantId !== tenantId || r.objectId !== objectId) return false;
528
+ return Object.values(r.values).some(
529
+ (val) => String(val).toLowerCase().includes(lowerQuery)
530
+ );
531
+ });
532
+ const total = results.length;
533
+ if (options?.limit) {
534
+ results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
535
+ }
536
+ const records = results.map(({ tenantId: _, ...r }) => r);
537
+ return Promise.resolve({ records, total });
538
+ },
539
+ globalSearch(tenantId, query, options) {
540
+ const lowerQuery = query.toLowerCase();
541
+ const objectsMap = /* @__PURE__ */ new Map();
542
+ for (const obj of stores.objects.values()) {
543
+ if (obj.tenantId === tenantId) {
544
+ objectsMap.set(obj.id, obj);
545
+ }
546
+ }
547
+ const allowedObjectIds = /* @__PURE__ */ new Set();
548
+ if (options?.objectNames && options.objectNames.length > 0) {
549
+ for (const obj of objectsMap.values()) {
550
+ if (options.objectNames.includes(obj.name)) {
551
+ allowedObjectIds.add(obj.id);
552
+ }
553
+ }
554
+ } else {
555
+ for (const obj of objectsMap.values()) {
556
+ allowedObjectIds.add(obj.id);
557
+ }
558
+ }
559
+ let matchingRecords = Array.from(stores.objectRecords.values()).filter((r) => {
560
+ if (r.tenantId !== tenantId) return false;
561
+ if (!allowedObjectIds.has(r.objectId)) return false;
562
+ return Object.values(r.values).some(
563
+ (val) => String(val).toLowerCase().includes(lowerQuery)
564
+ );
565
+ });
566
+ const total = matchingRecords.length;
567
+ if (options?.limit) {
568
+ matchingRecords = matchingRecords.slice(
569
+ options.offset ?? 0,
570
+ (options.offset ?? 0) + options.limit
571
+ );
572
+ }
573
+ const results = matchingRecords.map((r) => {
574
+ const obj = objectsMap.get(r.objectId);
575
+ const labelExpression = obj?.labelExpression ?? "{{ name }}";
576
+ return {
577
+ objectId: r.objectId,
578
+ objectName: obj?.name ?? "unknown",
579
+ objectLabel: obj?.label ?? "Unknown",
580
+ label: renderLabelExpression(labelExpression, r.values),
581
+ recordId: r.id,
582
+ values: r.values,
583
+ completionStatus: r.completionStatus,
584
+ createdAt: r.createdAt,
585
+ updatedAt: r.updatedAt
586
+ };
587
+ });
588
+ return Promise.resolve({ results, total });
589
+ }
590
+ };
591
+ }
592
+ function createMockViewsRepository(stores) {
593
+ return {
594
+ findById(id) {
595
+ return Promise.resolve(stores.views.get(id) ?? null);
596
+ },
597
+ findByName(tenantId, objectName, viewName) {
598
+ return Promise.resolve(
599
+ Array.from(stores.views.values()).find(
600
+ (v) => v.tenantId === tenantId && v.objectName === objectName && v.name === viewName
601
+ ) ?? null
602
+ );
603
+ },
604
+ findByObjectName(tenantId, objectName) {
605
+ return Promise.resolve(
606
+ Array.from(stores.views.values()).filter(
607
+ (v) => v.tenantId === tenantId && v.objectName === objectName
608
+ )
609
+ );
610
+ },
611
+ findAllForTenant(tenantId) {
612
+ return Promise.resolve(
613
+ Array.from(stores.views.values()).filter((v) => v.tenantId === tenantId)
614
+ );
615
+ },
616
+ findSystemByName(objectName, viewName) {
617
+ return Promise.resolve(
618
+ Array.from(stores.views.values()).find(
619
+ (v) => v.objectName === objectName && v.name === viewName && v.system
620
+ ) ?? null
621
+ );
622
+ },
623
+ findSystemByObjectName(objectName) {
624
+ return Promise.resolve(
625
+ Array.from(stores.views.values()).filter((v) => v.objectName === objectName && v.system)
626
+ );
627
+ },
628
+ create(data) {
629
+ const id = generateId();
630
+ const now = /* @__PURE__ */ new Date();
631
+ const dbView = {
632
+ id,
633
+ tenantId: data.tenantId,
634
+ objectName: data.objectName,
635
+ name: data.name,
636
+ label: data.label,
637
+ description: data.description,
638
+ icon: data.icon,
639
+ tabs: data.tabs,
640
+ default: data.default ?? false,
641
+ system: data.system ?? false,
642
+ metadata: data.metadata,
643
+ createdAt: now,
644
+ updatedAt: now
645
+ };
646
+ stores.views.set(id, dbView);
647
+ return Promise.resolve(dbView);
648
+ },
649
+ update(id, data) {
650
+ const view = stores.views.get(id);
651
+ if (!view) {
652
+ return Promise.reject(new Error(`View not found: ${id}`));
653
+ }
654
+ const updated = {
655
+ ...view,
656
+ ...data,
657
+ updatedAt: /* @__PURE__ */ new Date()
658
+ };
659
+ stores.views.set(id, updated);
660
+ return Promise.resolve(updated);
661
+ },
662
+ delete(id) {
663
+ stores.views.delete(id);
664
+ return Promise.resolve();
665
+ },
666
+ deleteNotIn(objectName, keepViewNames) {
667
+ let deleted = 0;
668
+ for (const [id, view] of stores.views.entries()) {
669
+ if (view.objectName === objectName && view.system && !keepViewNames.includes(view.name)) {
670
+ stores.views.delete(id);
671
+ deleted++;
672
+ }
673
+ }
674
+ return Promise.resolve(deleted);
675
+ },
676
+ async upsert(data) {
677
+ const existing = data.system ? await this.findSystemByName(data.objectName, data.name) : await this.findByName(data.tenantId, data.objectName, data.name);
678
+ if (existing) {
679
+ return this.update(existing.id, {
680
+ label: data.label,
681
+ description: data.description,
682
+ icon: data.icon,
683
+ tabs: data.tabs,
684
+ default: data.default,
685
+ metadata: data.metadata
686
+ });
687
+ }
688
+ return this.create(data);
689
+ }
690
+ };
691
+ }
692
+ function createMockAdapter() {
693
+ const stores = {
694
+ objects: /* @__PURE__ */ new Map(),
695
+ attributes: /* @__PURE__ */ new Map(),
696
+ userProfiles: /* @__PURE__ */ new Map(),
697
+ files: /* @__PURE__ */ new Map(),
698
+ objectRecords: /* @__PURE__ */ new Map(),
699
+ views: /* @__PURE__ */ new Map()
700
+ };
701
+ const adapter = {
702
+ objects: createMockObjectsRepository(stores),
703
+ attributes: createMockAttributesRepository(stores),
704
+ userProfiles: createMockUserProfilesRepository(stores),
705
+ files: createMockFilesRepository(stores),
706
+ objectRecords: createMockObjectRecordsRepository(stores),
707
+ views: createMockViewsRepository(stores),
708
+ async transaction(callback) {
709
+ return await callback(adapter);
710
+ },
711
+ // Expose stores for testing inspection
712
+ _stores: stores,
713
+ // Reset all stores (useful between tests)
714
+ reset() {
715
+ stores.objects.clear();
716
+ stores.attributes.clear();
717
+ stores.userProfiles.clear();
718
+ stores.files.clear();
719
+ stores.objectRecords.clear();
720
+ stores.views.clear();
721
+ }
722
+ };
723
+ return adapter;
724
+ }
725
+
726
+ // src/runtime/services/file.service.ts
727
+ var FileService = class {
728
+ constructor(adapter, tenantId) {
729
+ this.adapter = adapter;
730
+ this.tenantId = tenantId;
731
+ }
732
+ /**
733
+ * Create a new file record (after upload to storage)
734
+ *
735
+ * @param data - File metadata
736
+ * @returns Created file record
737
+ *
738
+ * @example
739
+ * ```typescript
740
+ * const service = new FileService(adapter, "tenant-123");
741
+ *
742
+ * // After uploading to S3
743
+ * const file = await service.createFile({
744
+ * tenantId: "tenant-123",
745
+ * name: "contract-2025.pdf",
746
+ * originalName: "Contract Acme Corp 2025.pdf",
747
+ * mimeType: "application/pdf",
748
+ * size: 2458624,
749
+ * storageProvider: "s3",
750
+ * storagePath: "tenants/123/files/2025/contract.pdf",
751
+ * storageBucket: "my-app-files",
752
+ * url: "https://cdn.example.com/files/file-123",
753
+ * uploadedBy: "profile-456",
754
+ * visibility: "private"
755
+ * });
756
+ * ```
757
+ */
758
+ async createFile(data) {
759
+ if (data.tenantId !== this.tenantId) {
760
+ throw new Error(
761
+ `Tenant mismatch: service initialized with "${this.tenantId}" but data has "${data.tenantId}"`
762
+ );
763
+ }
764
+ return await this.adapter.files.create(data);
765
+ }
766
+ /**
767
+ * Get file by ID
768
+ */
769
+ async getFile(fileId) {
770
+ const file = await this.adapter.files.findById(fileId);
771
+ if (file?.deletedAt) {
772
+ return null;
773
+ }
774
+ return file;
775
+ }
776
+ /**
777
+ * Get file by ID or throw
778
+ */
779
+ async getFileOrThrow(fileId) {
780
+ const file = await this.getFile(fileId);
781
+ if (!file) {
782
+ throw new Error(`File with id "${fileId}" not found`);
783
+ }
784
+ return file;
785
+ }
786
+ /**
787
+ * Update file metadata
788
+ *
789
+ * @param fileId - File UUID
790
+ * @param data - Data to update
791
+ * @returns Updated file
792
+ */
793
+ async updateFile(fileId, data) {
794
+ await this.getFileOrThrow(fileId);
795
+ return await this.adapter.files.update(fileId, data);
796
+ }
797
+ /**
798
+ * Delete file (soft delete)
799
+ *
800
+ * @param fileId - File UUID
801
+ * @param options - Delete options
802
+ */
803
+ async deleteFile(fileId, options) {
804
+ if (options?.checkOwnership && options.userId) {
805
+ const file = await this.getFileOrThrow(fileId);
806
+ if (file.uploadedBy !== options.userId) {
807
+ throw new Error("You can only delete files you uploaded");
808
+ }
809
+ }
810
+ if (options?.hard) {
811
+ await this.adapter.files.hardDelete(fileId);
812
+ } else {
813
+ await this.adapter.files.delete(fileId);
814
+ }
815
+ }
816
+ /**
817
+ * List files for the tenant
818
+ */
819
+ async listFiles(options) {
820
+ return await this.adapter.files.list(this.tenantId, options);
821
+ }
822
+ /**
823
+ * List files by folder
824
+ */
825
+ async listFilesByFolder(folderPath) {
826
+ return await this.adapter.files.findByFolder(this.tenantId, folderPath);
827
+ }
828
+ /**
829
+ * List files uploaded by a specific user
830
+ */
831
+ async listFilesByUploader(uploadedBy) {
832
+ return await this.adapter.files.findByUploader(uploadedBy);
833
+ }
834
+ /**
835
+ * Change file visibility
836
+ *
837
+ * @param fileId - File UUID
838
+ * @param visibility - New visibility level
839
+ * @param allowedUsers - Users allowed to access (if restricted)
840
+ */
841
+ async changeVisibility(fileId, visibility, allowedUsers) {
842
+ const updateData = { visibility };
843
+ if (visibility === "restricted" && allowedUsers) {
844
+ updateData.allowedUsers = allowedUsers;
845
+ }
846
+ return await this.updateFile(fileId, updateData);
847
+ }
848
+ /**
849
+ * Grant access to a file for specific users
850
+ *
851
+ * @param fileId - File UUID
852
+ * @param userIds - User IDs to grant access
853
+ */
854
+ async grantAccess(fileId, userIds) {
855
+ const file = await this.getFileOrThrow(fileId);
856
+ const currentAllowed = file.allowedUsers || [];
857
+ const newAllowed = [.../* @__PURE__ */ new Set([...currentAllowed, ...userIds])];
858
+ return await this.updateFile(fileId, {
859
+ visibility: "restricted",
860
+ allowedUsers: newAllowed
861
+ });
862
+ }
863
+ /**
864
+ * Revoke access to a file for specific users
865
+ *
866
+ * @param fileId - File UUID
867
+ * @param userIds - User IDs to revoke access
868
+ */
869
+ async revokeAccess(fileId, userIds) {
870
+ const file = await this.getFileOrThrow(fileId);
871
+ const currentAllowed = file.allowedUsers || [];
872
+ const newAllowed = currentAllowed.filter((id) => !userIds.includes(id));
873
+ return await this.updateFile(fileId, {
874
+ allowedUsers: newAllowed
875
+ });
876
+ }
877
+ /**
878
+ * Check if user has access to a file
879
+ *
880
+ * @param fileId - File UUID
881
+ * @param userId - User ID to check
882
+ * @returns true if user can access the file
883
+ */
884
+ async canAccess(fileId, userId) {
885
+ const file = await this.getFile(fileId);
886
+ if (!file) {
887
+ return false;
888
+ }
889
+ if (file.visibility === "public") {
890
+ return true;
891
+ }
892
+ if (file.uploadedBy === userId) {
893
+ return true;
894
+ }
895
+ if (file.visibility === "restricted") {
896
+ return file.allowedUsers?.includes(userId) ?? false;
897
+ }
898
+ return false;
899
+ }
900
+ /**
901
+ * Move file to different folder
902
+ */
903
+ async moveToFolder(fileId, newFolderPath) {
904
+ return await this.updateFile(fileId, { folderPath: newFolderPath });
905
+ }
906
+ /**
907
+ * Add tags to file
908
+ */
909
+ async addTags(fileId, tags) {
910
+ const file = await this.getFileOrThrow(fileId);
911
+ const currentTags = file.tags || [];
912
+ const newTags = [.../* @__PURE__ */ new Set([...currentTags, ...tags])];
913
+ return await this.updateFile(fileId, { tags: newTags });
914
+ }
915
+ /**
916
+ * Remove tags from file
917
+ */
918
+ async removeTags(fileId, tags) {
919
+ const file = await this.getFileOrThrow(fileId);
920
+ const currentTags = file.tags || [];
921
+ const newTags = currentTags.filter((tag) => !tags.includes(tag));
922
+ return await this.updateFile(fileId, { tags: newTags });
923
+ }
924
+ };
925
+
926
+ // src/runtime/services/geocoding.service.ts
927
+ var GeocodingService = class {
928
+ constructor(adapter) {
929
+ this.adapter = adapter;
930
+ }
931
+ /**
932
+ * Search for address suggestions as the user types
933
+ */
934
+ async autocomplete(params) {
935
+ if (!params.query || params.query.trim().length < 2) {
936
+ return [];
937
+ }
938
+ return await this.adapter.autocomplete({
939
+ ...params,
940
+ query: params.query.trim(),
941
+ limit: params.limit ?? 5
942
+ });
943
+ }
944
+ /**
945
+ * Reverse geocode coordinates to an address
946
+ */
947
+ async reverse(params) {
948
+ if (!this.adapter.reverse) {
949
+ throw new Error("Reverse geocoding is not supported by the configured adapter");
950
+ }
951
+ return await this.adapter.reverse(params);
952
+ }
953
+ /**
954
+ * Geocode a structured address to coordinates
955
+ */
956
+ async geocode(params) {
957
+ if (!this.adapter.geocode) {
958
+ throw new Error("Geocoding is not supported by the configured adapter");
959
+ }
960
+ return await this.adapter.geocode(params);
961
+ }
962
+ };
963
+
964
+ // src/runtime/services/global-search.service.ts
965
+ var GlobalSearchService = class {
966
+ constructor(adapter, tenantId) {
967
+ this.adapter = adapter;
968
+ this.tenantId = tenantId;
969
+ }
970
+ /**
971
+ * Search across all object records for the tenant
972
+ *
973
+ * @param query - Search query string
974
+ * @param options - Search options (pagination, object filters)
975
+ * @returns Matching records with object metadata and total count
976
+ *
977
+ * @example
978
+ * ```typescript
979
+ * // Basic search
980
+ * const { results, total } = await service.search("nike air");
981
+ *
982
+ * // With pagination
983
+ * const { results, total } = await service.search("nike", {
984
+ * limit: 10,
985
+ * offset: 20
986
+ * });
987
+ *
988
+ * // Filter by object types
989
+ * const { results, total } = await service.search("nike", {
990
+ * objectNames: ["products", "orders"]
991
+ * });
992
+ * ```
993
+ */
994
+ async search(query, options) {
995
+ if (!query || query.trim().length === 0) {
996
+ return { results: [], total: 0 };
997
+ }
998
+ return await this.adapter.objectRecords.globalSearch(this.tenantId, query.trim(), {
999
+ limit: options?.limit ?? 20,
1000
+ offset: options?.offset ?? 0,
1001
+ objectNames: options?.objectNames,
1002
+ includeObjectInfo: options?.includeObjectInfo ?? true
1003
+ });
1004
+ }
1005
+ /**
1006
+ * Search and group results by object type
1007
+ *
1008
+ * @param query - Search query string
1009
+ * @param options - Search options
1010
+ * @returns Results grouped by object name
1011
+ */
1012
+ async searchGrouped(query, options) {
1013
+ const { results, total } = await this.search(query, {
1014
+ ...options,
1015
+ limit: 100,
1016
+ // Get more for grouping
1017
+ offset: 0
1018
+ });
1019
+ const groupMap = /* @__PURE__ */ new Map();
1020
+ for (const result of results) {
1021
+ const existing = groupMap.get(result.objectName);
1022
+ if (existing) {
1023
+ existing.results.push(result);
1024
+ } else {
1025
+ groupMap.set(result.objectName, {
1026
+ objectName: result.objectName,
1027
+ objectLabel: result.objectLabel,
1028
+ results: [result]
1029
+ });
1030
+ }
1031
+ }
1032
+ const groups = Array.from(groupMap.values()).map((g) => ({
1033
+ ...g,
1034
+ count: g.results.length
1035
+ }));
1036
+ groups.sort((a, b) => b.count - a.count);
1037
+ return { groups, total };
1038
+ }
1039
+ };
1040
+
1041
+ // src/builders/attribute-validators.ts
1042
+ var import_constants = require("@stndrds/constants");
1043
+ var VALID_ICONS = new Set(import_constants.ICONS);
1044
+
1045
+ // src/builders/object-builder.ts
1046
+ var import_zod = __toESM(require("zod"));
1047
+ var ObjectBuilder = class {
1048
+ constructor(config) {
1049
+ this.validateName(config.name);
1050
+ this.obj = {
1051
+ name: config.name,
1052
+ label: config.label,
1053
+ attributes: [],
1054
+ system: false
1055
+ };
1056
+ }
1057
+ /**
1058
+ * Set object description
1059
+ */
1060
+ description(value) {
1061
+ this.obj.description = value;
1062
+ return this;
1063
+ }
1064
+ /**
1065
+ * Set the plural form of the label (e.g., "Products" for "Product")
1066
+ * Used in UI when displaying collections
1067
+ */
1068
+ pluralLabel(value) {
1069
+ this._pluralLabel = value;
1070
+ return this;
1071
+ }
1072
+ /**
1073
+ * Set object icon
1074
+ */
1075
+ icon(value) {
1076
+ this.obj.icon = value;
1077
+ return this;
1078
+ }
1079
+ /**
1080
+ * Mark as system/native object (defined in code, protected)
1081
+ * System objects cannot be deleted/modified by clients
1082
+ */
1083
+ system() {
1084
+ this.obj.system = true;
1085
+ return this;
1086
+ }
1087
+ /**
1088
+ * Add an attribute to the object with type accumulation
1089
+ *
1090
+ * The return type accumulates the attribute's name and value type
1091
+ * into the TRecord generic, enabling full inference.
1092
+ *
1093
+ * @param attr - Attribute definition or builder (auto-builds if builder)
1094
+ */
1095
+ attribute(attr) {
1096
+ if (!this.obj.attributes) {
1097
+ this.obj.attributes = [];
1098
+ }
1099
+ const finalAttr = "build" in attr && typeof attr.build === "function" ? attr.build() : attr;
1100
+ this.obj.attributes.push(finalAttr);
1101
+ return this;
1102
+ }
1103
+ /**
1104
+ * Add multiple attributes at once
1105
+ * Note: For full type inference, prefer chaining .attribute() calls
1106
+ */
1107
+ attributes(attrs) {
1108
+ if (!this.obj.attributes) {
1109
+ this.obj.attributes = [];
1110
+ }
1111
+ const finalAttrs = attrs.map((attr) => "build" in attr ? attr.build() : attr);
1112
+ this.obj.attributes.push(...finalAttrs);
1113
+ return this;
1114
+ }
1115
+ /**
1116
+ * Set metadata
1117
+ */
1118
+ metadata(value) {
1119
+ this.obj.metadata = value;
1120
+ return this;
1121
+ }
1122
+ /**
1123
+ * Set the label expression template for displaying records
1124
+ * This is REQUIRED for every object.
1125
+ *
1126
+ * Supports variable interpolation with `{{ attribute }}` syntax
1127
+ * and pipes for formatting: `{{ name | UPPER }}`, `{{ name | capitalize }}`
1128
+ *
1129
+ * Available pipes: UPPER, LOWER, capitalize, trim
1130
+ *
1131
+ * @param template - The label expression template
1132
+ *
1133
+ * @example
1134
+ * ```typescript
1135
+ * object({ name: "contacts", label: "Contact" })
1136
+ * .labelExpression("{{ firstName }} {{ lastName }}")
1137
+ * .attribute(text({ name: "firstName", label: "First Name" }).required())
1138
+ * .attribute(text({ name: "lastName", label: "Last Name" }).required())
1139
+ * ```
1140
+ */
1141
+ labelExpression(template) {
1142
+ this._labelExpression = template;
1143
+ return this;
1144
+ }
1145
+ /**
1146
+ * Build the final ObjectDefinition
1147
+ * @throws Error if required fields are missing or invalid
1148
+ */
1149
+ build() {
1150
+ if (!this.obj.name) {
1151
+ throw new Error("[ObjectBuilder] Missing required field: name");
1152
+ }
1153
+ if (!this.obj.label) {
1154
+ throw new Error("[ObjectBuilder] Missing required field: label");
1155
+ }
1156
+ if (!this._labelExpression) {
1157
+ throw new Error(
1158
+ `[ObjectBuilder] Object "${this.obj.name}" must have a labelExpression.
1159
+ The labelExpression defines how records are displayed in lists and relations.
1160
+ \u2705 Example: .labelExpression("{{ name }}")
1161
+ \u2705 Example: .labelExpression("{{ firstName }} {{ lastName }}")`
1162
+ );
1163
+ }
1164
+ return {
1165
+ ...this.obj,
1166
+ pluralLabel: this._pluralLabel,
1167
+ labelExpression: this._labelExpression
1168
+ };
1169
+ }
1170
+ /**
1171
+ * Validate object name format
1172
+ * Must be kebab-case (slug format), max 63 characters
1173
+ * @internal
1174
+ */
1175
+ validateName(name) {
1176
+ if (name === void 0) {
1177
+ return;
1178
+ }
1179
+ const objectNameSchema = import_zod.default.string().min(1, "Object name cannot be empty").max(63, "Object name is too long (max 63 characters)").regex(/^[a-z][a-z0-9-]*$/, {
1180
+ message: "Invalid object name format.\nName must be in kebab-case (slug format):\n \u2705 Valid: 'products', 'user-profiles', 'order-items'\n \u274C Invalid: 'Products', 'userProfiles', 'user_profiles'"
1181
+ });
1182
+ try {
1183
+ objectNameSchema.parse(name);
1184
+ } catch (error) {
1185
+ if (error instanceof import_zod.default.ZodError) {
1186
+ throw new Error(`[ObjectBuilder] ${error.errors[0].message}`);
1187
+ }
1188
+ throw error;
1189
+ }
1190
+ }
1191
+ };
1192
+ function object(config) {
1193
+ return new ObjectBuilder(config);
1194
+ }
1195
+
1196
+ // src/builders/view-builder.ts
1197
+ var import_zod2 = require("zod");
1198
+
1199
+ // src/validation/validators.ts
1200
+ var import_zod3 = require("zod");
1201
+ var baseConfigSchema = import_zod3.z.object({
1202
+ disabled: import_zod3.z.boolean().optional(),
1203
+ placeholder: import_zod3.z.string().optional(),
1204
+ description: import_zod3.z.string().optional(),
1205
+ defaultValue: import_zod3.z.unknown().optional(),
1206
+ icon: import_zod3.z.string().optional(),
1207
+ order: import_zod3.z.number().int().optional(),
1208
+ hidden: import_zod3.z.boolean().optional(),
1209
+ archived: import_zod3.z.boolean().optional(),
1210
+ deprecated: import_zod3.z.boolean().optional(),
1211
+ metadata: import_zod3.z.record(import_zod3.z.unknown()).optional()
1212
+ });
1213
+ var optionSchema = import_zod3.z.object({
1214
+ id: import_zod3.z.string().min(1),
1215
+ label: import_zod3.z.string().min(1),
1216
+ value: import_zod3.z.string().min(1),
1217
+ color: import_zod3.z.string().optional(),
1218
+ icon: import_zod3.z.string().optional(),
1219
+ description: import_zod3.z.string().optional(),
1220
+ group: import_zod3.z.enum(["idle", "in_progress", "finished"]).optional()
1221
+ });
1222
+ var relationTargetSchema = import_zod3.z.object({
1223
+ object: import_zod3.z.string().min(1),
1224
+ displayTemplate: import_zod3.z.string().optional(),
1225
+ filter: import_zod3.z.record(import_zod3.z.unknown()).optional()
1226
+ });
1227
+ var documentTypeConfigSchema = import_zod3.z.object({
1228
+ type: import_zod3.z.enum([
1229
+ "id_card",
1230
+ "passport",
1231
+ "incorporation_certificate",
1232
+ "driver_license",
1233
+ "birth_certificate",
1234
+ "residence_permit",
1235
+ "bank_statement",
1236
+ "proof_of_address",
1237
+ "custom"
1238
+ ]),
1239
+ label: import_zod3.z.string(),
1240
+ faces: import_zod3.z.array(import_zod3.z.enum(["front", "back", "single"])),
1241
+ attributeMapping: import_zod3.z.array(
1242
+ import_zod3.z.object({
1243
+ attributeId: import_zod3.z.string(),
1244
+ extractedKey: import_zod3.z.string(),
1245
+ face: import_zod3.z.enum(["front", "back", "single"]).optional(),
1246
+ required: import_zod3.z.boolean().optional()
1247
+ })
1248
+ ).optional()
1249
+ });
1250
+ var fileVerificationConfigSchema = import_zod3.z.object({
1251
+ enabled: import_zod3.z.boolean(),
1252
+ documentTypes: import_zod3.z.array(documentTypeConfigSchema),
1253
+ autoExtract: import_zod3.z.boolean().optional(),
1254
+ autoValidate: import_zod3.z.boolean().optional()
1255
+ });
1256
+ var textConfigSchema = baseConfigSchema.extend({
1257
+ minLength: import_zod3.z.number().int().min(0).optional(),
1258
+ maxLength: import_zod3.z.number().int().min(1).optional(),
1259
+ pattern: import_zod3.z.string().optional()
1260
+ });
1261
+ var textareaConfigSchema = baseConfigSchema;
1262
+ var numberConfigSchema = baseConfigSchema.extend({
1263
+ min: import_zod3.z.number().optional(),
1264
+ max: import_zod3.z.number().optional(),
1265
+ unit: import_zod3.z.enum(["integer", "decimal", "percentage"]).optional(),
1266
+ decimals: import_zod3.z.number().int().min(0).optional()
1267
+ });
1268
+ var checkboxConfigSchema = baseConfigSchema;
1269
+ var dateConfigSchema = baseConfigSchema.extend({
1270
+ dateFormat: import_zod3.z.enum(["short", "long", "full", "relative"]).optional(),
1271
+ minDate: import_zod3.z.string().optional(),
1272
+ maxDate: import_zod3.z.string().optional()
1273
+ });
1274
+ var phoneConfigSchema = baseConfigSchema.extend({
1275
+ defaultCountryCode: import_zod3.z.string().length(3).optional()
1276
+ });
1277
+ var currencyConfigSchema = baseConfigSchema.extend({
1278
+ defaultCurrency: import_zod3.z.string().length(3).optional(),
1279
+ allowedCurrencies: import_zod3.z.array(import_zod3.z.string().length(3)).optional()
1280
+ });
1281
+ var statusConfigSchema = baseConfigSchema.extend({
1282
+ options: import_zod3.z.array(optionSchema).min(1)
1283
+ });
1284
+ var locationConfigSchema = baseConfigSchema.extend({
1285
+ granularity: import_zod3.z.enum(["full", "address", "city", "state", "country", "coordinates"]),
1286
+ enableAutocomplete: import_zod3.z.boolean().optional(),
1287
+ enableMap: import_zod3.z.boolean().optional(),
1288
+ defaultCountry: import_zod3.z.string().length(3).optional(),
1289
+ allowedCountries: import_zod3.z.array(import_zod3.z.string().length(3)).optional(),
1290
+ displayFormat: import_zod3.z.enum(["single_line", "multi_line", "compact"]).optional()
1291
+ });
1292
+ var timestampConfigSchema = baseConfigSchema.extend({
1293
+ autoUpdate: import_zod3.z.boolean().optional()
1294
+ });
1295
+ var selectConfigSchema = baseConfigSchema.extend({
1296
+ options: import_zod3.z.array(optionSchema).min(1)
1297
+ });
1298
+ var multiselectConfigSchema = baseConfigSchema.extend({
1299
+ options: import_zod3.z.array(optionSchema).min(1)
1300
+ });
1301
+ var fileConfigSchema = baseConfigSchema.extend({
1302
+ maxFiles: import_zod3.z.number().int().min(1).optional(),
1303
+ maxSize: import_zod3.z.number().int().min(1).optional(),
1304
+ allowedTypes: import_zod3.z.array(import_zod3.z.string()).optional(),
1305
+ verification: fileVerificationConfigSchema.optional()
1306
+ });
1307
+ var userConfigSchema = baseConfigSchema.extend({
1308
+ allowedRoles: import_zod3.z.array(import_zod3.z.string()).optional()
1309
+ });
1310
+ var relationConfigSchema = baseConfigSchema.extend({
1311
+ targets: import_zod3.z.array(relationTargetSchema).min(1),
1312
+ onDelete: import_zod3.z.enum(["restrict", "cascade", "set_null"]).optional(),
1313
+ cardinality: import_zod3.z.enum(["one", "many"]),
1314
+ minItems: import_zod3.z.number().int().min(0).optional(),
1315
+ maxItems: import_zod3.z.number().int().min(1).optional()
1316
+ });
1317
+ var ratingConfigSchema = baseConfigSchema.extend({
1318
+ max: import_zod3.z.number().int().min(1).optional(),
1319
+ iconType: import_zod3.z.enum(["star", "heart", "thumbs", "number"]).optional()
1320
+ });
1321
+ var attributeConfigSchemas = {
1322
+ text: textConfigSchema,
1323
+ textarea: textareaConfigSchema,
1324
+ number: numberConfigSchema,
1325
+ checkbox: checkboxConfigSchema,
1326
+ date: dateConfigSchema,
1327
+ phone: phoneConfigSchema,
1328
+ currency: currencyConfigSchema,
1329
+ status: statusConfigSchema,
1330
+ location: locationConfigSchema,
1331
+ timestamp: timestampConfigSchema,
1332
+ select: selectConfigSchema,
1333
+ multiselect: multiselectConfigSchema,
1334
+ file: fileConfigSchema,
1335
+ user: userConfigSchema,
1336
+ relation: relationConfigSchema,
1337
+ rating: ratingConfigSchema
1338
+ };
1339
+ function getAttributeConfigSchema(type) {
1340
+ return attributeConfigSchemas[type];
1341
+ }
1342
+ function parseAttributeConfig(type, config) {
1343
+ const schema = getAttributeConfigSchema(type);
1344
+ return schema.strip().parse(config);
1345
+ }
1346
+ function createTextValidator(attr) {
1347
+ let schema = import_zod3.z.string();
1348
+ if (attr.minLength !== void 0) {
1349
+ schema = schema.min(
1350
+ attr.minLength,
1351
+ `${attr.label} must be at least ${attr.minLength} characters`
1352
+ );
1353
+ }
1354
+ if (attr.maxLength !== void 0) {
1355
+ schema = schema.max(
1356
+ attr.maxLength,
1357
+ `${attr.label} must be at most ${attr.maxLength} characters`
1358
+ );
1359
+ }
1360
+ if (attr.pattern) {
1361
+ schema = schema.regex(new RegExp(attr.pattern), `${attr.label} format is invalid`);
1362
+ }
1363
+ return schema;
1364
+ }
1365
+ function createNumberValidator(attr) {
1366
+ let schema = import_zod3.z.number();
1367
+ if (attr.min !== void 0) {
1368
+ schema = schema.min(attr.min, `${attr.label} must be at least ${attr.min}`);
1369
+ }
1370
+ if (attr.max !== void 0) {
1371
+ schema = schema.max(attr.max, `${attr.label} must be at most ${attr.max}`);
1372
+ }
1373
+ if (attr.unit === "integer") {
1374
+ schema = schema.int(`${attr.label} must be an integer`);
1375
+ }
1376
+ return schema;
1377
+ }
1378
+ function createCheckboxValidator(_attr) {
1379
+ return import_zod3.z.boolean();
1380
+ }
1381
+ function createDateValidator(attr) {
1382
+ return import_zod3.z.string().datetime({ message: `${attr.label} must be a valid ISO date` });
1383
+ }
1384
+ function createPhoneValidator(_attr) {
1385
+ return import_zod3.z.object({
1386
+ countryCode: import_zod3.z.string().length(3),
1387
+ phoneNumber: import_zod3.z.string().min(1)
1388
+ });
1389
+ }
1390
+ function createCurrencyValidator(_attr) {
1391
+ return import_zod3.z.object({
1392
+ code: import_zod3.z.string().length(3),
1393
+ value: import_zod3.z.number().min(0)
1394
+ });
1395
+ }
1396
+ function createStatusValidator(attr) {
1397
+ const validValues = attr.options.map((opt) => opt.value);
1398
+ return import_zod3.z.enum(validValues, {
1399
+ errorMap: () => ({
1400
+ message: `${attr.label} must be one of: ${validValues.join(", ")}`
1401
+ })
1402
+ });
1403
+ }
1404
+ function createSelectValidator(attr) {
1405
+ const validValues = attr.options.map((opt) => opt.value);
1406
+ return import_zod3.z.enum(validValues, {
1407
+ errorMap: () => ({
1408
+ message: `${attr.label} must be one of: ${validValues.join(", ")}`
1409
+ })
1410
+ });
1411
+ }
1412
+ function createMultiselectValidator(attr) {
1413
+ const validValues = attr.options.map((opt) => opt.value);
1414
+ return import_zod3.z.array(
1415
+ import_zod3.z.enum(validValues, {
1416
+ errorMap: () => ({
1417
+ message: `Each value must be one of: ${validValues.join(", ")}`
1418
+ })
1419
+ })
1420
+ );
1421
+ }
1422
+ function createLocationValidator(_attr) {
1423
+ return import_zod3.z.object({
1424
+ address: import_zod3.z.string().optional(),
1425
+ address2: import_zod3.z.string().optional(),
1426
+ city: import_zod3.z.string().optional(),
1427
+ state: import_zod3.z.string().optional(),
1428
+ postalCode: import_zod3.z.string().optional(),
1429
+ country: import_zod3.z.string().length(3).optional(),
1430
+ latitude: import_zod3.z.number().optional(),
1431
+ longitude: import_zod3.z.number().optional()
1432
+ });
1433
+ }
1434
+ function createTimestampValidator(_attr) {
1435
+ return import_zod3.z.number().int().positive();
1436
+ }
1437
+ function createFileValidator(_attr) {
1438
+ return import_zod3.z.string().uuid();
1439
+ }
1440
+ function createUserValidator(_attr) {
1441
+ return import_zod3.z.string().uuid();
1442
+ }
1443
+ function createSingleRelationValidator(attr) {
1444
+ const uuidSchema = import_zod3.z.string().uuid({
1445
+ message: `${attr.label} must be a valid record ID`
1446
+ });
1447
+ return import_zod3.z.union([uuidSchema, import_zod3.z.null()]);
1448
+ }
1449
+ function createMultiRelationValidator(attr) {
1450
+ const uuidSchema = import_zod3.z.string().uuid({
1451
+ message: `Each ${attr.label} item must be a valid record ID`
1452
+ });
1453
+ let arraySchema = import_zod3.z.array(uuidSchema);
1454
+ if (attr.minItems !== void 0) {
1455
+ arraySchema = arraySchema.min(
1456
+ attr.minItems,
1457
+ `${attr.label} must have at least ${attr.minItems} item${attr.minItems > 1 ? "s" : ""}`
1458
+ );
1459
+ }
1460
+ if (attr.maxItems !== void 0) {
1461
+ arraySchema = arraySchema.max(
1462
+ attr.maxItems,
1463
+ `${attr.label} must have at most ${attr.maxItems} item${attr.maxItems > 1 ? "s" : ""}`
1464
+ );
1465
+ }
1466
+ return arraySchema;
1467
+ }
1468
+ function createRelationValidator(attr) {
1469
+ if (attr.cardinality === "many") {
1470
+ return createMultiRelationValidator(attr);
1471
+ }
1472
+ return createSingleRelationValidator(attr);
1473
+ }
1474
+ function createRatingValidator(attr) {
1475
+ let schema = import_zod3.z.number().int().min(0);
1476
+ if (attr.max !== void 0) {
1477
+ schema = schema.max(attr.max, `${attr.label} must be at most ${attr.max}`);
1478
+ }
1479
+ return schema;
1480
+ }
1481
+ function createAttributeValidator(attr) {
1482
+ switch (attr.type) {
1483
+ case "text":
1484
+ return createTextValidator(attr);
1485
+ case "number":
1486
+ return createNumberValidator(attr);
1487
+ case "checkbox":
1488
+ return createCheckboxValidator(attr);
1489
+ case "date":
1490
+ return createDateValidator(attr);
1491
+ case "phone":
1492
+ return createPhoneValidator(attr);
1493
+ case "currency":
1494
+ return createCurrencyValidator(attr);
1495
+ case "status":
1496
+ return createStatusValidator(attr);
1497
+ case "location":
1498
+ return createLocationValidator(attr);
1499
+ case "timestamp":
1500
+ return createTimestampValidator(attr);
1501
+ case "select":
1502
+ return createSelectValidator(attr);
1503
+ case "multiselect":
1504
+ return createMultiselectValidator(attr);
1505
+ case "file":
1506
+ return createFileValidator(attr);
1507
+ case "user":
1508
+ return createUserValidator(attr);
1509
+ case "relation":
1510
+ return createRelationValidator(attr);
1511
+ case "rating":
1512
+ return createRatingValidator(attr);
1513
+ default:
1514
+ return import_zod3.z.unknown();
1515
+ }
1516
+ }
1517
+ function createObjectValidator(objectDef) {
1518
+ const shape = {};
1519
+ for (const attr of objectDef.attributes) {
1520
+ let validator = createAttributeValidator(attr);
1521
+ if (!attr.required) {
1522
+ validator = validator.optional();
1523
+ }
1524
+ shape[attr.name] = validator;
1525
+ }
1526
+ return import_zod3.z.object(shape);
1527
+ }
1528
+ function validateObject(objectDef, data) {
1529
+ const validator = createObjectValidator(objectDef);
1530
+ const result = validator.safeParse(data);
1531
+ if (result.success) {
1532
+ return {
1533
+ success: true,
1534
+ data: result.data
1535
+ };
1536
+ }
1537
+ return {
1538
+ success: false,
1539
+ errors: result.error.errors.map((err) => ({
1540
+ path: err.path.map(String),
1541
+ message: err.message
1542
+ }))
1543
+ };
1544
+ }
1545
+ function validateObjectOrThrow(objectDef, data) {
1546
+ const result = validateObject(objectDef, data);
1547
+ if (!result.success) {
1548
+ const errorMessages = result.errors?.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n") || "Unknown validation error";
1549
+ throw new Error(`Validation failed for ${objectDef.label}:
1550
+ ${errorMessages}`);
1551
+ }
1552
+ return result.data;
1553
+ }
1554
+ function createDraftValidator(objectDef) {
1555
+ const shape = {};
1556
+ for (const attr of objectDef.attributes) {
1557
+ const validator = createAttributeValidator(attr).optional();
1558
+ shape[attr.name] = validator;
1559
+ }
1560
+ return import_zod3.z.object(shape);
1561
+ }
1562
+ function validateDraft(objectDef, data) {
1563
+ const validator = createDraftValidator(objectDef);
1564
+ const result = validator.safeParse(data);
1565
+ if (result.success) {
1566
+ return {
1567
+ success: true,
1568
+ data: result.data
1569
+ };
1570
+ }
1571
+ return {
1572
+ success: false,
1573
+ errors: result.error.errors.map((err) => ({
1574
+ path: err.path.map(String),
1575
+ message: err.message
1576
+ }))
1577
+ };
1578
+ }
1579
+ function validateDraftOrThrow(objectDef, data) {
1580
+ const result = validateDraft(objectDef, data);
1581
+ if (!result.success) {
1582
+ const errorMessages = result.errors?.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n") || "Unknown validation error";
1583
+ throw new Error(`Draft validation failed for ${objectDef.label}:
1584
+ ${errorMessages}`);
1585
+ }
1586
+ return result.data;
1587
+ }
1588
+ function isValuePresent(value) {
1589
+ if (value === void 0 || value === null) {
1590
+ return false;
1591
+ }
1592
+ if (typeof value === "string" && value.trim() === "") {
1593
+ return false;
1594
+ }
1595
+ return true;
1596
+ }
1597
+ function getMissingRequiredAttributes(objectDef, data) {
1598
+ const missing = [];
1599
+ for (const attr of objectDef.attributes) {
1600
+ if (attr.required && !isValuePresent(data[attr.name])) {
1601
+ missing.push(attr);
1602
+ }
1603
+ }
1604
+ return missing;
1605
+ }
1606
+ function isRecordComplete(objectDef, data) {
1607
+ const missing = getMissingRequiredAttributes(objectDef, data);
1608
+ if (missing.length > 0) {
1609
+ return false;
1610
+ }
1611
+ const validation = validateObject(objectDef, data);
1612
+ return validation.success;
1613
+ }
1614
+ function computeRecordStatus(objectDef, data) {
1615
+ return isRecordComplete(objectDef, data) ? "complete" : "draft";
1616
+ }
1617
+
1618
+ // src/runtime/services/object-schema.service.ts
1619
+ var ObjectSchemaService = class {
1620
+ constructor(adapter, nativeRegistry) {
1621
+ this.adapter = adapter;
1622
+ this.nativeRegistry = nativeRegistry;
1623
+ }
1624
+ /**
1625
+ * Create a new custom object
1626
+ * Validates name format and ensures system=false
1627
+ *
1628
+ * @param definition - Object definition (attributes can be raw definitions or builders)
1629
+ * @param tenantId - Tenant ID owning the object
1630
+ * @returns Created ObjectDefinition
1631
+ */
1632
+ async createCustomObject(definition, tenantId) {
1633
+ const builder = object({
1634
+ name: definition.name,
1635
+ label: definition.label
1636
+ });
1637
+ if (definition.pluralLabel) builder.pluralLabel(definition.pluralLabel);
1638
+ if (definition.description) builder.description(definition.description);
1639
+ if (definition.icon) builder.icon(definition.icon);
1640
+ if (definition.metadata) builder.metadata(definition.metadata);
1641
+ builder.labelExpression(definition.labelExpression);
1642
+ if (definition.attributes && definition.attributes.length > 0) {
1643
+ builder.attributes(definition.attributes);
1644
+ }
1645
+ const objectDef = builder.build();
1646
+ const existing = await this.adapter.objects.findByName(tenantId, objectDef.name);
1647
+ if (existing) {
1648
+ throw new Error(`Object with name "${objectDef.name}" already exists`);
1649
+ }
1650
+ const validatedConfigs = [];
1651
+ if (objectDef.attributes.length > 0) {
1652
+ for (const attr of objectDef.attributes) {
1653
+ const config = await this.validateAttributeInput(
1654
+ attr,
1655
+ tenantId
1656
+ );
1657
+ validatedConfigs.push(config);
1658
+ }
1659
+ }
1660
+ const metadata = {
1661
+ ...objectDef.metadata,
1662
+ icon: objectDef.icon
1663
+ };
1664
+ const dbObject = await this.adapter.objects.create({
1665
+ tenantId,
1666
+ name: objectDef.name,
1667
+ label: objectDef.label,
1668
+ pluralLabel: objectDef.pluralLabel,
1669
+ description: objectDef.description,
1670
+ labelExpression: objectDef.labelExpression,
1671
+ system: false,
1672
+ // Always false for custom objects
1673
+ metadata
1674
+ });
1675
+ const createdAttributes = [];
1676
+ if (objectDef.attributes.length > 0) {
1677
+ for (let i = 0; i < objectDef.attributes.length; i++) {
1678
+ const attr = objectDef.attributes[i];
1679
+ const config = validatedConfigs[i];
1680
+ const dbAttr = await this.adapter.attributes.create({
1681
+ objectId: dbObject.id,
1682
+ name: attr.name,
1683
+ label: attr.label,
1684
+ type: attr.type,
1685
+ required: attr.required ?? false,
1686
+ unique: false,
1687
+ system: false,
1688
+ config
1689
+ });
1690
+ createdAttributes.push(this.convertDBAttributeToAttribute(dbAttr));
1691
+ }
1692
+ }
1693
+ return {
1694
+ ...objectDef,
1695
+ id: dbObject.id,
1696
+ attributes: createdAttributes,
1697
+ system: false
1698
+ };
1699
+ }
1700
+ /**
1701
+ * Add a custom attribute to an existing object
1702
+ * Can be used on both custom and native objects
1703
+ * Attributes added via API are always custom (system=false)
1704
+ *
1705
+ * @param objectId - Object UUID from database
1706
+ * @param attribute - Attribute definition
1707
+ * @param tenantId - Tenant ID (required for relation validation)
1708
+ * @returns Created Attribute
1709
+ */
1710
+ async addAttributeToObject(objectId, attribute, tenantId) {
1711
+ const dbObject = await this.adapter.objects.findById(objectId);
1712
+ if (!dbObject) {
1713
+ throw new Error(`Object with id "${objectId}" not found`);
1714
+ }
1715
+ const existingAttributes = await this.adapter.attributes.findByObjectId(objectId);
1716
+ const duplicate = existingAttributes.find((attr) => attr.name === attribute.name);
1717
+ if (duplicate) {
1718
+ throw new Error(
1719
+ `Attribute with name "${attribute.name}" already exists on object "${dbObject.name}"`
1720
+ );
1721
+ }
1722
+ const config = await this.validateAttributeInput(attribute, tenantId);
1723
+ const dbAttr = await this.adapter.attributes.create({
1724
+ objectId,
1725
+ name: attribute.name,
1726
+ label: attribute.label,
1727
+ type: attribute.type,
1728
+ required: attribute.required ?? false,
1729
+ unique: attribute.unique ?? false,
1730
+ system: false,
1731
+ // Always false for attributes added via API
1732
+ config
1733
+ });
1734
+ return this.convertDBAttributeToAttribute(dbAttr);
1735
+ }
1736
+ /**
1737
+ * Update an attribute
1738
+ * Can only update custom attributes (system=false)
1739
+ *
1740
+ * @param attributeId - Attribute UUID
1741
+ * @param updates - Partial attribute updates
1742
+ * @param tenantId - Tenant ID (required for relation validation)
1743
+ * @returns Updated Attribute
1744
+ */
1745
+ async updateAttribute(attributeId, updates, tenantId) {
1746
+ const dbAttr = await this.adapter.attributes.findById(attributeId);
1747
+ if (!dbAttr) {
1748
+ throw new Error(`Attribute with id "${attributeId}" not found`);
1749
+ }
1750
+ if (dbAttr.system) {
1751
+ throw new Error(
1752
+ `Cannot modify system attribute "${dbAttr.name}". System attributes are protected.`
1753
+ );
1754
+ }
1755
+ if (updates.name && updates.name !== dbAttr.name) {
1756
+ throw new Error(
1757
+ "Attribute name cannot be changed after creation. Create a new attribute instead."
1758
+ );
1759
+ }
1760
+ if (updates.type && updates.type !== dbAttr.type) {
1761
+ throw new Error(
1762
+ "Attribute type cannot be changed after creation. Create a new attribute instead."
1763
+ );
1764
+ }
1765
+ const mergedInput = {
1766
+ name: dbAttr.name,
1767
+ label: updates.label ?? dbAttr.label,
1768
+ type: dbAttr.type,
1769
+ required: updates.required ?? dbAttr.required,
1770
+ ...dbAttr.config,
1771
+ ...updates
1772
+ };
1773
+ const config = this.buildAttributeConfig(mergedInput);
1774
+ if (dbAttr.type === "relation" && updates.targets) {
1775
+ await this.validateRelationTargets(mergedInput, tenantId);
1776
+ }
1777
+ const updatedDbAttr = await this.adapter.attributes.update(attributeId, {
1778
+ label: updates.label,
1779
+ required: updates.required,
1780
+ config
1781
+ });
1782
+ return this.convertDBAttributeToAttribute(updatedDbAttr);
1783
+ }
1784
+ /**
1785
+ * Delete an attribute
1786
+ * Can only delete custom attributes (system=false)
1787
+ *
1788
+ * @param attributeId - Attribute UUID
1789
+ */
1790
+ async deleteAttribute(attributeId) {
1791
+ const dbAttr = await this.adapter.attributes.findById(attributeId);
1792
+ if (!dbAttr) {
1793
+ throw new Error(`Attribute with id "${attributeId}" not found`);
1794
+ }
1795
+ if (dbAttr.system) {
1796
+ throw new Error(
1797
+ `Cannot delete system attribute "${dbAttr.name}". System attributes are protected.`
1798
+ );
1799
+ }
1800
+ await this.adapter.attributes.delete(attributeId);
1801
+ }
1802
+ /**
1803
+ * List attributes for an object
1804
+ *
1805
+ * @param objectId - Object UUID
1806
+ * @param options - Filter options
1807
+ * @returns List of attributes
1808
+ */
1809
+ async listAttributes(objectId, options) {
1810
+ const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
1811
+ let filtered = dbAttributes;
1812
+ if (options?.systemOnly) {
1813
+ filtered = dbAttributes.filter((attr) => attr.system);
1814
+ } else if (options?.customOnly) {
1815
+ filtered = dbAttributes.filter((attr) => !attr.system);
1816
+ }
1817
+ return filtered.map((attr) => this.convertDBAttributeToAttribute(attr));
1818
+ }
1819
+ /**
1820
+ * Update a custom object
1821
+ * Can only update custom objects (system=false)
1822
+ *
1823
+ * @param objectId - Object UUID
1824
+ * @param updates - Partial object updates
1825
+ * @returns Updated ObjectDefinition
1826
+ *
1827
+ * @example
1828
+ * ```typescript
1829
+ * const updated = await service.updateObject("obj-123", {
1830
+ * label: "Updated Label",
1831
+ * description: "New description",
1832
+ * });
1833
+ * ```
1834
+ */
1835
+ async updateObject(objectId, updates) {
1836
+ const dbObject = await this.adapter.objects.findById(objectId);
1837
+ if (!dbObject) {
1838
+ throw new Error(`Object with id "${objectId}" not found`);
1839
+ }
1840
+ if (dbObject.system) {
1841
+ throw new Error(
1842
+ `Cannot modify system object "${dbObject.name}". System objects are protected.`
1843
+ );
1844
+ }
1845
+ let metadata = dbObject.metadata;
1846
+ if (updates.icon !== void 0 || updates.metadata !== void 0) {
1847
+ metadata = {
1848
+ ...dbObject.metadata,
1849
+ ...updates.metadata,
1850
+ icon: updates.icon ?? dbObject.metadata?.icon
1851
+ };
1852
+ }
1853
+ const updatedDbObject = await this.adapter.objects.update(objectId, {
1854
+ label: updates.label,
1855
+ pluralLabel: updates.pluralLabel,
1856
+ description: updates.description,
1857
+ labelExpression: updates.labelExpression,
1858
+ metadata
1859
+ });
1860
+ const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
1861
+ return this.convertDBObjectToDefinition(updatedDbObject, dbAttributes);
1862
+ }
1863
+ /**
1864
+ * Delete a custom object
1865
+ * Can only delete custom objects (system=false)
1866
+ * This will also delete all associated attributes and records
1867
+ *
1868
+ * @param objectId - Object UUID
1869
+ *
1870
+ * @example
1871
+ * ```typescript
1872
+ * await service.deleteObject("obj-123");
1873
+ * ```
1874
+ */
1875
+ async deleteObject(objectId) {
1876
+ const dbObject = await this.adapter.objects.findById(objectId);
1877
+ if (!dbObject) {
1878
+ throw new Error(`Object with id "${objectId}" not found`);
1879
+ }
1880
+ if (dbObject.system) {
1881
+ throw new Error(
1882
+ `Cannot delete system object "${dbObject.name}". System objects are protected.`
1883
+ );
1884
+ }
1885
+ await this.adapter.objects.delete(objectId);
1886
+ }
1887
+ /**
1888
+ * Validate attribute name format (variable identifier)
1889
+ * @internal
1890
+ */
1891
+ validateAttributeName(name) {
1892
+ const variableNameRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
1893
+ if (!name || name.length === 0) {
1894
+ throw new Error("Attribute name cannot be empty");
1895
+ }
1896
+ if (name.length > 63) {
1897
+ throw new Error("Attribute name is too long (max 63 characters)");
1898
+ }
1899
+ if (!variableNameRegex.test(name)) {
1900
+ throw new Error(
1901
+ "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'"
1902
+ );
1903
+ }
1904
+ }
1905
+ /**
1906
+ * Get complete object schema (system + custom attributes)
1907
+ * If object is native, merges registry definition with DB custom attributes
1908
+ * If object is custom, returns DB definition only
1909
+ *
1910
+ * @param objectId - Object UUID from database
1911
+ * @returns Complete ObjectDefinition with all attributes
1912
+ *
1913
+ * @example
1914
+ * ```typescript
1915
+ * const service = new ObjectSchemaService(adapter, registry);
1916
+ * const productSchema = await service.getObjectSchema("obj-123");
1917
+ *
1918
+ * console.log(productSchema.attributes); // System + custom attributes
1919
+ * ```
1920
+ */
1921
+ async getObjectSchema(objectId) {
1922
+ const dbObject = await this.adapter.objects.findById(objectId);
1923
+ if (!dbObject) {
1924
+ throw new Error(`Object with id "${objectId}" not found`);
1925
+ }
1926
+ const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
1927
+ if (dbObject.system) {
1928
+ return this.mergeNativeObject(dbObject, dbAttributes);
1929
+ }
1930
+ return this.convertDBObjectToDefinition(dbObject, dbAttributes);
1931
+ }
1932
+ /**
1933
+ * Get object schema by name (for system/native objects)
1934
+ * @deprecated Use getObjectSchemaByNameForTenant for custom objects support
1935
+ */
1936
+ async getObjectSchemaByName(name) {
1937
+ const dbObject = await this.adapter.objects.findSystemByName(name);
1938
+ if (!dbObject) {
1939
+ throw new Error(`Native object "${name}" not found in database`);
1940
+ }
1941
+ const dbAttributes = await this.adapter.attributes.findByObjectId(dbObject.id);
1942
+ return this.mergeNativeObject(dbObject, dbAttributes);
1943
+ }
1944
+ /**
1945
+ * Get object schema by name for a tenant (supports both native and custom objects)
1946
+ */
1947
+ async getObjectSchemaByNameForTenant(name, tenantId) {
1948
+ let dbObject = await this.adapter.objects.findByName(tenantId, name);
1949
+ if (!dbObject) {
1950
+ dbObject = await this.adapter.objects.findSystemByName(name);
1951
+ }
1952
+ if (!dbObject) {
1953
+ throw new Error(`Object "${name}" not found`);
1954
+ }
1955
+ const dbAttributes = await this.adapter.attributes.findByObjectId(dbObject.id);
1956
+ if (dbObject.system) {
1957
+ return this.mergeNativeObject(dbObject, dbAttributes);
1958
+ }
1959
+ return this.convertDBObjectToDefinition(dbObject, dbAttributes);
1960
+ }
1961
+ /**
1962
+ * List all object schemas for a tenant
1963
+ */
1964
+ async listObjectSchemas(tenantId) {
1965
+ const dbObjects = await this.adapter.objects.list(tenantId);
1966
+ const schemas = await Promise.all(
1967
+ dbObjects.map(async (dbObject) => {
1968
+ const dbAttributes = await this.adapter.attributes.findByObjectId(dbObject.id);
1969
+ if (dbObject.system) {
1970
+ return this.mergeNativeObject(dbObject, dbAttributes);
1971
+ }
1972
+ return this.convertDBObjectToDefinition(dbObject, dbAttributes);
1973
+ })
1974
+ );
1975
+ return schemas;
1976
+ }
1977
+ /**
1978
+ * Merge native object from registry with custom attributes from DB
1979
+ * @internal
1980
+ */
1981
+ mergeNativeObject(dbObject, dbAttributes) {
1982
+ const nativeObject = this.nativeRegistry.getByName(dbObject.name);
1983
+ if (!nativeObject) {
1984
+ throw new Error(
1985
+ `Native object "${dbObject.name}" not found in registry. Make sure to register it before querying.`
1986
+ );
1987
+ }
1988
+ const systemAttributesFromDB = dbAttributes.filter((attr) => attr.system);
1989
+ const customAttributes = dbAttributes.filter((attr) => !attr.system);
1990
+ const dbAttrByName = new Map(systemAttributesFromDB.map((attr) => [attr.name, attr]));
1991
+ const systemAttrs = nativeObject.attributes.map((registryAttr) => {
1992
+ const dbAttr = dbAttrByName.get(registryAttr.name);
1993
+ if (dbAttr) {
1994
+ return {
1995
+ ...registryAttr,
1996
+ id: dbAttr.id
1997
+ };
1998
+ }
1999
+ return registryAttr;
2000
+ });
2001
+ const customAttrs = customAttributes.map(
2002
+ (dbAttr) => this.convertDBAttributeToAttribute(dbAttr)
2003
+ );
2004
+ return {
2005
+ ...nativeObject,
2006
+ id: dbObject.id,
2007
+ attributes: [
2008
+ ...systemAttrs,
2009
+ // System attributes from registry (with DB IDs if synced)
2010
+ ...customAttrs
2011
+ // Custom attributes from DB
2012
+ ]
2013
+ };
2014
+ }
2015
+ /**
2016
+ * Convert DB object + attributes to ObjectDefinition
2017
+ * @internal
2018
+ */
2019
+ convertDBObjectToDefinition(dbObject, dbAttributes) {
2020
+ const attributes = dbAttributes.map(
2021
+ (dbAttr) => this.convertDBAttributeToAttribute(dbAttr)
2022
+ );
2023
+ return {
2024
+ id: dbObject.id,
2025
+ name: dbObject.name,
2026
+ label: dbObject.label,
2027
+ pluralLabel: dbObject.pluralLabel,
2028
+ description: dbObject.description,
2029
+ icon: dbObject.metadata?.icon,
2030
+ labelExpression: dbObject.labelExpression,
2031
+ attributes,
2032
+ system: dbObject.system,
2033
+ metadata: dbObject.metadata
2034
+ };
2035
+ }
2036
+ /**
2037
+ * Validate attribute input and return parsed config
2038
+ * Validates: name format, config schema, relation targets
2039
+ * @internal
2040
+ */
2041
+ async validateAttributeInput(attribute, tenantId) {
2042
+ this.validateAttributeName(attribute.name);
2043
+ const config = this.buildAttributeConfig(attribute);
2044
+ if (attribute.type === "relation") {
2045
+ await this.validateRelationTargets(attribute, tenantId);
2046
+ }
2047
+ return config;
2048
+ }
2049
+ /**
2050
+ * Build and validate attribute config from input
2051
+ * Uses Zod schema to validate and strip unknown properties
2052
+ * @internal
2053
+ */
2054
+ buildAttributeConfig(attribute) {
2055
+ const {
2056
+ name: _,
2057
+ label: __,
2058
+ type: ___,
2059
+ required: ____,
2060
+ unique: _____,
2061
+ ...configInput
2062
+ } = attribute;
2063
+ try {
2064
+ return parseAttributeConfig(attribute.type, configInput);
2065
+ } catch (error) {
2066
+ if (error instanceof Error) {
2067
+ throw new Error(`Invalid config for ${attribute.type} attribute: ${error.message}`);
2068
+ }
2069
+ throw error;
2070
+ }
2071
+ }
2072
+ /**
2073
+ * Validate that relation target objects exist
2074
+ * @internal
2075
+ */
2076
+ async validateRelationTargets(attribute, tenantId) {
2077
+ const targets = attribute.targets;
2078
+ if (!targets || targets.length === 0) {
2079
+ throw new Error("Relation attribute must have at least one target object");
2080
+ }
2081
+ const cardinality = attribute.cardinality;
2082
+ if (!(cardinality && ["one", "many"].includes(cardinality))) {
2083
+ throw new Error("Relation attribute must specify cardinality: 'one' or 'many'");
2084
+ }
2085
+ for (const target of targets) {
2086
+ if (!target.object) {
2087
+ throw new Error("Each relation target must specify an object name");
2088
+ }
2089
+ let targetObject = tenantId ? await this.adapter.objects.findByName(tenantId, target.object) : null;
2090
+ if (!targetObject) {
2091
+ targetObject = await this.adapter.objects.findSystemByName(target.object);
2092
+ }
2093
+ if (!targetObject) {
2094
+ const nativeObject = this.nativeRegistry.getByName(target.object);
2095
+ if (!nativeObject) {
2096
+ throw new Error(
2097
+ `Relation target object "${target.object}" does not exist. Make sure the object is created before adding a relation to it.`
2098
+ );
2099
+ }
2100
+ }
2101
+ }
2102
+ }
2103
+ /**
2104
+ * Convert DB attribute to Attribute type
2105
+ * @internal
2106
+ */
2107
+ convertDBAttributeToAttribute(dbAttr) {
2108
+ const baseAttr = {
2109
+ ...dbAttr.config,
2110
+ id: dbAttr.id,
2111
+ name: dbAttr.name,
2112
+ label: dbAttr.label,
2113
+ type: dbAttr.type,
2114
+ required: dbAttr.required,
2115
+ system: dbAttr.system,
2116
+ unique: dbAttr.unique
2117
+ };
2118
+ return baseAttr;
2119
+ }
2120
+ };
2121
+
2122
+ // src/native/registry.ts
2123
+ var NativeObjectRegistryClass = class {
2124
+ constructor() {
2125
+ this.objects = /* @__PURE__ */ new Map();
2126
+ }
2127
+ /**
2128
+ * Register one or more native objects
2129
+ * Auto-builds if ObjectBuilder is passed instead of ObjectDefinition
2130
+ * @param objects - Single object/builder or array of objects/builders to register
2131
+ * @returns this (for chaining)
2132
+ * @throws Error if object is invalid or already registered
2133
+ *
2134
+ * @example
2135
+ * ```typescript
2136
+ * // No .build() needed - registry auto-builds!
2137
+ * const PRODUCT = object({ id: "obj-product", name: "products", label: "Product" })
2138
+ * .system()
2139
+ * .attribute(text({ id: "attr-name", name: "name", label: "Name" }));
2140
+ *
2141
+ * registry.register(PRODUCT); // ← Auto-builds here
2142
+ *
2143
+ * // Can still explicitly .build() if you want
2144
+ * registry.register(PRODUCT.build());
2145
+ *
2146
+ * // Register multiple
2147
+ * registry.register([PRODUCT, ORDER, CUSTOMER]);
2148
+ * ```
2149
+ */
2150
+ register(objects) {
2151
+ const objectsArray = Array.isArray(objects) ? objects : [objects];
2152
+ for (const obj of objectsArray) {
2153
+ const object2 = "build" in obj && typeof obj.build === "function" ? obj.build() : obj;
2154
+ this.validateAndRegister(object2);
2155
+ }
2156
+ return this;
2157
+ }
2158
+ /**
2159
+ * Validate and register a single object
2160
+ * @internal
2161
+ */
2162
+ validateAndRegister(object2) {
2163
+ if (!object2.system) {
2164
+ throw new Error(
2165
+ `[NativeObjectRegistry] Cannot register object "${object2.name}" (id: ${object2.id}): missing system flag.
2166
+ Native objects must have system=true. Did you forget to call .system() in your builder?`
2167
+ );
2168
+ }
2169
+ if (this.objects.has(object2.name)) {
2170
+ const existing = this.objects.get(object2.name);
2171
+ throw new Error(
2172
+ `[NativeObjectRegistry] Duplicate object name "${object2.name}":
2173
+ - Existing: "${existing?.label}" (id: ${existing?.id})
2174
+ - New: "${object2.label}" (id: ${object2.id})
2175
+ Please use unique names for each native object.`
2176
+ );
2177
+ }
2178
+ this.objects.set(object2.name, object2);
2179
+ }
2180
+ /**
2181
+ * Get a native object by its name
2182
+ * @param name - The object name
2183
+ * @returns The object definition or undefined if not found
2184
+ *
2185
+ * @example
2186
+ * ```typescript
2187
+ * const product = registry.getByName("products");
2188
+ * if (product) {
2189
+ * console.log(product.label); // "Product"
2190
+ * }
2191
+ * ```
2192
+ */
2193
+ getByName(name) {
2194
+ return this.objects.get(name);
2195
+ }
2196
+ /**
2197
+ * Get a native object or throw if not found
2198
+ * @param name - The object name
2199
+ * @returns The object definition
2200
+ * @throws Error if not found
2201
+ */
2202
+ getByNameOrThrow(name) {
2203
+ const object2 = this.objects.get(name);
2204
+ if (!object2) {
2205
+ throw new Error(
2206
+ `[NativeObjectRegistry] Native object "${name}" not found.
2207
+ Available objects: ${this.listNames().join(", ")}`
2208
+ );
2209
+ }
2210
+ return object2;
2211
+ }
2212
+ /**
2213
+ * Get all registered native objects
2214
+ * @returns Array of all registered object definitions
2215
+ */
2216
+ getAll() {
2217
+ return Array.from(this.objects.values());
2218
+ }
2219
+ /**
2220
+ * Get all registered native object names
2221
+ * @returns Array of object names
2222
+ */
2223
+ listNames() {
2224
+ return Array.from(this.objects.keys());
2225
+ }
2226
+ /**
2227
+ * Check if a native object is registered
2228
+ * @param name - The object name
2229
+ * @returns true if registered, false otherwise
2230
+ */
2231
+ has(name) {
2232
+ return this.objects.has(name);
2233
+ }
2234
+ /**
2235
+ * Get the number of registered objects
2236
+ */
2237
+ get size() {
2238
+ return this.objects.size;
2239
+ }
2240
+ /**
2241
+ * Clear all registered objects (useful for testing)
2242
+ * @internal
2243
+ */
2244
+ clear() {
2245
+ this.objects.clear();
2246
+ }
2247
+ /**
2248
+ * Get registry summary for debugging
2249
+ */
2250
+ summary() {
2251
+ const objects = this.getAll();
2252
+ return [
2253
+ `[NativeObjectRegistry] ${this.size} native object(s) registered:`,
2254
+ ...objects.map(
2255
+ (obj) => ` - ${obj.name}: "${obj.label}" (${obj.attributes.length} attributes)`
2256
+ )
2257
+ ].join("\n");
2258
+ }
2259
+ /**
2260
+ * Log registry summary to console
2261
+ */
2262
+ debug() {
2263
+ console.info(this.summary());
2264
+ }
2265
+ };
2266
+ var registry = new NativeObjectRegistryClass();
2267
+
2268
+ // src/runtime/services/relation.service.ts
2269
+ var RelationService = class {
2270
+ constructor(adapter, nativeRegistry) {
2271
+ this.adapter = adapter;
2272
+ this.schemaService = new ObjectSchemaService(adapter, nativeRegistry);
2273
+ }
2274
+ /**
2275
+ * Validate all relation attributes in the data
2276
+ *
2277
+ * @param schema - Object schema containing attribute definitions
2278
+ * @param data - Record data to validate
2279
+ * @returns Validation result with errors if any
2280
+ *
2281
+ * @example
2282
+ * ```typescript
2283
+ * const result = await relationService.validateRelations(schema, {
2284
+ * company: "rec-123",
2285
+ * contacts: ["rec-456", "rec-789"]
2286
+ * });
2287
+ *
2288
+ * if (!result.valid) {
2289
+ * console.log(result.errors);
2290
+ * // [{ attribute: "company", message: "Record not found", invalidIds: ["rec-123"] }]
2291
+ * }
2292
+ * ```
2293
+ */
2294
+ async validateRelations(schema, data) {
2295
+ const errors = [];
2296
+ const relationAttrs = schema.attributes.filter(
2297
+ (attr) => attr.type === "relation"
2298
+ );
2299
+ for (const attr of relationAttrs) {
2300
+ const value = data[attr.name];
2301
+ if (value === void 0 || value === null) {
2302
+ continue;
2303
+ }
2304
+ const attrErrors = await this.validateRelationAttribute(attr, value);
2305
+ errors.push(...attrErrors);
2306
+ }
2307
+ return {
2308
+ valid: errors.length === 0,
2309
+ errors
2310
+ };
2311
+ }
2312
+ /**
2313
+ * Validate a single relation attribute value
2314
+ */
2315
+ async validateRelationAttribute(attr, value) {
2316
+ const errors = [];
2317
+ const ids = this.extractIds(attr, value);
2318
+ if (ids.length === 0) {
2319
+ return errors;
2320
+ }
2321
+ const validObjectIds = await this.getValidObjectIds(attr.targets);
2322
+ if (validObjectIds.size === 0) {
2323
+ errors.push({
2324
+ attribute: attr.name,
2325
+ message: `No valid target objects found for ${attr.label}`
2326
+ });
2327
+ return errors;
2328
+ }
2329
+ const invalidIds = [];
2330
+ for (const id of ids) {
2331
+ const record = await this.adapter.objectRecords.findById(id);
2332
+ if (!record) {
2333
+ invalidIds.push(id);
2334
+ continue;
2335
+ }
2336
+ if (!validObjectIds.has(record.objectId)) {
2337
+ invalidIds.push(id);
2338
+ }
2339
+ }
2340
+ if (invalidIds.length > 0) {
2341
+ errors.push({
2342
+ attribute: attr.name,
2343
+ message: `Invalid or non-existent records for ${attr.label}`,
2344
+ invalidIds
2345
+ });
2346
+ }
2347
+ return errors;
2348
+ }
2349
+ /**
2350
+ * Extract IDs from relation value based on cardinality
2351
+ */
2352
+ extractIds(attr, value) {
2353
+ if (attr.cardinality === "many") {
2354
+ if (!Array.isArray(value)) {
2355
+ return [];
2356
+ }
2357
+ return value.filter((v) => typeof v === "string");
2358
+ }
2359
+ if (typeof value === "string") {
2360
+ return [value];
2361
+ }
2362
+ return [];
2363
+ }
2364
+ /**
2365
+ * Get valid object IDs from relation targets
2366
+ */
2367
+ async getValidObjectIds(targets) {
2368
+ const objectIds = /* @__PURE__ */ new Set();
2369
+ for (const target of targets) {
2370
+ try {
2371
+ const objectSchema = await this.schemaService.getObjectSchemaByName(target.object);
2372
+ if (objectSchema?.id) {
2373
+ objectIds.add(objectSchema.id);
2374
+ }
2375
+ } catch {
2376
+ }
2377
+ }
2378
+ return objectIds;
2379
+ }
2380
+ /**
2381
+ * Validate relations and throw if invalid
2382
+ */
2383
+ async validateRelationsOrThrow(schema, data) {
2384
+ const result = await this.validateRelations(schema, data);
2385
+ if (!result.valid) {
2386
+ const messages = result.errors.map((e) => `${e.attribute}: ${e.message}`).join("; ");
2387
+ throw new Error(`Relation validation failed: ${messages}`);
2388
+ }
2389
+ }
2390
+ // ============================================================================
2391
+ // OPTIONS & RESOLUTION
2392
+ // ============================================================================
2393
+ /**
2394
+ * Get available options for a relation attribute
2395
+ * Searches across all target objects defined in the relation
2396
+ *
2397
+ * @param attribute - Relation attribute definition
2398
+ * @param tenantId - Tenant ID for multi-tenant isolation
2399
+ * @param params - Query parameters
2400
+ *
2401
+ * @example
2402
+ * ```typescript
2403
+ * const options = await relationService.getOptions(attribute, "tenant-1", {
2404
+ * query: "nike",
2405
+ * page: 1,
2406
+ * pageSize: 20
2407
+ * });
2408
+ * ```
2409
+ */
2410
+ async getOptions(attribute, tenantId, params = {}) {
2411
+ const { query = "", page = 1, pageSize = 20, targetObject } = params;
2412
+ const targets = attribute.targets;
2413
+ const filteredTargets = targetObject ? targets.filter((t) => t.object === targetObject) : targets;
2414
+ if (filteredTargets.length === 0) {
2415
+ return { options: [], hasMore: false, total: 0 };
2416
+ }
2417
+ const allOptions = [];
2418
+ let totalCount = 0;
2419
+ const recordService = new RecordService(this.adapter, tenantId);
2420
+ for (const target of filteredTargets) {
2421
+ const objectSchema = await this.schemaService.getObjectSchemaByName(target.object);
2422
+ if (!objectSchema?.id) {
2423
+ continue;
2424
+ }
2425
+ const result = query ? await recordService.searchRecords(objectSchema.id, query, {
2426
+ limit: pageSize,
2427
+ offset: (page - 1) * pageSize
2428
+ }) : await recordService.listRecords(objectSchema.id, {
2429
+ limit: pageSize,
2430
+ offset: (page - 1) * pageSize
2431
+ });
2432
+ totalCount += result.total;
2433
+ for (const record of result.records) {
2434
+ const template = target.displayTemplate || objectSchema.labelExpression;
2435
+ const label = renderLabelExpression(template, record.values);
2436
+ allOptions.push({
2437
+ id: record.id,
2438
+ objectId: objectSchema.id,
2439
+ objectName: objectSchema.name,
2440
+ objectLabel: objectSchema.label,
2441
+ objectIcon: objectSchema.icon,
2442
+ label,
2443
+ data: record.values
2444
+ });
2445
+ }
2446
+ }
2447
+ const hasMore = totalCount > page * pageSize;
2448
+ return {
2449
+ options: allOptions.slice(0, pageSize),
2450
+ hasMore,
2451
+ total: totalCount
2452
+ };
2453
+ }
2454
+ /**
2455
+ * Resolve record IDs to their display labels
2456
+ * Useful for displaying current values in the UI
2457
+ *
2458
+ * @param ids - Record IDs to resolve
2459
+ * @param tenantId - Tenant ID for multi-tenant isolation
2460
+ *
2461
+ * @example
2462
+ * ```typescript
2463
+ * const resolved = await relationService.resolveIds(["rec-1", "rec-2"], "tenant-1");
2464
+ * // [{ id: "rec-1", label: "Nike Air Max", objectName: "products", ... }]
2465
+ * ```
2466
+ */
2467
+ async resolveIds(ids, tenantId) {
2468
+ if (!ids || ids.length === 0) {
2469
+ return [];
2470
+ }
2471
+ const recordService = new RecordService(this.adapter, tenantId);
2472
+ const resolved = [];
2473
+ for (const id of ids) {
2474
+ const record = await recordService.getRecord(id);
2475
+ if (!record) {
2476
+ continue;
2477
+ }
2478
+ const objectSchema = await this.schemaService.getObjectSchema(record.objectId);
2479
+ if (!objectSchema) {
2480
+ continue;
2481
+ }
2482
+ const label = renderLabelExpression(objectSchema.labelExpression, record.values);
2483
+ resolved.push({
2484
+ id: record.id,
2485
+ objectId: record.objectId,
2486
+ objectName: objectSchema.name,
2487
+ objectLabel: objectSchema.label,
2488
+ objectIcon: objectSchema.icon,
2489
+ label,
2490
+ data: record.values
2491
+ });
2492
+ }
2493
+ return resolved;
2494
+ }
2495
+ /**
2496
+ * Find a relation attribute by ID
2497
+ */
2498
+ async findAttributeById(attributeId) {
2499
+ const attribute = await this.adapter.attributes.findById(attributeId);
2500
+ if (!attribute || attribute.type !== "relation") {
2501
+ return null;
2502
+ }
2503
+ return {
2504
+ ...attribute.config,
2505
+ ...attribute
2506
+ };
2507
+ }
2508
+ };
2509
+
2510
+ // src/runtime/services/record.service.ts
2511
+ var RecordService = class {
2512
+ constructor(adapter, tenantId, options) {
2513
+ this.adapter = adapter;
2514
+ this.tenantId = tenantId;
2515
+ this.schemaService = new ObjectSchemaService(adapter, registry);
2516
+ this.relationService = new RelationService(adapter, registry);
2517
+ this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
2518
+ }
2519
+ /**
2520
+ * Compute display label from schema expression
2521
+ * Automatically resolves relation attribute values to their labels
2522
+ * @internal
2523
+ */
2524
+ async computeLabel(schema, values) {
2525
+ const attrNames = extractAttributeNames(schema.labelExpression);
2526
+ const relationAttrs = schema.attributes.filter(
2527
+ (attr) => attr.type === "relation" && attrNames.includes(attr.name)
2528
+ );
2529
+ if (relationAttrs.length === 0) {
2530
+ return renderLabelExpression(schema.labelExpression, values);
2531
+ }
2532
+ const idsToResolve = [];
2533
+ for (const attr of relationAttrs) {
2534
+ const val = values[attr.name];
2535
+ if (typeof val === "string") {
2536
+ idsToResolve.push(val);
2537
+ } else if (Array.isArray(val) && typeof val[0] === "string") {
2538
+ idsToResolve.push(val[0]);
2539
+ }
2540
+ }
2541
+ if (idsToResolve.length === 0) {
2542
+ return renderLabelExpression(schema.labelExpression, values);
2543
+ }
2544
+ const resolved = await this.relationService.resolveIds(idsToResolve, this.tenantId);
2545
+ const resolvedMap = new Map(resolved.map((r) => [r.id, r.label]));
2546
+ const enrichedValues = { ...values };
2547
+ for (const attr of relationAttrs) {
2548
+ const val = values[attr.name];
2549
+ if (typeof val === "string" && resolvedMap.has(val)) {
2550
+ enrichedValues[attr.name] = resolvedMap.get(val);
2551
+ } else if (Array.isArray(val) && typeof val[0] === "string" && resolvedMap.has(val[0])) {
2552
+ enrichedValues[attr.name] = resolvedMap.get(val[0]);
2553
+ }
2554
+ }
2555
+ return renderLabelExpression(schema.labelExpression, enrichedValues);
2556
+ }
2557
+ /**
2558
+ * Create a new record with validation
2559
+ *
2560
+ * Triggers beforeCreate and afterCreate hooks if a HookRegistry is configured.
2561
+ *
2562
+ * @param objectId - Object UUID
2563
+ * @param data - Record data (attribute values)
2564
+ * @param options - Creation options
2565
+ * @returns Created record with computed completionStatus
2566
+ *
2567
+ * @example
2568
+ * ```typescript
2569
+ * const service = new RecordService(adapter, "tenant-123");
2570
+ *
2571
+ * // Create a complete record (strict validation)
2572
+ * const product = await service.createRecord("obj-product", {
2573
+ * name: "Nike Air Max",
2574
+ * price: 129.99,
2575
+ * status: "active"
2576
+ * });
2577
+ * // → product.completionStatus = "complete"
2578
+ *
2579
+ * // Create a draft record (allows missing required fields)
2580
+ * const draft = await service.createRecord("obj-product", {
2581
+ * name: "Draft Product"
2582
+ * }, { allowDraft: true });
2583
+ * // → draft.completionStatus = "draft"
2584
+ * ```
2585
+ */
2586
+ async createRecord(objectId, data, options) {
2587
+ const schema = await this.schemaService.getObjectSchema(objectId);
2588
+ const hookCtx = this.buildCreateHookContext(schema, data, options?.hookMetadata);
2589
+ if (!options?.skipHooks) {
2590
+ await this.hookRegistry.execute("beforeCreate", schema.name, hookCtx);
2591
+ }
2592
+ if (options?.validate !== false) {
2593
+ if (options?.allowDraft) {
2594
+ validateDraftOrThrow(schema, data);
2595
+ } else {
2596
+ validateObjectOrThrow(schema, data);
2597
+ }
2598
+ if (!options?.skipRelationValidation) {
2599
+ await this.relationService.validateRelationsOrThrow(schema, data);
2600
+ }
2601
+ }
2602
+ const completionStatus = computeRecordStatus(schema, data);
2603
+ const label = await this.computeLabel(schema, data);
2604
+ const record = await this.adapter.objectRecords.create({
2605
+ tenantId: this.tenantId,
2606
+ objectId,
2607
+ data,
2608
+ label,
2609
+ completionStatus
2610
+ });
2611
+ if (!options?.skipHooks) {
2612
+ const afterCtx = {
2613
+ ...hookCtx,
2614
+ recordId: record.id,
2615
+ record
2616
+ };
2617
+ await this.hookRegistry.execute("afterCreate", schema.name, afterCtx);
2618
+ }
2619
+ return record;
2620
+ }
2621
+ /**
2622
+ * Get a record by ID
2623
+ *
2624
+ * @param recordId - Record UUID
2625
+ * @param options - Query options
2626
+ * @returns Record or null if not found
2627
+ */
2628
+ async getRecord(recordId, options) {
2629
+ const record = await this.adapter.objectRecords.findById(recordId);
2630
+ if (!record) {
2631
+ return null;
2632
+ }
2633
+ if (options?.includeSchema) {
2634
+ const schema = await this.schemaService.getObjectSchema(record.objectId);
2635
+ const recordWithSchema = record;
2636
+ recordWithSchema.schema = schema;
2637
+ return recordWithSchema;
2638
+ }
2639
+ return record;
2640
+ }
2641
+ /**
2642
+ * Get a record by ID or throw if not found
2643
+ */
2644
+ async getRecordOrThrow(recordId) {
2645
+ const record = await this.getRecord(recordId);
2646
+ if (!record) {
2647
+ throw new Error(`Record with id "${recordId}" not found`);
2648
+ }
2649
+ return record;
2650
+ }
2651
+ /**
2652
+ * Update a record with validation
2653
+ *
2654
+ * The completion status is automatically recalculated after each update.
2655
+ * A draft record becomes complete when all required fields are filled.
2656
+ *
2657
+ * Triggers beforeUpdate and afterUpdate hooks if a HookRegistry is configured.
2658
+ *
2659
+ * @param recordId - Record UUID
2660
+ * @param data - Partial data to update
2661
+ * @param options - Update options
2662
+ * @returns Updated record with recalculated completionStatus
2663
+ *
2664
+ * @example
2665
+ * ```typescript
2666
+ * // Update a draft record to make it complete
2667
+ * const updated = await service.updateRecord(draftId, {
2668
+ * price: 99.99,
2669
+ * status: "active"
2670
+ * });
2671
+ * // → updated.completionStatus = "complete" if all required fields now present
2672
+ * ```
2673
+ */
2674
+ async updateRecord(recordId, data, options) {
2675
+ const existing = await this.getRecordOrThrow(recordId);
2676
+ const schema = await this.schemaService.getObjectSchema(existing.objectId);
2677
+ const mergedData = { ...existing.values, ...data };
2678
+ const changedAttributes = Object.keys(data).filter((key) => existing.values[key] !== data[key]);
2679
+ const hookCtx = this.buildHookContext(
2680
+ schema,
2681
+ existing,
2682
+ data,
2683
+ mergedData,
2684
+ changedAttributes,
2685
+ options?.hookMetadata
2686
+ );
2687
+ if (!options?.skipHooks) {
2688
+ await this.hookRegistry.execute("beforeUpdate", schema.name, hookCtx);
2689
+ }
2690
+ if (options?.validate !== false) {
2691
+ if (options?.partial) {
2692
+ validateDraftOrThrow(schema, mergedData);
2693
+ } else {
2694
+ validateObjectOrThrow(schema, mergedData);
2695
+ }
2696
+ if (!options?.skipRelationValidation) {
2697
+ await this.relationService.validateRelationsOrThrow(schema, data);
2698
+ }
2699
+ }
2700
+ const completionStatus = computeRecordStatus(schema, mergedData);
2701
+ const label = await this.computeLabel(schema, mergedData);
2702
+ const updated = await this.adapter.objectRecords.update(recordId, {
2703
+ ...data,
2704
+ __completionStatus: completionStatus,
2705
+ __label: label
2706
+ });
2707
+ if (!options?.skipHooks) {
2708
+ const afterCtx = {
2709
+ ...hookCtx,
2710
+ record: updated
2711
+ };
2712
+ await this.hookRegistry.execute("afterUpdate", schema.name, afterCtx);
2713
+ }
2714
+ return updated;
2715
+ }
2716
+ /**
2717
+ * Build hook context for update operations
2718
+ * @internal
2719
+ */
2720
+ buildHookContext(schema, existing, newData, mergedData, changedAttributes, metadata) {
2721
+ const oldValuesSnapshot = { ...existing.values };
2722
+ return {
2723
+ objectId: schema.id,
2724
+ objectName: schema.name,
2725
+ recordId: existing.id,
2726
+ tenantId: this.tenantId,
2727
+ record: existing,
2728
+ oldValues: oldValuesSnapshot,
2729
+ newValues: mergedData,
2730
+ changedAttributes,
2731
+ getChange: (attr) => ({
2732
+ oldValue: oldValuesSnapshot[attr],
2733
+ newValue: newData[attr] ?? oldValuesSnapshot[attr],
2734
+ changed: changedAttributes.includes(attr)
2735
+ }),
2736
+ metadata: metadata ?? {},
2737
+ timestamp: /* @__PURE__ */ new Date()
2738
+ };
2739
+ }
2740
+ /**
2741
+ * Build hook context for create operations (no existing record)
2742
+ * @internal
2743
+ */
2744
+ buildCreateHookContext(schema, data, metadata) {
2745
+ const attributeNames = Object.keys(data);
2746
+ return {
2747
+ objectId: schema.id,
2748
+ objectName: schema.name,
2749
+ recordId: "",
2750
+ // Will be set after creation
2751
+ tenantId: this.tenantId,
2752
+ record: null,
2753
+ // Will be set after creation
2754
+ oldValues: {},
2755
+ newValues: data,
2756
+ changedAttributes: attributeNames,
2757
+ // All attributes are "new"
2758
+ getChange: (attr) => ({
2759
+ oldValue: void 0,
2760
+ newValue: data[attr],
2761
+ changed: attributeNames.includes(attr)
2762
+ }),
2763
+ metadata: metadata ?? {},
2764
+ timestamp: /* @__PURE__ */ new Date()
2765
+ };
2766
+ }
2767
+ /**
2768
+ * Build hook context for delete operations
2769
+ * @internal
2770
+ */
2771
+ buildDeleteHookContext(schema, record, metadata) {
2772
+ const attributeNames = Object.keys(record.values);
2773
+ return {
2774
+ objectId: schema.id,
2775
+ objectName: schema.name,
2776
+ recordId: record.id,
2777
+ tenantId: this.tenantId,
2778
+ record,
2779
+ oldValues: record.values,
2780
+ newValues: {},
2781
+ changedAttributes: attributeNames,
2782
+ // All attributes are being "removed"
2783
+ getChange: (attr) => ({
2784
+ oldValue: record.values[attr],
2785
+ newValue: void 0,
2786
+ changed: attributeNames.includes(attr)
2787
+ }),
2788
+ metadata: metadata ?? {},
2789
+ timestamp: /* @__PURE__ */ new Date()
2790
+ };
2791
+ }
2792
+ /**
2793
+ * Delete a record
2794
+ *
2795
+ * Triggers beforeDelete and afterDelete hooks if a HookRegistry is configured.
2796
+ *
2797
+ * @param recordId - Record UUID
2798
+ * @param options - Delete options
2799
+ */
2800
+ async deleteRecord(recordId, options) {
2801
+ const record = await this.getRecordOrThrow(recordId);
2802
+ const schema = await this.schemaService.getObjectSchema(record.objectId);
2803
+ if (options?.checkSystem) {
2804
+ if (schema.system) {
2805
+ throw new Error(`Cannot delete record of system object "${schema.label}"`);
2806
+ }
2807
+ }
2808
+ const hookCtx = this.buildDeleteHookContext(schema, record, options?.hookMetadata);
2809
+ if (!options?.skipHooks) {
2810
+ await this.hookRegistry.execute("beforeDelete", schema.name, hookCtx);
2811
+ }
2812
+ await this.adapter.objectRecords.delete(recordId);
2813
+ if (!options?.skipHooks) {
2814
+ await this.hookRegistry.execute("afterDelete", schema.name, hookCtx);
2815
+ }
2816
+ }
2817
+ /**
2818
+ * List records for an object with pagination
2819
+ *
2820
+ * @param objectId - Object UUID
2821
+ * @param options - List options
2822
+ * @returns Records and total count
2823
+ */
2824
+ async listRecords(objectId, options) {
2825
+ return await this.adapter.objectRecords.list(this.tenantId, objectId, options);
2826
+ }
2827
+ /**
2828
+ * Search records using full-text search
2829
+ *
2830
+ * @param objectId - Object UUID
2831
+ * @param query - Search query
2832
+ * @param options - Search options
2833
+ * @returns Matching records and total count
2834
+ */
2835
+ async searchRecords(objectId, query, options) {
2836
+ return await this.adapter.objectRecords.search(this.tenantId, objectId, query, options);
2837
+ }
2838
+ /**
2839
+ * Validate data against object schema without saving
2840
+ *
2841
+ * @param objectId - Object UUID
2842
+ * @param data - Data to validate
2843
+ * @returns Validation result
2844
+ */
2845
+ async validateData(objectId, data) {
2846
+ const schema = await this.schemaService.getObjectSchema(objectId);
2847
+ return validateObject(schema, data);
2848
+ }
2849
+ /**
2850
+ * Compute the completion status for given data without saving.
2851
+ * Useful for UI to show draft/complete status before submitting.
2852
+ *
2853
+ * @param objectId - Object UUID
2854
+ * @param data - Data to check
2855
+ * @returns Computed completion status
2856
+ */
2857
+ async computeStatus(objectId, data) {
2858
+ const schema = await this.schemaService.getObjectSchema(objectId);
2859
+ return computeRecordStatus(schema, data);
2860
+ }
2861
+ /**
2862
+ * Refresh the completion status of an existing record.
2863
+ * Useful when schema changes and you need to recompute statuses.
2864
+ *
2865
+ * @param recordId - Record UUID
2866
+ * @returns Updated completion status
2867
+ */
2868
+ async refreshRecordStatus(recordId) {
2869
+ const record = await this.getRecordOrThrow(recordId);
2870
+ const schema = await this.schemaService.getObjectSchema(record.objectId);
2871
+ const newStatus = computeRecordStatus(schema, record.values);
2872
+ if (record.completionStatus !== newStatus) {
2873
+ await this.adapter.objectRecords.update(recordId, {
2874
+ __completionStatus: newStatus
2875
+ });
2876
+ }
2877
+ return newStatus;
2878
+ }
2879
+ };
2880
+
2881
+ // src/runtime/services/user-profile.service.ts
2882
+ var UserProfileService = class {
2883
+ constructor(adapter, tenantId) {
2884
+ this.adapter = adapter;
2885
+ this.tenantId = tenantId;
2886
+ }
2887
+ /**
2888
+ * Create a new user profile (typically after first auth)
2889
+ *
2890
+ * @param data - User profile creation data
2891
+ * @returns Created user profile
2892
+ *
2893
+ * @example
2894
+ * ```typescript
2895
+ * const service = new UserProfileService(adapter, "tenant-123");
2896
+ *
2897
+ * // After Supabase auth
2898
+ * const profile = await service.createProfile({
2899
+ * tenantId: "tenant-123",
2900
+ * authId: authUser.id,
2901
+ * email: authUser.email,
2902
+ * firstName: authUser.user_metadata.first_name,
2903
+ * lastName: authUser.user_metadata.last_name,
2904
+ * role: "member",
2905
+ * status: "active"
2906
+ * });
2907
+ * ```
2908
+ */
2909
+ async createProfile(data) {
2910
+ if (data.tenantId !== this.tenantId) {
2911
+ throw new Error(
2912
+ `Tenant mismatch: service initialized with "${this.tenantId}" but data has "${data.tenantId}"`
2913
+ );
2914
+ }
2915
+ const existing = await this.adapter.userProfiles.findByAuthId(data.authId);
2916
+ if (existing) {
2917
+ throw new Error(`User profile with authId "${data.authId}" already exists`);
2918
+ }
2919
+ const existingEmail = await this.adapter.userProfiles.findByEmail(this.tenantId, data.email);
2920
+ if (existingEmail) {
2921
+ throw new Error(`User profile with email "${data.email}" already exists in this tenant`);
2922
+ }
2923
+ return await this.adapter.userProfiles.create(data);
2924
+ }
2925
+ /**
2926
+ * Get user profile by ID
2927
+ */
2928
+ async getProfile(profileId) {
2929
+ return await this.adapter.userProfiles.findById(profileId);
2930
+ }
2931
+ /**
2932
+ * Get user profile by ID or throw
2933
+ */
2934
+ async getProfileOrThrow(profileId) {
2935
+ const profile = await this.getProfile(profileId);
2936
+ if (!profile) {
2937
+ throw new Error(`User profile with id "${profileId}" not found`);
2938
+ }
2939
+ return profile;
2940
+ }
2941
+ /**
2942
+ * Get user profile by auth provider ID
2943
+ *
2944
+ * @param authId - Auth provider user ID (Supabase, Clerk, etc.)
2945
+ * @returns User profile or null
2946
+ */
2947
+ async getProfileByAuthId(authId) {
2948
+ return await this.adapter.userProfiles.findByAuthId(authId);
2949
+ }
2950
+ /**
2951
+ * Get or create user profile (idempotent operation)
2952
+ * Useful for auth callbacks - ensures profile exists
2953
+ *
2954
+ * @param authId - Auth provider user ID
2955
+ * @param data - Profile data to create if doesn't exist
2956
+ * @returns Existing or newly created profile
2957
+ *
2958
+ * @example
2959
+ * ```typescript
2960
+ * // In Supabase auth callback
2961
+ * const profile = await service.getOrCreateProfile(
2962
+ * authUser.id,
2963
+ * {
2964
+ * tenantId: "tenant-123",
2965
+ * authId: authUser.id,
2966
+ * email: authUser.email,
2967
+ * role: "member",
2968
+ * status: "active"
2969
+ * }
2970
+ * );
2971
+ * ```
2972
+ */
2973
+ async getOrCreateProfile(authId, data) {
2974
+ const existing = await this.getProfileByAuthId(authId);
2975
+ if (existing) {
2976
+ return existing;
2977
+ }
2978
+ return await this.createProfile(data);
2979
+ }
2980
+ /**
2981
+ * Update user profile
2982
+ */
2983
+ async updateProfile(profileId, data) {
2984
+ await this.getProfileOrThrow(profileId);
2985
+ return await this.adapter.userProfiles.update(profileId, data);
2986
+ }
2987
+ /**
2988
+ * Delete user profile
2989
+ *
2990
+ * @param profileId - Profile UUID
2991
+ * @param options - Delete options
2992
+ */
2993
+ async deleteProfile(profileId, options) {
2994
+ if (options?.checkAdmin) {
2995
+ const profile = await this.getProfileOrThrow(profileId);
2996
+ if (profile.role === "admin") {
2997
+ const allProfiles = await this.listProfiles();
2998
+ const adminCount = allProfiles.filter((p) => p.role === "admin").length;
2999
+ if (adminCount <= 1) {
3000
+ throw new Error("Cannot delete the last admin user");
3001
+ }
3002
+ }
3003
+ }
3004
+ await this.adapter.userProfiles.delete(profileId);
3005
+ }
3006
+ /**
3007
+ * List all user profiles for the tenant
3008
+ */
3009
+ async listProfiles(options) {
3010
+ return await this.adapter.userProfiles.list(this.tenantId, options);
3011
+ }
3012
+ /**
3013
+ * Update last login timestamp
3014
+ *
3015
+ * @param profileId - Profile UUID
3016
+ *
3017
+ * @example
3018
+ * ```typescript
3019
+ * // After successful auth
3020
+ * await service.updateLastLogin(profile.id);
3021
+ * ```
3022
+ */
3023
+ async updateLastLogin(profileId) {
3024
+ await this.adapter.userProfiles.updateLastLogin(profileId);
3025
+ }
3026
+ /**
3027
+ * Change user role
3028
+ *
3029
+ * @param profileId - Profile UUID
3030
+ * @param newRole - New role
3031
+ */
3032
+ async changeRole(profileId, newRole) {
3033
+ return await this.updateProfile(profileId, { role: newRole });
3034
+ }
3035
+ /**
3036
+ * Change user status
3037
+ *
3038
+ * @param profileId - Profile UUID
3039
+ * @param newStatus - New status
3040
+ */
3041
+ async changeStatus(profileId, newStatus) {
3042
+ return await this.updateProfile(profileId, { status: newStatus });
3043
+ }
3044
+ /**
3045
+ * Get user by email
3046
+ */
3047
+ async getProfileByEmail(email) {
3048
+ return await this.adapter.userProfiles.findByEmail(this.tenantId, email);
3049
+ }
3050
+ /**
3051
+ * Check if user has role
3052
+ */
3053
+ async hasRole(profileId, role) {
3054
+ const profile = await this.getProfile(profileId);
3055
+ return profile?.role === role;
3056
+ }
3057
+ /**
3058
+ * Check if user is admin
3059
+ */
3060
+ async isAdmin(profileId) {
3061
+ return await this.hasRole(profileId, "admin");
3062
+ }
3063
+ };
3064
+
3065
+ // src/runtime/services/view.service.ts
3066
+ var ViewService = class {
3067
+ constructor(adapter, nativeViews) {
3068
+ this.adapter = adapter;
3069
+ this.nativeViews = nativeViews;
3070
+ }
3071
+ /**
3072
+ * Get all views for an object (native + custom)
3073
+ *
3074
+ * @param objectName - Object name
3075
+ * @param tenantId - Tenant ID
3076
+ * @returns All views for the object
3077
+ */
3078
+ async getViewsForObject(objectName, tenantId) {
3079
+ const nativeViewDefs = this.nativeViews.getByObjectName(objectName);
3080
+ const dbViews = await this.adapter.views.findByObjectName(tenantId, objectName);
3081
+ const customViews = dbViews.filter((v) => !v.system).map(this.convertDBViewToDefinition);
3082
+ return [...nativeViewDefs, ...customViews];
3083
+ }
3084
+ /**
3085
+ * Get a specific view by name
3086
+ *
3087
+ * @param objectName - Object name
3088
+ * @param viewName - View name
3089
+ * @param tenantId - Tenant ID
3090
+ * @returns View definition or null
3091
+ */
3092
+ async getView(objectName, viewName, tenantId) {
3093
+ const nativeView = this.nativeViews.get(objectName, viewName);
3094
+ if (nativeView) {
3095
+ return nativeView;
3096
+ }
3097
+ const dbView = await this.adapter.views.findByName(tenantId, objectName, viewName);
3098
+ if (dbView) {
3099
+ return this.convertDBViewToDefinition(dbView);
3100
+ }
3101
+ return null;
3102
+ }
3103
+ /**
3104
+ * Get the default view for an object
3105
+ *
3106
+ * Priority:
3107
+ * 1. Custom view marked as default
3108
+ * 2. Native view marked as default
3109
+ * 3. First available view
3110
+ *
3111
+ * @param objectName - Object name
3112
+ * @param tenantId - Tenant ID
3113
+ * @returns Default view or null
3114
+ */
3115
+ async getDefaultView(objectName, tenantId) {
3116
+ const views = await this.getViewsForObject(objectName, tenantId);
3117
+ const customDefault = views.find((v) => v.default && !v.system);
3118
+ if (customDefault) return customDefault;
3119
+ const nativeDefault = views.find((v) => v.default && v.system);
3120
+ if (nativeDefault) return nativeDefault;
3121
+ return views[0] ?? null;
3122
+ }
3123
+ /**
3124
+ * Create a custom view
3125
+ *
3126
+ * @param input - View definition
3127
+ * @param tenantId - Tenant ID
3128
+ * @returns Created view
3129
+ */
3130
+ async createView(input, tenantId) {
3131
+ this.validateViewName(input.name);
3132
+ const existing = await this.adapter.views.findByName(tenantId, input.objectName, input.name);
3133
+ if (existing) {
3134
+ throw new Error(`View "${input.name}" already exists for object "${input.objectName}"`);
3135
+ }
3136
+ if (this.nativeViews.has(input.objectName, input.name)) {
3137
+ throw new Error(
3138
+ `Cannot create view "${input.name}": a system view with this name already exists`
3139
+ );
3140
+ }
3141
+ if (!input.tabs || input.tabs.length === 0) {
3142
+ throw new Error("View must have at least one tab");
3143
+ }
3144
+ const dbView = await this.adapter.views.create({
3145
+ tenantId,
3146
+ objectName: input.objectName,
3147
+ name: input.name,
3148
+ label: input.label,
3149
+ description: input.description,
3150
+ icon: input.icon,
3151
+ tabs: input.tabs,
3152
+ default: input.default ?? false,
3153
+ system: false,
3154
+ // Custom views are never system
3155
+ metadata: input.metadata
3156
+ });
3157
+ return this.convertDBViewToDefinition(dbView);
3158
+ }
3159
+ /**
3160
+ * Update a custom view
3161
+ *
3162
+ * @param viewId - View ID
3163
+ * @param input - Update data
3164
+ * @returns Updated view
3165
+ */
3166
+ async updateView(viewId, input) {
3167
+ const dbView = await this.adapter.views.findById(viewId);
3168
+ if (!dbView) {
3169
+ throw new Error(`View with id "${viewId}" not found`);
3170
+ }
3171
+ if (dbView.system) {
3172
+ throw new Error("Cannot modify system views. System views are protected.");
3173
+ }
3174
+ if (input.tabs && input.tabs.length === 0) {
3175
+ throw new Error("View must have at least one tab");
3176
+ }
3177
+ const updated = await this.adapter.views.update(viewId, {
3178
+ label: input.label,
3179
+ description: input.description,
3180
+ icon: input.icon,
3181
+ tabs: input.tabs,
3182
+ default: input.default,
3183
+ metadata: input.metadata
3184
+ });
3185
+ return this.convertDBViewToDefinition(updated);
3186
+ }
3187
+ /**
3188
+ * Delete a custom view
3189
+ *
3190
+ * @param viewId - View ID
3191
+ */
3192
+ async deleteView(viewId) {
3193
+ const dbView = await this.adapter.views.findById(viewId);
3194
+ if (!dbView) {
3195
+ throw new Error(`View with id "${viewId}" not found`);
3196
+ }
3197
+ if (dbView.system) {
3198
+ throw new Error("Cannot delete system views. System views are protected.");
3199
+ }
3200
+ await this.adapter.views.delete(viewId);
3201
+ }
3202
+ /**
3203
+ * Set a view as default for its object
3204
+ *
3205
+ * @param viewId - View ID
3206
+ * @param tenantId - Tenant ID
3207
+ * @returns Updated view
3208
+ */
3209
+ async setDefaultView(viewId, tenantId) {
3210
+ const dbView = await this.adapter.views.findById(viewId);
3211
+ if (!dbView) {
3212
+ throw new Error(`View with id "${viewId}" not found`);
3213
+ }
3214
+ const currentViews = await this.adapter.views.findByObjectName(tenantId, dbView.objectName);
3215
+ for (const v of currentViews) {
3216
+ if (v.default && v.id !== viewId && !v.system) {
3217
+ await this.adapter.views.update(v.id, { default: false });
3218
+ }
3219
+ }
3220
+ const updated = await this.adapter.views.update(viewId, { default: true });
3221
+ return this.convertDBViewToDefinition(updated);
3222
+ }
3223
+ // ============================================================================
3224
+ // PRIVATE HELPERS
3225
+ // ============================================================================
3226
+ /**
3227
+ * Validate view name format (kebab-case)
3228
+ */
3229
+ validateViewName(name) {
3230
+ if (!name || name.length === 0) {
3231
+ throw new Error("View name cannot be empty");
3232
+ }
3233
+ if (name.length > 63) {
3234
+ throw new Error("View name is too long (max 63 characters)");
3235
+ }
3236
+ const kebabCaseRegex = /^[a-z][a-z0-9-]*$/;
3237
+ if (!kebabCaseRegex.test(name)) {
3238
+ throw new Error(
3239
+ "Invalid view name format. Name must be in kebab-case (e.g., 'detail', 'list-view')"
3240
+ );
3241
+ }
3242
+ }
3243
+ /**
3244
+ * Convert database view to ViewDefinition
3245
+ */
3246
+ convertDBViewToDefinition(dbView) {
3247
+ return {
3248
+ id: dbView.id,
3249
+ name: dbView.name,
3250
+ label: dbView.label,
3251
+ description: dbView.description,
3252
+ icon: dbView.icon,
3253
+ object: dbView.objectName,
3254
+ tabs: dbView.tabs,
3255
+ default: dbView.default,
3256
+ system: dbView.system,
3257
+ metadata: dbView.metadata
3258
+ };
3259
+ }
3260
+ };
3261
+
3262
+ // src/runtime/view-sync.ts
3263
+ async function syncNativeViews(adapter, nativeViewRegistry, options = {}) {
3264
+ const result = {
3265
+ success: true,
3266
+ viewsSynced: 0,
3267
+ viewsCreated: 0,
3268
+ viewsUpdated: 0,
3269
+ viewsDeleted: 0,
3270
+ errors: []
3271
+ };
3272
+ const nativeViews = nativeViewRegistry.getAll();
3273
+ if (options.verbose) {
3274
+ console.info(`[ViewSync] Starting sync for ${nativeViews.length} native views...`);
3275
+ }
3276
+ try {
3277
+ await adapter.transaction(async (tx) => {
3278
+ const viewsByObject = await syncAllViews(tx, nativeViews, result, options);
3279
+ await cleanupRemovedViews(tx, viewsByObject, result, options);
3280
+ });
3281
+ } catch (error) {
3282
+ handleTransactionError(result, error);
3283
+ }
3284
+ logSyncComplete(result, options);
3285
+ return result;
3286
+ }
3287
+ async function syncAllViews(tx, nativeViews, result, options) {
3288
+ const viewsByObject = /* @__PURE__ */ new Map();
3289
+ for (const nativeView of nativeViews) {
3290
+ try {
3291
+ await syncSingleView(tx, nativeView, result, options);
3292
+ trackViewByObject(viewsByObject, nativeView);
3293
+ } catch (error) {
3294
+ handleViewSyncError(result, nativeView, error, options);
3295
+ }
3296
+ }
3297
+ return viewsByObject;
3298
+ }
3299
+ function trackViewByObject(viewsByObject, nativeView) {
3300
+ const objectViews = viewsByObject.get(nativeView.object) ?? [];
3301
+ objectViews.push(nativeView.name);
3302
+ viewsByObject.set(nativeView.object, objectViews);
3303
+ }
3304
+ function handleViewSyncError(result, nativeView, error, options) {
3305
+ result.success = false;
3306
+ result.errors.push({
3307
+ viewName: nativeView.name,
3308
+ objectName: nativeView.object,
3309
+ error: error instanceof Error ? error.message : String(error)
3310
+ });
3311
+ if (options.verbose) {
3312
+ console.error(`[ViewSync] \u2717 Failed to sync ${nativeView.object}:${nativeView.name}:`, error);
3313
+ }
3314
+ }
3315
+ async function cleanupRemovedViews(tx, viewsByObject, result, options) {
3316
+ if (options.dryRun) return;
3317
+ for (const [objectName, viewNames] of viewsByObject) {
3318
+ const deletedCount = await tx.views.deleteNotIn(objectName, viewNames);
3319
+ result.viewsDeleted += deletedCount;
3320
+ if (options.verbose && deletedCount > 0) {
3321
+ console.info(`[ViewSync] Deleted ${deletedCount} obsolete views for ${objectName}`);
3322
+ }
3323
+ }
3324
+ }
3325
+ function handleTransactionError(result, error) {
3326
+ result.success = false;
3327
+ result.errors.push({
3328
+ viewName: "transaction",
3329
+ objectName: "",
3330
+ error: error instanceof Error ? error.message : String(error)
3331
+ });
3332
+ }
3333
+ function logSyncComplete(result, options) {
3334
+ if (options.verbose) {
3335
+ console.info(
3336
+ `[ViewSync] ${result.success ? "\u2713" : "\u2717"} Sync complete:
3337
+ Views: ${result.viewsCreated} created, ${result.viewsUpdated} updated, ${result.viewsDeleted} deleted
3338
+ Errors: ${result.errors.length}`
3339
+ );
3340
+ }
3341
+ }
3342
+ async function syncSingleView(adapter, nativeView, result, options) {
3343
+ if (!nativeView.system) {
3344
+ throw new Error(`View ${nativeView.name} is not marked as system`);
3345
+ }
3346
+ const existingView = await adapter.views.findSystemByName(nativeView.object, nativeView.name);
3347
+ const isNew = !existingView;
3348
+ if (isNew) {
3349
+ result.viewsCreated++;
3350
+ } else {
3351
+ result.viewsUpdated++;
3352
+ }
3353
+ result.viewsSynced++;
3354
+ if (options.dryRun) {
3355
+ if (options.verbose) {
3356
+ console.info(
3357
+ `[ViewSync] Would ${isNew ? "create" : "update"} view: ${nativeView.object}:${nativeView.name}`
3358
+ );
3359
+ }
3360
+ return;
3361
+ }
3362
+ await adapter.views.upsert({
3363
+ tenantId: options.tenantId || "default",
3364
+ objectName: nativeView.object,
3365
+ name: nativeView.name,
3366
+ label: nativeView.label,
3367
+ description: nativeView.description,
3368
+ icon: nativeView.icon,
3369
+ tabs: nativeView.tabs,
3370
+ default: nativeView.default ?? false,
3371
+ system: true,
3372
+ metadata: nativeView.metadata
3373
+ });
3374
+ if (options.verbose) {
3375
+ console.info(
3376
+ `[ViewSync] \u2713 Synced ${nativeView.object}:${nativeView.name}: ${nativeView.tabs.length} tabs`
3377
+ );
3378
+ }
3379
+ }
3380
+ async function verifyNativeViewsSync(adapter, nativeViewRegistry) {
3381
+ const nativeViews = nativeViewRegistry.getAll();
3382
+ for (const view of nativeViews) {
3383
+ if (!view.system) continue;
3384
+ const existing = await adapter.views.findSystemByName(view.object, view.name);
3385
+ if (!existing) {
3386
+ return false;
3387
+ }
3388
+ }
3389
+ return true;
3390
+ }
3391
+ async function getViewSyncPreview(adapter, nativeViewRegistry) {
3392
+ return await syncNativeViews(adapter, nativeViewRegistry, { dryRun: true });
3393
+ }
3394
+
3395
+ // src/runtime/sync.ts
3396
+ var BASE_ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
3397
+ "id",
3398
+ "name",
3399
+ "label",
3400
+ "type",
3401
+ "required",
3402
+ "system",
3403
+ "unique",
3404
+ "order"
3405
+ ]);
3406
+ function extractAttributeConfig(attr) {
3407
+ return Object.fromEntries(
3408
+ Object.entries(attr).filter(
3409
+ ([key, value]) => !BASE_ATTRIBUTE_KEYS.has(key) && value !== void 0
3410
+ )
3411
+ );
3412
+ }
3413
+ async function syncNativeObjects(adapter, nativeRegistry, options = {}) {
3414
+ const result = {
3415
+ success: true,
3416
+ objectsSynced: 0,
3417
+ attributesSynced: 0,
3418
+ objectsCreated: 0,
3419
+ objectsUpdated: 0,
3420
+ attributesCreated: 0,
3421
+ attributesUpdated: 0,
3422
+ attributesDeleted: 0,
3423
+ errors: []
3424
+ };
3425
+ const nativeObjects = nativeRegistry.getAll();
3426
+ if (options.verbose) {
3427
+ console.info(`[SyncService] Starting sync for ${nativeObjects.length} native objects...`);
3428
+ }
3429
+ try {
3430
+ await adapter.transaction(async (tx) => {
3431
+ for (const nativeObject of nativeObjects) {
3432
+ try {
3433
+ await syncSingleObject(tx, nativeObject, result, options);
3434
+ } catch (error) {
3435
+ result.success = false;
3436
+ result.errors.push({
3437
+ objectName: nativeObject.name,
3438
+ error: error instanceof Error ? error.message : String(error)
3439
+ });
3440
+ if (options.verbose) {
3441
+ console.error(`[SyncService] \u2717 Failed to sync ${nativeObject.name}:`, error);
3442
+ }
3443
+ }
3444
+ }
3445
+ });
3446
+ } catch (error) {
3447
+ result.success = false;
3448
+ result.errors.push({
3449
+ objectName: "transaction",
3450
+ error: error instanceof Error ? error.message : String(error)
3451
+ });
3452
+ }
3453
+ if (options.verbose) {
3454
+ console.info(
3455
+ `[SyncService] ${result.success ? "\u2713" : "\u2717"} Sync complete:
3456
+ Objects: ${result.objectsCreated} created, ${result.objectsUpdated} updated
3457
+ Attributes: ${result.attributesCreated} created, ${result.attributesUpdated} updated, ${result.attributesDeleted} deleted
3458
+ Errors: ${result.errors.length}`
3459
+ );
3460
+ }
3461
+ return result;
3462
+ }
3463
+ async function syncSingleObject(adapter, nativeObject, result, options) {
3464
+ if (!nativeObject.system) {
3465
+ throw new Error(`Object ${nativeObject.name} is not marked as system`);
3466
+ }
3467
+ const existingObject = await adapter.objects.findSystemByName(nativeObject.name);
3468
+ const isNew = !existingObject;
3469
+ updateObjectStats(result, isNew);
3470
+ if (options.dryRun) {
3471
+ await handleDryRun(adapter, nativeObject, existingObject, result, options, isNew);
3472
+ return;
3473
+ }
3474
+ const dbObject = await upsertObject(adapter, nativeObject, options);
3475
+ await syncAttributes(adapter, nativeObject, dbObject, existingObject, result);
3476
+ if (!isNew) {
3477
+ await cleanupAttributes(adapter, dbObject.id, nativeObject, result);
3478
+ }
3479
+ logSyncSuccess(nativeObject, options);
3480
+ }
3481
+ function updateObjectStats(result, isNew) {
3482
+ if (isNew) {
3483
+ result.objectsCreated++;
3484
+ } else {
3485
+ result.objectsUpdated++;
3486
+ }
3487
+ result.objectsSynced++;
3488
+ }
3489
+ async function handleDryRun(adapter, nativeObject, existingObject, result, options, isNew) {
3490
+ if (options.verbose) {
3491
+ console.info(`[SyncService] Would ${isNew ? "create" : "update"} object: ${nativeObject.name}`);
3492
+ }
3493
+ for (const attr of nativeObject.attributes) {
3494
+ const existingAttr = existingObject ? await adapter.attributes.findByObjectId(existingObject.id).then((attrs) => attrs.find((a) => a.name === attr.name)) : null;
3495
+ if (existingAttr) {
3496
+ result.attributesUpdated++;
3497
+ } else {
3498
+ result.attributesCreated++;
3499
+ }
3500
+ result.attributesSynced++;
3501
+ }
3502
+ }
3503
+ async function upsertObject(adapter, nativeObject, options) {
3504
+ return await adapter.objects.upsert({
3505
+ tenantId: options.tenantId || "default",
3506
+ system: true,
3507
+ name: nativeObject.name,
3508
+ label: nativeObject.label,
3509
+ description: nativeObject.description,
3510
+ labelExpression: nativeObject.labelExpression,
3511
+ icon: nativeObject.icon,
3512
+ metadata: nativeObject.metadata
3513
+ });
3514
+ }
3515
+ async function syncAttributes(adapter, nativeObject, dbObject, existingObject, result) {
3516
+ for (const [index, attr] of nativeObject.attributes.entries()) {
3517
+ const existingAttr = existingObject ? await adapter.attributes.findByObjectId(dbObject.id).then((attrs) => attrs.find((a) => a.name === attr.name)) : null;
3518
+ await adapter.attributes.upsert({
3519
+ objectId: dbObject.id,
3520
+ name: attr.name,
3521
+ label: attr.label,
3522
+ type: attr.type,
3523
+ description: attr.description,
3524
+ icon: attr.icon,
3525
+ system: attr.system ?? true,
3526
+ required: attr.required ?? false,
3527
+ order: attr.order ?? index,
3528
+ hidden: attr.hidden,
3529
+ archived: attr.archived,
3530
+ deprecated: attr.deprecated,
3531
+ defaultValue: attr.defaultValue,
3532
+ config: extractAttributeConfig(attr)
3533
+ });
3534
+ if (existingAttr) {
3535
+ result.attributesUpdated++;
3536
+ } else {
3537
+ result.attributesCreated++;
3538
+ }
3539
+ result.attributesSynced++;
3540
+ }
3541
+ }
3542
+ async function cleanupAttributes(adapter, objectId, nativeObject, result) {
3543
+ const attributeNames = nativeObject.attributes.map((attr) => attr.name);
3544
+ const deletedCount = await adapter.attributes.deleteByNames(objectId, attributeNames);
3545
+ result.attributesDeleted += deletedCount;
3546
+ }
3547
+ function logSyncSuccess(nativeObject, options) {
3548
+ if (options.verbose) {
3549
+ console.info(
3550
+ `[SyncService] \u2713 Synced ${nativeObject.name}: ${nativeObject.attributes.length} attributes`
3551
+ );
3552
+ }
3553
+ }
3554
+ async function verifyNativeObjectsSync(adapter, nativeRegistry) {
3555
+ const nativeObjects = nativeRegistry.getAll();
3556
+ for (const obj of nativeObjects) {
3557
+ if (!obj.system) continue;
3558
+ const existing = await adapter.objects.findSystemByName(obj.name);
3559
+ if (!existing) {
3560
+ return false;
3561
+ }
3562
+ }
3563
+ return true;
3564
+ }
3565
+ async function getSyncPreview(adapter, nativeRegistry) {
3566
+ return await syncNativeObjects(adapter, nativeRegistry, { dryRun: true });
3567
+ }
3568
+ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}) {
3569
+ if (options.verbose) {
3570
+ console.info("[SyncAll] Starting full sync...");
3571
+ }
3572
+ const objectsResult = await syncNativeObjects(adapter, objectRegistry, options);
3573
+ const viewsResult = await syncNativeViews(adapter, nativeViewRegistry, options);
3574
+ const result = {
3575
+ success: objectsResult.success && viewsResult.success,
3576
+ objects: objectsResult,
3577
+ views: viewsResult
3578
+ };
3579
+ if (options.verbose) {
3580
+ console.info(
3581
+ `[SyncAll] ${result.success ? "\u2713" : "\u2717"} Full sync complete:
3582
+ Objects: ${objectsResult.objectsSynced} synced (${objectsResult.objectsCreated} created, ${objectsResult.objectsUpdated} updated)
3583
+ Views: ${viewsResult.viewsSynced} synced (${viewsResult.viewsCreated} created, ${viewsResult.viewsUpdated} updated)
3584
+ Errors: ${objectsResult.errors.length + viewsResult.errors.length}`
3585
+ );
3586
+ }
3587
+ return result;
3588
+ }
3589
+
3590
+ // src/types/geocoding.ts
3591
+ var NoopGeocodingAdapter = class {
3592
+ async autocomplete() {
3593
+ return await Promise.resolve([]);
3594
+ }
3595
+ async reverse() {
3596
+ return await Promise.resolve(null);
3597
+ }
3598
+ async geocode() {
3599
+ return await Promise.resolve(null);
3600
+ }
3601
+ };
3602
+ // Annotate the CommonJS export names for ESM import in node:
3603
+ 0 && (module.exports = {
3604
+ DEFAULT_LABEL_FALLBACK,
3605
+ FileService,
3606
+ GeocodingService,
3607
+ GlobalSearchService,
3608
+ NoopGeocodingAdapter,
3609
+ NoopHookRegistry,
3610
+ ObjectSchemaService,
3611
+ RecordService,
3612
+ RelationService,
3613
+ UserProfileService,
3614
+ ViewService,
3615
+ createMockAdapter,
3616
+ extractAttributeNames,
3617
+ getSyncPreview,
3618
+ getViewSyncPreview,
3619
+ isLabelExpression,
3620
+ renderLabelExpression,
3621
+ syncAll,
3622
+ syncNativeObjects,
3623
+ syncNativeViews,
3624
+ verifyNativeObjectsSync,
3625
+ verifyNativeViewsSync
3626
+ });