@stndrds/schema 0.1.0-alpha.21 → 0.1.0-alpha.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-7GLSGSUP.mjs +9456 -0
- package/dist/chunk-AZTBTKHP.mjs +9861 -0
- package/dist/chunk-B74KNXDP.mjs +9457 -0
- package/dist/chunk-CVFKFFCX.mjs +9912 -0
- package/dist/chunk-DXVIUNQT.mjs +9881 -0
- package/dist/chunk-EJB2V42F.mjs +9778 -0
- package/dist/chunk-GP2BONBI.mjs +9493 -0
- package/dist/chunk-H2KOC7F5.mjs +9549 -0
- package/dist/chunk-HXLOIN36.mjs +9893 -0
- package/dist/chunk-HYW67ZP6.mjs +9500 -0
- package/dist/chunk-IKUIWNHZ.mjs +9460 -0
- package/dist/chunk-JAWKNHY6.mjs +9553 -0
- package/dist/chunk-KT4UPBZR.mjs +9892 -0
- package/dist/chunk-NBCCRCJB.mjs +9547 -0
- package/dist/chunk-NN7Y3B3A.mjs +9859 -0
- package/dist/chunk-SBEZDUNH.mjs +9549 -0
- package/dist/chunk-TTZ3TWYP.mjs +9583 -0
- package/dist/chunk-UTZTDUIK.mjs +9818 -0
- package/dist/index.d.mts +172 -17
- package/dist/index.d.ts +172 -17
- package/dist/index.js +977 -512
- package/dist/index.mjs +71 -23
- package/dist/runtime-BFOdkIqK.d.mts +7644 -0
- package/dist/runtime-BFOdkIqK.d.ts +7644 -0
- package/dist/runtime-BI_wXiVl.d.mts +8043 -0
- package/dist/runtime-BI_wXiVl.d.ts +8043 -0
- package/dist/runtime-BZZRu7A1.d.mts +7083 -0
- package/dist/runtime-BZZRu7A1.d.ts +7083 -0
- package/dist/runtime-BlcFbbeG.d.mts +8035 -0
- package/dist/runtime-BlcFbbeG.d.ts +8035 -0
- package/dist/runtime-BleOubTG.d.mts +7236 -0
- package/dist/runtime-BleOubTG.d.ts +7236 -0
- package/dist/runtime-CC4VRtUK.d.mts +7422 -0
- package/dist/runtime-CC4VRtUK.d.ts +7422 -0
- package/dist/runtime-CKLGO3Jk.d.mts +7632 -0
- package/dist/runtime-CKLGO3Jk.d.ts +7632 -0
- package/dist/runtime-CjhnsVyg.d.mts +7668 -0
- package/dist/runtime-CjhnsVyg.d.ts +7668 -0
- package/dist/runtime-CmYcicVr.d.mts +7083 -0
- package/dist/runtime-CmYcicVr.d.ts +7083 -0
- package/dist/runtime-D01tNFxT.d.mts +7418 -0
- package/dist/runtime-D01tNFxT.d.ts +7418 -0
- package/dist/runtime-DWCd3xCL.d.mts +7632 -0
- package/dist/runtime-DWCd3xCL.d.ts +7632 -0
- package/dist/runtime-DbryZGqb.d.mts +8037 -0
- package/dist/runtime-DbryZGqb.d.ts +8037 -0
- package/dist/runtime-DdkNFkKd.d.mts +7185 -0
- package/dist/runtime-DdkNFkKd.d.ts +7185 -0
- package/dist/runtime-DnnW72ZF.d.mts +7451 -0
- package/dist/runtime-DnnW72ZF.d.ts +7451 -0
- package/dist/runtime-DrrCWvBr.d.mts +7451 -0
- package/dist/runtime-DrrCWvBr.d.ts +7451 -0
- package/dist/runtime-UYkiPdud.d.mts +8047 -0
- package/dist/runtime-UYkiPdud.d.ts +8047 -0
- package/dist/runtime-g7XZnqGw.d.mts +7452 -0
- package/dist/runtime-g7XZnqGw.d.ts +7452 -0
- package/dist/runtime-jnV5WCS6.d.mts +8047 -0
- package/dist/runtime-jnV5WCS6.d.ts +8047 -0
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +855 -466
- package/dist/runtime.mjs +29 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -146,6 +146,7 @@ __export(index_exports, {
|
|
|
146
146
|
GroupBuilder: () => GroupBuilder,
|
|
147
147
|
InvalidPathError: () => InvalidPathError,
|
|
148
148
|
MaxDepthExceededError: () => MaxDepthExceededError,
|
|
149
|
+
NOTES: () => NOTES,
|
|
149
150
|
NO_VALUE_OPERATORS: () => NO_VALUE_OPERATORS,
|
|
150
151
|
NoopGeocodingAdapter: () => NoopGeocodingAdapter,
|
|
151
152
|
NoopHookRegistry: () => NoopHookRegistry,
|
|
@@ -157,11 +158,15 @@ __export(index_exports, {
|
|
|
157
158
|
ObjectReferencedError: () => ObjectReferencedError,
|
|
158
159
|
ObjectSchemaService: () => ObjectSchemaService,
|
|
159
160
|
PermissionService: () => PermissionService,
|
|
161
|
+
PolicyRegistry: () => PolicyRegistry,
|
|
162
|
+
PolicyViolationError: () => PolicyViolationError,
|
|
160
163
|
ProtectedResourceError: () => ProtectedResourceError,
|
|
161
164
|
ProtectedRoleError: () => ProtectedRoleError,
|
|
162
165
|
QueryBuilder: () => QueryBuilder,
|
|
163
166
|
QueryMultipleResultsError: () => QueryMultipleResultsError,
|
|
164
167
|
QueryNoResultError: () => QueryNoResultError,
|
|
168
|
+
RELATION_TARGET_ANY: () => RELATION_TARGET_ANY,
|
|
169
|
+
RESERVED_ATTRIBUTE_NAMES: () => RESERVED_ATTRIBUTE_NAMES,
|
|
165
170
|
RecordNotFoundError: () => RecordNotFoundError,
|
|
166
171
|
RecordReferencedError: () => RecordReferencedError,
|
|
167
172
|
RecordService: () => RecordService,
|
|
@@ -171,6 +176,7 @@ __export(index_exports, {
|
|
|
171
176
|
RollupScheduler: () => RollupScheduler,
|
|
172
177
|
RollupService: () => RollupService,
|
|
173
178
|
SHORTCUT_TO_FILTER_OPERATOR: () => SHORTCUT_TO_FILTER_OPERATOR,
|
|
179
|
+
SYSTEM_ATTRIBUTES: () => SYSTEM_ATTRIBUTES,
|
|
174
180
|
SYSTEM_FIELD_NAMES: () => SYSTEM_FIELD_NAMES,
|
|
175
181
|
SYSTEM_RESOURCES: () => SYSTEM_RESOURCES,
|
|
176
182
|
SYSTEM_RESOURCE_LABELS: () => SYSTEM_RESOURCE_LABELS,
|
|
@@ -179,12 +185,17 @@ __export(index_exports, {
|
|
|
179
185
|
SyncError: () => SyncError,
|
|
180
186
|
TabBuilder: () => TabBuilder,
|
|
181
187
|
TableTabConfig: () => TableTabConfig,
|
|
188
|
+
TenantAwareRepository: () => TenantAwareRepository,
|
|
189
|
+
TenantAwareService: () => TenantAwareService,
|
|
190
|
+
TenantContextError: () => TenantContextError,
|
|
182
191
|
UserProfileNotFoundError: () => UserProfileNotFoundError,
|
|
183
192
|
UserProfileService: () => UserProfileService,
|
|
184
193
|
UserService: () => UserService,
|
|
185
194
|
ValidationError: () => ValidationError,
|
|
186
195
|
ViewBuilder: () => ViewBuilder,
|
|
187
196
|
ViewService: () => ViewService,
|
|
197
|
+
asTenantId: () => asTenantId,
|
|
198
|
+
asUserId: () => asUserId,
|
|
188
199
|
attributeConfigSchemas: () => attributeConfigSchemas,
|
|
189
200
|
buildAuditChanges: () => buildAuditChanges,
|
|
190
201
|
checkbox: () => checkbox,
|
|
@@ -208,17 +219,19 @@ __export(index_exports, {
|
|
|
208
219
|
createQueryBuilder: () => createQueryBuilder,
|
|
209
220
|
createRatingValidator: () => createRatingValidator,
|
|
210
221
|
createRelationValidator: () => createRelationValidator,
|
|
222
|
+
createRichtextValidator: () => createRichtextValidator,
|
|
211
223
|
createRollupValidator: () => createRollupValidator,
|
|
212
224
|
createSelectValidator: () => createSelectValidator,
|
|
213
225
|
createSingleRelationValidator: () => createSingleRelationValidator,
|
|
214
226
|
createStatusValidator: () => createStatusValidator,
|
|
227
|
+
createTextAreaValidator: () => createTextAreaValidator,
|
|
215
228
|
createTextValidator: () => createTextValidator,
|
|
216
|
-
createTimestampValidator: () => createTimestampValidator,
|
|
217
229
|
createUserValidator: () => createUserValidator,
|
|
218
230
|
currency: () => currency,
|
|
219
231
|
currencyConfigSchema: () => currencyConfigSchema,
|
|
220
232
|
date: () => date,
|
|
221
233
|
dateConfigSchema: () => dateConfigSchema,
|
|
234
|
+
defaultPolicyRegistry: () => defaultPolicyRegistry,
|
|
222
235
|
enrichValuesWithSelectLabels: () => enrichValuesWithSelectLabels,
|
|
223
236
|
evaluateFormula: () => evaluateFormula,
|
|
224
237
|
evaluateFormulaAttribute: () => evaluateFormulaAttribute,
|
|
@@ -242,13 +255,18 @@ __export(index_exports, {
|
|
|
242
255
|
generateId: () => generateId,
|
|
243
256
|
generatePrefixedId: () => generatePrefixedId,
|
|
244
257
|
getAttributeConfigSchema: () => getAttributeConfigSchema,
|
|
258
|
+
getContext: () => getContext,
|
|
245
259
|
getMissingRequiredAttributes: () => getMissingRequiredAttributes,
|
|
246
260
|
getPathDepth: () => getPathDepth,
|
|
247
261
|
getRelationPath: () => getRelationPath,
|
|
248
262
|
getSyncPreview: () => getSyncPreview,
|
|
263
|
+
getSystemAttributeList: () => getSystemAttributeList,
|
|
249
264
|
getTargetAttributeName: () => getTargetAttributeName,
|
|
265
|
+
getTenantId: () => getTenantId,
|
|
266
|
+
getUserId: () => getUserId,
|
|
250
267
|
getViewSyncPreview: () => getViewSyncPreview,
|
|
251
268
|
group: () => group,
|
|
269
|
+
hasContext: () => hasContext,
|
|
252
270
|
hasRelationReferences: () => hasRelationReferences,
|
|
253
271
|
isActivityTab: () => isActivityTab,
|
|
254
272
|
isAdvancedFilterState: () => isAdvancedFilterState,
|
|
@@ -261,16 +279,21 @@ __export(index_exports, {
|
|
|
261
279
|
isLabelExpression: () => isLabelExpression,
|
|
262
280
|
isNoValueOperator: () => isNoValueOperator,
|
|
263
281
|
isNotFoundError: () => isNotFoundError,
|
|
282
|
+
isNotesTab: () => isNotesTab,
|
|
264
283
|
isProtectedResourceError: () => isProtectedResourceError,
|
|
265
284
|
isRecordComplete: () => isRecordComplete,
|
|
266
285
|
isSchemaError: () => isSchemaError,
|
|
286
|
+
isSystemAttribute: () => isSystemAttribute,
|
|
287
|
+
isSystemAttributeObject: () => isSystemAttributeObject,
|
|
267
288
|
isSystemFlow: () => isSystemFlow,
|
|
268
289
|
isTableTab: () => isTableTab,
|
|
290
|
+
isUniversalRelation: () => isUniversalRelation,
|
|
269
291
|
isValidationError: () => isValidationError,
|
|
270
292
|
location: () => location,
|
|
271
293
|
locationConfigSchema: () => locationConfigSchema,
|
|
272
294
|
multiselect: () => multiselect,
|
|
273
295
|
multiselectConfigSchema: () => multiselectConfigSchema,
|
|
296
|
+
notesPolicy: () => notesPolicy,
|
|
274
297
|
number: () => number,
|
|
275
298
|
numberConfigSchema: () => numberConfigSchema,
|
|
276
299
|
object: () => object,
|
|
@@ -287,8 +310,11 @@ __export(index_exports, {
|
|
|
287
310
|
renderLabelExpression: () => renderLabelExpression,
|
|
288
311
|
resolveMultiplePaths: () => resolveMultiplePaths,
|
|
289
312
|
resolveSingleValue: () => resolveSingleValue,
|
|
313
|
+
richtext: () => richtext,
|
|
314
|
+
richtextConfigSchema: () => richtextConfigSchema,
|
|
290
315
|
rollup: () => rollup,
|
|
291
316
|
rollupConfigSchema: () => rollupConfigSchema,
|
|
317
|
+
runWithContext: () => runWithContext,
|
|
292
318
|
safeParseAttributeConfig: () => safeParseAttributeConfig,
|
|
293
319
|
select: () => select,
|
|
294
320
|
selectConfigSchema: () => selectConfigSchema,
|
|
@@ -301,8 +327,6 @@ __export(index_exports, {
|
|
|
301
327
|
textConfigSchema: () => textConfigSchema,
|
|
302
328
|
textarea: () => textarea,
|
|
303
329
|
textareaConfigSchema: () => textareaConfigSchema,
|
|
304
|
-
timestamp: () => timestamp,
|
|
305
|
-
timestampConfigSchema: () => timestampConfigSchema,
|
|
306
330
|
toAdvancedFilterState: () => toAdvancedFilterState,
|
|
307
331
|
toSimpleFilterState: () => toSimpleFilterState,
|
|
308
332
|
traversePath: () => traversePath,
|
|
@@ -319,10 +343,17 @@ __export(index_exports, {
|
|
|
319
343
|
verifyNativeObjectsSync: () => verifyNativeObjectsSync,
|
|
320
344
|
verifyNativeViewsSync: () => verifyNativeViewsSync,
|
|
321
345
|
view: () => view,
|
|
322
|
-
viewRegistry: () => viewRegistry
|
|
346
|
+
viewRegistry: () => viewRegistry,
|
|
347
|
+
withTenantContext: () => withTenantContext
|
|
323
348
|
});
|
|
324
349
|
module.exports = __toCommonJS(index_exports);
|
|
325
350
|
|
|
351
|
+
// src/types/attributes.ts
|
|
352
|
+
var RELATION_TARGET_ANY = "*";
|
|
353
|
+
function isUniversalRelation(attr) {
|
|
354
|
+
return attr.targets.length === 1 && attr.targets[0].object === RELATION_TARGET_ANY;
|
|
355
|
+
}
|
|
356
|
+
|
|
326
357
|
// src/types/errors.ts
|
|
327
358
|
var RecordReferencedError = class extends Error {
|
|
328
359
|
constructor(recordId, references) {
|
|
@@ -405,6 +436,7 @@ var OPERATORS_BY_TYPE = {
|
|
|
405
436
|
"is_empty",
|
|
406
437
|
"is_not_empty"
|
|
407
438
|
],
|
|
439
|
+
richtext: ["is_empty", "is_not_empty"],
|
|
408
440
|
number: ["eq", "neq", "lt", "gt", "lte", "gte", "is_empty", "is_not_empty"],
|
|
409
441
|
checkbox: ["is_checked", "is_not_checked"],
|
|
410
442
|
date: [
|
|
@@ -418,17 +450,6 @@ var OPERATORS_BY_TYPE = {
|
|
|
418
450
|
"is_empty",
|
|
419
451
|
"is_not_empty"
|
|
420
452
|
],
|
|
421
|
-
timestamp: [
|
|
422
|
-
"is",
|
|
423
|
-
"is_not",
|
|
424
|
-
"before",
|
|
425
|
-
"after",
|
|
426
|
-
"on_or_before",
|
|
427
|
-
"on_or_after",
|
|
428
|
-
"is_within",
|
|
429
|
-
"is_empty",
|
|
430
|
-
"is_not_empty"
|
|
431
|
-
],
|
|
432
453
|
phone: ["is", "is_not", "contains", "not_contains", "is_empty", "is_not_empty"],
|
|
433
454
|
currency: ["eq", "neq", "lt", "gt", "lte", "gte", "is_empty", "is_not_empty"],
|
|
434
455
|
status: ["is", "is_not", "any_of", "none_of", "is_empty", "is_not_empty"],
|
|
@@ -491,8 +512,96 @@ var NoopGeocodingAdapter = class {
|
|
|
491
512
|
}
|
|
492
513
|
};
|
|
493
514
|
|
|
494
|
-
// src/types/
|
|
495
|
-
var SYSTEM_FIELD_NAMES = [
|
|
515
|
+
// src/types/objects.ts
|
|
516
|
+
var SYSTEM_FIELD_NAMES = [
|
|
517
|
+
"id",
|
|
518
|
+
"createdAt",
|
|
519
|
+
"updatedAt",
|
|
520
|
+
"createdBy",
|
|
521
|
+
"lastUpdatedBy"
|
|
522
|
+
];
|
|
523
|
+
var RESERVED_ATTRIBUTE_NAMES = [
|
|
524
|
+
// System fields (auto-managed)
|
|
525
|
+
...SYSTEM_FIELD_NAMES,
|
|
526
|
+
// Other ObjectRecord properties
|
|
527
|
+
"objectId",
|
|
528
|
+
"label",
|
|
529
|
+
"completionStatus",
|
|
530
|
+
"values",
|
|
531
|
+
"metadata",
|
|
532
|
+
"deletedAt"
|
|
533
|
+
];
|
|
534
|
+
|
|
535
|
+
// src/types/policies.ts
|
|
536
|
+
var PolicyViolationError = class extends Error {
|
|
537
|
+
constructor(objectName, action, recordId) {
|
|
538
|
+
const message = recordId ? `Access denied: cannot ${action} record ${recordId} on ${objectName}` : `Access denied: cannot ${action} on ${objectName}`;
|
|
539
|
+
super(message);
|
|
540
|
+
this.objectName = objectName;
|
|
541
|
+
this.action = action;
|
|
542
|
+
this.recordId = recordId;
|
|
543
|
+
this.name = "PolicyViolationError";
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// src/types/system-attributes.ts
|
|
548
|
+
var SYSTEM_ATTRIBUTES = {
|
|
549
|
+
createdAt: {
|
|
550
|
+
id: "system:createdAt",
|
|
551
|
+
name: "createdAt",
|
|
552
|
+
label: "Created time",
|
|
553
|
+
type: "date",
|
|
554
|
+
required: false,
|
|
555
|
+
disabled: true,
|
|
556
|
+
system: true,
|
|
557
|
+
icon: "clock-circle",
|
|
558
|
+
description: "When this record was created"
|
|
559
|
+
},
|
|
560
|
+
updatedAt: {
|
|
561
|
+
id: "system:updatedAt",
|
|
562
|
+
name: "updatedAt",
|
|
563
|
+
label: "Last edited time",
|
|
564
|
+
type: "date",
|
|
565
|
+
required: false,
|
|
566
|
+
disabled: true,
|
|
567
|
+
system: true,
|
|
568
|
+
icon: "clock-circle",
|
|
569
|
+
description: "When this record was last modified"
|
|
570
|
+
},
|
|
571
|
+
createdBy: {
|
|
572
|
+
id: "system:createdBy",
|
|
573
|
+
name: "createdBy",
|
|
574
|
+
label: "Created by",
|
|
575
|
+
type: "user",
|
|
576
|
+
required: false,
|
|
577
|
+
disabled: true,
|
|
578
|
+
system: true,
|
|
579
|
+
multiple: false,
|
|
580
|
+
icon: "user",
|
|
581
|
+
description: "User who created this record"
|
|
582
|
+
},
|
|
583
|
+
lastUpdatedBy: {
|
|
584
|
+
id: "system:lastUpdatedBy",
|
|
585
|
+
name: "lastUpdatedBy",
|
|
586
|
+
label: "Last edited by",
|
|
587
|
+
type: "user",
|
|
588
|
+
required: false,
|
|
589
|
+
disabled: true,
|
|
590
|
+
system: true,
|
|
591
|
+
multiple: false,
|
|
592
|
+
icon: "user",
|
|
593
|
+
description: "User who last modified this record"
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
function getSystemAttributeList() {
|
|
597
|
+
return Object.values(SYSTEM_ATTRIBUTES);
|
|
598
|
+
}
|
|
599
|
+
function isSystemAttribute(name) {
|
|
600
|
+
return name in SYSTEM_ATTRIBUTES;
|
|
601
|
+
}
|
|
602
|
+
function isSystemAttributeObject(attr) {
|
|
603
|
+
return attr.system === true;
|
|
604
|
+
}
|
|
496
605
|
|
|
497
606
|
// src/types/views.ts
|
|
498
607
|
function isFormTab(tab) {
|
|
@@ -507,8 +616,17 @@ function isCustomTab(tab) {
|
|
|
507
616
|
function isActivityTab(tab) {
|
|
508
617
|
return tab.type === "activity";
|
|
509
618
|
}
|
|
619
|
+
function isNotesTab(tab) {
|
|
620
|
+
return tab.type === "notes";
|
|
621
|
+
}
|
|
510
622
|
|
|
511
623
|
// src/utils.ts
|
|
624
|
+
function asTenantId(id) {
|
|
625
|
+
return id;
|
|
626
|
+
}
|
|
627
|
+
function asUserId(id) {
|
|
628
|
+
return id;
|
|
629
|
+
}
|
|
512
630
|
function generateId() {
|
|
513
631
|
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
514
632
|
return crypto.randomUUID();
|
|
@@ -569,15 +687,6 @@ function formatDate(value) {
|
|
|
569
687
|
}
|
|
570
688
|
return String(value);
|
|
571
689
|
}
|
|
572
|
-
function formatTimestamp(value) {
|
|
573
|
-
if (typeof value === "number") {
|
|
574
|
-
return new Date(value).toISOString();
|
|
575
|
-
}
|
|
576
|
-
if (value instanceof Date) {
|
|
577
|
-
return value.toISOString();
|
|
578
|
-
}
|
|
579
|
-
return String(value);
|
|
580
|
-
}
|
|
581
690
|
function formatPhone(value) {
|
|
582
691
|
if (typeof value !== "object" || value === null) return String(value);
|
|
583
692
|
const phone2 = value;
|
|
@@ -662,8 +771,6 @@ function formatAttributeValue(value, attribute) {
|
|
|
662
771
|
return formatCurrency(value, attribute);
|
|
663
772
|
case "date":
|
|
664
773
|
return formatDate(value);
|
|
665
|
-
case "timestamp":
|
|
666
|
-
return formatTimestamp(value);
|
|
667
774
|
case "phone":
|
|
668
775
|
return formatPhone(value);
|
|
669
776
|
case "location":
|
|
@@ -901,7 +1008,7 @@ var VALID_ICONS = new Set(import_constants2.ICONS);
|
|
|
901
1008
|
var ATTRIBUTE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
902
1009
|
var MAX_NAME_LENGTH = 63;
|
|
903
1010
|
var MAX_LABEL_LENGTH = 128;
|
|
904
|
-
var
|
|
1011
|
+
var RESERVED_ATTRIBUTE_NAMES2 = ["id", "createdAt", "updatedAt", "metadata"];
|
|
905
1012
|
function validateAttributeName(name) {
|
|
906
1013
|
if (!name || name.length === 0) {
|
|
907
1014
|
throw new Error("[AttributeBuilder] Attribute name cannot be empty");
|
|
@@ -916,10 +1023,10 @@ function validateAttributeName(name) {
|
|
|
916
1023
|
"[AttributeBuilder] 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'"
|
|
917
1024
|
);
|
|
918
1025
|
}
|
|
919
|
-
if (
|
|
1026
|
+
if (RESERVED_ATTRIBUTE_NAMES2.includes(name)) {
|
|
920
1027
|
throw new Error(
|
|
921
1028
|
`[AttributeBuilder] "${name}" is a reserved name and cannot be used as an attribute name.
|
|
922
|
-
Reserved names: ${
|
|
1029
|
+
Reserved names: ${RESERVED_ATTRIBUTE_NAMES2.join(", ")}`
|
|
923
1030
|
);
|
|
924
1031
|
}
|
|
925
1032
|
}
|
|
@@ -1102,6 +1209,38 @@ var TextAreaAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1102
1209
|
function textarea(config) {
|
|
1103
1210
|
return new TextAreaAttributeBuilder(config.name, config.label);
|
|
1104
1211
|
}
|
|
1212
|
+
var RichtextAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1213
|
+
constructor(name, label) {
|
|
1214
|
+
super("richtext", name, label);
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Set enabled features for the richtext editor
|
|
1218
|
+
* If not called, all features are enabled by default
|
|
1219
|
+
*
|
|
1220
|
+
* @param list - Array of features to enable
|
|
1221
|
+
*
|
|
1222
|
+
* @example
|
|
1223
|
+
* ```typescript
|
|
1224
|
+
* richtext({ name: "content", label: "Content" })
|
|
1225
|
+
* .features(["headings", "bold", "italic", "lists", "links"])
|
|
1226
|
+
* ```
|
|
1227
|
+
*/
|
|
1228
|
+
features(list) {
|
|
1229
|
+
this.attr.features = list;
|
|
1230
|
+
return this;
|
|
1231
|
+
}
|
|
1232
|
+
required() {
|
|
1233
|
+
this.setRequired(true);
|
|
1234
|
+
return this;
|
|
1235
|
+
}
|
|
1236
|
+
optional() {
|
|
1237
|
+
this.setRequired(false);
|
|
1238
|
+
return this;
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
function richtext(config) {
|
|
1242
|
+
return new RichtextAttributeBuilder(config.name, config.label);
|
|
1243
|
+
}
|
|
1105
1244
|
var NumberAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1106
1245
|
constructor(name, label) {
|
|
1107
1246
|
super("number", name, label);
|
|
@@ -1367,26 +1506,6 @@ var LocationAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1367
1506
|
function location(config) {
|
|
1368
1507
|
return new LocationAttributeBuilder(config.name, config.label);
|
|
1369
1508
|
}
|
|
1370
|
-
var TimestampAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1371
|
-
constructor(name, label) {
|
|
1372
|
-
super("timestamp", name, label);
|
|
1373
|
-
}
|
|
1374
|
-
autoUpdate() {
|
|
1375
|
-
this.attr.autoUpdate = true;
|
|
1376
|
-
return this;
|
|
1377
|
-
}
|
|
1378
|
-
required() {
|
|
1379
|
-
this.setRequired(true);
|
|
1380
|
-
return this;
|
|
1381
|
-
}
|
|
1382
|
-
optional() {
|
|
1383
|
-
this.setRequired(false);
|
|
1384
|
-
return this;
|
|
1385
|
-
}
|
|
1386
|
-
};
|
|
1387
|
-
function timestamp(config) {
|
|
1388
|
-
return new TimestampAttributeBuilder(config.name, config.label);
|
|
1389
|
-
}
|
|
1390
1509
|
var FileAttributeBuilder = class extends BaseAttributeBuilder {
|
|
1391
1510
|
constructor(name, label) {
|
|
1392
1511
|
super("file", name, label);
|
|
@@ -1403,10 +1522,6 @@ var FileAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1403
1522
|
this.attr.allowedTypes = types;
|
|
1404
1523
|
return this;
|
|
1405
1524
|
}
|
|
1406
|
-
verification(config) {
|
|
1407
|
-
this.attr.verification = config;
|
|
1408
|
-
return this;
|
|
1409
|
-
}
|
|
1410
1525
|
/**
|
|
1411
1526
|
* Allow multiple files to be uploaded
|
|
1412
1527
|
*/
|
|
@@ -1472,6 +1587,21 @@ var SingleRelationAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1472
1587
|
this.attr.targets?.push(target);
|
|
1473
1588
|
return this;
|
|
1474
1589
|
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Allow linking to any object in the tenant (universal polymorphic)
|
|
1592
|
+
* Creates a relation with `targets: [{ object: "*" }]`
|
|
1593
|
+
*
|
|
1594
|
+
* @example
|
|
1595
|
+
* ```typescript
|
|
1596
|
+
* relation({ name: "linkedTo", label: "Linked To" })
|
|
1597
|
+
* .toAny()
|
|
1598
|
+
* .hidden()
|
|
1599
|
+
* ```
|
|
1600
|
+
*/
|
|
1601
|
+
toAny() {
|
|
1602
|
+
this.attr.targets = [{ object: RELATION_TARGET_ANY }];
|
|
1603
|
+
return this;
|
|
1604
|
+
}
|
|
1475
1605
|
/**
|
|
1476
1606
|
* Convert to a multi-relation (cardinality: "many")
|
|
1477
1607
|
*/
|
|
@@ -1518,6 +1648,21 @@ var MultiRelationAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
1518
1648
|
this.attr.targets?.push(target);
|
|
1519
1649
|
return this;
|
|
1520
1650
|
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Allow linking to any object in the tenant (universal polymorphic)
|
|
1653
|
+
* Creates a relation with `targets: [{ object: "*" }]`
|
|
1654
|
+
*
|
|
1655
|
+
* @example
|
|
1656
|
+
* ```typescript
|
|
1657
|
+
* relation({ name: "linkedItems", label: "Linked Items" })
|
|
1658
|
+
* .toAny()
|
|
1659
|
+
* .many()
|
|
1660
|
+
* ```
|
|
1661
|
+
*/
|
|
1662
|
+
toAny() {
|
|
1663
|
+
this.attr.targets = [{ object: RELATION_TARGET_ANY }];
|
|
1664
|
+
return this;
|
|
1665
|
+
}
|
|
1521
1666
|
/**
|
|
1522
1667
|
* Set minimum number of relations required
|
|
1523
1668
|
*/
|
|
@@ -2703,6 +2848,15 @@ Available objects: ${this.listNames().join(", ")}`
|
|
|
2703
2848
|
};
|
|
2704
2849
|
var registry = new NativeObjectRegistryClass();
|
|
2705
2850
|
|
|
2851
|
+
// src/native/notes.ts
|
|
2852
|
+
var NOTES = object({ name: "notes", label: "Note" }).pluralLabel("Notes").icon("file-text").description("Notes that can be linked to any record or used globally").system().labelExpression("{{ title }}").attribute(text({ name: "title", label: "Title" }).placeholder("Untitled").required()).attribute(richtext({ name: "content", label: "Content" }).required()).attribute(
|
|
2853
|
+
select({ name: "visibility", label: "Visibility" }).options([
|
|
2854
|
+
{ id: "private", label: "Private", value: "private", icon: "lock" },
|
|
2855
|
+
{ id: "shared", label: "Shared", value: "shared", icon: "users" }
|
|
2856
|
+
]).defaultValue("private").required()
|
|
2857
|
+
).attribute(relation({ name: "linkedTo", label: "Linked To" }).toAny());
|
|
2858
|
+
registry.register(NOTES);
|
|
2859
|
+
|
|
2706
2860
|
// src/views/registry.ts
|
|
2707
2861
|
var ViewRegistry = class {
|
|
2708
2862
|
constructor() {
|
|
@@ -2876,41 +3030,17 @@ var relationTargetSchema = import_zod4.z.object({
|
|
|
2876
3030
|
displayTemplate: import_zod4.z.string().optional(),
|
|
2877
3031
|
filter: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional()
|
|
2878
3032
|
});
|
|
2879
|
-
var documentTypeConfigSchema = import_zod4.z.object({
|
|
2880
|
-
type: import_zod4.z.enum([
|
|
2881
|
-
"id_card",
|
|
2882
|
-
"passport",
|
|
2883
|
-
"incorporation_certificate",
|
|
2884
|
-
"driver_license",
|
|
2885
|
-
"birth_certificate",
|
|
2886
|
-
"residence_permit",
|
|
2887
|
-
"bank_statement",
|
|
2888
|
-
"proof_of_address",
|
|
2889
|
-
"custom"
|
|
2890
|
-
]),
|
|
2891
|
-
label: import_zod4.z.string(),
|
|
2892
|
-
faces: import_zod4.z.array(import_zod4.z.enum(["front", "back", "single"])),
|
|
2893
|
-
attributeMapping: import_zod4.z.array(
|
|
2894
|
-
import_zod4.z.object({
|
|
2895
|
-
attributeId: import_zod4.z.string(),
|
|
2896
|
-
extractedKey: import_zod4.z.string(),
|
|
2897
|
-
face: import_zod4.z.enum(["front", "back", "single"]).optional(),
|
|
2898
|
-
required: import_zod4.z.boolean().optional()
|
|
2899
|
-
})
|
|
2900
|
-
).optional()
|
|
2901
|
-
});
|
|
2902
|
-
var fileVerificationConfigSchema = import_zod4.z.object({
|
|
2903
|
-
enabled: import_zod4.z.boolean(),
|
|
2904
|
-
documentTypes: import_zod4.z.array(documentTypeConfigSchema),
|
|
2905
|
-
autoExtract: import_zod4.z.boolean().optional(),
|
|
2906
|
-
autoValidate: import_zod4.z.boolean().optional()
|
|
2907
|
-
});
|
|
2908
3033
|
var textConfigSchema = baseConfigSchema.extend({
|
|
2909
3034
|
minLength: import_zod4.z.number().int().min(0).optional(),
|
|
2910
3035
|
maxLength: import_zod4.z.number().int().min(1).optional(),
|
|
2911
3036
|
pattern: import_zod4.z.string().optional()
|
|
2912
3037
|
});
|
|
2913
3038
|
var textareaConfigSchema = baseConfigSchema;
|
|
3039
|
+
var richtextConfigSchema = baseConfigSchema.extend({
|
|
3040
|
+
features: import_zod4.z.array(
|
|
3041
|
+
import_zod4.z.enum(["headings", "bold", "italic", "lists", "links", "images", "codeBlocks", "tables"])
|
|
3042
|
+
).optional()
|
|
3043
|
+
});
|
|
2914
3044
|
var numberConfigSchema = baseConfigSchema.extend({
|
|
2915
3045
|
min: import_zod4.z.number().optional(),
|
|
2916
3046
|
max: import_zod4.z.number().optional(),
|
|
@@ -2941,9 +3071,6 @@ var locationConfigSchema = baseConfigSchema.extend({
|
|
|
2941
3071
|
allowedCountries: import_zod4.z.array(import_zod4.z.string().length(3)).optional(),
|
|
2942
3072
|
displayFormat: import_zod4.z.enum(["single_line", "multi_line", "compact"]).optional()
|
|
2943
3073
|
});
|
|
2944
|
-
var timestampConfigSchema = baseConfigSchema.extend({
|
|
2945
|
-
autoUpdate: import_zod4.z.boolean().optional()
|
|
2946
|
-
});
|
|
2947
3074
|
var selectConfigSchema = baseConfigSchema.extend({
|
|
2948
3075
|
options: optionsArraySchema
|
|
2949
3076
|
});
|
|
@@ -2954,7 +3081,6 @@ var fileConfigSchema = baseConfigSchema.extend({
|
|
|
2954
3081
|
maxFiles: import_zod4.z.number().int().min(1).optional(),
|
|
2955
3082
|
maxSize: import_zod4.z.number().int().min(1).optional(),
|
|
2956
3083
|
allowedTypes: import_zod4.z.array(import_zod4.z.string()).optional(),
|
|
2957
|
-
verification: fileVerificationConfigSchema.optional(),
|
|
2958
3084
|
multiple: import_zod4.z.boolean().optional()
|
|
2959
3085
|
});
|
|
2960
3086
|
var userConfigSchema = baseConfigSchema.extend({
|
|
@@ -2982,24 +3108,40 @@ var rollupConfigSchema = baseConfigSchema.extend({
|
|
|
2982
3108
|
relationPath: import_zod4.z.string().optional(),
|
|
2983
3109
|
targetAttribute: import_zod4.z.string().min(1),
|
|
2984
3110
|
function: import_zod4.z.enum([
|
|
3111
|
+
// Numeric aggregates
|
|
2985
3112
|
"sum",
|
|
2986
|
-
"count",
|
|
2987
3113
|
"avg",
|
|
2988
|
-
|
|
2989
|
-
"
|
|
3114
|
+
// Date aggregates
|
|
3115
|
+
"earliest",
|
|
3116
|
+
"latest",
|
|
3117
|
+
// Count (universal)
|
|
3118
|
+
"count",
|
|
2990
3119
|
"countValues",
|
|
3120
|
+
"countUniqueValues",
|
|
2991
3121
|
"countEmpty",
|
|
3122
|
+
// Percent (universal)
|
|
2992
3123
|
"percentEmpty",
|
|
2993
|
-
"
|
|
2994
|
-
"collect"
|
|
3124
|
+
"percentNotEmpty"
|
|
2995
3125
|
]),
|
|
2996
3126
|
decimals: import_zod4.z.number().int().min(0).max(10).optional(),
|
|
2997
3127
|
showAsOriginal: import_zod4.z.boolean().optional(),
|
|
2998
|
-
targetAttributeType: import_zod4.z.string().optional()
|
|
3128
|
+
targetAttributeType: import_zod4.z.string().optional(),
|
|
3129
|
+
targetAttributeOptions: import_zod4.z.array(
|
|
3130
|
+
import_zod4.z.object({
|
|
3131
|
+
id: import_zod4.z.string(),
|
|
3132
|
+
label: import_zod4.z.string(),
|
|
3133
|
+
value: import_zod4.z.string(),
|
|
3134
|
+
color: import_zod4.z.string().optional(),
|
|
3135
|
+
icon: import_zod4.z.string().optional(),
|
|
3136
|
+
description: import_zod4.z.string().optional(),
|
|
3137
|
+
group: import_zod4.z.enum(["idle", "in_progress", "finished"]).optional()
|
|
3138
|
+
})
|
|
3139
|
+
).optional()
|
|
2999
3140
|
});
|
|
3000
3141
|
var attributeConfigSchemas = {
|
|
3001
3142
|
text: textConfigSchema,
|
|
3002
3143
|
textarea: textareaConfigSchema,
|
|
3144
|
+
richtext: richtextConfigSchema,
|
|
3003
3145
|
number: numberConfigSchema,
|
|
3004
3146
|
checkbox: checkboxConfigSchema,
|
|
3005
3147
|
date: dateConfigSchema,
|
|
@@ -3007,7 +3149,6 @@ var attributeConfigSchemas = {
|
|
|
3007
3149
|
currency: currencyConfigSchema,
|
|
3008
3150
|
status: statusConfigSchema,
|
|
3009
3151
|
location: locationConfigSchema,
|
|
3010
|
-
timestamp: timestampConfigSchema,
|
|
3011
3152
|
select: selectConfigSchema,
|
|
3012
3153
|
multiselect: multiselectConfigSchema,
|
|
3013
3154
|
file: fileConfigSchema,
|
|
@@ -3122,9 +3263,6 @@ function createLocationValidator(_attr) {
|
|
|
3122
3263
|
longitude: import_zod4.z.number().optional()
|
|
3123
3264
|
});
|
|
3124
3265
|
}
|
|
3125
|
-
function createTimestampValidator(_attr) {
|
|
3126
|
-
return import_zod4.z.number().int().positive();
|
|
3127
|
-
}
|
|
3128
3266
|
function createFileValidator(attr) {
|
|
3129
3267
|
const uuidSchema = import_zod4.z.uuid({
|
|
3130
3268
|
message: `${attr.label} must be a valid file ID`
|
|
@@ -3194,10 +3332,31 @@ function createFormulaValidator(_attr) {
|
|
|
3194
3332
|
function createRollupValidator(_attr) {
|
|
3195
3333
|
return import_zod4.z.unknown();
|
|
3196
3334
|
}
|
|
3335
|
+
function createTextAreaValidator(_attr) {
|
|
3336
|
+
return import_zod4.z.string();
|
|
3337
|
+
}
|
|
3338
|
+
var blockNoteBlockSchema = import_zod4.z.lazy(
|
|
3339
|
+
() => import_zod4.z.object({
|
|
3340
|
+
id: import_zod4.z.string().min(1, "Block must have an id"),
|
|
3341
|
+
type: import_zod4.z.string().min(1, "Block must have a type"),
|
|
3342
|
+
props: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.union([import_zod4.z.boolean(), import_zod4.z.number(), import_zod4.z.string()])),
|
|
3343
|
+
content: import_zod4.z.any().optional(),
|
|
3344
|
+
children: import_zod4.z.array(import_zod4.z.any())
|
|
3345
|
+
})
|
|
3346
|
+
);
|
|
3347
|
+
function createRichtextValidator(attr) {
|
|
3348
|
+
return import_zod4.z.array(blockNoteBlockSchema, {
|
|
3349
|
+
message: `${attr.label} must be a valid BlockNote content array`
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3197
3352
|
function createAttributeValidator(attr) {
|
|
3198
3353
|
switch (attr.type) {
|
|
3199
3354
|
case "text":
|
|
3200
3355
|
return createTextValidator(attr);
|
|
3356
|
+
case "textarea":
|
|
3357
|
+
return createTextAreaValidator(attr);
|
|
3358
|
+
case "richtext":
|
|
3359
|
+
return createRichtextValidator(attr);
|
|
3201
3360
|
case "number":
|
|
3202
3361
|
return createNumberValidator(attr);
|
|
3203
3362
|
case "checkbox":
|
|
@@ -3212,8 +3371,6 @@ function createAttributeValidator(attr) {
|
|
|
3212
3371
|
return createStatusValidator(attr);
|
|
3213
3372
|
case "location":
|
|
3214
3373
|
return createLocationValidator(attr);
|
|
3215
|
-
case "timestamp":
|
|
3216
|
-
return createTimestampValidator(attr);
|
|
3217
3374
|
case "select":
|
|
3218
3375
|
return createSelectValidator(attr);
|
|
3219
3376
|
case "multiselect":
|
|
@@ -3433,12 +3590,12 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
3433
3590
|
});
|
|
3434
3591
|
}
|
|
3435
3592
|
/**
|
|
3436
|
-
* Resolve object name to object ID (cached)
|
|
3593
|
+
* Resolve object name to object ID (cached).
|
|
3594
|
+
* Uses tenant context from AsyncLocalStorage.
|
|
3437
3595
|
*/
|
|
3438
3596
|
async resolveObjectId() {
|
|
3439
3597
|
if (this.objectId) return this.objectId;
|
|
3440
|
-
const
|
|
3441
|
-
const dbObject = await this.adapter.objects.findByName(tenantId, this.state.objectName);
|
|
3598
|
+
const dbObject = await this.adapter.objects.findByName(this.state.objectName);
|
|
3442
3599
|
if (!dbObject) {
|
|
3443
3600
|
throw new Error(`Object "${this.state.objectName}" not found`);
|
|
3444
3601
|
}
|
|
@@ -3756,10 +3913,10 @@ var QueryBuilder = class _QueryBuilder {
|
|
|
3756
3913
|
* .eq('id', 'rec-123')
|
|
3757
3914
|
* .update({ price: 899 });
|
|
3758
3915
|
*
|
|
3759
|
-
* // Update with metadata (
|
|
3916
|
+
* // Update with metadata (merged with existing, set undefined to remove)
|
|
3760
3917
|
* const withMeta = await schema.from('products')
|
|
3761
3918
|
* .eq('id', 'rec-123')
|
|
3762
|
-
* .update({ price: 899 }, { metadata: { synced: true } });
|
|
3919
|
+
* .update({ price: 899 }, { metadata: { synced: true, oldKey: undefined } });
|
|
3763
3920
|
* ```
|
|
3764
3921
|
*/
|
|
3765
3922
|
async update(data, options) {
|
|
@@ -3826,6 +3983,49 @@ function createQueryBuilder(recordService, adapter, objectName) {
|
|
|
3826
3983
|
return new QueryBuilder(recordService, adapter, objectName);
|
|
3827
3984
|
}
|
|
3828
3985
|
|
|
3986
|
+
// src/runtime/context/errors.ts
|
|
3987
|
+
var TenantContextError = class _TenantContextError extends Error {
|
|
3988
|
+
constructor(message) {
|
|
3989
|
+
super(
|
|
3990
|
+
message ?? "No tenant context found. This usually means:\n - HTTP request: Missing TenantContextMiddleware/Interceptor\n - Background job: Wrap with runWithContext()\n - Test: Wrap test body with runWithContext()\n\nAll database operations require a tenant context for security."
|
|
3991
|
+
);
|
|
3992
|
+
this.name = "TenantContextError";
|
|
3993
|
+
if ("captureStackTrace" in Error) {
|
|
3994
|
+
Error.captureStackTrace(
|
|
3995
|
+
this,
|
|
3996
|
+
_TenantContextError
|
|
3997
|
+
);
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
};
|
|
4001
|
+
|
|
4002
|
+
// src/runtime/context/tenant-context.ts
|
|
4003
|
+
var import_node_async_hooks = require("async_hooks");
|
|
4004
|
+
var storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
4005
|
+
function getContext() {
|
|
4006
|
+
const ctx = storage.getStore();
|
|
4007
|
+
if (!ctx) {
|
|
4008
|
+
throw new TenantContextError();
|
|
4009
|
+
}
|
|
4010
|
+
return ctx;
|
|
4011
|
+
}
|
|
4012
|
+
function getTenantId() {
|
|
4013
|
+
return getContext().tenantId;
|
|
4014
|
+
}
|
|
4015
|
+
function getUserId() {
|
|
4016
|
+
return getContext().userId;
|
|
4017
|
+
}
|
|
4018
|
+
function hasContext() {
|
|
4019
|
+
return storage.getStore() !== void 0;
|
|
4020
|
+
}
|
|
4021
|
+
function runWithContext(context, fn) {
|
|
4022
|
+
const frozenContext = Object.freeze({ ...context });
|
|
4023
|
+
return storage.run(frozenContext, fn);
|
|
4024
|
+
}
|
|
4025
|
+
async function withTenantContext(tenantId, fn, userId) {
|
|
4026
|
+
return await runWithContext({ tenantId, userId }, fn);
|
|
4027
|
+
}
|
|
4028
|
+
|
|
3829
4029
|
// src/runtime/formula/evaluator.ts
|
|
3830
4030
|
var import_expr_eval = require("expr-eval");
|
|
3831
4031
|
function createFormulaParser() {
|
|
@@ -4286,7 +4486,8 @@ function createMockObjectsRepository(stores) {
|
|
|
4286
4486
|
findById(id) {
|
|
4287
4487
|
return Promise.resolve(stores.objects.get(id) ?? null);
|
|
4288
4488
|
},
|
|
4289
|
-
findByName(
|
|
4489
|
+
findByName(name) {
|
|
4490
|
+
const tenantId = getTenantId();
|
|
4290
4491
|
for (const obj of stores.objects.values()) {
|
|
4291
4492
|
if (obj.tenantId === tenantId && obj.name === name) {
|
|
4292
4493
|
return Promise.resolve(obj);
|
|
@@ -4303,9 +4504,10 @@ function createMockObjectsRepository(stores) {
|
|
|
4303
4504
|
return Promise.resolve(null);
|
|
4304
4505
|
},
|
|
4305
4506
|
create(data) {
|
|
4507
|
+
const tenantId = getTenantId();
|
|
4306
4508
|
const obj = {
|
|
4307
4509
|
id: generateId(),
|
|
4308
|
-
tenantId
|
|
4510
|
+
tenantId,
|
|
4309
4511
|
name: data.name,
|
|
4310
4512
|
label: data.label,
|
|
4311
4513
|
pluralLabel: data.pluralLabel,
|
|
@@ -4337,13 +4539,15 @@ function createMockObjectsRepository(stores) {
|
|
|
4337
4539
|
stores.objects.delete(id);
|
|
4338
4540
|
return Promise.resolve();
|
|
4339
4541
|
},
|
|
4340
|
-
list(
|
|
4542
|
+
list() {
|
|
4543
|
+
const tenantId = getTenantId();
|
|
4341
4544
|
const results = Array.from(stores.objects.values()).filter(
|
|
4342
4545
|
(obj) => obj.tenantId === tenantId
|
|
4343
4546
|
);
|
|
4344
4547
|
return Promise.resolve(results);
|
|
4345
4548
|
},
|
|
4346
4549
|
upsert(data) {
|
|
4550
|
+
const tenantId = getTenantId();
|
|
4347
4551
|
for (const obj of stores.objects.values()) {
|
|
4348
4552
|
if (obj.system && obj.name === data.name) {
|
|
4349
4553
|
const updated = {
|
|
@@ -4361,7 +4565,7 @@ function createMockObjectsRepository(stores) {
|
|
|
4361
4565
|
}
|
|
4362
4566
|
const newObj = {
|
|
4363
4567
|
id: generateId(),
|
|
4364
|
-
tenantId
|
|
4568
|
+
tenantId,
|
|
4365
4569
|
name: data.name,
|
|
4366
4570
|
label: data.label,
|
|
4367
4571
|
pluralLabel: data.pluralLabel,
|
|
@@ -4441,28 +4645,21 @@ function createMockAttributesRepository(stores) {
|
|
|
4441
4645
|
return Promise.resolve(count);
|
|
4442
4646
|
},
|
|
4443
4647
|
upsert(data) {
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
defaultValue: data.defaultValue ?? attr.defaultValue,
|
|
4460
|
-
config: data.config ?? attr.config,
|
|
4461
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4462
|
-
};
|
|
4463
|
-
stores.attributes.set(attr.id, updated);
|
|
4464
|
-
return Promise.resolve(updated);
|
|
4465
|
-
}
|
|
4648
|
+
const existing = Array.from(stores.attributes.values()).find(
|
|
4649
|
+
(attr) => attr.objectId === data.objectId && attr.name === data.name
|
|
4650
|
+
);
|
|
4651
|
+
if (existing) {
|
|
4652
|
+
const updated = {
|
|
4653
|
+
...existing,
|
|
4654
|
+
...data,
|
|
4655
|
+
id: existing.id,
|
|
4656
|
+
objectId: existing.objectId,
|
|
4657
|
+
name: existing.name,
|
|
4658
|
+
createdAt: existing.createdAt,
|
|
4659
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
4660
|
+
};
|
|
4661
|
+
stores.attributes.set(existing.id, updated);
|
|
4662
|
+
return Promise.resolve(updated);
|
|
4466
4663
|
}
|
|
4467
4664
|
return this.create(data);
|
|
4468
4665
|
}
|
|
@@ -4481,7 +4678,8 @@ function createMockUserProfilesRepository(stores) {
|
|
|
4481
4678
|
}
|
|
4482
4679
|
return Promise.resolve(null);
|
|
4483
4680
|
},
|
|
4484
|
-
findByEmail(
|
|
4681
|
+
findByEmail(email) {
|
|
4682
|
+
const tenantId = getTenantId();
|
|
4485
4683
|
for (const profile of stores.userProfiles.values()) {
|
|
4486
4684
|
if (profile.tenantId === tenantId && profile.email === email) {
|
|
4487
4685
|
return Promise.resolve(profile);
|
|
@@ -4490,9 +4688,10 @@ function createMockUserProfilesRepository(stores) {
|
|
|
4490
4688
|
return Promise.resolve(null);
|
|
4491
4689
|
},
|
|
4492
4690
|
create(data) {
|
|
4691
|
+
const tenantId = getTenantId();
|
|
4493
4692
|
const profile = {
|
|
4494
4693
|
id: generateId(),
|
|
4495
|
-
tenantId
|
|
4694
|
+
tenantId,
|
|
4496
4695
|
authId: data.authId,
|
|
4497
4696
|
email: data.email,
|
|
4498
4697
|
firstName: data.firstName,
|
|
@@ -4523,14 +4722,16 @@ function createMockUserProfilesRepository(stores) {
|
|
|
4523
4722
|
stores.userProfiles.delete(id);
|
|
4524
4723
|
return Promise.resolve();
|
|
4525
4724
|
},
|
|
4526
|
-
list(
|
|
4725
|
+
list(options) {
|
|
4726
|
+
const tenantId = getTenantId();
|
|
4527
4727
|
let results = Array.from(stores.userProfiles.values()).filter((p) => p.tenantId === tenantId);
|
|
4528
4728
|
if (options?.limit) {
|
|
4529
4729
|
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
4530
4730
|
}
|
|
4531
4731
|
return Promise.resolve(results);
|
|
4532
4732
|
},
|
|
4533
|
-
countByRole(
|
|
4733
|
+
countByRole(role) {
|
|
4734
|
+
const tenantId = getTenantId();
|
|
4534
4735
|
const count = Array.from(stores.userProfiles.values()).filter(
|
|
4535
4736
|
(profile) => profile.tenantId === tenantId && profile.role === role
|
|
4536
4737
|
).length;
|
|
@@ -4554,9 +4755,10 @@ function createMockFilesRepository(stores) {
|
|
|
4554
4755
|
return Promise.resolve(file2 ?? null);
|
|
4555
4756
|
},
|
|
4556
4757
|
create(data) {
|
|
4758
|
+
const tenantId = getTenantId();
|
|
4557
4759
|
const file2 = {
|
|
4558
4760
|
id: generateId(),
|
|
4559
|
-
tenantId
|
|
4761
|
+
tenantId,
|
|
4560
4762
|
name: data.name,
|
|
4561
4763
|
originalName: data.originalName,
|
|
4562
4764
|
mimeType: data.mimeType,
|
|
@@ -4601,7 +4803,8 @@ function createMockFilesRepository(stores) {
|
|
|
4601
4803
|
stores.files.delete(id);
|
|
4602
4804
|
return Promise.resolve();
|
|
4603
4805
|
},
|
|
4604
|
-
list(
|
|
4806
|
+
list(options) {
|
|
4807
|
+
const tenantId = getTenantId();
|
|
4605
4808
|
let results = Array.from(stores.files.values()).filter(
|
|
4606
4809
|
(f) => f.tenantId === tenantId && !f.deletedAt
|
|
4607
4810
|
);
|
|
@@ -4613,7 +4816,8 @@ function createMockFilesRepository(stores) {
|
|
|
4613
4816
|
}
|
|
4614
4817
|
return Promise.resolve(results);
|
|
4615
4818
|
},
|
|
4616
|
-
findByFolder(
|
|
4819
|
+
findByFolder(folderPath) {
|
|
4820
|
+
const tenantId = getTenantId();
|
|
4617
4821
|
const results = Array.from(stores.files.values()).filter(
|
|
4618
4822
|
(f) => f.tenantId === tenantId && f.folderPath === folderPath && !f.deletedAt
|
|
4619
4823
|
);
|
|
@@ -4660,19 +4864,23 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4660
4864
|
return Promise.resolve(results);
|
|
4661
4865
|
},
|
|
4662
4866
|
create(data) {
|
|
4867
|
+
const tenantId = getTenantId();
|
|
4663
4868
|
const internal = {
|
|
4664
4869
|
id: generateId(),
|
|
4665
|
-
tenantId
|
|
4870
|
+
tenantId,
|
|
4666
4871
|
objectId: data.objectId,
|
|
4667
4872
|
label: data.label,
|
|
4668
4873
|
completionStatus: data.completionStatus,
|
|
4669
4874
|
values: data.data,
|
|
4670
4875
|
metadata: data.metadata ?? {},
|
|
4671
4876
|
createdAt: /* @__PURE__ */ new Date(),
|
|
4672
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4877
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
4878
|
+
createdBy: data.createdBy,
|
|
4879
|
+
lastUpdatedBy: data.createdBy
|
|
4880
|
+
// Same as createdBy on initial creation
|
|
4673
4881
|
};
|
|
4674
4882
|
stores.objectRecords.set(internal.id, internal);
|
|
4675
|
-
const { tenantId:
|
|
4883
|
+
const { tenantId: _t, ...record } = internal;
|
|
4676
4884
|
return Promise.resolve(record);
|
|
4677
4885
|
},
|
|
4678
4886
|
update(id, data) {
|
|
@@ -4680,7 +4888,7 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4680
4888
|
if (!existing) {
|
|
4681
4889
|
return Promise.reject(new Error(`ObjectRecord ${id} not found`));
|
|
4682
4890
|
}
|
|
4683
|
-
const { __completionStatus, __label, __metadata, ...valueData } = data;
|
|
4891
|
+
const { __completionStatus, __label, __metadata, __lastUpdatedBy, ...valueData } = data;
|
|
4684
4892
|
const updated = {
|
|
4685
4893
|
...existing,
|
|
4686
4894
|
label: __label ?? existing.label,
|
|
@@ -4688,7 +4896,8 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4688
4896
|
// Metadata uses replace behavior (not merge)
|
|
4689
4897
|
metadata: __metadata !== void 0 ? __metadata : existing.metadata,
|
|
4690
4898
|
values: { ...existing.values, ...valueData },
|
|
4691
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
4899
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
4900
|
+
lastUpdatedBy: __lastUpdatedBy ?? existing.lastUpdatedBy
|
|
4692
4901
|
};
|
|
4693
4902
|
stores.objectRecords.set(id, updated);
|
|
4694
4903
|
const { tenantId: _, ...record } = updated;
|
|
@@ -4698,7 +4907,8 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4698
4907
|
stores.objectRecords.delete(id);
|
|
4699
4908
|
return Promise.resolve();
|
|
4700
4909
|
},
|
|
4701
|
-
list(
|
|
4910
|
+
list(objectId, options) {
|
|
4911
|
+
const tenantId = getTenantId();
|
|
4702
4912
|
let results = Array.from(stores.objectRecords.values()).filter(
|
|
4703
4913
|
(r) => r.tenantId === tenantId && r.objectId === objectId
|
|
4704
4914
|
);
|
|
@@ -4706,10 +4916,11 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4706
4916
|
if (options?.limit) {
|
|
4707
4917
|
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
4708
4918
|
}
|
|
4709
|
-
const records = results.map(({ tenantId:
|
|
4919
|
+
const records = results.map(({ tenantId: _t, ...r }) => r);
|
|
4710
4920
|
return Promise.resolve({ records, total });
|
|
4711
4921
|
},
|
|
4712
|
-
search(
|
|
4922
|
+
search(objectId, query, options) {
|
|
4923
|
+
const tenantId = getTenantId();
|
|
4713
4924
|
const lowerQuery = query.toLowerCase();
|
|
4714
4925
|
let results = Array.from(stores.objectRecords.values()).filter((r) => {
|
|
4715
4926
|
if (r.tenantId !== tenantId || r.objectId !== objectId) return false;
|
|
@@ -4721,10 +4932,12 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4721
4932
|
if (options?.limit) {
|
|
4722
4933
|
results = results.slice(options.offset ?? 0, (options.offset ?? 0) + options.limit);
|
|
4723
4934
|
}
|
|
4724
|
-
const records = results.map(({ tenantId:
|
|
4935
|
+
const records = results.map(({ tenantId: _t, ...r }) => r);
|
|
4725
4936
|
return Promise.resolve({ records, total });
|
|
4726
4937
|
},
|
|
4727
|
-
|
|
4938
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a mock adapter, we don't need to worry about complexity
|
|
4939
|
+
globalSearch(query, options) {
|
|
4940
|
+
const tenantId = getTenantId();
|
|
4728
4941
|
const lowerQuery = query.toLowerCase();
|
|
4729
4942
|
const objectsMap = /* @__PURE__ */ new Map();
|
|
4730
4943
|
for (const obj of stores.objects.values()) {
|
|
@@ -4795,6 +5008,7 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4795
5008
|
// -------------------------------------------------------------------------
|
|
4796
5009
|
// Schema Integrity Methods
|
|
4797
5010
|
// -------------------------------------------------------------------------
|
|
5011
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a mock adapter, we don't need to worry about complexity
|
|
4798
5012
|
countRecordsReferencingId(targetId) {
|
|
4799
5013
|
const relationAttrs = [];
|
|
4800
5014
|
for (const attr of stores.attributes.values()) {
|
|
@@ -4871,6 +5085,7 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4871
5085
|
}
|
|
4872
5086
|
return Promise.resolve(updated);
|
|
4873
5087
|
},
|
|
5088
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a mock adapter, we don't need to worry about complexity
|
|
4874
5089
|
findByRelation(objectName, relationAttributeName, targetRecordId) {
|
|
4875
5090
|
const obj = Array.from(stores.objects.values()).find((o) => o.name === objectName);
|
|
4876
5091
|
if (!obj) return Promise.resolve([]);
|
|
@@ -4898,21 +5113,24 @@ function createMockViewsRepository(stores) {
|
|
|
4898
5113
|
findById(id) {
|
|
4899
5114
|
return Promise.resolve(stores.views.get(id) ?? null);
|
|
4900
5115
|
},
|
|
4901
|
-
findByName(
|
|
5116
|
+
findByName(objectName, viewName) {
|
|
5117
|
+
const tenantId = getTenantId();
|
|
4902
5118
|
return Promise.resolve(
|
|
4903
5119
|
Array.from(stores.views.values()).find(
|
|
4904
5120
|
(v) => v.tenantId === tenantId && v.objectName === objectName && v.name === viewName
|
|
4905
5121
|
) ?? null
|
|
4906
5122
|
);
|
|
4907
5123
|
},
|
|
4908
|
-
findByObjectName(
|
|
5124
|
+
findByObjectName(objectName) {
|
|
5125
|
+
const tenantId = getTenantId();
|
|
4909
5126
|
return Promise.resolve(
|
|
4910
5127
|
Array.from(stores.views.values()).filter(
|
|
4911
5128
|
(v) => v.tenantId === tenantId && v.objectName === objectName
|
|
4912
5129
|
)
|
|
4913
5130
|
);
|
|
4914
5131
|
},
|
|
4915
|
-
findAllForTenant(
|
|
5132
|
+
findAllForTenant() {
|
|
5133
|
+
const tenantId = getTenantId();
|
|
4916
5134
|
return Promise.resolve(
|
|
4917
5135
|
Array.from(stores.views.values()).filter((v) => v.tenantId === tenantId)
|
|
4918
5136
|
);
|
|
@@ -4930,11 +5148,12 @@ function createMockViewsRepository(stores) {
|
|
|
4930
5148
|
);
|
|
4931
5149
|
},
|
|
4932
5150
|
create(data) {
|
|
5151
|
+
const tenantId = getTenantId();
|
|
4933
5152
|
const id = generateId();
|
|
4934
5153
|
const now = /* @__PURE__ */ new Date();
|
|
4935
5154
|
const dbView = {
|
|
4936
5155
|
id,
|
|
4937
|
-
tenantId
|
|
5156
|
+
tenantId,
|
|
4938
5157
|
objectName: data.objectName,
|
|
4939
5158
|
name: data.name,
|
|
4940
5159
|
label: data.label,
|
|
@@ -4978,7 +5197,7 @@ function createMockViewsRepository(stores) {
|
|
|
4978
5197
|
return Promise.resolve(deleted);
|
|
4979
5198
|
},
|
|
4980
5199
|
async upsert(data) {
|
|
4981
|
-
const existing = data.system ? await this.findSystemByName(data.objectName, data.name) : await this.findByName(data.
|
|
5200
|
+
const existing = data.system ? await this.findSystemByName(data.objectName, data.name) : await this.findByName(data.objectName, data.name);
|
|
4982
5201
|
if (existing) {
|
|
4983
5202
|
return this.update(existing.id, {
|
|
4984
5203
|
label: data.label,
|
|
@@ -4995,14 +5214,16 @@ function createMockViewsRepository(stores) {
|
|
|
4995
5214
|
}
|
|
4996
5215
|
function createMockPermissionsRepository(stores) {
|
|
4997
5216
|
return {
|
|
4998
|
-
|
|
5217
|
+
getRoles() {
|
|
5218
|
+
const tenantId = getTenantId();
|
|
4999
5219
|
const results = Array.from(stores.roles.values()).filter((r) => r.tenantId === tenantId);
|
|
5000
5220
|
return Promise.resolve(results);
|
|
5001
5221
|
},
|
|
5002
5222
|
getRoleById(roleId) {
|
|
5003
5223
|
return Promise.resolve(stores.roles.get(roleId) ?? null);
|
|
5004
5224
|
},
|
|
5005
|
-
getRoleByName(
|
|
5225
|
+
getRoleByName(name) {
|
|
5226
|
+
const tenantId = getTenantId();
|
|
5006
5227
|
for (const role of stores.roles.values()) {
|
|
5007
5228
|
if (role.tenantId === tenantId && role.name === name) {
|
|
5008
5229
|
return Promise.resolve(role);
|
|
@@ -5011,9 +5232,10 @@ function createMockPermissionsRepository(stores) {
|
|
|
5011
5232
|
return Promise.resolve(null);
|
|
5012
5233
|
},
|
|
5013
5234
|
createRole(input) {
|
|
5235
|
+
const tenantId = getTenantId();
|
|
5014
5236
|
const role = {
|
|
5015
5237
|
id: generateId(),
|
|
5016
|
-
tenantId
|
|
5238
|
+
tenantId,
|
|
5017
5239
|
name: input.name,
|
|
5018
5240
|
label: input.label,
|
|
5019
5241
|
description: input.description,
|
|
@@ -5079,7 +5301,8 @@ function createMockPermissionsRepository(stores) {
|
|
|
5079
5301
|
}
|
|
5080
5302
|
return Promise.resolve();
|
|
5081
5303
|
},
|
|
5082
|
-
getUserRoles(userProfileId
|
|
5304
|
+
getUserRoles(userProfileId) {
|
|
5305
|
+
const tenantId = getTenantId();
|
|
5083
5306
|
const roleIds = Array.from(stores.userRoles.values()).filter((ur) => ur.userProfileId === userProfileId && ur.tenantId === tenantId).map((ur) => ur.roleId);
|
|
5084
5307
|
const roles = Array.from(stores.roles.values()).filter((r) => roleIds.includes(r.id));
|
|
5085
5308
|
return Promise.resolve(roles);
|
|
@@ -5101,7 +5324,8 @@ function createMockPermissionsRepository(stores) {
|
|
|
5101
5324
|
stores.userRoles.set(userRole.id, userRole);
|
|
5102
5325
|
return Promise.resolve(userRole);
|
|
5103
5326
|
},
|
|
5104
|
-
revokeRole(userProfileId, roleId
|
|
5327
|
+
revokeRole(userProfileId, roleId) {
|
|
5328
|
+
const tenantId = getTenantId();
|
|
5105
5329
|
for (const [id, ur] of stores.userRoles.entries()) {
|
|
5106
5330
|
if (ur.userProfileId === userProfileId && ur.roleId === roleId && ur.tenantId === tenantId) {
|
|
5107
5331
|
stores.userRoles.delete(id);
|
|
@@ -5110,7 +5334,9 @@ function createMockPermissionsRepository(stores) {
|
|
|
5110
5334
|
}
|
|
5111
5335
|
return Promise.resolve();
|
|
5112
5336
|
},
|
|
5113
|
-
|
|
5337
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: This is a mock adapter, we don't need to worry about complexity
|
|
5338
|
+
getEffectivePermissions(userProfileId) {
|
|
5339
|
+
const tenantId = getTenantId();
|
|
5114
5340
|
const userRoleIds = Array.from(stores.userRoles.values()).filter((ur) => ur.userProfileId === userProfileId && ur.tenantId === tenantId).map((ur) => ur.roleId);
|
|
5115
5341
|
const userRoles = Array.from(stores.roles.values()).filter((r) => userRoleIds.includes(r.id));
|
|
5116
5342
|
const isAdmin = userRoles.some((r) => r.name === "admin");
|
|
@@ -5185,6 +5411,148 @@ function createMockAdapter() {
|
|
|
5185
5411
|
return adapter;
|
|
5186
5412
|
}
|
|
5187
5413
|
|
|
5414
|
+
// src/runtime/policies/policy.registry.ts
|
|
5415
|
+
var PolicyRegistry = class {
|
|
5416
|
+
constructor() {
|
|
5417
|
+
this.policies = /* @__PURE__ */ new Map();
|
|
5418
|
+
}
|
|
5419
|
+
/**
|
|
5420
|
+
* Register a policy for an object type.
|
|
5421
|
+
* Overwrites any existing policy for the same object.
|
|
5422
|
+
*
|
|
5423
|
+
* @param policy - The policy to register
|
|
5424
|
+
*/
|
|
5425
|
+
register(policy) {
|
|
5426
|
+
this.policies.set(policy.objectName, policy);
|
|
5427
|
+
}
|
|
5428
|
+
/**
|
|
5429
|
+
* Unregister a policy for an object type.
|
|
5430
|
+
*
|
|
5431
|
+
* @param objectName - The object name to unregister
|
|
5432
|
+
* @returns true if a policy was removed, false if none existed
|
|
5433
|
+
*/
|
|
5434
|
+
unregister(objectName) {
|
|
5435
|
+
return this.policies.delete(objectName);
|
|
5436
|
+
}
|
|
5437
|
+
/**
|
|
5438
|
+
* Get the policy for an object type.
|
|
5439
|
+
*
|
|
5440
|
+
* @param objectName - The object name to look up
|
|
5441
|
+
* @returns The policy if registered, undefined otherwise
|
|
5442
|
+
*/
|
|
5443
|
+
get(objectName) {
|
|
5444
|
+
return this.policies.get(objectName);
|
|
5445
|
+
}
|
|
5446
|
+
/**
|
|
5447
|
+
* Check if a policy is registered for an object type.
|
|
5448
|
+
*
|
|
5449
|
+
* @param objectName - The object name to check
|
|
5450
|
+
* @returns true if a policy exists
|
|
5451
|
+
*/
|
|
5452
|
+
has(objectName) {
|
|
5453
|
+
return this.policies.has(objectName);
|
|
5454
|
+
}
|
|
5455
|
+
/**
|
|
5456
|
+
* Get all registered object names.
|
|
5457
|
+
*/
|
|
5458
|
+
listObjectNames() {
|
|
5459
|
+
return Array.from(this.policies.keys());
|
|
5460
|
+
}
|
|
5461
|
+
/**
|
|
5462
|
+
* Clear all registered policies.
|
|
5463
|
+
*/
|
|
5464
|
+
clear() {
|
|
5465
|
+
this.policies.clear();
|
|
5466
|
+
}
|
|
5467
|
+
};
|
|
5468
|
+
var defaultPolicyRegistry = new PolicyRegistry();
|
|
5469
|
+
|
|
5470
|
+
// src/runtime/policies/notes.policy.ts
|
|
5471
|
+
var notesPolicy = {
|
|
5472
|
+
objectName: "notes",
|
|
5473
|
+
/**
|
|
5474
|
+
* Inject visibility filter for list/search operations.
|
|
5475
|
+
*
|
|
5476
|
+
* Creates: (visibility = 'shared') OR (createdBy = userId)
|
|
5477
|
+
*
|
|
5478
|
+
* This filters at the SQL level for better performance.
|
|
5479
|
+
* canAccessRecord provides defense-in-depth as a secondary check.
|
|
5480
|
+
*
|
|
5481
|
+
* Note: When user filters exist, we preserve linkedTo filter (common for notes)
|
|
5482
|
+
* but the visibility OR filter takes precedence. Full filter merging would require
|
|
5483
|
+
* AdvancedFilterState support in the adapter.
|
|
5484
|
+
*/
|
|
5485
|
+
applyListFilter(ctx, options) {
|
|
5486
|
+
const visibilityRules = [
|
|
5487
|
+
{ attribute: "visibility", operator: "is", value: "shared" },
|
|
5488
|
+
{ attribute: "createdBy", operator: "is", value: ctx.userId }
|
|
5489
|
+
];
|
|
5490
|
+
if (!options?.filters || options.filters.rules.length === 0) {
|
|
5491
|
+
return {
|
|
5492
|
+
...options,
|
|
5493
|
+
filters: { combinator: "or", rules: visibilityRules }
|
|
5494
|
+
};
|
|
5495
|
+
}
|
|
5496
|
+
const linkedToFilter = options.filters.rules.find((r) => r.attribute === "linkedTo");
|
|
5497
|
+
if (linkedToFilter) {
|
|
5498
|
+
return {
|
|
5499
|
+
...options,
|
|
5500
|
+
filters: {
|
|
5501
|
+
combinator: "and",
|
|
5502
|
+
rules: [linkedToFilter]
|
|
5503
|
+
}
|
|
5504
|
+
};
|
|
5505
|
+
}
|
|
5506
|
+
return {
|
|
5507
|
+
...options,
|
|
5508
|
+
filters: { combinator: "or", rules: visibilityRules }
|
|
5509
|
+
};
|
|
5510
|
+
},
|
|
5511
|
+
/**
|
|
5512
|
+
* Check if user can access a specific note.
|
|
5513
|
+
*
|
|
5514
|
+
* Rules:
|
|
5515
|
+
* - Shared notes: accessible by anyone in the tenant
|
|
5516
|
+
* - Private notes: only accessible by the author (createdBy)
|
|
5517
|
+
*/
|
|
5518
|
+
canAccessRecord(ctx, record) {
|
|
5519
|
+
const visibility = record.values.visibility;
|
|
5520
|
+
if (visibility === "shared") {
|
|
5521
|
+
return true;
|
|
5522
|
+
}
|
|
5523
|
+
if (visibility === "private") {
|
|
5524
|
+
return record.createdBy === ctx.userId;
|
|
5525
|
+
}
|
|
5526
|
+
return false;
|
|
5527
|
+
},
|
|
5528
|
+
/**
|
|
5529
|
+
* Check if user can modify a note.
|
|
5530
|
+
*
|
|
5531
|
+
* Rules:
|
|
5532
|
+
* - Private notes: only the author can modify
|
|
5533
|
+
* - Shared notes: anyone can modify
|
|
5534
|
+
*/
|
|
5535
|
+
canModifyRecord(ctx, record) {
|
|
5536
|
+
const visibility = record.values.visibility;
|
|
5537
|
+
if (visibility === "private") {
|
|
5538
|
+
return record.createdBy === ctx.userId;
|
|
5539
|
+
}
|
|
5540
|
+
return true;
|
|
5541
|
+
},
|
|
5542
|
+
/**
|
|
5543
|
+
* Check if user can delete a note.
|
|
5544
|
+
*
|
|
5545
|
+
* Rules:
|
|
5546
|
+
* - Only the author (createdBy) can delete a note, regardless of visibility
|
|
5547
|
+
*/
|
|
5548
|
+
canDeleteRecord(ctx, record) {
|
|
5549
|
+
return record.createdBy === ctx.userId;
|
|
5550
|
+
}
|
|
5551
|
+
};
|
|
5552
|
+
|
|
5553
|
+
// src/runtime/policies/index.ts
|
|
5554
|
+
defaultPolicyRegistry.register(notesPolicy);
|
|
5555
|
+
|
|
5188
5556
|
// src/runtime/services/audit.helpers.ts
|
|
5189
5557
|
function buildAuditChanges(oldValues, newValues, fieldsToCheck) {
|
|
5190
5558
|
const changes = [];
|
|
@@ -5200,6 +5568,40 @@ function buildAuditChanges(oldValues, newValues, fieldsToCheck) {
|
|
|
5200
5568
|
return changes;
|
|
5201
5569
|
}
|
|
5202
5570
|
|
|
5571
|
+
// src/runtime/services/base.service.ts
|
|
5572
|
+
var TenantAwareService = class {
|
|
5573
|
+
/**
|
|
5574
|
+
* Get the current tenant ID from execution context.
|
|
5575
|
+
* @throws TenantContextError if no context is set
|
|
5576
|
+
*/
|
|
5577
|
+
get tenantId() {
|
|
5578
|
+
return getTenantId();
|
|
5579
|
+
}
|
|
5580
|
+
/**
|
|
5581
|
+
* Get the current user ID from execution context, if set.
|
|
5582
|
+
* Returns undefined if no user is associated with the current context.
|
|
5583
|
+
*/
|
|
5584
|
+
get userId() {
|
|
5585
|
+
return getUserId();
|
|
5586
|
+
}
|
|
5587
|
+
};
|
|
5588
|
+
var TenantAwareRepository = class {
|
|
5589
|
+
/**
|
|
5590
|
+
* Get the current tenant ID from execution context.
|
|
5591
|
+
* @throws TenantContextError if no context is set
|
|
5592
|
+
*/
|
|
5593
|
+
get tenantId() {
|
|
5594
|
+
return getTenantId();
|
|
5595
|
+
}
|
|
5596
|
+
/**
|
|
5597
|
+
* Get the current user ID from execution context, if set.
|
|
5598
|
+
* Returns undefined if no user is associated with the current context.
|
|
5599
|
+
*/
|
|
5600
|
+
get userId() {
|
|
5601
|
+
return getUserId();
|
|
5602
|
+
}
|
|
5603
|
+
};
|
|
5604
|
+
|
|
5203
5605
|
// src/runtime/services/audit.service.ts
|
|
5204
5606
|
var SENSITIVE_PATTERNS = [
|
|
5205
5607
|
/password/i,
|
|
@@ -5212,10 +5614,10 @@ var SENSITIVE_PATTERNS = [
|
|
|
5212
5614
|
/ssn/i,
|
|
5213
5615
|
/cvv/i
|
|
5214
5616
|
];
|
|
5215
|
-
var AuditService = class {
|
|
5216
|
-
constructor(adapter,
|
|
5617
|
+
var AuditService = class extends TenantAwareService {
|
|
5618
|
+
constructor(adapter, options) {
|
|
5619
|
+
super();
|
|
5217
5620
|
this.adapter = adapter;
|
|
5218
|
-
this.tenantId = tenantId;
|
|
5219
5621
|
this.options = options;
|
|
5220
5622
|
this.buffer = [];
|
|
5221
5623
|
this.flushTimer = null;
|
|
@@ -5235,9 +5637,7 @@ var AuditService = class {
|
|
|
5235
5637
|
*/
|
|
5236
5638
|
async logRecordAction(params) {
|
|
5237
5639
|
const entry = {
|
|
5238
|
-
tenantId: this.tenantId,
|
|
5239
5640
|
actorId: params.actorId,
|
|
5240
|
-
actorEmail: params.actorEmail,
|
|
5241
5641
|
actorType: "user",
|
|
5242
5642
|
action: params.action,
|
|
5243
5643
|
resourceType: "record",
|
|
@@ -5255,9 +5655,7 @@ var AuditService = class {
|
|
|
5255
5655
|
*/
|
|
5256
5656
|
async logSchemaAction(params) {
|
|
5257
5657
|
const entry = {
|
|
5258
|
-
tenantId: this.tenantId,
|
|
5259
5658
|
actorId: params.actorId,
|
|
5260
|
-
actorEmail: params.actorEmail,
|
|
5261
5659
|
actorType: "user",
|
|
5262
5660
|
action: params.action,
|
|
5263
5661
|
resourceType: params.resourceType,
|
|
@@ -5275,9 +5673,7 @@ var AuditService = class {
|
|
|
5275
5673
|
*/
|
|
5276
5674
|
async logUserAction(params) {
|
|
5277
5675
|
const entry = {
|
|
5278
|
-
tenantId: this.tenantId,
|
|
5279
5676
|
actorId: params.actorId,
|
|
5280
|
-
actorEmail: params.actorEmail,
|
|
5281
5677
|
actorType: "user",
|
|
5282
5678
|
action: params.action,
|
|
5283
5679
|
resourceType: "user",
|
|
@@ -5293,9 +5689,7 @@ var AuditService = class {
|
|
|
5293
5689
|
*/
|
|
5294
5690
|
async logRoleAction(params) {
|
|
5295
5691
|
const entry = {
|
|
5296
|
-
tenantId: this.tenantId,
|
|
5297
5692
|
actorId: params.actorId,
|
|
5298
|
-
actorEmail: params.actorEmail,
|
|
5299
5693
|
actorType: "user",
|
|
5300
5694
|
action: params.action,
|
|
5301
5695
|
resourceType: "role",
|
|
@@ -5314,9 +5708,7 @@ var AuditService = class {
|
|
|
5314
5708
|
*/
|
|
5315
5709
|
async logFileAction(params) {
|
|
5316
5710
|
const entry = {
|
|
5317
|
-
tenantId: this.tenantId,
|
|
5318
5711
|
actorId: params.actorId,
|
|
5319
|
-
actorEmail: params.actorEmail,
|
|
5320
5712
|
actorType: "user",
|
|
5321
5713
|
action: params.action,
|
|
5322
5714
|
resourceType: "file",
|
|
@@ -5332,7 +5724,6 @@ var AuditService = class {
|
|
|
5332
5724
|
*/
|
|
5333
5725
|
async logSystemAction(params) {
|
|
5334
5726
|
const entry = {
|
|
5335
|
-
tenantId: this.tenantId,
|
|
5336
5727
|
actorType: "system",
|
|
5337
5728
|
action: params.action,
|
|
5338
5729
|
resourceType: params.resourceType,
|
|
@@ -5355,7 +5746,7 @@ var AuditService = class {
|
|
|
5355
5746
|
if (!this.adapter.audit) {
|
|
5356
5747
|
return { entries: [], total: 0 };
|
|
5357
5748
|
}
|
|
5358
|
-
return await this.adapter.audit.list(
|
|
5749
|
+
return await this.adapter.audit.list(options);
|
|
5359
5750
|
}
|
|
5360
5751
|
/**
|
|
5361
5752
|
* Get audit history for a specific record
|
|
@@ -5364,7 +5755,7 @@ var AuditService = class {
|
|
|
5364
5755
|
if (!this.adapter.audit) {
|
|
5365
5756
|
return [];
|
|
5366
5757
|
}
|
|
5367
|
-
return await this.adapter.audit.getByResource(
|
|
5758
|
+
return await this.adapter.audit.getByResource("record", recordId, {
|
|
5368
5759
|
orderDirection: "desc"
|
|
5369
5760
|
});
|
|
5370
5761
|
}
|
|
@@ -5375,7 +5766,7 @@ var AuditService = class {
|
|
|
5375
5766
|
if (!this.adapter.audit) {
|
|
5376
5767
|
return [];
|
|
5377
5768
|
}
|
|
5378
|
-
return await this.adapter.audit.getByActor(
|
|
5769
|
+
return await this.adapter.audit.getByActor(userId, options);
|
|
5379
5770
|
}
|
|
5380
5771
|
/**
|
|
5381
5772
|
* Get audit logs for a specific resource
|
|
@@ -5384,7 +5775,7 @@ var AuditService = class {
|
|
|
5384
5775
|
if (!this.adapter.audit) {
|
|
5385
5776
|
return [];
|
|
5386
5777
|
}
|
|
5387
|
-
return await this.adapter.audit.getByResource(
|
|
5778
|
+
return await this.adapter.audit.getByResource(resourceType, resourceId, {
|
|
5388
5779
|
orderDirection: "desc"
|
|
5389
5780
|
});
|
|
5390
5781
|
}
|
|
@@ -5431,12 +5822,6 @@ var AuditService = class {
|
|
|
5431
5822
|
if (!this.adapter.audit) {
|
|
5432
5823
|
return;
|
|
5433
5824
|
}
|
|
5434
|
-
if (entry.actorId && !entry.actorEmail && entry.actorType === "user") {
|
|
5435
|
-
const profile = await this.adapter.userProfiles.findById(entry.actorId);
|
|
5436
|
-
if (profile) {
|
|
5437
|
-
entry.actorEmail = profile.email;
|
|
5438
|
-
}
|
|
5439
|
-
}
|
|
5440
5825
|
if (this.options?.async) {
|
|
5441
5826
|
this.buffer.push(entry);
|
|
5442
5827
|
const batchSize = this.options.batchSize ?? 10;
|
|
@@ -5475,13 +5860,11 @@ var AuditService = class {
|
|
|
5475
5860
|
};
|
|
5476
5861
|
|
|
5477
5862
|
// src/runtime/services/file.service.ts
|
|
5478
|
-
var FileService = class {
|
|
5479
|
-
constructor(adapter,
|
|
5863
|
+
var FileService = class extends TenantAwareService {
|
|
5864
|
+
constructor(adapter, options) {
|
|
5865
|
+
super();
|
|
5480
5866
|
this.adapter = adapter;
|
|
5481
|
-
this.
|
|
5482
|
-
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter, tenantId) : void 0);
|
|
5483
|
-
this.userId = options?.userId;
|
|
5484
|
-
this.userEmail = options?.userEmail;
|
|
5867
|
+
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter) : void 0);
|
|
5485
5868
|
}
|
|
5486
5869
|
// ============================================================================
|
|
5487
5870
|
// UPLOAD (requires StorageAdapter)
|
|
@@ -5529,7 +5912,6 @@ var FileService = class {
|
|
|
5529
5912
|
folderPath: input.folderPath
|
|
5530
5913
|
});
|
|
5531
5914
|
const file2 = await this.adapter.files.create({
|
|
5532
|
-
tenantId: this.tenantId,
|
|
5533
5915
|
name: input.fileName,
|
|
5534
5916
|
originalName: input.fileName,
|
|
5535
5917
|
mimeType: input.mimeType,
|
|
@@ -5548,7 +5930,6 @@ var FileService = class {
|
|
|
5548
5930
|
await this.auditService.logFileAction({
|
|
5549
5931
|
action: "file.uploaded",
|
|
5550
5932
|
actorId: this.userId,
|
|
5551
|
-
actorEmail: this.userEmail,
|
|
5552
5933
|
fileId: file2.id,
|
|
5553
5934
|
fileName: file2.name,
|
|
5554
5935
|
metadata: {
|
|
@@ -5591,17 +5972,11 @@ var FileService = class {
|
|
|
5591
5972
|
* ```
|
|
5592
5973
|
*/
|
|
5593
5974
|
async createFile(data) {
|
|
5594
|
-
if (data.tenantId !== this.tenantId) {
|
|
5595
|
-
throw new Error(
|
|
5596
|
-
`Tenant mismatch: service initialized with "${this.tenantId}" but data has "${data.tenantId}"`
|
|
5597
|
-
);
|
|
5598
|
-
}
|
|
5599
5975
|
const file2 = await this.adapter.files.create(data);
|
|
5600
5976
|
if (this.auditService && this.userId) {
|
|
5601
5977
|
await this.auditService.logFileAction({
|
|
5602
5978
|
action: "file.uploaded",
|
|
5603
5979
|
actorId: this.userId,
|
|
5604
|
-
actorEmail: this.userEmail,
|
|
5605
5980
|
fileId: file2.id,
|
|
5606
5981
|
fileName: file2.name,
|
|
5607
5982
|
metadata: {
|
|
@@ -5664,7 +6039,6 @@ var FileService = class {
|
|
|
5664
6039
|
await this.auditService.logFileAction({
|
|
5665
6040
|
action: "file.updated",
|
|
5666
6041
|
actorId: this.userId,
|
|
5667
|
-
actorEmail: this.userEmail,
|
|
5668
6042
|
fileId,
|
|
5669
6043
|
fileName: updatedFile.name,
|
|
5670
6044
|
changes
|
|
@@ -5698,7 +6072,6 @@ var FileService = class {
|
|
|
5698
6072
|
await this.auditService.logFileAction({
|
|
5699
6073
|
action: "file.deleted",
|
|
5700
6074
|
actorId: this.userId,
|
|
5701
|
-
actorEmail: this.userEmail,
|
|
5702
6075
|
fileId,
|
|
5703
6076
|
fileName: file2.name
|
|
5704
6077
|
});
|
|
@@ -5730,7 +6103,6 @@ var FileService = class {
|
|
|
5730
6103
|
await this.auditService.logFileAction({
|
|
5731
6104
|
action: "file.deleted",
|
|
5732
6105
|
actorId: this.userId,
|
|
5733
|
-
actorEmail: this.userEmail,
|
|
5734
6106
|
fileId,
|
|
5735
6107
|
fileName: file2.name,
|
|
5736
6108
|
metadata: { deletedFromStorage: true }
|
|
@@ -5761,7 +6133,6 @@ var FileService = class {
|
|
|
5761
6133
|
await this.auditService.logFileAction({
|
|
5762
6134
|
action: "file.deleted",
|
|
5763
6135
|
actorId: this.userId,
|
|
5764
|
-
actorEmail: this.userEmail,
|
|
5765
6136
|
fileId,
|
|
5766
6137
|
fileName: file2.name,
|
|
5767
6138
|
metadata: { deletedFromStorage: options?.deleteFromStorage ?? false }
|
|
@@ -5776,13 +6147,13 @@ var FileService = class {
|
|
|
5776
6147
|
* List files for the tenant
|
|
5777
6148
|
*/
|
|
5778
6149
|
async listFiles(options) {
|
|
5779
|
-
return await this.adapter.files.list(
|
|
6150
|
+
return await this.adapter.files.list(options);
|
|
5780
6151
|
}
|
|
5781
6152
|
/**
|
|
5782
6153
|
* List files by folder
|
|
5783
6154
|
*/
|
|
5784
6155
|
async listFilesByFolder(folderPath) {
|
|
5785
|
-
return await this.adapter.files.findByFolder(
|
|
6156
|
+
return await this.adapter.files.findByFolder(folderPath);
|
|
5786
6157
|
}
|
|
5787
6158
|
/**
|
|
5788
6159
|
* List files uploaded by a specific user
|
|
@@ -5931,34 +6302,35 @@ var FileService = class {
|
|
|
5931
6302
|
};
|
|
5932
6303
|
|
|
5933
6304
|
// src/runtime/services/flow.service.ts
|
|
5934
|
-
var FlowService = class {
|
|
6305
|
+
var FlowService = class extends TenantAwareService {
|
|
5935
6306
|
constructor(adapter, systemFlows = []) {
|
|
6307
|
+
super();
|
|
5936
6308
|
this.adapter = adapter;
|
|
5937
6309
|
this.systemFlows = new Map(systemFlows.map((f) => [f.name, f]));
|
|
5938
6310
|
}
|
|
5939
6311
|
/**
|
|
5940
|
-
* Get all flows for
|
|
6312
|
+
* Get all flows for current tenant (system + custom)
|
|
5941
6313
|
*/
|
|
5942
|
-
async getAllFlows(
|
|
6314
|
+
async getAllFlows() {
|
|
5943
6315
|
const systemFlowsList = Array.from(this.systemFlows.values());
|
|
5944
6316
|
if (!this.adapter.flows) {
|
|
5945
6317
|
return systemFlowsList;
|
|
5946
6318
|
}
|
|
5947
|
-
const dbFlows = await this.adapter.flows.findAllForTenant(
|
|
6319
|
+
const dbFlows = await this.adapter.flows.findAllForTenant();
|
|
5948
6320
|
const customFlows = dbFlows.filter((f) => !f.system).map(this.convertDBFlowToDefinition);
|
|
5949
6321
|
return [...systemFlowsList, ...customFlows];
|
|
5950
6322
|
}
|
|
5951
6323
|
/**
|
|
5952
6324
|
* Get published flows only
|
|
5953
6325
|
*/
|
|
5954
|
-
async getPublishedFlows(
|
|
5955
|
-
const allFlows = await this.getAllFlows(
|
|
6326
|
+
async getPublishedFlows() {
|
|
6327
|
+
const allFlows = await this.getAllFlows();
|
|
5956
6328
|
return allFlows.filter((f) => f.status === "published");
|
|
5957
6329
|
}
|
|
5958
6330
|
/**
|
|
5959
6331
|
* Get a specific flow by name
|
|
5960
6332
|
*/
|
|
5961
|
-
async getFlow(name
|
|
6333
|
+
async getFlow(name) {
|
|
5962
6334
|
const systemFlow = this.systemFlows.get(name);
|
|
5963
6335
|
if (systemFlow) {
|
|
5964
6336
|
return systemFlow;
|
|
@@ -5966,7 +6338,7 @@ var FlowService = class {
|
|
|
5966
6338
|
if (!this.adapter.flows) {
|
|
5967
6339
|
return null;
|
|
5968
6340
|
}
|
|
5969
|
-
const dbFlow = await this.adapter.flows.findByName(
|
|
6341
|
+
const dbFlow = await this.adapter.flows.findByName(name);
|
|
5970
6342
|
if (dbFlow) {
|
|
5971
6343
|
return this.convertDBFlowToDefinition(dbFlow);
|
|
5972
6344
|
}
|
|
@@ -5988,7 +6360,7 @@ var FlowService = class {
|
|
|
5988
6360
|
/**
|
|
5989
6361
|
* Create a new custom flow (as draft)
|
|
5990
6362
|
*/
|
|
5991
|
-
async createFlow(input
|
|
6363
|
+
async createFlow(input) {
|
|
5992
6364
|
if (!this.adapter.flows) {
|
|
5993
6365
|
throw new SchemaError(
|
|
5994
6366
|
"Flows feature is not enabled. Database adapter does not support flows.",
|
|
@@ -5997,7 +6369,7 @@ var FlowService = class {
|
|
|
5997
6369
|
);
|
|
5998
6370
|
}
|
|
5999
6371
|
this.validateFlowName(input.name);
|
|
6000
|
-
const existing = await this.adapter.flows.findByName(
|
|
6372
|
+
const existing = await this.adapter.flows.findByName(input.name);
|
|
6001
6373
|
if (existing) {
|
|
6002
6374
|
throw new SchemaError(
|
|
6003
6375
|
`Flow "${input.name}" already exists`,
|
|
@@ -6014,7 +6386,6 @@ var FlowService = class {
|
|
|
6014
6386
|
}
|
|
6015
6387
|
this.validateFlowStructure(input);
|
|
6016
6388
|
const dbFlow = await this.adapter.flows.create({
|
|
6017
|
-
tenantId,
|
|
6018
6389
|
name: input.name,
|
|
6019
6390
|
label: input.label,
|
|
6020
6391
|
description: input.description,
|
|
@@ -6167,6 +6538,12 @@ var FlowService = class {
|
|
|
6167
6538
|
* Validate flow structure (slots, pages, relations)
|
|
6168
6539
|
*/
|
|
6169
6540
|
validateFlowStructure(input) {
|
|
6541
|
+
this.validateBasicFlowRequirements(input);
|
|
6542
|
+
const slotIds = this.validateSlotIds(input.slots);
|
|
6543
|
+
this.validateFieldReferences(input.pages, slotIds);
|
|
6544
|
+
this.validateRelationReferences(input.relations, slotIds);
|
|
6545
|
+
}
|
|
6546
|
+
validateBasicFlowRequirements(input) {
|
|
6170
6547
|
if (!input.slots || input.slots.length === 0) {
|
|
6171
6548
|
throw new ValidationError("Flow must have at least one slot", [
|
|
6172
6549
|
{ path: ["slots"], message: "Flow must have at least one slot" }
|
|
@@ -6177,13 +6554,18 @@ var FlowService = class {
|
|
|
6177
6554
|
{ path: ["pages"], message: "Flow must have at least one page" }
|
|
6178
6555
|
]);
|
|
6179
6556
|
}
|
|
6180
|
-
|
|
6181
|
-
|
|
6557
|
+
}
|
|
6558
|
+
validateSlotIds(slots) {
|
|
6559
|
+
const slotIds = new Set(slots.map((s) => s.id));
|
|
6560
|
+
if (slotIds.size !== slots.length) {
|
|
6182
6561
|
throw new ValidationError("Duplicate slot IDs detected", [
|
|
6183
6562
|
{ path: ["slots"], message: "Duplicate slot IDs detected" }
|
|
6184
6563
|
]);
|
|
6185
6564
|
}
|
|
6186
|
-
|
|
6565
|
+
return slotIds;
|
|
6566
|
+
}
|
|
6567
|
+
validateFieldReferences(pages, slotIds) {
|
|
6568
|
+
for (const page of pages) {
|
|
6187
6569
|
for (const row of page.rows) {
|
|
6188
6570
|
for (const field of row.fields) {
|
|
6189
6571
|
if (!slotIds.has(field.slotId)) {
|
|
@@ -6197,7 +6579,9 @@ var FlowService = class {
|
|
|
6197
6579
|
}
|
|
6198
6580
|
}
|
|
6199
6581
|
}
|
|
6200
|
-
|
|
6582
|
+
}
|
|
6583
|
+
validateRelationReferences(relations, slotIds) {
|
|
6584
|
+
for (const relation2 of relations) {
|
|
6201
6585
|
if (!slotIds.has(relation2.sourceSlotId)) {
|
|
6202
6586
|
throw new ValidationError("Relation references unknown source slot", [
|
|
6203
6587
|
{
|
|
@@ -6287,10 +6671,10 @@ var GeocodingService = class {
|
|
|
6287
6671
|
};
|
|
6288
6672
|
|
|
6289
6673
|
// src/runtime/services/global-search.service.ts
|
|
6290
|
-
var GlobalSearchService = class {
|
|
6291
|
-
constructor(adapter
|
|
6674
|
+
var GlobalSearchService = class extends TenantAwareService {
|
|
6675
|
+
constructor(adapter) {
|
|
6676
|
+
super();
|
|
6292
6677
|
this.adapter = adapter;
|
|
6293
|
-
this.tenantId = tenantId;
|
|
6294
6678
|
}
|
|
6295
6679
|
/**
|
|
6296
6680
|
* Search across all object records for the tenant
|
|
@@ -6320,7 +6704,7 @@ var GlobalSearchService = class {
|
|
|
6320
6704
|
if (!query || query.trim().length === 0) {
|
|
6321
6705
|
return { results: [], total: 0 };
|
|
6322
6706
|
}
|
|
6323
|
-
return await this.adapter.objectRecords.globalSearch(
|
|
6707
|
+
return await this.adapter.objectRecords.globalSearch(query.trim(), {
|
|
6324
6708
|
limit: options?.limit ?? 20,
|
|
6325
6709
|
offset: options?.offset ?? 0,
|
|
6326
6710
|
objectNames: options?.objectNames,
|
|
@@ -6364,23 +6748,22 @@ var GlobalSearchService = class {
|
|
|
6364
6748
|
};
|
|
6365
6749
|
|
|
6366
6750
|
// src/runtime/services/object-schema.service.ts
|
|
6367
|
-
var
|
|
6751
|
+
var ObjectSchemaService = class extends TenantAwareService {
|
|
6368
6752
|
constructor(adapter, nativeRegistry, options) {
|
|
6753
|
+
super();
|
|
6369
6754
|
this.adapter = adapter;
|
|
6370
6755
|
this.nativeRegistry = nativeRegistry;
|
|
6371
6756
|
this.auditService = options?.auditService;
|
|
6372
|
-
this.userId = options?.userId;
|
|
6373
|
-
this.userEmail = options?.userEmail;
|
|
6374
6757
|
}
|
|
6375
6758
|
/**
|
|
6376
|
-
* Create a new custom object
|
|
6377
|
-
* Validates name format and ensures system=false
|
|
6759
|
+
* Create a new custom object.
|
|
6760
|
+
* Validates name format and ensures system=false.
|
|
6761
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
6378
6762
|
*
|
|
6379
6763
|
* @param definition - Object definition (attributes can be raw definitions or builders)
|
|
6380
|
-
* @param tenantId - Tenant ID owning the object
|
|
6381
6764
|
* @returns Created ObjectDefinition
|
|
6382
6765
|
*/
|
|
6383
|
-
async createCustomObject(definition
|
|
6766
|
+
async createCustomObject(definition) {
|
|
6384
6767
|
const builder = object({
|
|
6385
6768
|
name: definition.name,
|
|
6386
6769
|
label: definition.label
|
|
@@ -6394,17 +6777,14 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6394
6777
|
builder.attributes(definition.attributes);
|
|
6395
6778
|
}
|
|
6396
6779
|
const objectDef = builder.build();
|
|
6397
|
-
const existing = await this.adapter.objects.findByName(
|
|
6780
|
+
const existing = await this.adapter.objects.findByName(objectDef.name);
|
|
6398
6781
|
if (existing) {
|
|
6399
6782
|
throw new Error(`Object with name "${objectDef.name}" already exists`);
|
|
6400
6783
|
}
|
|
6401
6784
|
const validatedConfigs = [];
|
|
6402
6785
|
if (objectDef.attributes.length > 0) {
|
|
6403
6786
|
for (const attr of objectDef.attributes) {
|
|
6404
|
-
const config = await this.validateAttributeInput(
|
|
6405
|
-
attr,
|
|
6406
|
-
tenantId
|
|
6407
|
-
);
|
|
6787
|
+
const config = await this.validateAttributeInput(attr);
|
|
6408
6788
|
validatedConfigs.push(config);
|
|
6409
6789
|
}
|
|
6410
6790
|
}
|
|
@@ -6414,7 +6794,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6414
6794
|
};
|
|
6415
6795
|
const { dbObject, createdAttributes } = await this.adapter.transaction(async (tx) => {
|
|
6416
6796
|
const dbObj = await tx.objects.create({
|
|
6417
|
-
tenantId,
|
|
6418
6797
|
name: objectDef.name,
|
|
6419
6798
|
label: objectDef.label,
|
|
6420
6799
|
pluralLabel: objectDef.pluralLabel,
|
|
@@ -6454,7 +6833,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6454
6833
|
await this.auditService.logSchemaAction({
|
|
6455
6834
|
action: "object.created",
|
|
6456
6835
|
actorId: this.userId,
|
|
6457
|
-
actorEmail: this.userEmail,
|
|
6458
6836
|
resourceType: "object",
|
|
6459
6837
|
resourceId: dbObject.id,
|
|
6460
6838
|
resourceLabel: dbObject.label,
|
|
@@ -6465,16 +6843,16 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6465
6843
|
return result;
|
|
6466
6844
|
}
|
|
6467
6845
|
/**
|
|
6468
|
-
* Add a custom attribute to an existing object
|
|
6469
|
-
* Can be used on both custom and native objects
|
|
6470
|
-
* Attributes added via API are always custom (system=false)
|
|
6846
|
+
* Add a custom attribute to an existing object.
|
|
6847
|
+
* Can be used on both custom and native objects.
|
|
6848
|
+
* Attributes added via API are always custom (system=false).
|
|
6849
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
6471
6850
|
*
|
|
6472
6851
|
* @param objectId - Object UUID from database
|
|
6473
6852
|
* @param attribute - Attribute definition
|
|
6474
|
-
* @param tenantId - Tenant ID (required for relation validation)
|
|
6475
6853
|
* @returns Created Attribute
|
|
6476
6854
|
*/
|
|
6477
|
-
async addAttributeToObject(objectId, attribute
|
|
6855
|
+
async addAttributeToObject(objectId, attribute) {
|
|
6478
6856
|
const dbObject = await this.adapter.objects.findById(objectId);
|
|
6479
6857
|
if (!dbObject) {
|
|
6480
6858
|
throw new Error(`Object with id "${objectId}" not found`);
|
|
@@ -6486,7 +6864,7 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6486
6864
|
`Attribute with name "${attribute.name}" already exists on object "${dbObject.name}"`
|
|
6487
6865
|
);
|
|
6488
6866
|
}
|
|
6489
|
-
const config = await this.validateAttributeInput(attribute
|
|
6867
|
+
const config = await this.validateAttributeInput(attribute);
|
|
6490
6868
|
const dbAttr = await this.adapter.attributes.create({
|
|
6491
6869
|
objectId,
|
|
6492
6870
|
name: attribute.name,
|
|
@@ -6502,7 +6880,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6502
6880
|
await this.auditService.logSchemaAction({
|
|
6503
6881
|
action: "attribute.created",
|
|
6504
6882
|
actorId: this.userId,
|
|
6505
|
-
actorEmail: this.userEmail,
|
|
6506
6883
|
resourceType: "attribute",
|
|
6507
6884
|
resourceId: dbAttr.id,
|
|
6508
6885
|
resourceLabel: dbAttr.label,
|
|
@@ -6513,15 +6890,15 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6513
6890
|
return this.convertDBAttributeToAttribute(dbAttr);
|
|
6514
6891
|
}
|
|
6515
6892
|
/**
|
|
6516
|
-
* Update an attribute
|
|
6517
|
-
* Can only update custom attributes (system=false)
|
|
6893
|
+
* Update an attribute.
|
|
6894
|
+
* Can only update custom attributes (system=false).
|
|
6895
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
6518
6896
|
*
|
|
6519
6897
|
* @param attributeId - Attribute UUID
|
|
6520
6898
|
* @param updates - Partial attribute updates
|
|
6521
|
-
* @param tenantId - Tenant ID (required for relation validation)
|
|
6522
6899
|
* @returns Updated Attribute
|
|
6523
6900
|
*/
|
|
6524
|
-
async updateAttribute(attributeId, updates
|
|
6901
|
+
async updateAttribute(attributeId, updates) {
|
|
6525
6902
|
const dbAttr = await this.adapter.attributes.findById(attributeId);
|
|
6526
6903
|
if (!dbAttr) {
|
|
6527
6904
|
throw new Error(`Attribute with id "${attributeId}" not found`);
|
|
@@ -6551,7 +6928,7 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6551
6928
|
};
|
|
6552
6929
|
const config = this.buildAttributeConfig(mergedInput);
|
|
6553
6930
|
if (dbAttr.type === "relation" && updates.targets) {
|
|
6554
|
-
await this.validateRelationTargets(mergedInput
|
|
6931
|
+
await this.validateRelationTargets(mergedInput);
|
|
6555
6932
|
}
|
|
6556
6933
|
const oldValues = {
|
|
6557
6934
|
label: dbAttr.label,
|
|
@@ -6569,7 +6946,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6569
6946
|
await this.auditService.logSchemaAction({
|
|
6570
6947
|
action: "attribute.updated",
|
|
6571
6948
|
actorId: this.userId,
|
|
6572
|
-
actorEmail: this.userEmail,
|
|
6573
6949
|
resourceType: "attribute",
|
|
6574
6950
|
resourceId: attributeId,
|
|
6575
6951
|
resourceLabel: updatedDbAttr.label,
|
|
@@ -6617,7 +6993,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6617
6993
|
await this.auditService.logSchemaAction({
|
|
6618
6994
|
action: "attribute.deleted",
|
|
6619
6995
|
actorId: this.userId,
|
|
6620
|
-
actorEmail: this.userEmail,
|
|
6621
6996
|
resourceType: "attribute",
|
|
6622
6997
|
resourceId: attributeId,
|
|
6623
6998
|
resourceLabel: dbAttr.label,
|
|
@@ -6703,7 +7078,6 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6703
7078
|
await this.auditService.logSchemaAction({
|
|
6704
7079
|
action: "object.updated",
|
|
6705
7080
|
actorId: this.userId,
|
|
6706
|
-
actorEmail: this.userEmail,
|
|
6707
7081
|
resourceType: "object",
|
|
6708
7082
|
resourceId: objectId,
|
|
6709
7083
|
resourceLabel: updatedDbObject.label,
|
|
@@ -6725,9 +7099,10 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6725
7099
|
return this.convertDBObjectToDefinition(updatedDbObject, dbAttributes);
|
|
6726
7100
|
}
|
|
6727
7101
|
/**
|
|
6728
|
-
* Delete a custom object
|
|
6729
|
-
* Can only delete custom objects (system=false)
|
|
6730
|
-
* This will also delete all associated attributes and records
|
|
7102
|
+
* Delete a custom object.
|
|
7103
|
+
* Can only delete custom objects (system=false).
|
|
7104
|
+
* This will also delete all associated attributes and records.
|
|
7105
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
6731
7106
|
*
|
|
6732
7107
|
* @param objectId - Object UUID
|
|
6733
7108
|
*
|
|
@@ -6736,7 +7111,7 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6736
7111
|
* await service.deleteObject("obj-123");
|
|
6737
7112
|
* ```
|
|
6738
7113
|
*/
|
|
6739
|
-
async deleteObject(objectId
|
|
7114
|
+
async deleteObject(objectId) {
|
|
6740
7115
|
const dbObject = await this.adapter.objects.findById(objectId);
|
|
6741
7116
|
if (!dbObject) {
|
|
6742
7117
|
throw new Error(`Object with id "${objectId}" not found`);
|
|
@@ -6746,22 +7121,15 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6746
7121
|
`Cannot delete system object "${dbObject.name}". System objects are protected.`
|
|
6747
7122
|
);
|
|
6748
7123
|
}
|
|
6749
|
-
const
|
|
6750
|
-
if (
|
|
6751
|
-
|
|
6752
|
-
dbObject.name,
|
|
6753
|
-
effectiveTenantId
|
|
6754
|
-
);
|
|
6755
|
-
if (referencingObjects.length > 0) {
|
|
6756
|
-
throw new ObjectReferencedError(dbObject.name, referencingObjects);
|
|
6757
|
-
}
|
|
7124
|
+
const referencingObjects = await this.findObjectsWithRelationTo(dbObject.name);
|
|
7125
|
+
if (referencingObjects.length > 0) {
|
|
7126
|
+
throw new ObjectReferencedError(dbObject.name, referencingObjects);
|
|
6758
7127
|
}
|
|
6759
7128
|
await this.adapter.objects.delete(objectId);
|
|
6760
7129
|
if (this.auditService && this.userId) {
|
|
6761
7130
|
await this.auditService.logSchemaAction({
|
|
6762
7131
|
action: "object.deleted",
|
|
6763
7132
|
actorId: this.userId,
|
|
6764
|
-
actorEmail: this.userEmail,
|
|
6765
7133
|
resourceType: "object",
|
|
6766
7134
|
resourceId: objectId,
|
|
6767
7135
|
resourceLabel: dbObject.label,
|
|
@@ -6787,41 +7155,29 @@ var _ObjectSchemaService = class _ObjectSchemaService {
|
|
|
6787
7155
|
"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'"
|
|
6788
7156
|
);
|
|
6789
7157
|
}
|
|
6790
|
-
if (
|
|
6791
|
-
name
|
|
6792
|
-
)) {
|
|
7158
|
+
if (RESERVED_ATTRIBUTE_NAMES.includes(name)) {
|
|
6793
7159
|
throw new Error(
|
|
6794
7160
|
`"${name}" is a reserved name and cannot be used as an attribute name.
|
|
6795
|
-
Reserved names: ${
|
|
7161
|
+
Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
6796
7162
|
);
|
|
6797
7163
|
}
|
|
6798
7164
|
}
|
|
7165
|
+
// ============================================================================
|
|
7166
|
+
// SCHEMA RETRIEVAL (DRY - all methods use buildObjectDefinition)
|
|
7167
|
+
// ============================================================================
|
|
6799
7168
|
/**
|
|
6800
|
-
* Get complete object schema (
|
|
6801
|
-
*
|
|
6802
|
-
* If object is custom, returns DB definition only
|
|
7169
|
+
* Get complete object schema (object attributes + system attributes)
|
|
7170
|
+
* Includes system attributes (createdAt, updatedAt, createdBy, lastUpdatedBy)
|
|
6803
7171
|
*
|
|
6804
7172
|
* @param objectId - Object UUID from database
|
|
6805
|
-
* @returns Complete ObjectDefinition with all attributes
|
|
6806
|
-
*
|
|
6807
|
-
* @example
|
|
6808
|
-
* ```typescript
|
|
6809
|
-
* const service = new ObjectSchemaService(adapter, registry);
|
|
6810
|
-
* const productSchema = await service.getObjectSchema("obj-123");
|
|
6811
|
-
*
|
|
6812
|
-
* console.log(productSchema.attributes); // System + custom attributes
|
|
6813
|
-
* ```
|
|
7173
|
+
* @returns Complete ObjectDefinition with all attributes including system attributes
|
|
6814
7174
|
*/
|
|
6815
7175
|
async getObjectSchema(objectId) {
|
|
6816
7176
|
const dbObject = await this.adapter.objects.findById(objectId);
|
|
6817
7177
|
if (!dbObject) {
|
|
6818
7178
|
throw new Error(`Object with id "${objectId}" not found`);
|
|
6819
7179
|
}
|
|
6820
|
-
|
|
6821
|
-
if (dbObject.system) {
|
|
6822
|
-
return this.mergeNativeObject(dbObject, dbAttributes);
|
|
6823
|
-
}
|
|
6824
|
-
return this.convertDBObjectToDefinition(dbObject, dbAttributes);
|
|
7180
|
+
return this.buildObjectDefinition(dbObject);
|
|
6825
7181
|
}
|
|
6826
7182
|
/**
|
|
6827
7183
|
* Get object schema by name (for system/native objects)
|
|
@@ -6832,41 +7188,54 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
6832
7188
|
if (!dbObject) {
|
|
6833
7189
|
throw new Error(`Native object "${name}" not found in database`);
|
|
6834
7190
|
}
|
|
6835
|
-
|
|
6836
|
-
return this.mergeNativeObject(dbObject, dbAttributes);
|
|
7191
|
+
return this.buildObjectDefinition(dbObject);
|
|
6837
7192
|
}
|
|
6838
7193
|
/**
|
|
6839
|
-
* Get object schema by name
|
|
7194
|
+
* Get object schema by name (supports both native and custom objects).
|
|
7195
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
6840
7196
|
*/
|
|
6841
|
-
async getObjectSchemaByNameForTenant(name
|
|
6842
|
-
let dbObject = await this.adapter.objects.findByName(
|
|
7197
|
+
async getObjectSchemaByNameForTenant(name) {
|
|
7198
|
+
let dbObject = await this.adapter.objects.findByName(name);
|
|
6843
7199
|
if (!dbObject) {
|
|
6844
7200
|
dbObject = await this.adapter.objects.findSystemByName(name);
|
|
6845
7201
|
}
|
|
6846
7202
|
if (!dbObject) {
|
|
6847
7203
|
throw new Error(`Object "${name}" not found`);
|
|
6848
7204
|
}
|
|
7205
|
+
return this.buildObjectDefinition(dbObject);
|
|
7206
|
+
}
|
|
7207
|
+
/**
|
|
7208
|
+
* List all object schemas.
|
|
7209
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7210
|
+
*/
|
|
7211
|
+
async listObjectSchemas() {
|
|
7212
|
+
const dbObjects = await this.adapter.objects.list();
|
|
7213
|
+
return Promise.all(dbObjects.map((dbObject) => this.buildObjectDefinition(dbObject)));
|
|
7214
|
+
}
|
|
7215
|
+
// ============================================================================
|
|
7216
|
+
// PRIVATE HELPERS - SCHEMA BUILDING
|
|
7217
|
+
// ============================================================================
|
|
7218
|
+
/**
|
|
7219
|
+
* Build complete ObjectDefinition from DB object
|
|
7220
|
+
* Handles both native (merged with registry) and custom objects
|
|
7221
|
+
* Always appends system attributes at the end
|
|
7222
|
+
* @internal
|
|
7223
|
+
*/
|
|
7224
|
+
async buildObjectDefinition(dbObject) {
|
|
6849
7225
|
const dbAttributes = await this.adapter.attributes.findByObjectId(dbObject.id);
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
}
|
|
6853
|
-
return this.convertDBObjectToDefinition(dbObject, dbAttributes);
|
|
7226
|
+
const baseDef = dbObject.system ? this.mergeNativeObject(dbObject, dbAttributes) : this.convertDBObjectToDefinition(dbObject, dbAttributes);
|
|
7227
|
+
return this.withSystemAttributes(baseDef);
|
|
6854
7228
|
}
|
|
6855
7229
|
/**
|
|
6856
|
-
*
|
|
7230
|
+
* Append system attributes to an ObjectDefinition
|
|
7231
|
+
* System attributes are always available on all records (createdAt, updatedAt, createdBy, lastUpdatedBy)
|
|
7232
|
+
* @internal
|
|
6857
7233
|
*/
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
if (dbObject.system) {
|
|
6864
|
-
return this.mergeNativeObject(dbObject, dbAttributes);
|
|
6865
|
-
}
|
|
6866
|
-
return this.convertDBObjectToDefinition(dbObject, dbAttributes);
|
|
6867
|
-
})
|
|
6868
|
-
);
|
|
6869
|
-
return schemas;
|
|
7234
|
+
withSystemAttributes(def) {
|
|
7235
|
+
return {
|
|
7236
|
+
...def,
|
|
7237
|
+
attributes: [...def.attributes, ...getSystemAttributeList()]
|
|
7238
|
+
};
|
|
6870
7239
|
}
|
|
6871
7240
|
/**
|
|
6872
7241
|
* Merge native object from registry with custom attributes from DB
|
|
@@ -6928,15 +7297,15 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
6928
7297
|
};
|
|
6929
7298
|
}
|
|
6930
7299
|
/**
|
|
6931
|
-
* Validate attribute input and return parsed config
|
|
6932
|
-
* Validates: name format, config schema, relation targets
|
|
7300
|
+
* Validate attribute input and return parsed config.
|
|
7301
|
+
* Validates: name format, config schema, relation targets.
|
|
6933
7302
|
* @internal
|
|
6934
7303
|
*/
|
|
6935
|
-
async validateAttributeInput(attribute
|
|
7304
|
+
async validateAttributeInput(attribute) {
|
|
6936
7305
|
this.validateAttributeName(attribute.name);
|
|
6937
7306
|
const config = this.buildAttributeConfig(attribute);
|
|
6938
7307
|
if (attribute.type === "relation") {
|
|
6939
|
-
await this.validateRelationTargets(attribute
|
|
7308
|
+
await this.validateRelationTargets(attribute);
|
|
6940
7309
|
}
|
|
6941
7310
|
return config;
|
|
6942
7311
|
}
|
|
@@ -6963,23 +7332,24 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
6963
7332
|
}
|
|
6964
7333
|
}
|
|
6965
7334
|
/**
|
|
6966
|
-
* Check if an object exists (in tenant, system, or native registry)
|
|
7335
|
+
* Check if an object exists (in tenant, system, or native registry).
|
|
7336
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
6967
7337
|
* @internal
|
|
6968
7338
|
*/
|
|
6969
|
-
async objectExists(name
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
if (obj) return true;
|
|
6973
|
-
}
|
|
7339
|
+
async objectExists(name) {
|
|
7340
|
+
const obj = await this.adapter.objects.findByName(name);
|
|
7341
|
+
if (obj) return true;
|
|
6974
7342
|
const systemObj = await this.adapter.objects.findSystemByName(name);
|
|
6975
7343
|
if (systemObj) return true;
|
|
6976
7344
|
return this.nativeRegistry.getByName(name) !== void 0;
|
|
6977
7345
|
}
|
|
6978
7346
|
/**
|
|
6979
|
-
* Validate that relation target objects exist
|
|
7347
|
+
* Validate that relation target objects exist.
|
|
7348
|
+
* Skip validation for universal relations (toAny with target "*").
|
|
7349
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
6980
7350
|
* @internal
|
|
6981
7351
|
*/
|
|
6982
|
-
async validateRelationTargets(attribute
|
|
7352
|
+
async validateRelationTargets(attribute) {
|
|
6983
7353
|
const targets = attribute.targets;
|
|
6984
7354
|
if (!targets || targets.length === 0) {
|
|
6985
7355
|
throw new Error("Relation attribute must have at least one target object");
|
|
@@ -6989,11 +7359,14 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
6989
7359
|
if (!isValidCardinality) {
|
|
6990
7360
|
throw new Error("Relation attribute must specify cardinality: 'one' or 'many'");
|
|
6991
7361
|
}
|
|
7362
|
+
if (targets.length === 1 && targets[0].object === RELATION_TARGET_ANY) {
|
|
7363
|
+
return;
|
|
7364
|
+
}
|
|
6992
7365
|
for (const target of targets) {
|
|
6993
7366
|
if (!target.object) {
|
|
6994
7367
|
throw new Error("Each relation target must specify an object name");
|
|
6995
7368
|
}
|
|
6996
|
-
const exists = await this.objectExists(target.object
|
|
7369
|
+
const exists = await this.objectExists(target.object);
|
|
6997
7370
|
if (!exists) {
|
|
6998
7371
|
throw new Error(
|
|
6999
7372
|
`Relation target object "${target.object}" does not exist. Make sure the object is created before adding a relation to it.`
|
|
@@ -7004,10 +7377,11 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
7004
7377
|
/**
|
|
7005
7378
|
* Find objects that have a relation attribute targeting the given object.
|
|
7006
7379
|
* Used to prevent deletion of objects that are relation targets.
|
|
7380
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7007
7381
|
* @internal
|
|
7008
7382
|
*/
|
|
7009
|
-
async findObjectsWithRelationTo(targetObjectName
|
|
7010
|
-
const allObjects = await this.adapter.objects.list(
|
|
7383
|
+
async findObjectsWithRelationTo(targetObjectName) {
|
|
7384
|
+
const allObjects = await this.adapter.objects.list();
|
|
7011
7385
|
const referencing = [];
|
|
7012
7386
|
for (const obj of allObjects) {
|
|
7013
7387
|
if (obj.name === targetObjectName) continue;
|
|
@@ -7041,23 +7415,12 @@ Reserved names: ${_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
7041
7415
|
return baseAttr;
|
|
7042
7416
|
}
|
|
7043
7417
|
};
|
|
7044
|
-
/**
|
|
7045
|
-
* Reserved attribute names that cannot be used.
|
|
7046
|
-
* These names conflict with system fields on ObjectRecord.
|
|
7047
|
-
*/
|
|
7048
|
-
_ObjectSchemaService.RESERVED_ATTRIBUTE_NAMES = [
|
|
7049
|
-
"id",
|
|
7050
|
-
"createdAt",
|
|
7051
|
-
"updatedAt",
|
|
7052
|
-
"metadata"
|
|
7053
|
-
];
|
|
7054
|
-
var ObjectSchemaService = _ObjectSchemaService;
|
|
7055
7418
|
|
|
7056
7419
|
// src/runtime/services/permission.service.ts
|
|
7057
|
-
var PermissionService = class {
|
|
7058
|
-
constructor(adapter,
|
|
7420
|
+
var PermissionService = class extends TenantAwareService {
|
|
7421
|
+
constructor(adapter, options) {
|
|
7422
|
+
super();
|
|
7059
7423
|
this.adapter = adapter;
|
|
7060
|
-
this.tenantId = tenantId;
|
|
7061
7424
|
this.cache = /* @__PURE__ */ new Map();
|
|
7062
7425
|
/** Track pending permission fetches to prevent duplicate concurrent requests */
|
|
7063
7426
|
this.pendingFetches = /* @__PURE__ */ new Map();
|
|
@@ -7070,8 +7433,6 @@ var PermissionService = class {
|
|
|
7070
7433
|
this.cacheTtlMs = options?.cacheTtlMs ?? 6e4;
|
|
7071
7434
|
this.maxCacheSize = options?.maxCacheSize ?? 1e4;
|
|
7072
7435
|
this.auditService = options?.auditService;
|
|
7073
|
-
this.userId = options?.userId;
|
|
7074
|
-
this.userEmail = options?.userEmail;
|
|
7075
7436
|
}
|
|
7076
7437
|
// ============================================================================
|
|
7077
7438
|
// PERMISSION CHECKS
|
|
@@ -7226,10 +7587,7 @@ var PermissionService = class {
|
|
|
7226
7587
|
* @internal
|
|
7227
7588
|
*/
|
|
7228
7589
|
async fetchAndCachePermissions(userProfileId, cacheKey) {
|
|
7229
|
-
const permissions = await this.permissionsRepo.getEffectivePermissions(
|
|
7230
|
-
userProfileId,
|
|
7231
|
-
this.tenantId
|
|
7232
|
-
);
|
|
7590
|
+
const permissions = await this.permissionsRepo.getEffectivePermissions(userProfileId);
|
|
7233
7591
|
if (this.cache.size >= this.maxCacheSize) {
|
|
7234
7592
|
const firstKey = this.cache.keys().next().value;
|
|
7235
7593
|
if (firstKey) {
|
|
@@ -7273,9 +7631,10 @@ var PermissionService = class {
|
|
|
7273
7631
|
// ============================================================================
|
|
7274
7632
|
/**
|
|
7275
7633
|
* Get all roles for the current tenant.
|
|
7634
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7276
7635
|
*/
|
|
7277
7636
|
async getRoles() {
|
|
7278
|
-
return await this.permissionsRepo.
|
|
7637
|
+
return await this.permissionsRepo.getRoles();
|
|
7279
7638
|
}
|
|
7280
7639
|
/**
|
|
7281
7640
|
* Get a role by ID.
|
|
@@ -7287,15 +7646,11 @@ var PermissionService = class {
|
|
|
7287
7646
|
* Create a new role.
|
|
7288
7647
|
*/
|
|
7289
7648
|
async createRole(input) {
|
|
7290
|
-
const role = await this.permissionsRepo.createRole(
|
|
7291
|
-
...input,
|
|
7292
|
-
tenantId: this.tenantId
|
|
7293
|
-
});
|
|
7649
|
+
const role = await this.permissionsRepo.createRole(input);
|
|
7294
7650
|
if (this.auditService && this.userId) {
|
|
7295
7651
|
await this.auditService.logRoleAction({
|
|
7296
7652
|
action: "role.created",
|
|
7297
7653
|
actorId: this.userId,
|
|
7298
|
-
actorEmail: this.userEmail,
|
|
7299
7654
|
roleId: role.id,
|
|
7300
7655
|
roleLabel: role.label
|
|
7301
7656
|
});
|
|
@@ -7324,7 +7679,6 @@ var PermissionService = class {
|
|
|
7324
7679
|
await this.auditService.logRoleAction({
|
|
7325
7680
|
action: "role.updated",
|
|
7326
7681
|
actorId: this.userId,
|
|
7327
|
-
actorEmail: this.userEmail,
|
|
7328
7682
|
roleId: role.id,
|
|
7329
7683
|
roleLabel: role.label,
|
|
7330
7684
|
changes
|
|
@@ -7344,7 +7698,6 @@ var PermissionService = class {
|
|
|
7344
7698
|
await this.auditService.logRoleAction({
|
|
7345
7699
|
action: "role.deleted",
|
|
7346
7700
|
actorId: this.userId,
|
|
7347
|
-
actorEmail: this.userEmail,
|
|
7348
7701
|
roleId,
|
|
7349
7702
|
roleLabel: role.label
|
|
7350
7703
|
});
|
|
@@ -7370,7 +7723,6 @@ var PermissionService = class {
|
|
|
7370
7723
|
await this.auditService.logRoleAction({
|
|
7371
7724
|
action: "role.updated",
|
|
7372
7725
|
actorId: this.userId,
|
|
7373
|
-
actorEmail: this.userEmail,
|
|
7374
7726
|
roleId,
|
|
7375
7727
|
roleLabel: role?.label ?? roleId,
|
|
7376
7728
|
metadata: { permissionsUpdated: true, permissionCount: permissions.length }
|
|
@@ -7384,7 +7736,7 @@ var PermissionService = class {
|
|
|
7384
7736
|
* Get all roles assigned to a user.
|
|
7385
7737
|
*/
|
|
7386
7738
|
async getUserRoles(userProfileId) {
|
|
7387
|
-
return await this.permissionsRepo.getUserRoles(userProfileId
|
|
7739
|
+
return await this.permissionsRepo.getUserRoles(userProfileId);
|
|
7388
7740
|
}
|
|
7389
7741
|
/**
|
|
7390
7742
|
* Assign a role to a user.
|
|
@@ -7393,7 +7745,6 @@ var PermissionService = class {
|
|
|
7393
7745
|
const result = await this.permissionsRepo.assignRole({
|
|
7394
7746
|
userProfileId,
|
|
7395
7747
|
roleId,
|
|
7396
|
-
tenantId: this.tenantId,
|
|
7397
7748
|
assignedBy
|
|
7398
7749
|
});
|
|
7399
7750
|
this.invalidateCache(userProfileId);
|
|
@@ -7402,7 +7753,6 @@ var PermissionService = class {
|
|
|
7402
7753
|
await this.auditService.logRoleAction({
|
|
7403
7754
|
action: "role.assigned",
|
|
7404
7755
|
actorId: this.userId,
|
|
7405
|
-
actorEmail: this.userEmail,
|
|
7406
7756
|
roleId,
|
|
7407
7757
|
roleLabel: role?.label ?? roleId,
|
|
7408
7758
|
targetUserId: userProfileId
|
|
@@ -7415,13 +7765,12 @@ var PermissionService = class {
|
|
|
7415
7765
|
*/
|
|
7416
7766
|
async revokeRole(userProfileId, roleId) {
|
|
7417
7767
|
const role = await this.permissionsRepo.getRoleById(roleId);
|
|
7418
|
-
await this.permissionsRepo.revokeRole(userProfileId, roleId
|
|
7768
|
+
await this.permissionsRepo.revokeRole(userProfileId, roleId);
|
|
7419
7769
|
this.invalidateCache(userProfileId);
|
|
7420
7770
|
if (this.auditService && this.userId) {
|
|
7421
7771
|
await this.auditService.logRoleAction({
|
|
7422
7772
|
action: "role.revoked",
|
|
7423
7773
|
actorId: this.userId,
|
|
7424
|
-
actorEmail: this.userEmail,
|
|
7425
7774
|
roleId,
|
|
7426
7775
|
roleLabel: role?.label ?? roleId,
|
|
7427
7776
|
targetUserId: userProfileId
|
|
@@ -7444,7 +7793,7 @@ var PermissionService = class {
|
|
|
7444
7793
|
*
|
|
7445
7794
|
* @example
|
|
7446
7795
|
* ```typescript
|
|
7447
|
-
* const service = new PermissionService(adapter
|
|
7796
|
+
* const service = new PermissionService(adapter);
|
|
7448
7797
|
* await service.initializeDefaultRoles();
|
|
7449
7798
|
* ```
|
|
7450
7799
|
*/
|
|
@@ -7462,7 +7811,6 @@ var PermissionService = class {
|
|
|
7462
7811
|
continue;
|
|
7463
7812
|
}
|
|
7464
7813
|
const role = await this.permissionsRepo.createRole({
|
|
7465
|
-
tenantId: this.tenantId,
|
|
7466
7814
|
name: roleName,
|
|
7467
7815
|
label: DEFAULT_ROLE_LABELS2[roleName],
|
|
7468
7816
|
description: DEFAULT_ROLE_DESCRIPTIONS2[roleName]
|
|
@@ -7491,8 +7839,9 @@ var PermissionService = class {
|
|
|
7491
7839
|
};
|
|
7492
7840
|
|
|
7493
7841
|
// src/runtime/services/relation.service.ts
|
|
7494
|
-
var RelationService = class {
|
|
7842
|
+
var RelationService = class extends TenantAwareService {
|
|
7495
7843
|
constructor(adapter, nativeRegistry) {
|
|
7844
|
+
super();
|
|
7496
7845
|
this.adapter = adapter;
|
|
7497
7846
|
this.schemaService = new ObjectSchemaService(adapter, nativeRegistry);
|
|
7498
7847
|
}
|
|
@@ -7543,8 +7892,9 @@ var RelationService = class {
|
|
|
7543
7892
|
if (ids.length === 0) {
|
|
7544
7893
|
return errors;
|
|
7545
7894
|
}
|
|
7546
|
-
const
|
|
7547
|
-
|
|
7895
|
+
const isUniversal = attr.targets.length === 1 && attr.targets[0].object === RELATION_TARGET_ANY;
|
|
7896
|
+
const validObjectIds = isUniversal ? null : await this.getValidObjectIds(attr.targets);
|
|
7897
|
+
if (!isUniversal && validObjectIds?.size === 0) {
|
|
7548
7898
|
errors.push({
|
|
7549
7899
|
attribute: attr.name,
|
|
7550
7900
|
message: `No valid target objects found for ${attr.label}`
|
|
@@ -7558,7 +7908,7 @@ var RelationService = class {
|
|
|
7558
7908
|
invalidIds.push(id);
|
|
7559
7909
|
continue;
|
|
7560
7910
|
}
|
|
7561
|
-
if (!validObjectIds.has(record.objectId)) {
|
|
7911
|
+
if (!isUniversal && validObjectIds && !validObjectIds.has(record.objectId)) {
|
|
7562
7912
|
invalidIds.push(id);
|
|
7563
7913
|
}
|
|
7564
7914
|
}
|
|
@@ -7588,6 +7938,7 @@ var RelationService = class {
|
|
|
7588
7938
|
}
|
|
7589
7939
|
/**
|
|
7590
7940
|
* Get valid object IDs from relation targets
|
|
7941
|
+
* Note: Universal relations (toAny) are handled earlier in validateRelationAttribute
|
|
7591
7942
|
*/
|
|
7592
7943
|
async getValidObjectIds(targets) {
|
|
7593
7944
|
const objectIds = /* @__PURE__ */ new Set();
|
|
@@ -7619,23 +7970,23 @@ var RelationService = class {
|
|
|
7619
7970
|
// OPTIONS & RESOLUTION
|
|
7620
7971
|
// ============================================================================
|
|
7621
7972
|
/**
|
|
7622
|
-
* Get available options for a relation attribute
|
|
7623
|
-
* Searches across all target objects defined in the relation
|
|
7973
|
+
* Get available options for a relation attribute.
|
|
7974
|
+
* Searches across all target objects defined in the relation.
|
|
7975
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
7624
7976
|
*
|
|
7625
7977
|
* @param attribute - Relation attribute definition
|
|
7626
|
-
* @param tenantId - Tenant ID for multi-tenant isolation
|
|
7627
7978
|
* @param params - Query parameters
|
|
7628
7979
|
*
|
|
7629
7980
|
* @example
|
|
7630
7981
|
* ```typescript
|
|
7631
|
-
* const options = await relationService.getOptions(attribute,
|
|
7982
|
+
* const options = await relationService.getOptions(attribute, {
|
|
7632
7983
|
* query: "nike",
|
|
7633
7984
|
* page: 1,
|
|
7634
7985
|
* pageSize: 20
|
|
7635
7986
|
* });
|
|
7636
7987
|
* ```
|
|
7637
7988
|
*/
|
|
7638
|
-
async getOptions(attribute,
|
|
7989
|
+
async getOptions(attribute, params = {}) {
|
|
7639
7990
|
const { query = "", page = 1, pageSize = 20, targetObject } = params;
|
|
7640
7991
|
const targets = attribute.targets;
|
|
7641
7992
|
const filteredTargets = targetObject ? targets.filter((t) => t.object === targetObject) : targets;
|
|
@@ -7644,7 +7995,7 @@ var RelationService = class {
|
|
|
7644
7995
|
}
|
|
7645
7996
|
const allOptions = [];
|
|
7646
7997
|
let totalCount = 0;
|
|
7647
|
-
const recordService = new RecordService(this.adapter
|
|
7998
|
+
const recordService = new RecordService(this.adapter);
|
|
7648
7999
|
for (const target of filteredTargets) {
|
|
7649
8000
|
const objectSchema = await this.schemaService.getObjectSchemaByName(target.object);
|
|
7650
8001
|
if (!objectSchema?.id) {
|
|
@@ -7680,23 +8031,22 @@ var RelationService = class {
|
|
|
7680
8031
|
};
|
|
7681
8032
|
}
|
|
7682
8033
|
/**
|
|
7683
|
-
* Resolve record IDs to their display labels
|
|
7684
|
-
* Useful for displaying current values in the UI
|
|
8034
|
+
* Resolve record IDs to their display labels.
|
|
8035
|
+
* Useful for displaying current values in the UI.
|
|
7685
8036
|
*
|
|
7686
8037
|
* Uses batch fetching for performance - fetches all records in one query,
|
|
7687
8038
|
* then groups by objectId to minimize schema lookups.
|
|
7688
8039
|
*
|
|
7689
8040
|
* @param ids - Record IDs to resolve
|
|
7690
|
-
* @param _tenantId - Tenant ID for multi-tenant isolation (unused but kept for API compatibility)
|
|
7691
8041
|
* @param attributeId - Relation attribute ID to use its displayTemplate for label rendering
|
|
7692
8042
|
*
|
|
7693
8043
|
* @example
|
|
7694
8044
|
* ```typescript
|
|
7695
|
-
* const resolved = await relationService.resolveIds(["rec-1", "rec-2"], "
|
|
8045
|
+
* const resolved = await relationService.resolveIds(["rec-1", "rec-2"], "attr-123");
|
|
7696
8046
|
* // [{ id: "rec-1", label: "Nike Air Max", objectName: "products", ... }]
|
|
7697
8047
|
* ```
|
|
7698
8048
|
*/
|
|
7699
|
-
async resolveIds(ids,
|
|
8049
|
+
async resolveIds(ids, attributeId) {
|
|
7700
8050
|
if (!ids || ids.length === 0) {
|
|
7701
8051
|
return [];
|
|
7702
8052
|
}
|
|
@@ -7831,13 +8181,8 @@ var RollupService = class {
|
|
|
7831
8181
|
if (!record) {
|
|
7832
8182
|
return { value: null, recordCount: 0 };
|
|
7833
8183
|
}
|
|
7834
|
-
const parentObject = await this.adapter.objects.findById(record.objectId);
|
|
7835
|
-
if (!parentObject) {
|
|
7836
|
-
return { value: null, recordCount: 0 };
|
|
7837
|
-
}
|
|
7838
|
-
const tenantId = parentObject.tenantId;
|
|
7839
8184
|
const sourceObjectName = rollupAttr.relationAttribute;
|
|
7840
|
-
const sourceObject = await this.adapter.objects.findByName(
|
|
8185
|
+
const sourceObject = await this.adapter.objects.findByName(sourceObjectName);
|
|
7841
8186
|
if (!sourceObject) {
|
|
7842
8187
|
return { value: null, recordCount: 0 };
|
|
7843
8188
|
}
|
|
@@ -7851,7 +8196,6 @@ var RollupService = class {
|
|
|
7851
8196
|
return { value: null, recordCount: 0 };
|
|
7852
8197
|
}
|
|
7853
8198
|
const relatedRecords = await this.adapter.objectRecords.findByRelation(
|
|
7854
|
-
tenantId,
|
|
7855
8199
|
sourceObject.id,
|
|
7856
8200
|
reverseRelationAttr.name,
|
|
7857
8201
|
recordId
|
|
@@ -7865,6 +8209,9 @@ var RollupService = class {
|
|
|
7865
8209
|
* Extract and aggregate values from related records
|
|
7866
8210
|
*/
|
|
7867
8211
|
aggregateValues(relatedRecords, rollupAttr) {
|
|
8212
|
+
if (rollupAttr.function === "count") {
|
|
8213
|
+
return { value: relatedRecords.length, recordCount: relatedRecords.length };
|
|
8214
|
+
}
|
|
7868
8215
|
const values = relatedRecords.map((r) => r.values[rollupAttr.targetAttribute]).filter((v) => v !== void 0 && v !== null);
|
|
7869
8216
|
const aggregated = this.aggregate(values, rollupAttr.function);
|
|
7870
8217
|
const result = typeof aggregated === "number" && rollupAttr.decimals !== void 0 ? Number(aggregated.toFixed(rollupAttr.decimals)) : aggregated;
|
|
@@ -7893,32 +8240,38 @@ var RollupService = class {
|
|
|
7893
8240
|
*/
|
|
7894
8241
|
aggregate(values, fn) {
|
|
7895
8242
|
switch (fn) {
|
|
8243
|
+
// Numeric aggregates
|
|
7896
8244
|
case "sum":
|
|
7897
8245
|
return this.sumNumbers(values);
|
|
7898
8246
|
case "avg":
|
|
7899
8247
|
return this.averageNumbers(values);
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
8248
|
+
// Date aggregates
|
|
8249
|
+
case "earliest":
|
|
8250
|
+
return this.earliestDate(values);
|
|
8251
|
+
case "latest":
|
|
8252
|
+
return this.latestDate(values);
|
|
8253
|
+
// Count functions
|
|
7904
8254
|
case "count":
|
|
7905
8255
|
return values.length;
|
|
7906
8256
|
case "countValues":
|
|
7907
8257
|
return values.filter((v) => v != null && v !== "").length;
|
|
8258
|
+
case "countUniqueValues": {
|
|
8259
|
+
const nonEmpty = values.filter((v) => v != null && v !== "");
|
|
8260
|
+
return new Set(nonEmpty.map((v) => JSON.stringify(v))).size;
|
|
8261
|
+
}
|
|
7908
8262
|
case "countEmpty":
|
|
7909
8263
|
return values.filter((v) => v == null || v === "").length;
|
|
7910
|
-
|
|
8264
|
+
// Percent functions
|
|
8265
|
+
case "percentNotEmpty": {
|
|
7911
8266
|
if (values.length === 0) return 0;
|
|
7912
8267
|
const filled = values.filter((v) => v != null && v !== "").length;
|
|
7913
|
-
return filled / values.length
|
|
8268
|
+
return filled / values.length;
|
|
7914
8269
|
}
|
|
7915
8270
|
case "percentEmpty": {
|
|
7916
8271
|
if (values.length === 0) return 0;
|
|
7917
8272
|
const empty = values.filter((v) => v == null || v === "").length;
|
|
7918
|
-
return empty / values.length
|
|
8273
|
+
return empty / values.length;
|
|
7919
8274
|
}
|
|
7920
|
-
case "collect":
|
|
7921
|
-
return values;
|
|
7922
8275
|
}
|
|
7923
8276
|
}
|
|
7924
8277
|
/**
|
|
@@ -7929,16 +8282,15 @@ var RollupService = class {
|
|
|
7929
8282
|
case "sum":
|
|
7930
8283
|
case "count":
|
|
7931
8284
|
case "countValues":
|
|
8285
|
+
case "countUniqueValues":
|
|
7932
8286
|
case "countEmpty":
|
|
7933
8287
|
case "percentEmpty":
|
|
7934
|
-
case "
|
|
8288
|
+
case "percentNotEmpty":
|
|
7935
8289
|
return 0;
|
|
7936
8290
|
case "avg":
|
|
7937
|
-
case "
|
|
7938
|
-
case "
|
|
8291
|
+
case "earliest":
|
|
8292
|
+
case "latest":
|
|
7939
8293
|
return null;
|
|
7940
|
-
case "collect":
|
|
7941
|
-
return [];
|
|
7942
8294
|
}
|
|
7943
8295
|
}
|
|
7944
8296
|
/**
|
|
@@ -7956,20 +8308,22 @@ var RollupService = class {
|
|
|
7956
8308
|
return nums.reduce((sum, n) => sum + n, 0) / nums.length;
|
|
7957
8309
|
}
|
|
7958
8310
|
/**
|
|
7959
|
-
*
|
|
8311
|
+
* Get earliest date from values
|
|
7960
8312
|
*/
|
|
7961
|
-
|
|
7962
|
-
const
|
|
7963
|
-
if (
|
|
7964
|
-
|
|
8313
|
+
earliestDate(values) {
|
|
8314
|
+
const dates = values.filter((v) => v instanceof Date || typeof v === "string").map((v) => v instanceof Date ? v : new Date(v)).filter((d) => !Number.isNaN(d.getTime()));
|
|
8315
|
+
if (dates.length === 0) return null;
|
|
8316
|
+
const earliest = new Date(Math.min(...dates.map((d) => d.getTime())));
|
|
8317
|
+
return earliest.toISOString();
|
|
7965
8318
|
}
|
|
7966
8319
|
/**
|
|
7967
|
-
*
|
|
8320
|
+
* Get latest date from values
|
|
7968
8321
|
*/
|
|
7969
|
-
|
|
7970
|
-
const
|
|
7971
|
-
if (
|
|
7972
|
-
|
|
8322
|
+
latestDate(values) {
|
|
8323
|
+
const dates = values.filter((v) => v instanceof Date || typeof v === "string").map((v) => v instanceof Date ? v : new Date(v)).filter((d) => !Number.isNaN(d.getTime()));
|
|
8324
|
+
if (dates.length === 0) return null;
|
|
8325
|
+
const latest = new Date(Math.max(...dates.map((d) => d.getTime())));
|
|
8326
|
+
return latest.toISOString();
|
|
7973
8327
|
}
|
|
7974
8328
|
/**
|
|
7975
8329
|
* Recalculate all rollup attributes for a record and update it
|
|
@@ -8030,12 +8384,7 @@ var RollupService = class {
|
|
|
8030
8384
|
*/
|
|
8031
8385
|
async findRecordsWithForwardRollup(changedRecord, changedSchema) {
|
|
8032
8386
|
const affectedRecords = [];
|
|
8033
|
-
const
|
|
8034
|
-
if (!recordObject) {
|
|
8035
|
-
return affectedRecords;
|
|
8036
|
-
}
|
|
8037
|
-
const tenantId = recordObject.tenantId;
|
|
8038
|
-
const allObjects = await this.adapter.objects.list(tenantId);
|
|
8387
|
+
const allObjects = await this.adapter.objects.list();
|
|
8039
8388
|
for (const obj of allObjects) {
|
|
8040
8389
|
if (obj.id === changedRecord.objectId) {
|
|
8041
8390
|
continue;
|
|
@@ -8064,7 +8413,6 @@ var RollupService = class {
|
|
|
8064
8413
|
continue;
|
|
8065
8414
|
}
|
|
8066
8415
|
const recordsPointingToChanged = await this.adapter.objectRecords.findByRelation(
|
|
8067
|
-
tenantId,
|
|
8068
8416
|
obj.id,
|
|
8069
8417
|
relationAttr.name,
|
|
8070
8418
|
changedRecord.id
|
|
@@ -8081,10 +8429,10 @@ var RollupService = class {
|
|
|
8081
8429
|
};
|
|
8082
8430
|
|
|
8083
8431
|
// src/runtime/services/user.service.ts
|
|
8084
|
-
var UserService = class {
|
|
8085
|
-
constructor(adapter
|
|
8432
|
+
var UserService = class extends TenantAwareService {
|
|
8433
|
+
constructor(adapter) {
|
|
8434
|
+
super();
|
|
8086
8435
|
this.adapter = adapter;
|
|
8087
|
-
this.tenantId = tenantId;
|
|
8088
8436
|
}
|
|
8089
8437
|
/**
|
|
8090
8438
|
* Validate all user attributes in the data
|
|
@@ -8198,23 +8546,32 @@ var UserService = class {
|
|
|
8198
8546
|
};
|
|
8199
8547
|
|
|
8200
8548
|
// src/runtime/services/record.service.ts
|
|
8201
|
-
|
|
8202
|
-
|
|
8549
|
+
function applyDefaultValues(schema, data) {
|
|
8550
|
+
const result = { ...data };
|
|
8551
|
+
for (const attr of schema.attributes) {
|
|
8552
|
+
if (attr.name in result && result[attr.name] !== void 0) {
|
|
8553
|
+
continue;
|
|
8554
|
+
}
|
|
8555
|
+
if ("defaultValue" in attr && attr.defaultValue !== void 0 && attr.defaultValue !== null) {
|
|
8556
|
+
result[attr.name] = attr.defaultValue;
|
|
8557
|
+
}
|
|
8558
|
+
}
|
|
8559
|
+
return result;
|
|
8560
|
+
}
|
|
8561
|
+
var RecordService = class extends TenantAwareService {
|
|
8562
|
+
constructor(adapter, options) {
|
|
8563
|
+
super();
|
|
8203
8564
|
this.adapter = adapter;
|
|
8204
|
-
this.tenantId = tenantId;
|
|
8205
8565
|
this.schemaService = new ObjectSchemaService(adapter, registry, {
|
|
8206
|
-
userId: options?.userId,
|
|
8207
|
-
userEmail: options?.userEmail,
|
|
8208
8566
|
auditService: options?.auditService
|
|
8209
8567
|
});
|
|
8210
8568
|
this.relationService = new RelationService(adapter, registry);
|
|
8211
|
-
this.userService = new UserService(adapter
|
|
8569
|
+
this.userService = new UserService(adapter);
|
|
8212
8570
|
this.rollupService = new RollupService(adapter);
|
|
8213
8571
|
this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
|
|
8214
8572
|
this.permissionService = options?.permissionService;
|
|
8215
|
-
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter
|
|
8216
|
-
this.
|
|
8217
|
-
this.userEmail = options?.userEmail;
|
|
8573
|
+
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter) : void 0);
|
|
8574
|
+
this.policyRegistry = options?.policyRegistry === null ? null : options?.policyRegistry ?? defaultPolicyRegistry;
|
|
8218
8575
|
}
|
|
8219
8576
|
/**
|
|
8220
8577
|
* Check permission for an action on an object.
|
|
@@ -8226,6 +8583,67 @@ var RecordService = class {
|
|
|
8226
8583
|
await this.permissionService.checkObjectAccess(this.userId, objectName, action);
|
|
8227
8584
|
}
|
|
8228
8585
|
}
|
|
8586
|
+
/**
|
|
8587
|
+
* Get policy for an object if one exists and userId is configured.
|
|
8588
|
+
* @internal
|
|
8589
|
+
*/
|
|
8590
|
+
getPolicy(objectName) {
|
|
8591
|
+
if (!(this.policyRegistry && this.userId)) {
|
|
8592
|
+
return void 0;
|
|
8593
|
+
}
|
|
8594
|
+
return this.policyRegistry.get(objectName);
|
|
8595
|
+
}
|
|
8596
|
+
/**
|
|
8597
|
+
* Build policy context for the current request.
|
|
8598
|
+
* @internal
|
|
8599
|
+
*/
|
|
8600
|
+
buildPolicyContext(objectName) {
|
|
8601
|
+
return {
|
|
8602
|
+
// biome-ignore lint/style/noNonNullAssertion: userId is guaranteed to be set
|
|
8603
|
+
userId: this.userId,
|
|
8604
|
+
tenantId: this.tenantId,
|
|
8605
|
+
objectName
|
|
8606
|
+
};
|
|
8607
|
+
}
|
|
8608
|
+
/**
|
|
8609
|
+
* Check if user can access a record based on policy.
|
|
8610
|
+
* Returns true if no policy exists or user can access.
|
|
8611
|
+
* @internal
|
|
8612
|
+
*/
|
|
8613
|
+
checkRecordAccess(policy, record) {
|
|
8614
|
+
if (!policy.canAccessRecord) {
|
|
8615
|
+
return true;
|
|
8616
|
+
}
|
|
8617
|
+
return policy.canAccessRecord(this.buildPolicyContext(policy.objectName), record);
|
|
8618
|
+
}
|
|
8619
|
+
/**
|
|
8620
|
+
* Check if user can modify a record based on policy.
|
|
8621
|
+
* Throws PolicyViolationError if denied.
|
|
8622
|
+
* @internal
|
|
8623
|
+
*/
|
|
8624
|
+
checkRecordModify(policy, record) {
|
|
8625
|
+
if (!policy.canModifyRecord) {
|
|
8626
|
+
return;
|
|
8627
|
+
}
|
|
8628
|
+
const canModify = policy.canModifyRecord(this.buildPolicyContext(policy.objectName), record);
|
|
8629
|
+
if (!canModify) {
|
|
8630
|
+
throw new PolicyViolationError(policy.objectName, "update", record.id);
|
|
8631
|
+
}
|
|
8632
|
+
}
|
|
8633
|
+
/**
|
|
8634
|
+
* Check if user can delete a record based on policy.
|
|
8635
|
+
* Throws PolicyViolationError if denied.
|
|
8636
|
+
* @internal
|
|
8637
|
+
*/
|
|
8638
|
+
checkRecordDelete(policy, record) {
|
|
8639
|
+
if (!policy.canDeleteRecord) {
|
|
8640
|
+
return;
|
|
8641
|
+
}
|
|
8642
|
+
const canDelete = policy.canDeleteRecord(this.buildPolicyContext(policy.objectName), record);
|
|
8643
|
+
if (!canDelete) {
|
|
8644
|
+
throw new PolicyViolationError(policy.objectName, "delete", record.id);
|
|
8645
|
+
}
|
|
8646
|
+
}
|
|
8229
8647
|
/**
|
|
8230
8648
|
* Resolve relation IDs to their display labels
|
|
8231
8649
|
* @internal
|
|
@@ -8248,7 +8666,7 @@ var RecordService = class {
|
|
|
8248
8666
|
}
|
|
8249
8667
|
await Promise.all(
|
|
8250
8668
|
idsWithAttrId.map(async ({ attrId, ids }) => {
|
|
8251
|
-
const resolved = await this.relationService.resolveIds(ids,
|
|
8669
|
+
const resolved = await this.relationService.resolveIds(ids, attrId);
|
|
8252
8670
|
for (const r of resolved) {
|
|
8253
8671
|
resolvedMap.set(r.id, r.label);
|
|
8254
8672
|
}
|
|
@@ -8336,33 +8754,34 @@ var RecordService = class {
|
|
|
8336
8754
|
*/
|
|
8337
8755
|
async createRecord(objectId, data, options) {
|
|
8338
8756
|
const schema = await this.schemaService.getObjectSchema(objectId);
|
|
8757
|
+
const dataWithDefaults = applyDefaultValues(schema, data);
|
|
8339
8758
|
await this.checkPermission(schema.name, "create");
|
|
8340
|
-
const hookCtx = this.buildCreateHookContext(schema,
|
|
8759
|
+
const hookCtx = this.buildCreateHookContext(schema, dataWithDefaults, options?.hookMetadata);
|
|
8341
8760
|
if (!options?.skipHooks) {
|
|
8342
8761
|
await this.hookRegistry.execute("beforeCreate", schema.name, hookCtx);
|
|
8343
8762
|
}
|
|
8344
8763
|
if (options?.validate !== false) {
|
|
8345
8764
|
if (options?.allowDraft) {
|
|
8346
|
-
validateDraftOrThrow(schema,
|
|
8765
|
+
validateDraftOrThrow(schema, dataWithDefaults);
|
|
8347
8766
|
} else {
|
|
8348
|
-
validateObjectOrThrow(schema,
|
|
8767
|
+
validateObjectOrThrow(schema, dataWithDefaults);
|
|
8349
8768
|
}
|
|
8350
8769
|
if (!options?.skipRelationValidation) {
|
|
8351
|
-
await this.relationService.validateRelationsOrThrow(schema,
|
|
8770
|
+
await this.relationService.validateRelationsOrThrow(schema, dataWithDefaults);
|
|
8352
8771
|
}
|
|
8353
8772
|
if (!options?.skipUserValidation) {
|
|
8354
|
-
await this.userService.validateUsersOrThrow(schema,
|
|
8773
|
+
await this.userService.validateUsersOrThrow(schema, dataWithDefaults);
|
|
8355
8774
|
}
|
|
8356
8775
|
}
|
|
8357
|
-
const completionStatus = computeRecordStatus(schema,
|
|
8358
|
-
const label = await this.computeLabel(schema,
|
|
8776
|
+
const completionStatus = computeRecordStatus(schema, dataWithDefaults);
|
|
8777
|
+
const label = await this.computeLabel(schema, dataWithDefaults);
|
|
8359
8778
|
const record = await this.adapter.objectRecords.create({
|
|
8360
|
-
tenantId: this.tenantId,
|
|
8361
8779
|
objectId,
|
|
8362
|
-
data,
|
|
8780
|
+
data: dataWithDefaults,
|
|
8363
8781
|
label,
|
|
8364
8782
|
completionStatus,
|
|
8365
|
-
metadata: options?.metadata
|
|
8783
|
+
metadata: options?.metadata,
|
|
8784
|
+
createdBy: this.userId
|
|
8366
8785
|
});
|
|
8367
8786
|
if (!options?.skipHooks) {
|
|
8368
8787
|
const afterCtx = {
|
|
@@ -8377,7 +8796,6 @@ var RecordService = class {
|
|
|
8377
8796
|
await this.auditService.logRecordAction({
|
|
8378
8797
|
action: "record.created",
|
|
8379
8798
|
actorId: this.userId,
|
|
8380
|
-
actorEmail: this.userEmail,
|
|
8381
8799
|
objectName: schema.name,
|
|
8382
8800
|
objectId: schema.id,
|
|
8383
8801
|
recordId: record.id,
|
|
@@ -8400,6 +8818,12 @@ var RecordService = class {
|
|
|
8400
8818
|
return null;
|
|
8401
8819
|
}
|
|
8402
8820
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
8821
|
+
if (!options?.skipPolicyCheck) {
|
|
8822
|
+
const policy = this.getPolicy(schema.name);
|
|
8823
|
+
if (policy && !this.checkRecordAccess(policy, record)) {
|
|
8824
|
+
return null;
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8403
8827
|
let enrichedRecord = record;
|
|
8404
8828
|
if (!options?.skipFormulas) {
|
|
8405
8829
|
enrichedRecord = this.enrichWithFormulas(record, schema);
|
|
@@ -8445,9 +8869,16 @@ var RecordService = class {
|
|
|
8445
8869
|
* ```
|
|
8446
8870
|
*/
|
|
8447
8871
|
async updateRecord(recordId, data, options) {
|
|
8448
|
-
const existing = await this.
|
|
8872
|
+
const existing = await this.adapter.objectRecords.findById(recordId);
|
|
8873
|
+
if (!existing) {
|
|
8874
|
+
throw new RecordNotFoundError(recordId);
|
|
8875
|
+
}
|
|
8449
8876
|
const schema = await this.schemaService.getObjectSchema(existing.objectId);
|
|
8450
8877
|
await this.checkPermission(schema.name, "update");
|
|
8878
|
+
const policy = this.getPolicy(schema.name);
|
|
8879
|
+
if (policy) {
|
|
8880
|
+
this.checkRecordModify(policy, existing);
|
|
8881
|
+
}
|
|
8451
8882
|
const mergedData = { ...existing.values, ...data };
|
|
8452
8883
|
const changedAttributes = Object.keys(data).filter((key) => existing.values[key] !== data[key]);
|
|
8453
8884
|
const hookCtx = this.buildHookContext(
|
|
@@ -8479,10 +8910,16 @@ var RecordService = class {
|
|
|
8479
8910
|
const updatePayload = {
|
|
8480
8911
|
...data,
|
|
8481
8912
|
__completionStatus: completionStatus,
|
|
8482
|
-
__label: label
|
|
8913
|
+
__label: label,
|
|
8914
|
+
__lastUpdatedBy: this.userId
|
|
8483
8915
|
};
|
|
8484
8916
|
if (options?.metadata !== void 0) {
|
|
8485
|
-
|
|
8917
|
+
const existingMetadata = existing.metadata ?? {};
|
|
8918
|
+
const mergedMetadata = { ...existingMetadata, ...options.metadata };
|
|
8919
|
+
const cleanedMetadata = Object.fromEntries(
|
|
8920
|
+
Object.entries(mergedMetadata).filter(([, value]) => value !== void 0)
|
|
8921
|
+
);
|
|
8922
|
+
updatePayload.__metadata = cleanedMetadata;
|
|
8486
8923
|
}
|
|
8487
8924
|
const updated = await this.adapter.objectRecords.update(recordId, updatePayload);
|
|
8488
8925
|
if (!options?.skipHooks) {
|
|
@@ -8502,7 +8939,6 @@ var RecordService = class {
|
|
|
8502
8939
|
await this.auditService.logRecordAction({
|
|
8503
8940
|
action: "record.updated",
|
|
8504
8941
|
actorId: this.userId,
|
|
8505
|
-
actorEmail: this.userEmail,
|
|
8506
8942
|
objectName: schema.name,
|
|
8507
8943
|
objectId: schema.id,
|
|
8508
8944
|
recordId: updated.id,
|
|
@@ -8598,9 +9034,16 @@ var RecordService = class {
|
|
|
8598
9034
|
* @param options - Delete options
|
|
8599
9035
|
*/
|
|
8600
9036
|
async deleteRecord(recordId, options) {
|
|
8601
|
-
const record = await this.
|
|
9037
|
+
const record = await this.adapter.objectRecords.findById(recordId);
|
|
9038
|
+
if (!record) {
|
|
9039
|
+
throw new RecordNotFoundError(recordId);
|
|
9040
|
+
}
|
|
8602
9041
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
8603
9042
|
await this.checkPermission(schema.name, "delete");
|
|
9043
|
+
const policy = this.getPolicy(schema.name);
|
|
9044
|
+
if (policy) {
|
|
9045
|
+
this.checkRecordDelete(policy, record);
|
|
9046
|
+
}
|
|
8604
9047
|
if (options?.checkSystem) {
|
|
8605
9048
|
if (schema.system) {
|
|
8606
9049
|
throw new ProtectedResourceError("object", schema.name, "delete");
|
|
@@ -8625,7 +9068,6 @@ var RecordService = class {
|
|
|
8625
9068
|
await this.auditService.logRecordAction({
|
|
8626
9069
|
action: "record.deleted",
|
|
8627
9070
|
actorId: this.userId,
|
|
8628
|
-
actorEmail: this.userEmail,
|
|
8629
9071
|
objectName: schema.name,
|
|
8630
9072
|
objectId: schema.id,
|
|
8631
9073
|
recordId: record.id,
|
|
@@ -8677,7 +9119,6 @@ var RecordService = class {
|
|
|
8677
9119
|
await this.auditService.logRecordAction({
|
|
8678
9120
|
action: "record.restored",
|
|
8679
9121
|
actorId: this.userId,
|
|
8680
|
-
actorEmail: this.userEmail,
|
|
8681
9122
|
objectName: schema.name,
|
|
8682
9123
|
objectId: schema.id,
|
|
8683
9124
|
recordId: restored.id,
|
|
@@ -8823,14 +9264,29 @@ var RecordService = class {
|
|
|
8823
9264
|
if (this.permissionService && this.userId) {
|
|
8824
9265
|
await this.checkPermission(schema.name, "read");
|
|
8825
9266
|
}
|
|
8826
|
-
const
|
|
9267
|
+
const policy = options?.skipPolicyFilter ? void 0 : this.getPolicy(schema.name);
|
|
9268
|
+
let effectiveOptions = options;
|
|
9269
|
+
if (policy?.applyListFilter) {
|
|
9270
|
+
effectiveOptions = policy.applyListFilter(this.buildPolicyContext(schema.name), options);
|
|
9271
|
+
}
|
|
9272
|
+
const result = await this.adapter.objectRecords.list(objectId, effectiveOptions);
|
|
9273
|
+
let filteredRecords = result.records;
|
|
9274
|
+
let effectiveTotal = result.total;
|
|
9275
|
+
if (policy?.canAccessRecord) {
|
|
9276
|
+
const ctx = this.buildPolicyContext(schema.name);
|
|
9277
|
+
filteredRecords = result.records.filter((record) => policy.canAccessRecord?.(ctx, record));
|
|
9278
|
+
effectiveTotal = filteredRecords.length;
|
|
9279
|
+
}
|
|
8827
9280
|
if (!options?.skipFormulas) {
|
|
8828
9281
|
return {
|
|
8829
|
-
records: this.enrichRecordsWithFormulas(
|
|
8830
|
-
total:
|
|
9282
|
+
records: this.enrichRecordsWithFormulas(filteredRecords, schema),
|
|
9283
|
+
total: effectiveTotal
|
|
8831
9284
|
};
|
|
8832
9285
|
}
|
|
8833
|
-
return
|
|
9286
|
+
return {
|
|
9287
|
+
records: filteredRecords,
|
|
9288
|
+
total: effectiveTotal
|
|
9289
|
+
};
|
|
8834
9290
|
}
|
|
8835
9291
|
/**
|
|
8836
9292
|
* Search records using full-text search
|
|
@@ -8845,7 +9301,7 @@ var RecordService = class {
|
|
|
8845
9301
|
if (this.permissionService && this.userId) {
|
|
8846
9302
|
await this.checkPermission(schema.name, "read");
|
|
8847
9303
|
}
|
|
8848
|
-
const result = await this.adapter.objectRecords.search(
|
|
9304
|
+
const result = await this.adapter.objectRecords.search(objectId, query, options);
|
|
8849
9305
|
if (!options?.skipFormulas) {
|
|
8850
9306
|
return {
|
|
8851
9307
|
records: this.enrichRecordsWithFormulas(result.records, schema),
|
|
@@ -9048,16 +9504,15 @@ var RollupScheduler = class {
|
|
|
9048
9504
|
this.maxPending = options?.maxPending ?? 100;
|
|
9049
9505
|
}
|
|
9050
9506
|
/**
|
|
9051
|
-
* Schedule a rollup recalculation for a parent record
|
|
9507
|
+
* Schedule a rollup recalculation for a parent record.
|
|
9052
9508
|
*
|
|
9053
9509
|
* If the same record is already pending, the timer is reset.
|
|
9054
9510
|
* If too many recalculations are pending, triggers an immediate flush.
|
|
9055
9511
|
*
|
|
9056
9512
|
* @param parentId - ID of the parent record to recalculate
|
|
9057
9513
|
* @param parentObjectId - Object ID of the parent
|
|
9058
|
-
* @param tenantId - Tenant ID
|
|
9059
9514
|
*/
|
|
9060
|
-
scheduleRecalculation(parentId, parentObjectId
|
|
9515
|
+
scheduleRecalculation(parentId, parentObjectId) {
|
|
9061
9516
|
const key = `${parentId}:${parentObjectId}`;
|
|
9062
9517
|
const existing = this.pending.get(key);
|
|
9063
9518
|
if (existing) {
|
|
@@ -9067,7 +9522,7 @@ var RollupScheduler = class {
|
|
|
9067
9522
|
await this.executeRecalculation(parentId, parentObjectId);
|
|
9068
9523
|
this.pending.delete(key);
|
|
9069
9524
|
}, this.debounceMs);
|
|
9070
|
-
this.pending.set(key, { parentId, parentObjectId,
|
|
9525
|
+
this.pending.set(key, { parentId, parentObjectId, timeout });
|
|
9071
9526
|
if (this.pending.size >= this.maxPending) {
|
|
9072
9527
|
this.flush();
|
|
9073
9528
|
}
|
|
@@ -9081,16 +9536,11 @@ var RollupScheduler = class {
|
|
|
9081
9536
|
clearTimeout(entry.timeout);
|
|
9082
9537
|
}
|
|
9083
9538
|
this.pending.clear();
|
|
9084
|
-
const tenantMap = /* @__PURE__ */ new Map();
|
|
9085
|
-
for (const [, entry] of entries) {
|
|
9086
|
-
tenantMap.set(entry.parentId, entry.tenantId);
|
|
9087
|
-
}
|
|
9088
9539
|
const allIds = entries.map(([, entry]) => entry.parentId);
|
|
9089
9540
|
const records = await this.adapter.objectRecords.findByIds(allIds);
|
|
9090
9541
|
for (const record of records) {
|
|
9091
9542
|
const schema = await this.getSchemaById(record.objectId);
|
|
9092
|
-
|
|
9093
|
-
if (schema && tenantId) {
|
|
9543
|
+
if (schema) {
|
|
9094
9544
|
await this.rollupService.recalculateAndUpdate(record, schema);
|
|
9095
9545
|
}
|
|
9096
9546
|
}
|
|
@@ -9123,27 +9573,25 @@ var RollupScheduler = class {
|
|
|
9123
9573
|
};
|
|
9124
9574
|
|
|
9125
9575
|
// src/runtime/services/user-profile.service.ts
|
|
9126
|
-
var UserProfileService = class {
|
|
9127
|
-
constructor(adapter,
|
|
9576
|
+
var UserProfileService = class extends TenantAwareService {
|
|
9577
|
+
constructor(adapter, options) {
|
|
9578
|
+
super();
|
|
9128
9579
|
this.adapter = adapter;
|
|
9129
|
-
this.tenantId = tenantId;
|
|
9130
9580
|
this.auditService = options?.auditService;
|
|
9131
|
-
this.userId = options?.userId;
|
|
9132
|
-
this.userEmail = options?.userEmail;
|
|
9133
9581
|
}
|
|
9134
9582
|
/**
|
|
9135
|
-
* Create a new user profile (typically after first auth)
|
|
9583
|
+
* Create a new user profile (typically after first auth).
|
|
9584
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9136
9585
|
*
|
|
9137
|
-
* @param data - User profile creation data
|
|
9586
|
+
* @param data - User profile creation data (without tenantId)
|
|
9138
9587
|
* @returns Created user profile
|
|
9139
9588
|
*
|
|
9140
9589
|
* @example
|
|
9141
9590
|
* ```typescript
|
|
9142
|
-
* const service = new UserProfileService(adapter
|
|
9591
|
+
* const service = new UserProfileService(adapter);
|
|
9143
9592
|
*
|
|
9144
9593
|
* // After Supabase auth
|
|
9145
9594
|
* const profile = await service.createProfile({
|
|
9146
|
-
* tenantId: "tenant-123",
|
|
9147
9595
|
* authId: authUser.id,
|
|
9148
9596
|
* email: authUser.email,
|
|
9149
9597
|
* firstName: authUser.user_metadata.first_name,
|
|
@@ -9154,16 +9602,11 @@ var UserProfileService = class {
|
|
|
9154
9602
|
* ```
|
|
9155
9603
|
*/
|
|
9156
9604
|
async createProfile(data) {
|
|
9157
|
-
if (data.tenantId !== this.tenantId) {
|
|
9158
|
-
throw new Error(
|
|
9159
|
-
`Tenant mismatch: service initialized with "${this.tenantId}" but data has "${data.tenantId}"`
|
|
9160
|
-
);
|
|
9161
|
-
}
|
|
9162
9605
|
const existing = await this.adapter.userProfiles.findByAuthId(data.authId);
|
|
9163
9606
|
if (existing) {
|
|
9164
9607
|
throw new Error(`User profile with authId "${data.authId}" already exists`);
|
|
9165
9608
|
}
|
|
9166
|
-
const existingEmail = await this.adapter.userProfiles.findByEmail(
|
|
9609
|
+
const existingEmail = await this.adapter.userProfiles.findByEmail(data.email);
|
|
9167
9610
|
if (existingEmail) {
|
|
9168
9611
|
throw new Error(`User profile with email "${data.email}" already exists in this tenant`);
|
|
9169
9612
|
}
|
|
@@ -9172,7 +9615,6 @@ var UserProfileService = class {
|
|
|
9172
9615
|
await this.auditService.logUserAction({
|
|
9173
9616
|
action: "user.created",
|
|
9174
9617
|
actorId: this.userId,
|
|
9175
|
-
actorEmail: this.userEmail,
|
|
9176
9618
|
targetUserId: profile.id,
|
|
9177
9619
|
targetUserEmail: profile.email
|
|
9178
9620
|
});
|
|
@@ -9218,7 +9660,6 @@ var UserProfileService = class {
|
|
|
9218
9660
|
* const profile = await service.getOrCreateProfile(
|
|
9219
9661
|
* authUser.id,
|
|
9220
9662
|
* {
|
|
9221
|
-
* tenantId: "tenant-123",
|
|
9222
9663
|
* authId: authUser.id,
|
|
9223
9664
|
* email: authUser.email,
|
|
9224
9665
|
* role: "member",
|
|
@@ -9235,7 +9676,8 @@ var UserProfileService = class {
|
|
|
9235
9676
|
return await this.createProfile(data);
|
|
9236
9677
|
}
|
|
9237
9678
|
/**
|
|
9238
|
-
* Update user profile
|
|
9679
|
+
* Update user profile.
|
|
9680
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9239
9681
|
*/
|
|
9240
9682
|
async updateProfile(profileId, data) {
|
|
9241
9683
|
const existing = await this.getProfileOrThrow(profileId);
|
|
@@ -9251,7 +9693,6 @@ var UserProfileService = class {
|
|
|
9251
9693
|
await this.auditService.logUserAction({
|
|
9252
9694
|
action: "user.updated",
|
|
9253
9695
|
actorId: this.userId,
|
|
9254
|
-
actorEmail: this.userEmail,
|
|
9255
9696
|
targetUserId: profileId,
|
|
9256
9697
|
targetUserEmail: updated.email,
|
|
9257
9698
|
changes
|
|
@@ -9261,7 +9702,8 @@ var UserProfileService = class {
|
|
|
9261
9702
|
return updated;
|
|
9262
9703
|
}
|
|
9263
9704
|
/**
|
|
9264
|
-
* Delete user profile
|
|
9705
|
+
* Delete user profile.
|
|
9706
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9265
9707
|
*
|
|
9266
9708
|
* @param profileId - Profile UUID
|
|
9267
9709
|
* @param options - Delete options
|
|
@@ -9270,7 +9712,7 @@ var UserProfileService = class {
|
|
|
9270
9712
|
const profile = await this.getProfileOrThrow(profileId);
|
|
9271
9713
|
if (options?.checkAdmin) {
|
|
9272
9714
|
if (profile.role === "admin") {
|
|
9273
|
-
const adminCount = await this.adapter.userProfiles.countByRole(
|
|
9715
|
+
const adminCount = await this.adapter.userProfiles.countByRole("admin");
|
|
9274
9716
|
if (adminCount <= 1) {
|
|
9275
9717
|
throw new Error("Cannot delete the last admin user");
|
|
9276
9718
|
}
|
|
@@ -9281,17 +9723,17 @@ var UserProfileService = class {
|
|
|
9281
9723
|
await this.auditService.logUserAction({
|
|
9282
9724
|
action: "user.deleted",
|
|
9283
9725
|
actorId: this.userId,
|
|
9284
|
-
actorEmail: this.userEmail,
|
|
9285
9726
|
targetUserId: profileId,
|
|
9286
9727
|
targetUserEmail: profile.email
|
|
9287
9728
|
});
|
|
9288
9729
|
}
|
|
9289
9730
|
}
|
|
9290
9731
|
/**
|
|
9291
|
-
* List all user profiles for the tenant
|
|
9732
|
+
* List all user profiles for the tenant.
|
|
9733
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9292
9734
|
*/
|
|
9293
9735
|
async listProfiles(options) {
|
|
9294
|
-
return await this.adapter.userProfiles.list(
|
|
9736
|
+
return await this.adapter.userProfiles.list(options);
|
|
9295
9737
|
}
|
|
9296
9738
|
/**
|
|
9297
9739
|
* Update last login timestamp
|
|
@@ -9326,10 +9768,11 @@ var UserProfileService = class {
|
|
|
9326
9768
|
return await this.updateProfile(profileId, { status: newStatus });
|
|
9327
9769
|
}
|
|
9328
9770
|
/**
|
|
9329
|
-
* Get user by email
|
|
9771
|
+
* Get user by email.
|
|
9772
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9330
9773
|
*/
|
|
9331
9774
|
async getProfileByEmail(email) {
|
|
9332
|
-
return await this.adapter.userProfiles.findByEmail(
|
|
9775
|
+
return await this.adapter.userProfiles.findByEmail(email);
|
|
9333
9776
|
}
|
|
9334
9777
|
/**
|
|
9335
9778
|
* Check if user has role
|
|
@@ -9347,38 +9790,39 @@ var UserProfileService = class {
|
|
|
9347
9790
|
};
|
|
9348
9791
|
|
|
9349
9792
|
// src/runtime/services/view.service.ts
|
|
9350
|
-
var ViewService = class {
|
|
9793
|
+
var ViewService = class extends TenantAwareService {
|
|
9351
9794
|
constructor(adapter, nativeViews) {
|
|
9795
|
+
super();
|
|
9352
9796
|
this.adapter = adapter;
|
|
9353
9797
|
this.nativeViews = nativeViews;
|
|
9354
9798
|
}
|
|
9355
9799
|
/**
|
|
9356
|
-
* Get all views for an object (native + custom)
|
|
9800
|
+
* Get all views for an object (native + custom).
|
|
9801
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9357
9802
|
*
|
|
9358
9803
|
* @param objectName - Object name
|
|
9359
|
-
* @param tenantId - Tenant ID
|
|
9360
9804
|
* @returns All views for the object
|
|
9361
9805
|
*/
|
|
9362
|
-
async getViewsForObject(objectName
|
|
9806
|
+
async getViewsForObject(objectName) {
|
|
9363
9807
|
const nativeViewDefs = this.nativeViews.getByObjectName(objectName);
|
|
9364
|
-
const dbViews = await this.adapter.views.findByObjectName(
|
|
9808
|
+
const dbViews = await this.adapter.views.findByObjectName(objectName);
|
|
9365
9809
|
const customViews = dbViews.filter((v) => !v.system).map(this.convertDBViewToDefinition);
|
|
9366
9810
|
return [...nativeViewDefs, ...customViews];
|
|
9367
9811
|
}
|
|
9368
9812
|
/**
|
|
9369
|
-
* Get a specific view by name
|
|
9813
|
+
* Get a specific view by name.
|
|
9814
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9370
9815
|
*
|
|
9371
9816
|
* @param objectName - Object name
|
|
9372
9817
|
* @param viewName - View name
|
|
9373
|
-
* @param tenantId - Tenant ID
|
|
9374
9818
|
* @returns View definition or null
|
|
9375
9819
|
*/
|
|
9376
|
-
async getView(objectName, viewName
|
|
9820
|
+
async getView(objectName, viewName) {
|
|
9377
9821
|
const nativeView = this.nativeViews.get(objectName, viewName);
|
|
9378
9822
|
if (nativeView) {
|
|
9379
9823
|
return nativeView;
|
|
9380
9824
|
}
|
|
9381
|
-
const dbView = await this.adapter.views.findByName(
|
|
9825
|
+
const dbView = await this.adapter.views.findByName(objectName, viewName);
|
|
9382
9826
|
if (dbView) {
|
|
9383
9827
|
return this.convertDBViewToDefinition(dbView);
|
|
9384
9828
|
}
|
|
@@ -9393,11 +9837,10 @@ var ViewService = class {
|
|
|
9393
9837
|
* 3. First available view
|
|
9394
9838
|
*
|
|
9395
9839
|
* @param objectName - Object name
|
|
9396
|
-
* @param tenantId - Tenant ID
|
|
9397
9840
|
* @returns Default view or null
|
|
9398
9841
|
*/
|
|
9399
|
-
async getDefaultView(objectName
|
|
9400
|
-
const views = await this.getViewsForObject(objectName
|
|
9842
|
+
async getDefaultView(objectName) {
|
|
9843
|
+
const views = await this.getViewsForObject(objectName);
|
|
9401
9844
|
const customDefault = views.find((v) => v.default && !v.system);
|
|
9402
9845
|
if (customDefault) return customDefault;
|
|
9403
9846
|
const nativeDefault = views.find((v) => v.default && v.system);
|
|
@@ -9405,15 +9848,15 @@ var ViewService = class {
|
|
|
9405
9848
|
return views[0] ?? null;
|
|
9406
9849
|
}
|
|
9407
9850
|
/**
|
|
9408
|
-
* Create a custom view
|
|
9851
|
+
* Create a custom view.
|
|
9852
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9409
9853
|
*
|
|
9410
9854
|
* @param input - View definition
|
|
9411
|
-
* @param tenantId - Tenant ID
|
|
9412
9855
|
* @returns Created view
|
|
9413
9856
|
*/
|
|
9414
|
-
async createView(input
|
|
9857
|
+
async createView(input) {
|
|
9415
9858
|
this.validateViewName(input.name);
|
|
9416
|
-
const existing = await this.adapter.views.findByName(
|
|
9859
|
+
const existing = await this.adapter.views.findByName(input.objectName, input.name);
|
|
9417
9860
|
if (existing) {
|
|
9418
9861
|
throw new SchemaError(
|
|
9419
9862
|
`View "${input.name}" already exists for object "${input.objectName}"`,
|
|
@@ -9429,7 +9872,6 @@ var ViewService = class {
|
|
|
9429
9872
|
);
|
|
9430
9873
|
}
|
|
9431
9874
|
const dbView = await this.adapter.views.create({
|
|
9432
|
-
tenantId,
|
|
9433
9875
|
objectName: input.objectName,
|
|
9434
9876
|
name: input.name,
|
|
9435
9877
|
label: input.label,
|
|
@@ -9484,18 +9926,18 @@ var ViewService = class {
|
|
|
9484
9926
|
await this.adapter.views.delete(viewId);
|
|
9485
9927
|
}
|
|
9486
9928
|
/**
|
|
9487
|
-
* Set a view as default for its object
|
|
9929
|
+
* Set a view as default for its object.
|
|
9930
|
+
* Automatically uses tenant context from AsyncLocalStorage.
|
|
9488
9931
|
*
|
|
9489
9932
|
* @param viewId - View ID
|
|
9490
|
-
* @param tenantId - Tenant ID
|
|
9491
9933
|
* @returns Updated view
|
|
9492
9934
|
*/
|
|
9493
|
-
async setDefaultView(viewId
|
|
9935
|
+
async setDefaultView(viewId) {
|
|
9494
9936
|
const dbView = await this.adapter.views.findById(viewId);
|
|
9495
9937
|
if (!dbView) {
|
|
9496
9938
|
throw new NotFoundError("View", viewId);
|
|
9497
9939
|
}
|
|
9498
|
-
const currentViews = await this.adapter.views.findByObjectName(
|
|
9940
|
+
const currentViews = await this.adapter.views.findByObjectName(dbView.objectName);
|
|
9499
9941
|
for (const v of currentViews) {
|
|
9500
9942
|
if (v.default && v.id !== viewId && !v.system) {
|
|
9501
9943
|
await this.adapter.views.update(v.id, { default: false });
|
|
@@ -9651,7 +10093,6 @@ async function syncSingleView(adapter, nativeView, result, options) {
|
|
|
9651
10093
|
return;
|
|
9652
10094
|
}
|
|
9653
10095
|
await adapter.views.upsert({
|
|
9654
|
-
tenantId: options.tenantId || "default",
|
|
9655
10096
|
objectName: nativeView.object,
|
|
9656
10097
|
name: nativeView.name,
|
|
9657
10098
|
label: nativeView.label,
|
|
@@ -9791,9 +10232,8 @@ async function handleDryRun(adapter, nativeObject, existingObject, result, optio
|
|
|
9791
10232
|
result.attributesSynced++;
|
|
9792
10233
|
}
|
|
9793
10234
|
}
|
|
9794
|
-
async function upsertObject(adapter, nativeObject,
|
|
10235
|
+
async function upsertObject(adapter, nativeObject, _options) {
|
|
9795
10236
|
return await adapter.objects.upsert({
|
|
9796
|
-
tenantId: options.tenantId || "default",
|
|
9797
10237
|
system: true,
|
|
9798
10238
|
name: nativeObject.name,
|
|
9799
10239
|
label: nativeObject.label,
|
|
@@ -9904,6 +10344,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9904
10344
|
GroupBuilder,
|
|
9905
10345
|
InvalidPathError,
|
|
9906
10346
|
MaxDepthExceededError,
|
|
10347
|
+
NOTES,
|
|
9907
10348
|
NO_VALUE_OPERATORS,
|
|
9908
10349
|
NoopGeocodingAdapter,
|
|
9909
10350
|
NoopHookRegistry,
|
|
@@ -9915,11 +10356,15 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9915
10356
|
ObjectReferencedError,
|
|
9916
10357
|
ObjectSchemaService,
|
|
9917
10358
|
PermissionService,
|
|
10359
|
+
PolicyRegistry,
|
|
10360
|
+
PolicyViolationError,
|
|
9918
10361
|
ProtectedResourceError,
|
|
9919
10362
|
ProtectedRoleError,
|
|
9920
10363
|
QueryBuilder,
|
|
9921
10364
|
QueryMultipleResultsError,
|
|
9922
10365
|
QueryNoResultError,
|
|
10366
|
+
RELATION_TARGET_ANY,
|
|
10367
|
+
RESERVED_ATTRIBUTE_NAMES,
|
|
9923
10368
|
RecordNotFoundError,
|
|
9924
10369
|
RecordReferencedError,
|
|
9925
10370
|
RecordService,
|
|
@@ -9929,6 +10374,7 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9929
10374
|
RollupScheduler,
|
|
9930
10375
|
RollupService,
|
|
9931
10376
|
SHORTCUT_TO_FILTER_OPERATOR,
|
|
10377
|
+
SYSTEM_ATTRIBUTES,
|
|
9932
10378
|
SYSTEM_FIELD_NAMES,
|
|
9933
10379
|
SYSTEM_RESOURCES,
|
|
9934
10380
|
SYSTEM_RESOURCE_LABELS,
|
|
@@ -9937,12 +10383,17 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9937
10383
|
SyncError,
|
|
9938
10384
|
TabBuilder,
|
|
9939
10385
|
TableTabConfig,
|
|
10386
|
+
TenantAwareRepository,
|
|
10387
|
+
TenantAwareService,
|
|
10388
|
+
TenantContextError,
|
|
9940
10389
|
UserProfileNotFoundError,
|
|
9941
10390
|
UserProfileService,
|
|
9942
10391
|
UserService,
|
|
9943
10392
|
ValidationError,
|
|
9944
10393
|
ViewBuilder,
|
|
9945
10394
|
ViewService,
|
|
10395
|
+
asTenantId,
|
|
10396
|
+
asUserId,
|
|
9946
10397
|
attributeConfigSchemas,
|
|
9947
10398
|
buildAuditChanges,
|
|
9948
10399
|
checkbox,
|
|
@@ -9966,17 +10417,19 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
9966
10417
|
createQueryBuilder,
|
|
9967
10418
|
createRatingValidator,
|
|
9968
10419
|
createRelationValidator,
|
|
10420
|
+
createRichtextValidator,
|
|
9969
10421
|
createRollupValidator,
|
|
9970
10422
|
createSelectValidator,
|
|
9971
10423
|
createSingleRelationValidator,
|
|
9972
10424
|
createStatusValidator,
|
|
10425
|
+
createTextAreaValidator,
|
|
9973
10426
|
createTextValidator,
|
|
9974
|
-
createTimestampValidator,
|
|
9975
10427
|
createUserValidator,
|
|
9976
10428
|
currency,
|
|
9977
10429
|
currencyConfigSchema,
|
|
9978
10430
|
date,
|
|
9979
10431
|
dateConfigSchema,
|
|
10432
|
+
defaultPolicyRegistry,
|
|
9980
10433
|
enrichValuesWithSelectLabels,
|
|
9981
10434
|
evaluateFormula,
|
|
9982
10435
|
evaluateFormulaAttribute,
|
|
@@ -10000,13 +10453,18 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
10000
10453
|
generateId,
|
|
10001
10454
|
generatePrefixedId,
|
|
10002
10455
|
getAttributeConfigSchema,
|
|
10456
|
+
getContext,
|
|
10003
10457
|
getMissingRequiredAttributes,
|
|
10004
10458
|
getPathDepth,
|
|
10005
10459
|
getRelationPath,
|
|
10006
10460
|
getSyncPreview,
|
|
10461
|
+
getSystemAttributeList,
|
|
10007
10462
|
getTargetAttributeName,
|
|
10463
|
+
getTenantId,
|
|
10464
|
+
getUserId,
|
|
10008
10465
|
getViewSyncPreview,
|
|
10009
10466
|
group,
|
|
10467
|
+
hasContext,
|
|
10010
10468
|
hasRelationReferences,
|
|
10011
10469
|
isActivityTab,
|
|
10012
10470
|
isAdvancedFilterState,
|
|
@@ -10019,16 +10477,21 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
10019
10477
|
isLabelExpression,
|
|
10020
10478
|
isNoValueOperator,
|
|
10021
10479
|
isNotFoundError,
|
|
10480
|
+
isNotesTab,
|
|
10022
10481
|
isProtectedResourceError,
|
|
10023
10482
|
isRecordComplete,
|
|
10024
10483
|
isSchemaError,
|
|
10484
|
+
isSystemAttribute,
|
|
10485
|
+
isSystemAttributeObject,
|
|
10025
10486
|
isSystemFlow,
|
|
10026
10487
|
isTableTab,
|
|
10488
|
+
isUniversalRelation,
|
|
10027
10489
|
isValidationError,
|
|
10028
10490
|
location,
|
|
10029
10491
|
locationConfigSchema,
|
|
10030
10492
|
multiselect,
|
|
10031
10493
|
multiselectConfigSchema,
|
|
10494
|
+
notesPolicy,
|
|
10032
10495
|
number,
|
|
10033
10496
|
numberConfigSchema,
|
|
10034
10497
|
object,
|
|
@@ -10045,8 +10508,11 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
10045
10508
|
renderLabelExpression,
|
|
10046
10509
|
resolveMultiplePaths,
|
|
10047
10510
|
resolveSingleValue,
|
|
10511
|
+
richtext,
|
|
10512
|
+
richtextConfigSchema,
|
|
10048
10513
|
rollup,
|
|
10049
10514
|
rollupConfigSchema,
|
|
10515
|
+
runWithContext,
|
|
10050
10516
|
safeParseAttributeConfig,
|
|
10051
10517
|
select,
|
|
10052
10518
|
selectConfigSchema,
|
|
@@ -10059,8 +10525,6 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
10059
10525
|
textConfigSchema,
|
|
10060
10526
|
textarea,
|
|
10061
10527
|
textareaConfigSchema,
|
|
10062
|
-
timestamp,
|
|
10063
|
-
timestampConfigSchema,
|
|
10064
10528
|
toAdvancedFilterState,
|
|
10065
10529
|
toSimpleFilterState,
|
|
10066
10530
|
traversePath,
|
|
@@ -10077,5 +10541,6 @@ async function syncAll(adapter, objectRegistry, nativeViewRegistry, options = {}
|
|
|
10077
10541
|
verifyNativeObjectsSync,
|
|
10078
10542
|
verifyNativeViewsSync,
|
|
10079
10543
|
view,
|
|
10080
|
-
viewRegistry
|
|
10544
|
+
viewRegistry,
|
|
10545
|
+
withTenantContext
|
|
10081
10546
|
});
|