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