@stndrds/schema 0.1.0-alpha.51 → 0.1.0-alpha.53
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-EK56IQJ7.mjs → chunk-2EIZ6QXN.mjs} +272 -135
- package/dist/{chunk-IEHBD7WL.js → chunk-67XEOXQL.js} +362 -225
- package/dist/index.d.mts +96 -8
- package/dist/index.d.ts +96 -8
- package/dist/index.js +10 -6
- package/dist/index.mjs +7 -3
- package/dist/{runtime-DRiI1SCJ.d.mts → runtime-B5JYQdZx.d.mts} +661 -689
- package/dist/{runtime-DRiI1SCJ.d.ts → runtime-B5JYQdZx.d.ts} +661 -689
- package/dist/runtime.d.mts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +6 -2
- package/dist/runtime.mjs +7 -3
- package/package.json +2 -2
|
@@ -473,7 +473,26 @@ var cacheKeys = {
|
|
|
473
473
|
// Invalidation Patterns (global)
|
|
474
474
|
// -------------------------------------------------------------------------
|
|
475
475
|
/** All cache for a tenant (nuclear option) */
|
|
476
|
-
allForTenant: (tenantId) => `*:${tenantId}
|
|
476
|
+
allForTenant: (tenantId) => `*:${tenantId}:*`,
|
|
477
|
+
// -------------------------------------------------------------------------
|
|
478
|
+
// Shared Resources - No tenant scoping
|
|
479
|
+
// -------------------------------------------------------------------------
|
|
480
|
+
/** Object schema for shared object (not tenant-scoped) */
|
|
481
|
+
sharedObjectSchema: (objectId) => `shared:schema:obj:${objectId}`,
|
|
482
|
+
/** Object schema for shared object by name */
|
|
483
|
+
sharedObjectSchemaByName: (name) => `shared:schema:name:${name}`,
|
|
484
|
+
/** Attributes for a shared object */
|
|
485
|
+
sharedObjectAttributes: (objectId) => `shared:attrs:${objectId}`,
|
|
486
|
+
/** Record from a shared object */
|
|
487
|
+
sharedRecord: (recordId) => `shared:record:${recordId}`,
|
|
488
|
+
/** Record list for a shared object */
|
|
489
|
+
sharedRecordList: (objectId, hash) => `shared:records:${objectId}:list:${hash}`,
|
|
490
|
+
/** All shared record lists for an object (for invalidation) */
|
|
491
|
+
allSharedRecordLists: (objectId) => `shared:records:${objectId}:list:*`,
|
|
492
|
+
/** All shared schemas (for invalidation) */
|
|
493
|
+
allSharedSchemas: () => "shared:schema:*",
|
|
494
|
+
/** All shared records (for invalidation) */
|
|
495
|
+
allSharedRecords: () => "shared:record:*"
|
|
477
496
|
};
|
|
478
497
|
var cacheTtl = {
|
|
479
498
|
/** Object schemas - rarely change (1 hour) */
|
|
@@ -2885,6 +2904,7 @@ function createMockObjectsRepository(stores) {
|
|
|
2885
2904
|
icon: data.icon,
|
|
2886
2905
|
labelExpression: data.labelExpression,
|
|
2887
2906
|
system: data.system ?? false,
|
|
2907
|
+
sharingMode: data.sharingMode ?? "private",
|
|
2888
2908
|
metadata: data.metadata,
|
|
2889
2909
|
createdAt: /* @__PURE__ */ new Date(),
|
|
2890
2910
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -2943,6 +2963,7 @@ function createMockObjectsRepository(stores) {
|
|
|
2943
2963
|
icon: data.icon,
|
|
2944
2964
|
labelExpression: data.labelExpression,
|
|
2945
2965
|
system: data.system,
|
|
2966
|
+
sharingMode: data.sharingMode,
|
|
2946
2967
|
metadata: data.metadata,
|
|
2947
2968
|
createdAt: /* @__PURE__ */ new Date(),
|
|
2948
2969
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -5692,6 +5713,7 @@ function rollup(config) {
|
|
|
5692
5713
|
import z2 from "zod";
|
|
5693
5714
|
var ObjectBuilder = class {
|
|
5694
5715
|
constructor(config) {
|
|
5716
|
+
this._sharingMode = "private";
|
|
5695
5717
|
this.validateName(config.name);
|
|
5696
5718
|
this.obj = {
|
|
5697
5719
|
name: config.name,
|
|
@@ -5730,6 +5752,29 @@ var ObjectBuilder = class {
|
|
|
5730
5752
|
this.obj.system = true;
|
|
5731
5753
|
return this;
|
|
5732
5754
|
}
|
|
5755
|
+
/**
|
|
5756
|
+
* Mark this object as shared across all tenants.
|
|
5757
|
+
*
|
|
5758
|
+
* Shared objects are:
|
|
5759
|
+
* - Readable by all tenants
|
|
5760
|
+
* - Only writable by the owner tenant (tenant_id)
|
|
5761
|
+
* - Only syncable by the master tenant (requires masterTenantId in config)
|
|
5762
|
+
*
|
|
5763
|
+
* Records belonging to a shared object inherit the sharing mode.
|
|
5764
|
+
*
|
|
5765
|
+
* @example
|
|
5766
|
+
* ```typescript
|
|
5767
|
+
* const PRODUCT_CATALOG = object({ name: "product-catalog", label: "Product Catalog" })
|
|
5768
|
+
* .shared() // All tenants can read, only master can write
|
|
5769
|
+
* .system()
|
|
5770
|
+
* .labelExpression("{{ name }}")
|
|
5771
|
+
* .attribute(text({ name: "name", label: "Name" }).required());
|
|
5772
|
+
* ```
|
|
5773
|
+
*/
|
|
5774
|
+
shared() {
|
|
5775
|
+
this._sharingMode = "shared";
|
|
5776
|
+
return this;
|
|
5777
|
+
}
|
|
5733
5778
|
/**
|
|
5734
5779
|
* Add an attribute to the object with type accumulation
|
|
5735
5780
|
*
|
|
@@ -5810,7 +5855,8 @@ The labelExpression defines how records are displayed in lists and relations.
|
|
|
5810
5855
|
return {
|
|
5811
5856
|
...this.obj,
|
|
5812
5857
|
pluralLabel: this._pluralLabel,
|
|
5813
|
-
labelExpression: this._labelExpression
|
|
5858
|
+
labelExpression: this._labelExpression,
|
|
5859
|
+
sharingMode: this._sharingMode
|
|
5814
5860
|
};
|
|
5815
5861
|
}
|
|
5816
5862
|
/**
|
|
@@ -7543,17 +7589,8 @@ function createRollupValidator(_attr, _messages = DEFAULT_VALIDATION_MESSAGES) {
|
|
|
7543
7589
|
function createTextAreaValidator(_attr, _messages = DEFAULT_VALIDATION_MESSAGES) {
|
|
7544
7590
|
return z5.string();
|
|
7545
7591
|
}
|
|
7546
|
-
var blockNoteBlockSchema = z5.lazy(
|
|
7547
|
-
() => z5.object({
|
|
7548
|
-
id: z5.string().min(1, "Block must have an id"),
|
|
7549
|
-
type: z5.string().min(1, "Block must have a type"),
|
|
7550
|
-
props: z5.record(z5.string(), z5.union([z5.boolean(), z5.number(), z5.string()])),
|
|
7551
|
-
content: z5.any().optional(),
|
|
7552
|
-
children: z5.array(z5.any())
|
|
7553
|
-
})
|
|
7554
|
-
);
|
|
7555
7592
|
function createRichtextValidator(attr, messages = DEFAULT_VALIDATION_MESSAGES) {
|
|
7556
|
-
return z5.
|
|
7593
|
+
return z5.string({
|
|
7557
7594
|
message: messages.invalidRichtext(attr)
|
|
7558
7595
|
});
|
|
7559
7596
|
}
|
|
@@ -8239,6 +8276,25 @@ Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
|
|
|
8239
8276
|
const dbObjects = await this.adapter.objects.list();
|
|
8240
8277
|
return Promise.all(dbObjects.map((dbObject) => this.buildObjectDefinition(dbObject)));
|
|
8241
8278
|
}
|
|
8279
|
+
/**
|
|
8280
|
+
* Get object ownership info for write access checks.
|
|
8281
|
+
*
|
|
8282
|
+
* Used by RecordService to verify if the current tenant can write
|
|
8283
|
+
* to a shared object's records.
|
|
8284
|
+
*
|
|
8285
|
+
* @param objectId - Object UUID
|
|
8286
|
+
* @returns Object ownership info with tenantId and sharingMode
|
|
8287
|
+
*/
|
|
8288
|
+
async getObjectOwnerInfo(objectId) {
|
|
8289
|
+
const dbObject = await this.adapter.objects.findById(objectId);
|
|
8290
|
+
if (!dbObject) {
|
|
8291
|
+
throw new Error(`Object with id "${objectId}" not found`);
|
|
8292
|
+
}
|
|
8293
|
+
return {
|
|
8294
|
+
tenantId: dbObject.tenantId,
|
|
8295
|
+
sharingMode: dbObject.sharingMode
|
|
8296
|
+
};
|
|
8297
|
+
}
|
|
8242
8298
|
/**
|
|
8243
8299
|
* Invalidate all schema-related cache for the current tenant.
|
|
8244
8300
|
* Called automatically after schema mutations.
|
|
@@ -9228,6 +9284,15 @@ function checkRecordDeleteOrThrow(policy, record, context) {
|
|
|
9228
9284
|
throw new PolicyViolationError(policy.objectName, "delete", record.id);
|
|
9229
9285
|
}
|
|
9230
9286
|
}
|
|
9287
|
+
function checkSharedObjectWriteAccess(objectName, sharingMode, objectOwnerTenantId, currentTenantId) {
|
|
9288
|
+
if (sharingMode === "shared" && objectOwnerTenantId !== currentTenantId) {
|
|
9289
|
+
throw new SchemaError(
|
|
9290
|
+
`Cannot modify shared object "${objectName}". Shared objects are read-only for non-owner tenants.`,
|
|
9291
|
+
SchemaErrorCode.FORBIDDEN,
|
|
9292
|
+
{ objectName, ownerTenantId: objectOwnerTenantId, currentTenantId }
|
|
9293
|
+
);
|
|
9294
|
+
}
|
|
9295
|
+
}
|
|
9231
9296
|
|
|
9232
9297
|
// src/runtime/services/record/helpers/label.ts
|
|
9233
9298
|
function extractRelationIds2(val) {
|
|
@@ -9596,12 +9661,105 @@ var RecordQueryService = class extends BaseService {
|
|
|
9596
9661
|
}
|
|
9597
9662
|
};
|
|
9598
9663
|
|
|
9664
|
+
// src/runtime/services/record/record-resolver.service.ts
|
|
9665
|
+
var RecordResolverService = class extends BaseService {
|
|
9666
|
+
constructor(adapter) {
|
|
9667
|
+
super(adapter);
|
|
9668
|
+
}
|
|
9669
|
+
// ============================================================================
|
|
9670
|
+
// CACHED RECORD ACCESS
|
|
9671
|
+
// ============================================================================
|
|
9672
|
+
/**
|
|
9673
|
+
* Find a record by ID with caching.
|
|
9674
|
+
*
|
|
9675
|
+
* Uses the shared record cache for optimal performance.
|
|
9676
|
+
* Delegates to findByIds for consistent cache handling.
|
|
9677
|
+
*
|
|
9678
|
+
* @param id - Record ID
|
|
9679
|
+
* @returns Record or null if not found
|
|
9680
|
+
*/
|
|
9681
|
+
async findById(id) {
|
|
9682
|
+
if (!id) return null;
|
|
9683
|
+
const results = await this.findByIds([id]);
|
|
9684
|
+
return results[0] ?? null;
|
|
9685
|
+
}
|
|
9686
|
+
/**
|
|
9687
|
+
* Find multiple records by IDs with caching.
|
|
9688
|
+
*
|
|
9689
|
+
* Each record is cached individually for reuse across services.
|
|
9690
|
+
* Only fetches records not already in cache.
|
|
9691
|
+
*
|
|
9692
|
+
* @param ids - Record IDs to fetch
|
|
9693
|
+
* @returns Array of found records (missing IDs are not included)
|
|
9694
|
+
*/
|
|
9695
|
+
async findByIds(ids) {
|
|
9696
|
+
if (!ids || ids.length === 0) {
|
|
9697
|
+
return [];
|
|
9698
|
+
}
|
|
9699
|
+
const uniqueIds = [...new Set(ids)];
|
|
9700
|
+
return this.cachedByMany(
|
|
9701
|
+
"record",
|
|
9702
|
+
uniqueIds,
|
|
9703
|
+
(missingIds) => this.adapter.objectRecords.findByIds(missingIds),
|
|
9704
|
+
(record) => record.id,
|
|
9705
|
+
cacheTtl.records
|
|
9706
|
+
);
|
|
9707
|
+
}
|
|
9708
|
+
// ============================================================================
|
|
9709
|
+
// FACTORY METHODS
|
|
9710
|
+
// ============================================================================
|
|
9711
|
+
/**
|
|
9712
|
+
* Create a RelationLabelResolver callback for computeLabelWithRelations.
|
|
9713
|
+
*
|
|
9714
|
+
* Used by RelationService.resolveLabel() and ObjectSchemaService.
|
|
9715
|
+
*
|
|
9716
|
+
* @returns Callback that resolves record IDs to their labels (cached)
|
|
9717
|
+
*/
|
|
9718
|
+
createRelationLabelResolver() {
|
|
9719
|
+
return async (ids) => {
|
|
9720
|
+
const records = await this.findByIds(ids);
|
|
9721
|
+
return new Map(records.map((r) => [r.id, r.label]));
|
|
9722
|
+
};
|
|
9723
|
+
}
|
|
9724
|
+
/**
|
|
9725
|
+
* Create a LabelResolver interface for label computation helpers.
|
|
9726
|
+
*
|
|
9727
|
+
* Used by RecordService for computing record labels.
|
|
9728
|
+
*
|
|
9729
|
+
* @param relationService - RelationService for resolving relation display labels
|
|
9730
|
+
* @returns LabelResolver interface with cached record fetching
|
|
9731
|
+
*/
|
|
9732
|
+
createLabelResolver(relationService) {
|
|
9733
|
+
return {
|
|
9734
|
+
resolveRelationIds: (ids, attrId) => relationService.resolveIds(ids, attrId),
|
|
9735
|
+
findRecordLabels: (ids) => this.findByIds(ids)
|
|
9736
|
+
};
|
|
9737
|
+
}
|
|
9738
|
+
/**
|
|
9739
|
+
* Create a RollupCascadeContext for rollup recalculation.
|
|
9740
|
+
*
|
|
9741
|
+
* Used by RecordService after create/update/delete operations.
|
|
9742
|
+
*
|
|
9743
|
+
* @param rollupService - RollupService for recalculating rollups
|
|
9744
|
+
* @param schemaService - ObjectSchemaService for fetching schemas
|
|
9745
|
+
* @returns Context with cached record fetching
|
|
9746
|
+
*/
|
|
9747
|
+
createRollupContext(rollupService, schemaService) {
|
|
9748
|
+
return {
|
|
9749
|
+
rollupService,
|
|
9750
|
+
schemaService,
|
|
9751
|
+
findRecordsByIds: (ids) => this.findByIds(ids)
|
|
9752
|
+
};
|
|
9753
|
+
}
|
|
9754
|
+
};
|
|
9755
|
+
|
|
9599
9756
|
// src/runtime/services/record/relation.service.ts
|
|
9600
9757
|
var RelationService = class extends BaseService {
|
|
9601
9758
|
constructor(adapter, nativeRegistry, options) {
|
|
9602
9759
|
super(adapter);
|
|
9603
9760
|
this.schemaService = new ObjectSchemaService(adapter, nativeRegistry);
|
|
9604
|
-
this.queryService = options
|
|
9761
|
+
this.queryService = options.queryService;
|
|
9762
|
+
this.recordResolver = options.recordResolver;
|
|
9605
9763
|
}
|
|
9606
9764
|
/**
|
|
9607
9765
|
* Set the query service after construction.
|
|
@@ -9679,7 +9837,7 @@ var RelationService = class extends BaseService {
|
|
|
9679
9837
|
});
|
|
9680
9838
|
return errors;
|
|
9681
9839
|
}
|
|
9682
|
-
const records = await this.
|
|
9840
|
+
const records = await this.recordResolver.findByIds(ids);
|
|
9683
9841
|
const recordMap = new Map(records.map((r) => [r.id, r]));
|
|
9684
9842
|
const invalidIds = [];
|
|
9685
9843
|
for (const id of ids) {
|
|
@@ -9801,20 +9959,7 @@ var RelationService = class extends BaseService {
|
|
|
9801
9959
|
const result = query ? await queryService.searchRecords(objectSchema.id, query, queryOptions) : await queryService.listRecords(objectSchema.id, queryOptions);
|
|
9802
9960
|
totalCount += result.total;
|
|
9803
9961
|
for (const record of result.records) {
|
|
9804
|
-
|
|
9805
|
-
if (target.displayTemplate) {
|
|
9806
|
-
label = await computeLabelWithRelations(
|
|
9807
|
-
target.displayTemplate,
|
|
9808
|
-
record.values,
|
|
9809
|
-
objectSchema.attributes,
|
|
9810
|
-
async (ids) => {
|
|
9811
|
-
const linkedRecords = await this.adapter.objectRecords.findByIds(ids);
|
|
9812
|
-
return new Map(linkedRecords.map((r) => [r.id, r.label]));
|
|
9813
|
-
}
|
|
9814
|
-
);
|
|
9815
|
-
} else {
|
|
9816
|
-
label = record.label;
|
|
9817
|
-
}
|
|
9962
|
+
const label = await this.resolveLabel(record, objectSchema, target.displayTemplate);
|
|
9818
9963
|
allOptions.push({
|
|
9819
9964
|
id: record.id,
|
|
9820
9965
|
objectId: objectSchema.id,
|
|
@@ -9852,17 +9997,8 @@ var RelationService = class extends BaseService {
|
|
|
9852
9997
|
if (!ids || ids.length === 0) {
|
|
9853
9998
|
return [];
|
|
9854
9999
|
}
|
|
9855
|
-
const
|
|
9856
|
-
return
|
|
9857
|
-
"resolvedRelation",
|
|
9858
|
-
compositeIds,
|
|
9859
|
-
async (missingCompositeIds) => {
|
|
9860
|
-
const missingRecordIds = missingCompositeIds.map((c) => c.split(":")[1]);
|
|
9861
|
-
return this.fetchResolveIds(missingRecordIds, attributeId);
|
|
9862
|
-
},
|
|
9863
|
-
(item) => `${attributeId}:${item.id}`,
|
|
9864
|
-
cacheTtl.resolvedRelations
|
|
9865
|
-
);
|
|
10000
|
+
const result = await this.resolveIdsBatch([{ attributeId, ids }]);
|
|
10001
|
+
return result[attributeId] ?? [];
|
|
9866
10002
|
}
|
|
9867
10003
|
/**
|
|
9868
10004
|
* Resolve multiple attribute/IDs batches in a single operation.
|
|
@@ -9905,7 +10041,7 @@ var RelationService = class extends BaseService {
|
|
|
9905
10041
|
const allResolved = await this.cachedByMany(
|
|
9906
10042
|
"resolvedRelation",
|
|
9907
10043
|
allCompositeIds,
|
|
9908
|
-
(missingCompositeIds) => this.
|
|
10044
|
+
(missingCompositeIds) => this.resolveCompositeIds(missingCompositeIds),
|
|
9909
10045
|
(item) => item._compositeId,
|
|
9910
10046
|
cacheTtl.resolvedRelations
|
|
9911
10047
|
);
|
|
@@ -9926,10 +10062,12 @@ var RelationService = class extends BaseService {
|
|
|
9926
10062
|
return response;
|
|
9927
10063
|
}
|
|
9928
10064
|
/**
|
|
9929
|
-
* Internal method to
|
|
10065
|
+
* Internal method to resolve multiple composite IDs at once.
|
|
9930
10066
|
* Optimized for batch operations - single DB query for all records.
|
|
10067
|
+
*
|
|
10068
|
+
* @param compositeIds - Array of composite IDs in format "attributeId:recordId"
|
|
9931
10069
|
*/
|
|
9932
|
-
async
|
|
10070
|
+
async resolveCompositeIds(compositeIds) {
|
|
9933
10071
|
if (compositeIds.length === 0) {
|
|
9934
10072
|
return [];
|
|
9935
10073
|
}
|
|
@@ -9939,7 +10077,7 @@ var RelationService = class extends BaseService {
|
|
|
9939
10077
|
});
|
|
9940
10078
|
const uniqueRecordIds = [...new Set(parsed.map((p) => p.recordId))];
|
|
9941
10079
|
const uniqueAttributeIds = [...new Set(parsed.map((p) => p.attributeId))];
|
|
9942
|
-
const records = await this.
|
|
10080
|
+
const records = await this.recordResolver.findByIds(uniqueRecordIds);
|
|
9943
10081
|
if (records.length === 0) {
|
|
9944
10082
|
return [];
|
|
9945
10083
|
}
|
|
@@ -9964,20 +10102,7 @@ var RelationService = class extends BaseService {
|
|
|
9964
10102
|
const attribute = attributeMap.get(attributeId);
|
|
9965
10103
|
const targetConfig = attribute?.targets?.find((t) => t.object === objectSchema.name);
|
|
9966
10104
|
const customTemplate = targetConfig?.displayTemplate;
|
|
9967
|
-
|
|
9968
|
-
if (customTemplate) {
|
|
9969
|
-
label = await computeLabelWithRelations(
|
|
9970
|
-
customTemplate,
|
|
9971
|
-
record.values,
|
|
9972
|
-
objectSchema.attributes,
|
|
9973
|
-
async (nestedIds) => {
|
|
9974
|
-
const linkedRecords = await this.adapter.objectRecords.findByIds(nestedIds);
|
|
9975
|
-
return new Map(linkedRecords.map((r) => [r.id, r.label]));
|
|
9976
|
-
}
|
|
9977
|
-
);
|
|
9978
|
-
} else {
|
|
9979
|
-
label = record.label;
|
|
9980
|
-
}
|
|
10105
|
+
const label = await this.resolveLabel(record, objectSchema, customTemplate);
|
|
9981
10106
|
resolved.push({
|
|
9982
10107
|
_compositeId: compositeId,
|
|
9983
10108
|
id: record.id,
|
|
@@ -9991,59 +10116,22 @@ var RelationService = class extends BaseService {
|
|
|
9991
10116
|
return resolved;
|
|
9992
10117
|
}
|
|
9993
10118
|
/**
|
|
9994
|
-
*
|
|
9995
|
-
* Uses
|
|
9996
|
-
* then groups by objectId to minimize schema lookups.
|
|
10119
|
+
* Resolve the display label for a record.
|
|
10120
|
+
* Uses custom template if provided, otherwise falls back to pre-computed label.
|
|
9997
10121
|
*/
|
|
9998
|
-
async
|
|
9999
|
-
if (
|
|
10000
|
-
return
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
const recordsByObjectId = /* @__PURE__ */ new Map();
|
|
10008
|
-
for (const record of records) {
|
|
10009
|
-
const existing = recordsByObjectId.get(record.objectId) ?? [];
|
|
10010
|
-
existing.push(record);
|
|
10011
|
-
recordsByObjectId.set(record.objectId, existing);
|
|
10012
|
-
}
|
|
10013
|
-
const resolved = [];
|
|
10014
|
-
for (const [objectId, objectRecords] of recordsByObjectId) {
|
|
10015
|
-
const objectSchema = await this.schemaService.getObjectSchema(objectId);
|
|
10016
|
-
if (!objectSchema) {
|
|
10017
|
-
continue;
|
|
10018
|
-
}
|
|
10019
|
-
const targetConfig = attribute?.targets?.find((t) => t.object === objectSchema.name);
|
|
10020
|
-
const customTemplate = targetConfig?.displayTemplate;
|
|
10021
|
-
for (const record of objectRecords) {
|
|
10022
|
-
let label;
|
|
10023
|
-
if (customTemplate) {
|
|
10024
|
-
label = await computeLabelWithRelations(
|
|
10025
|
-
customTemplate,
|
|
10026
|
-
record.values,
|
|
10027
|
-
objectSchema.attributes,
|
|
10028
|
-
async (nestedIds) => {
|
|
10029
|
-
const linkedRecords = await this.adapter.objectRecords.findByIds(nestedIds);
|
|
10030
|
-
return new Map(linkedRecords.map((r) => [r.id, r.label]));
|
|
10031
|
-
}
|
|
10032
|
-
);
|
|
10033
|
-
} else {
|
|
10034
|
-
label = record.label;
|
|
10122
|
+
async resolveLabel(record, objectSchema, customTemplate) {
|
|
10123
|
+
if (customTemplate) {
|
|
10124
|
+
return computeLabelWithRelations(
|
|
10125
|
+
customTemplate,
|
|
10126
|
+
record.values,
|
|
10127
|
+
objectSchema.attributes,
|
|
10128
|
+
async (nestedIds) => {
|
|
10129
|
+
const linkedRecords = await this.recordResolver.findByIds(nestedIds);
|
|
10130
|
+
return new Map(linkedRecords.map((r) => [r.id, r.label]));
|
|
10035
10131
|
}
|
|
10036
|
-
|
|
10037
|
-
id: record.id,
|
|
10038
|
-
objectId: record.objectId,
|
|
10039
|
-
objectName: objectSchema.name,
|
|
10040
|
-
objectLabel: objectSchema.label,
|
|
10041
|
-
objectIcon: objectSchema.icon,
|
|
10042
|
-
label
|
|
10043
|
-
});
|
|
10044
|
-
}
|
|
10132
|
+
);
|
|
10045
10133
|
}
|
|
10046
|
-
return
|
|
10134
|
+
return record.label;
|
|
10047
10135
|
}
|
|
10048
10136
|
/**
|
|
10049
10137
|
* Find a relation attribute by ID.
|
|
@@ -10074,8 +10162,9 @@ var RelationService = class extends BaseService {
|
|
|
10074
10162
|
|
|
10075
10163
|
// src/runtime/services/record/rollup.service.ts
|
|
10076
10164
|
var RollupService = class extends BaseService {
|
|
10077
|
-
constructor(adapter) {
|
|
10165
|
+
constructor(adapter, options) {
|
|
10078
10166
|
super(adapter);
|
|
10167
|
+
this.recordResolver = options.recordResolver;
|
|
10079
10168
|
}
|
|
10080
10169
|
/**
|
|
10081
10170
|
* Calculate a rollup value for a record
|
|
@@ -10129,7 +10218,7 @@ var RollupService = class extends BaseService {
|
|
|
10129
10218
|
* Example: entreprise222 has relation "entreprises" → companies, rollup collects from companies
|
|
10130
10219
|
*/
|
|
10131
10220
|
async calculateForward(recordId, rollupAttr) {
|
|
10132
|
-
const record = await this.
|
|
10221
|
+
const record = await this.recordResolver.findById(recordId);
|
|
10133
10222
|
if (!record) {
|
|
10134
10223
|
return { value: null, recordCount: 0 };
|
|
10135
10224
|
}
|
|
@@ -10143,7 +10232,7 @@ var RollupService = class extends BaseService {
|
|
|
10143
10232
|
if (relatedIds.length === 0) {
|
|
10144
10233
|
return { value: this.getEmptyValue(rollupAttr.function), recordCount: 0 };
|
|
10145
10234
|
}
|
|
10146
|
-
const relatedRecords = await this.
|
|
10235
|
+
const relatedRecords = await this.recordResolver.findByIds(relatedIds);
|
|
10147
10236
|
if (relatedRecords.length === 0) {
|
|
10148
10237
|
return { value: this.getEmptyValue(rollupAttr.function), recordCount: 0 };
|
|
10149
10238
|
}
|
|
@@ -10154,7 +10243,7 @@ var RollupService = class extends BaseService {
|
|
|
10154
10243
|
* Example: Company has rollup on "orders", Order has relation "company" → companies
|
|
10155
10244
|
*/
|
|
10156
10245
|
async calculateReverse(recordId, rollupAttr, schema) {
|
|
10157
|
-
const record = await this.
|
|
10246
|
+
const record = await this.recordResolver.findById(recordId);
|
|
10158
10247
|
if (!record) {
|
|
10159
10248
|
return { value: null, recordCount: 0 };
|
|
10160
10249
|
}
|
|
@@ -10461,25 +10550,25 @@ var RecordService = class extends BaseService {
|
|
|
10461
10550
|
this.permissionService = options?.permissionService;
|
|
10462
10551
|
this.auditService = options?.auditService ?? (adapter.audit ? new AuditService(adapter) : void 0);
|
|
10463
10552
|
this.policyRegistry = options?.policyRegistry === null ? null : options?.policyRegistry ?? defaultPolicyRegistry;
|
|
10553
|
+
this.recordResolver = new RecordResolverService(adapter);
|
|
10464
10554
|
this.queryService = new RecordQueryService(adapter, this.schemaService, {
|
|
10465
10555
|
permissionService: this.permissionService,
|
|
10466
10556
|
policyRegistry: this.policyRegistry
|
|
10467
10557
|
});
|
|
10468
10558
|
this.relationService = new RelationService(adapter, registry, {
|
|
10469
|
-
queryService: this.queryService
|
|
10559
|
+
queryService: this.queryService,
|
|
10560
|
+
recordResolver: this.recordResolver
|
|
10561
|
+
});
|
|
10562
|
+
this.rollupService = new RollupService(adapter, {
|
|
10563
|
+
recordResolver: this.recordResolver
|
|
10470
10564
|
});
|
|
10471
10565
|
this.userService = new UserService(adapter);
|
|
10472
|
-
this.rollupService = new RollupService(adapter);
|
|
10473
10566
|
this.hookRegistry = options?.hookRegistry ?? new NoopHookRegistry();
|
|
10474
|
-
this.labelResolver =
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
rollupService: this.rollupService,
|
|
10480
|
-
schemaService: this.schemaService,
|
|
10481
|
-
findRecordsByIds: (ids) => this.adapter.objectRecords.findByIds(ids)
|
|
10482
|
-
};
|
|
10567
|
+
this.labelResolver = this.recordResolver.createLabelResolver(this.relationService);
|
|
10568
|
+
this.rollupContext = this.recordResolver.createRollupContext(
|
|
10569
|
+
this.rollupService,
|
|
10570
|
+
this.schemaService
|
|
10571
|
+
);
|
|
10483
10572
|
}
|
|
10484
10573
|
// ============================================================================
|
|
10485
10574
|
// CREATE
|
|
@@ -10496,6 +10585,13 @@ var RecordService = class extends BaseService {
|
|
|
10496
10585
|
*/
|
|
10497
10586
|
async createRecord(objectId, data, options) {
|
|
10498
10587
|
const schema = await this.schemaService.getObjectSchema(objectId);
|
|
10588
|
+
const ownerInfo = await this.schemaService.getObjectOwnerInfo(objectId);
|
|
10589
|
+
checkSharedObjectWriteAccess(
|
|
10590
|
+
schema.name,
|
|
10591
|
+
ownerInfo.sharingMode,
|
|
10592
|
+
ownerInfo.tenantId,
|
|
10593
|
+
this.tenantId
|
|
10594
|
+
);
|
|
10499
10595
|
const dataWithDefaults = applyDefaultValues(schema, data);
|
|
10500
10596
|
await checkPermission(this.permissionService, this.userId, schema.name, "create");
|
|
10501
10597
|
const hookCtx = createContextForCreate(
|
|
@@ -10613,6 +10709,13 @@ var RecordService = class extends BaseService {
|
|
|
10613
10709
|
throw new RecordNotFoundError(recordId);
|
|
10614
10710
|
}
|
|
10615
10711
|
const schema = await this.schemaService.getObjectSchema(existing.objectId);
|
|
10712
|
+
const ownerInfo = await this.schemaService.getObjectOwnerInfo(existing.objectId);
|
|
10713
|
+
checkSharedObjectWriteAccess(
|
|
10714
|
+
schema.name,
|
|
10715
|
+
ownerInfo.sharingMode,
|
|
10716
|
+
ownerInfo.tenantId,
|
|
10717
|
+
this.tenantId
|
|
10718
|
+
);
|
|
10616
10719
|
await checkPermission(this.permissionService, this.userId, schema.name, "update");
|
|
10617
10720
|
const policy = getPolicy(this.policyRegistry, this.userId, schema.name);
|
|
10618
10721
|
if (policy && this.userId) {
|
|
@@ -10722,6 +10825,13 @@ var RecordService = class extends BaseService {
|
|
|
10722
10825
|
throw new RecordNotFoundError(recordId);
|
|
10723
10826
|
}
|
|
10724
10827
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
10828
|
+
const ownerInfo = await this.schemaService.getObjectOwnerInfo(record.objectId);
|
|
10829
|
+
checkSharedObjectWriteAccess(
|
|
10830
|
+
schema.name,
|
|
10831
|
+
ownerInfo.sharingMode,
|
|
10832
|
+
ownerInfo.tenantId,
|
|
10833
|
+
this.tenantId
|
|
10834
|
+
);
|
|
10725
10835
|
await checkPermission(this.permissionService, this.userId, schema.name, "delete");
|
|
10726
10836
|
const policy = getPolicy(this.policyRegistry, this.userId, schema.name);
|
|
10727
10837
|
if (policy && this.userId) {
|
|
@@ -10793,6 +10903,13 @@ var RecordService = class extends BaseService {
|
|
|
10793
10903
|
);
|
|
10794
10904
|
}
|
|
10795
10905
|
const schema = await this.schemaService.getObjectSchema(record.objectId);
|
|
10906
|
+
const ownerInfo = await this.schemaService.getObjectOwnerInfo(record.objectId);
|
|
10907
|
+
checkSharedObjectWriteAccess(
|
|
10908
|
+
schema.name,
|
|
10909
|
+
ownerInfo.sharingMode,
|
|
10910
|
+
ownerInfo.tenantId,
|
|
10911
|
+
this.tenantId
|
|
10912
|
+
);
|
|
10796
10913
|
await checkPermission(this.permissionService, this.userId, schema.name, "update");
|
|
10797
10914
|
const hookCtx = createContextForRestore(schema, this.tenantId, record, options?.hookMetadata);
|
|
10798
10915
|
if (!options?.skipHooks) {
|
|
@@ -10874,15 +10991,19 @@ var RecordService = class extends BaseService {
|
|
|
10874
10991
|
}
|
|
10875
10992
|
};
|
|
10876
10993
|
|
|
10877
|
-
// src/runtime/services/record/
|
|
10878
|
-
var
|
|
10879
|
-
constructor(adapter) {
|
|
10880
|
-
|
|
10994
|
+
// src/runtime/services/record/formula-resolver.service.ts
|
|
10995
|
+
var FormulaResolverService = class extends BaseService {
|
|
10996
|
+
constructor(adapter, options) {
|
|
10997
|
+
super(adapter);
|
|
10998
|
+
this.recordResolver = options.recordResolver;
|
|
10881
10999
|
}
|
|
11000
|
+
// ============================================================================
|
|
11001
|
+
// RESOLUTION
|
|
11002
|
+
// ============================================================================
|
|
10882
11003
|
/**
|
|
10883
11004
|
* Resolve values from related records for formula evaluation
|
|
10884
11005
|
*
|
|
10885
|
-
*
|
|
11006
|
+
* Supports 1 level of relation traversal only.
|
|
10886
11007
|
*
|
|
10887
11008
|
* @param record - The source record
|
|
10888
11009
|
* @param schema - Schema of the source object
|
|
@@ -10891,7 +11012,6 @@ var RelationResolverService = class {
|
|
|
10891
11012
|
*
|
|
10892
11013
|
* @example
|
|
10893
11014
|
* ```typescript
|
|
10894
|
-
* // For an order with company relation
|
|
10895
11015
|
* const resolved = await resolver.resolveRelationValues(
|
|
10896
11016
|
* orderRecord,
|
|
10897
11017
|
* orderSchema,
|
|
@@ -10920,7 +11040,7 @@ var RelationResolverService = class {
|
|
|
10920
11040
|
if (idsToFetch.length === 0) {
|
|
10921
11041
|
return result;
|
|
10922
11042
|
}
|
|
10923
|
-
const relatedRecords = await this.
|
|
11043
|
+
const relatedRecords = await this.recordResolver.findByIds(idsToFetch);
|
|
10924
11044
|
for (const relatedRecord of relatedRecords) {
|
|
10925
11045
|
const attrName = attrIdMap.get(relatedRecord.id);
|
|
10926
11046
|
if (attrName) {
|
|
@@ -10966,7 +11086,7 @@ var RelationResolverService = class {
|
|
|
10966
11086
|
if (allIdsToFetch.size === 0) {
|
|
10967
11087
|
return resultMap;
|
|
10968
11088
|
}
|
|
10969
|
-
const relatedRecords = await this.
|
|
11089
|
+
const relatedRecords = await this.recordResolver.findByIds([...allIdsToFetch]);
|
|
10970
11090
|
const relatedRecordMap = new Map(relatedRecords.map((r) => [r.id, r]));
|
|
10971
11091
|
for (const record of records) {
|
|
10972
11092
|
const result = resultMap.get(record.id);
|
|
@@ -10999,10 +11119,12 @@ var RelationResolverService = class {
|
|
|
10999
11119
|
}
|
|
11000
11120
|
return flat;
|
|
11001
11121
|
}
|
|
11122
|
+
// ============================================================================
|
|
11123
|
+
// PRIVATE HELPERS
|
|
11124
|
+
// ============================================================================
|
|
11002
11125
|
/**
|
|
11003
11126
|
* Extract a single relation ID from a value
|
|
11004
11127
|
* Handles both single (string) and multi (array) relations
|
|
11005
|
-
* @internal
|
|
11006
11128
|
*/
|
|
11007
11129
|
extractSingleId(value) {
|
|
11008
11130
|
if (typeof value === "string" && value.length > 0) {
|
|
@@ -11021,9 +11143,9 @@ var RollupScheduler = class {
|
|
|
11021
11143
|
this.adapter = adapter;
|
|
11022
11144
|
this.getSchemaById = getSchemaById;
|
|
11023
11145
|
this.pending = /* @__PURE__ */ new Map();
|
|
11024
|
-
this.rollupService = new RollupService(adapter);
|
|
11025
|
-
this.debounceMs = options
|
|
11026
|
-
this.maxPending = options
|
|
11146
|
+
this.rollupService = new RollupService(adapter, { recordResolver: options.recordResolver });
|
|
11147
|
+
this.debounceMs = options.debounceMs ?? 100;
|
|
11148
|
+
this.maxPending = options.maxPending ?? 100;
|
|
11027
11149
|
}
|
|
11028
11150
|
/**
|
|
11029
11151
|
* Schedule a rollup recalculation for a parent record.
|
|
@@ -14014,6 +14136,18 @@ async function syncSingleObject(adapter, nativeObject, result, options) {
|
|
|
14014
14136
|
if (!nativeObject.system) {
|
|
14015
14137
|
throw new Error(`Object ${nativeObject.name} is not marked as system`);
|
|
14016
14138
|
}
|
|
14139
|
+
if (nativeObject.sharingMode === "shared") {
|
|
14140
|
+
if (!options.masterTenantId) {
|
|
14141
|
+
throw new Error(
|
|
14142
|
+
`Cannot sync shared object "${nativeObject.name}": masterTenantId must be configured in tenant options`
|
|
14143
|
+
);
|
|
14144
|
+
}
|
|
14145
|
+
if (options.tenantId && options.tenantId !== options.masterTenantId) {
|
|
14146
|
+
throw new Error(
|
|
14147
|
+
`Cannot sync shared object "${nativeObject.name}": only master tenant "${options.masterTenantId}" can sync shared objects (current: "${options.tenantId}")`
|
|
14148
|
+
);
|
|
14149
|
+
}
|
|
14150
|
+
}
|
|
14017
14151
|
const existingObject = await adapter.objects.findSystemByName(nativeObject.name);
|
|
14018
14152
|
const isNew = !existingObject;
|
|
14019
14153
|
updateObjectStats(result, isNew);
|
|
@@ -14058,6 +14192,7 @@ async function upsertObject(adapter, nativeObject, _options) {
|
|
|
14058
14192
|
description: nativeObject.description,
|
|
14059
14193
|
labelExpression: nativeObject.labelExpression,
|
|
14060
14194
|
icon: nativeObject.icon,
|
|
14195
|
+
sharingMode: nativeObject.sharingMode ?? "private",
|
|
14061
14196
|
metadata: nativeObject.metadata
|
|
14062
14197
|
});
|
|
14063
14198
|
}
|
|
@@ -14449,6 +14584,7 @@ export {
|
|
|
14449
14584
|
checkRecordAccess,
|
|
14450
14585
|
checkRecordModifyOrThrow,
|
|
14451
14586
|
checkRecordDeleteOrThrow,
|
|
14587
|
+
checkSharedObjectWriteAccess,
|
|
14452
14588
|
computeLabel,
|
|
14453
14589
|
enrichWithFormulas,
|
|
14454
14590
|
enrichRecordsWithFormulas,
|
|
@@ -14458,10 +14594,11 @@ export {
|
|
|
14458
14594
|
createContextForRestore,
|
|
14459
14595
|
recalculateParentRollups,
|
|
14460
14596
|
RecordQueryService,
|
|
14597
|
+
RecordResolverService,
|
|
14461
14598
|
RelationService,
|
|
14462
14599
|
RollupService,
|
|
14463
14600
|
RecordService,
|
|
14464
|
-
|
|
14601
|
+
FormulaResolverService,
|
|
14465
14602
|
RollupScheduler,
|
|
14466
14603
|
WorkflowService,
|
|
14467
14604
|
WorkflowInstanceService,
|