@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.
@@ -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.array(blockNoteBlockSchema, {
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?.queryService;
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.adapter.objectRecords.findByIds(ids);
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
- let label;
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 compositeIds = ids.map((id) => `${attributeId}:${id}`);
9856
- return this.cachedByMany(
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.fetchResolveIdsBatch(missingCompositeIds),
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 fetch and resolve multiple composite IDs at once.
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 fetchResolveIdsBatch(compositeIds) {
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.adapter.objectRecords.findByIds(uniqueRecordIds);
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
- let label;
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
- * Internal method to fetch and resolve relation IDs (no caching).
9995
- * Uses batch fetching for performance - fetches all records in one query,
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 fetchResolveIds(ids, attributeId) {
9999
- if (ids.length === 0) {
10000
- return [];
10001
- }
10002
- const attribute = await this.findAttributeById(attributeId);
10003
- const records = await this.adapter.objectRecords.findByIds(ids);
10004
- if (records.length === 0) {
10005
- return [];
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
- resolved.push({
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 resolved;
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.adapter.objectRecords.findById(recordId);
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.adapter.objectRecords.findByIds(relatedIds);
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.adapter.objectRecords.findById(recordId);
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
- resolveRelationIds: (ids, attrId) => this.relationService.resolveIds(ids, attrId),
10476
- findRecordLabels: (ids) => this.adapter.objectRecords.findByIds(ids)
10477
- };
10478
- this.rollupContext = {
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/relation-resolver.service.ts
10878
- var RelationResolverService = class {
10879
- constructor(adapter) {
10880
- this.adapter = adapter;
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
- * Phase 2: Supports 1 level of relation traversal only
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.adapter.objectRecords.findByIds(idsToFetch);
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.adapter.objectRecords.findByIds([...allIdsToFetch]);
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?.debounceMs ?? 100;
11026
- this.maxPending = options?.maxPending ?? 100;
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
- RelationResolverService,
14601
+ FormulaResolverService,
14465
14602
  RollupScheduler,
14466
14603
  WorkflowService,
14467
14604
  WorkflowInstanceService,