@stndrds/schema 0.1.0-alpha.62 → 0.1.0-alpha.64

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.
@@ -10,6 +10,7 @@ var _chunkNEVERCM3js = require('./chunk-NEVERCM3.js');
10
10
 
11
11
 
12
12
 
13
+
13
14
  var _chunkU4AB53AMjs = require('./chunk-U4AB53AM.js');
14
15
 
15
16
 
@@ -4133,7 +4134,6 @@ function createMockRelationAttributesRepository(stores) {
4133
4134
  }
4134
4135
  const results = [];
4135
4136
  for (const item of items) {
4136
- const _key = `${context.tenantId}:${item.fromObject}:${item.fromId}:${item.fromAttribute}:${item.toId}`;
4137
4137
  const existing = Array.from(stores.relationAttributes.values()).find(
4138
4138
  (row) => row.tenantId === context.tenantId && row.fromObject === item.fromObject && row.fromId === item.fromId && row.fromAttribute === item.fromAttribute && row.toId === item.toId
4139
4139
  );
@@ -4180,6 +4180,36 @@ function createMockRelationAttributesRepository(stores) {
4180
4180
  (row) => row.tenantId === context.tenantId && row.toId === toId
4181
4181
  );
4182
4182
  },
4183
+ async findBySourceBatch(fromObject, fromIds, fromAttribute) {
4184
+ const context = getContext2();
4185
+ if (!context) {
4186
+ throw new Error("Context required for relationAttributes operations");
4187
+ }
4188
+ return Array.from(stores.relationAttributes.values()).filter(
4189
+ (row) => row.tenantId === context.tenantId && row.fromObject === fromObject && fromIds.includes(row.fromId) && row.fromAttribute === fromAttribute
4190
+ );
4191
+ },
4192
+ async findByTargetBatch(toIds) {
4193
+ const context = getContext2();
4194
+ if (!context) {
4195
+ throw new Error("Context required for relationAttributes operations");
4196
+ }
4197
+ return Array.from(stores.relationAttributes.values()).filter(
4198
+ (row) => row.tenantId === context.tenantId && toIds.includes(row.toId)
4199
+ );
4200
+ },
4201
+ async deleteBySourceAndTarget(fromObject, fromId, fromAttribute, toId) {
4202
+ const context = getContext2();
4203
+ if (!context) {
4204
+ throw new Error("Context required for relationAttributes operations");
4205
+ }
4206
+ const toDelete = Array.from(stores.relationAttributes.entries()).filter(
4207
+ ([_id, row]) => row.tenantId === context.tenantId && row.fromObject === fromObject && row.fromId === fromId && row.fromAttribute === fromAttribute && row.toId === toId
4208
+ );
4209
+ for (const [id] of toDelete) {
4210
+ stores.relationAttributes.delete(id);
4211
+ }
4212
+ },
4183
4213
  async deleteBySource(fromObject, fromId, fromAttribute) {
4184
4214
  const context = getContext2();
4185
4215
  if (!context) {
@@ -5244,92 +5274,6 @@ var PolicyRegistry = class {
5244
5274
  };
5245
5275
  var defaultPolicyRegistry = new PolicyRegistry();
5246
5276
 
5247
- // src/runtime/policies/notes.policy.ts
5248
- var notesPolicy = {
5249
- objectName: "notes",
5250
- /**
5251
- * Inject visibility filter for list/search operations.
5252
- *
5253
- * Creates: (visibility = 'shared') OR (createdBy = userId)
5254
- *
5255
- * This filters at the SQL level for better performance.
5256
- * canAccessRecord provides defense-in-depth as a secondary check.
5257
- *
5258
- * Note: When user filters exist, we preserve linkedTo filter (common for notes)
5259
- * but the visibility OR filter takes precedence. Full filter merging would require
5260
- * AdvancedFilterState support in the adapter.
5261
- */
5262
- applyListFilter(ctx, options) {
5263
- const visibilityRules = [
5264
- { attribute: "visibility", operator: "is", value: "shared" },
5265
- { attribute: "createdBy", operator: "is", value: ctx.userId }
5266
- ];
5267
- if (!_optionalChain([options, 'optionalAccess', _113 => _113.filters]) || options.filters.rules.length === 0) {
5268
- return {
5269
- ...options,
5270
- filters: { combinator: "or", rules: visibilityRules }
5271
- };
5272
- }
5273
- const linkedToFilter = options.filters.rules.find((r) => r.attribute === "linkedTo");
5274
- if (linkedToFilter) {
5275
- return {
5276
- ...options,
5277
- filters: {
5278
- combinator: "and",
5279
- rules: [linkedToFilter]
5280
- }
5281
- };
5282
- }
5283
- return {
5284
- ...options,
5285
- filters: { combinator: "or", rules: visibilityRules }
5286
- };
5287
- },
5288
- /**
5289
- * Check if user can access a specific note.
5290
- *
5291
- * Rules:
5292
- * - Shared notes: accessible by anyone in the tenant
5293
- * - Private notes: only accessible by the author (createdBy)
5294
- */
5295
- canAccessRecord(ctx, record) {
5296
- const visibility = record.values.visibility;
5297
- if (visibility === "shared") {
5298
- return true;
5299
- }
5300
- if (visibility === "private") {
5301
- return record.createdBy === ctx.userId;
5302
- }
5303
- return false;
5304
- },
5305
- /**
5306
- * Check if user can modify a note.
5307
- *
5308
- * Rules:
5309
- * - Private notes: only the author can modify
5310
- * - Shared notes: anyone can modify
5311
- */
5312
- canModifyRecord(ctx, record) {
5313
- const visibility = record.values.visibility;
5314
- if (visibility === "private") {
5315
- return record.createdBy === ctx.userId;
5316
- }
5317
- return true;
5318
- },
5319
- /**
5320
- * Check if user can delete a note.
5321
- *
5322
- * Rules:
5323
- * - Only the author (createdBy) can delete a note, regardless of visibility
5324
- */
5325
- canDeleteRecord(ctx, record) {
5326
- return record.createdBy === ctx.userId;
5327
- }
5328
- };
5329
-
5330
- // src/runtime/policies/index.ts
5331
- defaultPolicyRegistry.register(notesPolicy);
5332
-
5333
5277
  // src/runtime/services/base.service.ts
5334
5278
  var BaseService = class {
5335
5279
  constructor(adapter) {
@@ -5410,7 +5354,7 @@ var BaseService = class {
5410
5354
  * @param key - Cache key to invalidate
5411
5355
  */
5412
5356
  async invalidateCache(key) {
5413
- await _optionalChain([this, 'access', _114 => _114.cache, 'optionalAccess', _115 => _115.delete, 'call', _116 => _116(key)]);
5357
+ await _optionalChain([this, 'access', _113 => _113.cache, 'optionalAccess', _114 => _114.delete, 'call', _115 => _115(key)]);
5414
5358
  }
5415
5359
  /**
5416
5360
  * Invalidate all cache keys matching a pattern.
@@ -5418,7 +5362,7 @@ var BaseService = class {
5418
5362
  * @param pattern - Glob-style pattern (e.g., "schema:tenant-123:*")
5419
5363
  */
5420
5364
  async invalidateCachePattern(pattern) {
5421
- await _optionalChain([this, 'access', _117 => _117.cache, 'optionalAccess', _118 => _118.deletePattern, 'call', _119 => _119(pattern)]);
5365
+ await _optionalChain([this, 'access', _116 => _116.cache, 'optionalAccess', _117 => _117.deletePattern, 'call', _118 => _118(pattern)]);
5422
5366
  }
5423
5367
  /**
5424
5368
  * Invalidate all cached lists for a resource.
@@ -5543,6 +5487,23 @@ var RELATION_TARGET_ANY = "*";
5543
5487
  function isUniversalRelation(attr) {
5544
5488
  return attr.targets.length === 1 && attr.targets[0].object === RELATION_TARGET_ANY;
5545
5489
  }
5490
+ function isBilateralRelation(attr) {
5491
+ return attr.bilateral !== void 0;
5492
+ }
5493
+ function inferInverseCardinality(cardinality) {
5494
+ return cardinality === "one" ? "many" : "many";
5495
+ }
5496
+
5497
+ // src/types/relation-properties.ts
5498
+ var FORBIDDEN_PROPERTY_TYPES = [
5499
+ "formula",
5500
+ "rollup",
5501
+ "relation",
5502
+ "file",
5503
+ "user",
5504
+ "document",
5505
+ "richtext"
5506
+ ];
5546
5507
 
5547
5508
  // src/builders/attribute-validators.ts
5548
5509
 
@@ -5616,17 +5577,17 @@ function validateOptions(options, attributeName) {
5616
5577
  const ids = /* @__PURE__ */ new Set();
5617
5578
  const values = /* @__PURE__ */ new Set();
5618
5579
  for (const option of options) {
5619
- if (!_optionalChain([option, 'access', _120 => _120.id, 'optionalAccess', _121 => _121.trim, 'call', _122 => _122()])) {
5580
+ if (!_optionalChain([option, 'access', _119 => _119.id, 'optionalAccess', _120 => _120.trim, 'call', _121 => _121()])) {
5620
5581
  throw new Error(
5621
5582
  `[AttributeBuilder] Option in "${attributeName}" has an empty or missing id.`
5622
5583
  );
5623
5584
  }
5624
- if (!_optionalChain([option, 'access', _123 => _123.value, 'optionalAccess', _124 => _124.trim, 'call', _125 => _125()])) {
5585
+ if (!_optionalChain([option, 'access', _122 => _122.value, 'optionalAccess', _123 => _123.trim, 'call', _124 => _124()])) {
5625
5586
  throw new Error(
5626
5587
  `[AttributeBuilder] Option "${option.id}" in "${attributeName}" has an empty or missing value.`
5627
5588
  );
5628
5589
  }
5629
- if (!_optionalChain([option, 'access', _126 => _126.label, 'optionalAccess', _127 => _127.trim, 'call', _128 => _128()])) {
5590
+ if (!_optionalChain([option, 'access', _125 => _125.label, 'optionalAccess', _126 => _126.trim, 'call', _127 => _127()])) {
5630
5591
  throw new Error(
5631
5592
  `[AttributeBuilder] Option "${option.id}" in "${attributeName}" has an empty or missing label.`
5632
5593
  );
@@ -5649,292 +5610,6 @@ function validateOptions(options, attributeName) {
5649
5610
  }
5650
5611
  }
5651
5612
 
5652
- // src/types/relation-properties.ts
5653
- var FORBIDDEN_PROPERTY_TYPES = [
5654
- "formula",
5655
- "rollup",
5656
- "relation",
5657
- "file",
5658
- "user",
5659
- "document",
5660
- "richtext"
5661
- ];
5662
-
5663
- // src/builders/property-schema-builder.ts
5664
- var PropertySchemaBuilder = class {
5665
- constructor() {
5666
- this.definitions = [];
5667
- }
5668
- /**
5669
- * Add a property to the schema
5670
- * The type of property is automatically detected based on the builder methods used
5671
- */
5672
- add(name, configure) {
5673
- const builder = new PropertyTypeBuilder(name);
5674
- const configured = configure(builder);
5675
- const definition = configured.build();
5676
- this.definitions.push(definition);
5677
- return this;
5678
- }
5679
- /**
5680
- * Build the final PropertySchema
5681
- */
5682
- build() {
5683
- return {
5684
- definitions: this.definitions
5685
- };
5686
- }
5687
- };
5688
- var PropertyTypeBuilder = class {
5689
- constructor(name) {
5690
- this.name = name;
5691
- }
5692
- // Explicit type constructors
5693
- text() {
5694
- return new TextPropertyBuilder(this.name);
5695
- }
5696
- textarea() {
5697
- return new TextareaPropertyBuilder(this.name);
5698
- }
5699
- number() {
5700
- return new NumberPropertyBuilder(this.name);
5701
- }
5702
- checkbox() {
5703
- return new CheckboxPropertyBuilder(this.name);
5704
- }
5705
- date() {
5706
- return new DatePropertyBuilder(this.name);
5707
- }
5708
- phone() {
5709
- return new PhonePropertyBuilder(this.name);
5710
- }
5711
- currency() {
5712
- return new CurrencyPropertyBuilder(this.name);
5713
- }
5714
- status() {
5715
- return new StatusPropertyBuilder(this.name);
5716
- }
5717
- select() {
5718
- return new SelectPropertyBuilder(this.name);
5719
- }
5720
- multiselect() {
5721
- return new MultiselectPropertyBuilder(this.name);
5722
- }
5723
- rating() {
5724
- return new RatingPropertyBuilder(this.name);
5725
- }
5726
- location() {
5727
- return new LocationPropertyBuilder(this.name);
5728
- }
5729
- };
5730
- var BasePropertyBuilder = class {
5731
- constructor(name) {
5732
- this.definition = { name };
5733
- }
5734
- /**
5735
- * Set the label
5736
- */
5737
- label(label) {
5738
- this.definition.label = label;
5739
- return this;
5740
- }
5741
- /**
5742
- * Mark as required
5743
- */
5744
- required() {
5745
- this.definition.required = true;
5746
- return this;
5747
- }
5748
- /**
5749
- * Set description
5750
- */
5751
- description(description) {
5752
- this.definition.description = description;
5753
- return this;
5754
- }
5755
- /**
5756
- * Build the final definition
5757
- */
5758
- build() {
5759
- return this.definition;
5760
- }
5761
- };
5762
- var TextPropertyBuilder = class extends BasePropertyBuilder {
5763
- constructor(name) {
5764
- super(name);
5765
- this.definition.type = "text";
5766
- }
5767
- minLength(value) {
5768
- this.definition.minLength = value;
5769
- return this;
5770
- }
5771
- maxLength(value) {
5772
- this.definition.maxLength = value;
5773
- return this;
5774
- }
5775
- pattern(pattern) {
5776
- this.definition.pattern = pattern;
5777
- return this;
5778
- }
5779
- placeholder(value) {
5780
- this.definition.placeholder = value;
5781
- return this;
5782
- }
5783
- };
5784
- var TextareaPropertyBuilder = class extends BasePropertyBuilder {
5785
- constructor(name) {
5786
- super(name);
5787
- this.definition.type = "textarea";
5788
- }
5789
- minLength(value) {
5790
- this.definition.minLength = value;
5791
- return this;
5792
- }
5793
- maxLength(value) {
5794
- this.definition.maxLength = value;
5795
- return this;
5796
- }
5797
- placeholder(value) {
5798
- this.definition.placeholder = value;
5799
- return this;
5800
- }
5801
- };
5802
- var NumberPropertyBuilder = class extends BasePropertyBuilder {
5803
- constructor(name) {
5804
- super(name);
5805
- this.definition.type = "number";
5806
- }
5807
- min(value) {
5808
- this.definition.min = value;
5809
- return this;
5810
- }
5811
- max(value) {
5812
- this.definition.max = value;
5813
- return this;
5814
- }
5815
- decimal(places) {
5816
- this.definition.decimal = places;
5817
- return this;
5818
- }
5819
- integer() {
5820
- this.definition.integer = true;
5821
- return this;
5822
- }
5823
- placeholder(value) {
5824
- this.definition.placeholder = value;
5825
- return this;
5826
- }
5827
- };
5828
- var CheckboxPropertyBuilder = class extends BasePropertyBuilder {
5829
- constructor(name) {
5830
- super(name);
5831
- this.definition.type = "checkbox";
5832
- }
5833
- };
5834
- var DatePropertyBuilder = class extends BasePropertyBuilder {
5835
- constructor(name) {
5836
- super(name);
5837
- this.definition.type = "date";
5838
- }
5839
- includeTime() {
5840
- this.definition.includeTime = true;
5841
- return this;
5842
- }
5843
- min(date2) {
5844
- this.definition.min = date2;
5845
- return this;
5846
- }
5847
- max(date2) {
5848
- this.definition.max = date2;
5849
- return this;
5850
- }
5851
- };
5852
- var PhonePropertyBuilder = class extends BasePropertyBuilder {
5853
- constructor(name) {
5854
- super(name);
5855
- this.definition.type = "phone";
5856
- }
5857
- };
5858
- var CurrencyPropertyBuilder = class extends BasePropertyBuilder {
5859
- constructor(name) {
5860
- super(name);
5861
- this.definition.type = "currency";
5862
- }
5863
- currency(code) {
5864
- this.definition.currency = code;
5865
- return this;
5866
- }
5867
- min(value) {
5868
- this.definition.min = value;
5869
- return this;
5870
- }
5871
- max(value) {
5872
- this.definition.max = value;
5873
- return this;
5874
- }
5875
- };
5876
- var StatusPropertyBuilder = class extends BasePropertyBuilder {
5877
- constructor(name) {
5878
- super(name);
5879
- this.definition.type = "status";
5880
- }
5881
- options(options) {
5882
- this.definition.options = options;
5883
- return this;
5884
- }
5885
- };
5886
- var SelectPropertyBuilder = class extends BasePropertyBuilder {
5887
- constructor(name) {
5888
- super(name);
5889
- this.definition.type = "select";
5890
- }
5891
- options(options) {
5892
- this.definition.options = options;
5893
- return this;
5894
- }
5895
- };
5896
- var MultiselectPropertyBuilder = class extends BasePropertyBuilder {
5897
- constructor(name) {
5898
- super(name);
5899
- this.definition.type = "multiselect";
5900
- }
5901
- options(options) {
5902
- this.definition.options = options;
5903
- return this;
5904
- }
5905
- maxSelections(value) {
5906
- this.definition.maxSelections = value;
5907
- return this;
5908
- }
5909
- };
5910
- var RatingPropertyBuilder = class extends BasePropertyBuilder {
5911
- constructor(name) {
5912
- super(name);
5913
- this.definition.type = "rating";
5914
- }
5915
- max(value) {
5916
- this.definition.max = value;
5917
- return this;
5918
- }
5919
- icon(icon) {
5920
- this.definition.icon = icon;
5921
- return this;
5922
- }
5923
- };
5924
- var LocationPropertyBuilder = class extends BasePropertyBuilder {
5925
- constructor(name) {
5926
- super(name);
5927
- this.definition.type = "location";
5928
- }
5929
- };
5930
- function validatePropertyType(type) {
5931
- if (FORBIDDEN_PROPERTY_TYPES.includes(type)) {
5932
- throw new Error(
5933
- `Property type "${type}" is not supported in .qualifyWith(). Only simple types (text, number, date, select, etc.) are allowed. Complex types (formula, rollup, relation, file, user, document) would require duplicating backend behavior.`
5934
- );
5935
- }
5936
- }
5937
-
5938
5613
  // src/builders/attribute-builders.ts
5939
5614
  var BaseAttributeBuilder = class {
5940
5615
  constructor(type, name, label) {
@@ -6010,8 +5685,8 @@ var BaseAttributeBuilder = class {
6010
5685
  featureGate(flagName, options) {
6011
5686
  this.attr.featureGate = {
6012
5687
  flag: flagName,
6013
- expectedValue: _optionalChain([options, 'optionalAccess', _129 => _129.expectedValue]),
6014
- fallback: _optionalChain([options, 'optionalAccess', _130 => _130.fallback])
5688
+ expectedValue: _optionalChain([options, 'optionalAccess', _128 => _128.expectedValue]),
5689
+ fallback: _optionalChain([options, 'optionalAccess', _129 => _129.fallback])
6015
5690
  };
6016
5691
  return this;
6017
5692
  }
@@ -6443,12 +6118,7 @@ var UserAttributeBuilder = class extends BaseAttributeBuilder {
6443
6118
  function user(config) {
6444
6119
  return new UserAttributeBuilder(config.name, config.label);
6445
6120
  }
6446
- var SingleRelationAttributeBuilder = class extends BaseAttributeBuilder {
6447
- constructor(name, label) {
6448
- super("relation", name, label);
6449
- this.attr.cardinality = "one";
6450
- this.attr.targets = [];
6451
- }
6121
+ var BaseRelationAttributeBuilder = class extends BaseAttributeBuilder {
6452
6122
  /**
6453
6123
  * Add a target object that can be linked
6454
6124
  * @param objectName - Name of the object (e.g., "companies")
@@ -6459,111 +6129,110 @@ var SingleRelationAttributeBuilder = class extends BaseAttributeBuilder {
6459
6129
  object: objectName,
6460
6130
  ...options
6461
6131
  };
6462
- _optionalChain([this, 'access', _131 => _131.attr, 'access', _132 => _132.targets, 'optionalAccess', _133 => _133.push, 'call', _134 => _134(target)]);
6132
+ _optionalChain([this, 'access', _130 => _130.attr, 'access', _131 => _131.targets, 'optionalAccess', _132 => _132.push, 'call', _133 => _133(target)]);
6463
6133
  return this;
6464
6134
  }
6465
6135
  /**
6466
6136
  * Allow linking to any object in the tenant (universal polymorphic)
6467
6137
  * Creates a relation with `targets: [{ object: "*" }]`
6468
- *
6469
- * @example
6470
- * ```typescript
6471
- * relation({ name: "linkedTo", label: "Linked To" })
6472
- * .toAny()
6473
- * .hidden()
6474
- * ```
6475
6138
  */
6476
6139
  toAny() {
6477
6140
  this.attr.targets = [{ object: RELATION_TARGET_ANY }];
6478
6141
  return this;
6479
6142
  }
6480
6143
  /**
6481
- * Convert to a multi-relation (cardinality: "many")
6482
- */
6483
- many() {
6484
- const multiBuilder = new MultiRelationAttributeBuilder(
6485
- this.attr.name,
6486
- _nullishCoalesce(this.attr.label, () => ( "")),
6487
- {
6488
- targets: this.attr.targets,
6489
- isRequired: this.attr.required
6490
- }
6491
- );
6492
- return multiBuilder;
6493
- }
6494
- /**
6495
- * Add properties to qualify the relation
6496
- * Must be called AFTER .to() to ensure targets are defined
6144
+ * Add properties to qualify the relation using existing attribute builders.
6145
+ * Must be called AFTER .to() to ensure targets are defined.
6497
6146
  *
6498
6147
  * @example
6499
6148
  * ```typescript
6500
- * relation({ name: "mainCompany", label: "Main Company" })
6501
- * .to("companies")
6502
- * .qualifyWith(props => props
6503
- * .add("role", select => select.options([...]).required())
6504
- * .add("shares", number => number.min(0))
6149
+ * relation({ name: "companies", label: "Companies" })
6150
+ * .to("companies").many()
6151
+ * .qualifyWith(
6152
+ * select({ name: "role", label: "Role" }).options([...]).required(),
6153
+ * number({ name: "shares", label: "Shares" }).min(0),
6505
6154
  * )
6506
6155
  * ```
6507
6156
  */
6508
- qualifyWith(configure) {
6157
+ qualifyWith(...builders) {
6509
6158
  const targets = this.attr.targets;
6510
6159
  if (!targets || targets.length === 0) {
6511
6160
  throw new Error(
6512
- '.qualifyWith() must be called AFTER .to(). Example: relation({ name: "mainCompany" }).to("companies").qualifyWith(...)'
6161
+ '.qualifyWith() must be called AFTER .to(). Example: relation({ name: "companies" }).to("companies").qualifyWith(...)'
6513
6162
  );
6514
6163
  }
6515
- const builder = new PropertySchemaBuilder();
6516
- const schema = configure(builder).build();
6517
- this.attr.properties = schema;
6518
- return this;
6519
- }
6520
- required() {
6521
- this.setRequired(true);
6164
+ const definitions = builders.map((builder) => {
6165
+ const attr = builder.build();
6166
+ if (FORBIDDEN_PROPERTY_TYPES.includes(attr.type)) {
6167
+ throw new Error(`Property type "${attr.type}" is not supported in .qualifyWith()`);
6168
+ }
6169
+ return attr;
6170
+ });
6171
+ this.attr.properties = { definitions };
6522
6172
  return this;
6523
6173
  }
6524
- optional() {
6525
- this.setRequired(false);
6526
- return this;
6174
+ /**
6175
+ * Enable bilateral synchronization for this relation.
6176
+ * Must be called AFTER .to() to ensure targets are defined.
6177
+ */
6178
+ bilateral(config) {
6179
+ const targets = this.attr.targets;
6180
+ if (!targets || targets.length === 0) {
6181
+ throw new Error(
6182
+ '.bilateral() must be called AFTER .to(). Example: relation({ name: "company" }).to("companies").bilateral({ ... })'
6183
+ );
6184
+ }
6185
+ if (targets.length > 1) {
6186
+ throw new Error(
6187
+ "Bilateral relations are not supported for polymorphic relations (multiple .to() targets)."
6188
+ );
6189
+ }
6190
+ if (isUniversalRelation(this.attr)) {
6191
+ throw new Error("Bilateral relations are not supported for universal relations (.toAny()).");
6192
+ }
6193
+ this.attr.bilateral = config;
6194
+ return this;
6527
6195
  }
6528
6196
  };
6529
- var MultiRelationAttributeBuilder = class extends BaseAttributeBuilder {
6530
- constructor(name, label, initOptions) {
6197
+ var SingleRelationAttributeBuilder = class extends BaseRelationAttributeBuilder {
6198
+ constructor(name, label) {
6531
6199
  super("relation", name, label);
6532
- this.attr.cardinality = "many";
6533
- this.attr.targets = _nullishCoalesce(_optionalChain([initOptions, 'optionalAccess', _135 => _135.targets]), () => ( []));
6534
- this.attr.defaultValue = [];
6535
- if (_optionalChain([initOptions, 'optionalAccess', _136 => _136.isRequired])) {
6536
- this.setRequired(true);
6537
- }
6200
+ this.attr.cardinality = "one";
6201
+ this.attr.targets = [];
6538
6202
  }
6539
6203
  /**
6540
- * Add a target object that can be linked
6541
- * @param objectName - Name of the object (e.g., "companies")
6542
- * @param options - Display template and filter options
6204
+ * Convert to a multi-relation (cardinality: "many")
6543
6205
  */
6544
- to(objectName, options) {
6545
- const target = {
6546
- object: objectName,
6547
- ...options
6548
- };
6549
- _optionalChain([this, 'access', _137 => _137.attr, 'access', _138 => _138.targets, 'optionalAccess', _139 => _139.push, 'call', _140 => _140(target)]);
6206
+ many() {
6207
+ const multiBuilder = new MultiRelationAttributeBuilder(
6208
+ this.attr.name,
6209
+ _nullishCoalesce(this.attr.label, () => ( "")),
6210
+ {
6211
+ targets: this.attr.targets,
6212
+ isRequired: this.attr.required
6213
+ }
6214
+ );
6215
+ return multiBuilder;
6216
+ }
6217
+ required() {
6218
+ this.setRequired(true);
6550
6219
  return this;
6551
6220
  }
6552
- /**
6553
- * Allow linking to any object in the tenant (universal polymorphic)
6554
- * Creates a relation with `targets: [{ object: "*" }]`
6555
- *
6556
- * @example
6557
- * ```typescript
6558
- * relation({ name: "linkedItems", label: "Linked Items" })
6559
- * .toAny()
6560
- * .many()
6561
- * ```
6562
- */
6563
- toAny() {
6564
- this.attr.targets = [{ object: RELATION_TARGET_ANY }];
6221
+ optional() {
6222
+ this.setRequired(false);
6565
6223
  return this;
6566
6224
  }
6225
+ };
6226
+ var MultiRelationAttributeBuilder = class extends BaseRelationAttributeBuilder {
6227
+ constructor(name, label, initOptions) {
6228
+ super("relation", name, label);
6229
+ this.attr.cardinality = "many";
6230
+ this.attr.targets = _nullishCoalesce(_optionalChain([initOptions, 'optionalAccess', _134 => _134.targets]), () => ( []));
6231
+ this.attr.defaultValue = [];
6232
+ if (_optionalChain([initOptions, 'optionalAccess', _135 => _135.isRequired])) {
6233
+ this.setRequired(true);
6234
+ }
6235
+ }
6567
6236
  /**
6568
6237
  * Set minimum number of relations required
6569
6238
  */
@@ -6578,33 +6247,6 @@ var MultiRelationAttributeBuilder = class extends BaseAttributeBuilder {
6578
6247
  this.attr.maxItems = count;
6579
6248
  return this;
6580
6249
  }
6581
- /**
6582
- * Add properties to qualify the relation
6583
- * Must be called AFTER .to() or .many() to ensure targets are defined
6584
- *
6585
- * @example
6586
- * ```typescript
6587
- * relation({ name: "companies", label: "Companies" })
6588
- * .to("companies")
6589
- * .many()
6590
- * .qualifyWith(props => props
6591
- * .add("role", select => select.options([...]).required())
6592
- * .add("shares", number => number.min(0))
6593
- * )
6594
- * ```
6595
- */
6596
- qualifyWith(configure) {
6597
- const targets = this.attr.targets;
6598
- if (!targets || targets.length === 0) {
6599
- throw new Error(
6600
- '.qualifyWith() must be called AFTER .to() or .many(). Example: relation({ name: "companies" }).to("companies").many().qualifyWith(...)'
6601
- );
6602
- }
6603
- const builder = new PropertySchemaBuilder();
6604
- const schema = configure(builder).build();
6605
- this.attr.properties = schema;
6606
- return this;
6607
- }
6608
6250
  required() {
6609
6251
  this.setRequired(true);
6610
6252
  return this;
@@ -6672,8 +6314,9 @@ var FormulaAttributeBuilder = class extends BaseAttributeBuilder {
6672
6314
  // Override to prevent making formulas required
6673
6315
  // biome-ignore lint/suspicious/noExplicitAny: intentional override
6674
6316
  required() {
6675
- console.warn("Formula attributes cannot be required (they are read-only)");
6676
- return this;
6317
+ throw new Error(
6318
+ "Formula attributes cannot be required: they are computed read-only values. Remove .required() from this attribute definition."
6319
+ );
6677
6320
  }
6678
6321
  // biome-ignore lint/suspicious/noExplicitAny: intentional override
6679
6322
  optional() {
@@ -6747,8 +6390,9 @@ var RollupAttributeBuilder = class extends BaseAttributeBuilder {
6747
6390
  // Override to prevent making rollups required
6748
6391
  // biome-ignore lint/suspicious/noExplicitAny: intentional override
6749
6392
  required() {
6750
- console.warn("Rollup attributes cannot be required (they are read-only)");
6751
- return this;
6393
+ throw new Error(
6394
+ "Rollup attributes cannot be required: they are computed read-only values. Remove .required() from this attribute definition."
6395
+ );
6752
6396
  }
6753
6397
  // biome-ignore lint/suspicious/noExplicitAny: intentional override
6754
6398
  optional() {
@@ -7026,7 +6670,7 @@ var GroupBuilder = class {
7026
6670
  */
7027
6671
  fields(...names) {
7028
6672
  for (const name of names) {
7029
- _optionalChain([this, 'access', _141 => _141.data, 'access', _142 => _142.fields, 'optionalAccess', _143 => _143.push, 'call', _144 => _144({ attribute: name })]);
6673
+ _optionalChain([this, 'access', _136 => _136.data, 'access', _137 => _137.fields, 'optionalAccess', _138 => _138.push, 'call', _139 => _139({ attribute: name })]);
7030
6674
  }
7031
6675
  return this;
7032
6676
  }
@@ -7035,7 +6679,7 @@ var GroupBuilder = class {
7035
6679
  * @example .field("name", { span: 8, readOnly: true })
7036
6680
  */
7037
6681
  field(attribute, options) {
7038
- _optionalChain([this, 'access', _145 => _145.data, 'access', _146 => _146.fields, 'optionalAccess', _147 => _147.push, 'call', _148 => _148({ attribute, ...options })]);
6682
+ _optionalChain([this, 'access', _140 => _140.data, 'access', _141 => _141.fields, 'optionalAccess', _142 => _142.push, 'call', _143 => _143({ attribute, ...options })]);
7039
6683
  return this;
7040
6684
  }
7041
6685
  /**
@@ -7044,7 +6688,7 @@ var GroupBuilder = class {
7044
6688
  * @example .attributeGroup({ id: "address", label: "Address", attributes: ["street", "city", "postal_code"], displayTemplate: "{street}, {city}" })
7045
6689
  */
7046
6690
  attributeGroup(config, options) {
7047
- _optionalChain([this, 'access', _149 => _149.data, 'access', _150 => _150.fields, 'optionalAccess', _151 => _151.push, 'call', _152 => _152({ attributeGroup: config, ...options })]);
6691
+ _optionalChain([this, 'access', _144 => _144.data, 'access', _145 => _145.fields, 'optionalAccess', _146 => _146.push, 'call', _147 => _147({ attributeGroup: config, ...options })]);
7048
6692
  return this;
7049
6693
  }
7050
6694
  /**
@@ -7063,7 +6707,8 @@ var GroupBuilder = class {
7063
6707
  return this.data;
7064
6708
  }
7065
6709
  };
7066
- var BaseTableTabConfig = class {
6710
+ var TableTabConfig = class {
6711
+ /** @internal */
7067
6712
  constructor(view2, tabData) {
7068
6713
  this.view = view2;
7069
6714
  this.tabData = tabData;
@@ -7141,6 +6786,29 @@ var BaseTableTabConfig = class {
7141
6786
  this.tabData.sorts.push({ attribute, direction });
7142
6787
  return this;
7143
6788
  }
6789
+ /**
6790
+ * Traverse a 2nd-level relation to display nested data.
6791
+ * When active, `tab.columns` stores the 2nd-level object's attribute names.
6792
+ *
6793
+ * @param attribute - Relation attribute on the first-level target object
6794
+ * @example .table("members").through("companies").columns(["name", "sector"])
6795
+ */
6796
+ through(attribute) {
6797
+ this.tabData.through = { attribute };
6798
+ return this;
6799
+ }
6800
+ /**
6801
+ * Show _source and _target columns in flattened (through) view
6802
+ */
6803
+ showSourceTarget(value = true) {
6804
+ if (!this.tabData.through) {
6805
+ throw new Error(
6806
+ `[TableTabConfig] showSourceTarget() requires through() to be called first for tab "${this.tabData.name}"`
6807
+ );
6808
+ }
6809
+ this.tabData.through.showSourceTarget = value;
6810
+ return this;
6811
+ }
7144
6812
  /**
7145
6813
  * Continue building with a new tab
7146
6814
  */
@@ -7169,31 +6837,6 @@ var BaseTableTabConfig = class {
7169
6837
  this.view._addTab(this.tabData);
7170
6838
  }
7171
6839
  };
7172
- var DirectTableTabConfig = class extends BaseTableTabConfig {
7173
- /** @internal */
7174
- constructor(view2, base, relationAttribute) {
7175
- super(view2, {
7176
- ...base,
7177
- type: "table",
7178
- relationMode: "direct",
7179
- relationAttribute,
7180
- columns: []
7181
- });
7182
- }
7183
- };
7184
- var InverseTableTabConfig = class extends BaseTableTabConfig {
7185
- /** @internal */
7186
- constructor(view2, base, sourceObject, relationAttribute) {
7187
- super(view2, {
7188
- ...base,
7189
- type: "table",
7190
- relationMode: "inverse",
7191
- sourceObject,
7192
- relationAttribute,
7193
- columns: []
7194
- });
7195
- }
7196
- };
7197
6840
  var CustomTabConfig = class {
7198
6841
  /** @internal */
7199
6842
  constructor(view2, base, component) {
@@ -7229,24 +6872,17 @@ var CustomTabConfig = class {
7229
6872
  return this.view.build();
7230
6873
  }
7231
6874
  };
7232
- var NotesTabConfig = class {
6875
+ var RichtextTabConfig = class {
7233
6876
  /** @internal */
7234
- constructor(view2, base) {
6877
+ constructor(view2, base, attribute) {
7235
6878
  this.view = view2;
7236
- this.tabData = { ...base, type: "notes" };
7237
- }
7238
- /**
7239
- * Show only private notes of the current user
7240
- */
7241
- privateOnly() {
7242
- this.tabData.privateOnly = true;
7243
- return this;
6879
+ this.tabData = { ...base, type: "richtext", attribute };
7244
6880
  }
7245
6881
  /**
7246
- * Allow creating new notes from this tab
6882
+ * Set a text attribute to display as an editable title above the editor
7247
6883
  */
7248
- create() {
7249
- this.tabData.allowCreate = true;
6884
+ titleAttribute(name) {
6885
+ this.tabData.titleAttribute = name;
7250
6886
  return this;
7251
6887
  }
7252
6888
  /**
@@ -7485,27 +7121,37 @@ var TabBuilder = class {
7485
7121
  return this.view._addTab(tab);
7486
7122
  }
7487
7123
  /**
7488
- * Create a direct table tab for a relation attribute on the current object
7124
+ * Create a table tab for a relation attribute on the current object
7489
7125
  *
7490
7126
  * Use this when the current object has a relation attribute pointing to another object.
7491
7127
  *
7492
7128
  * @param relationAttribute - Name of the relation attribute on the current object
7493
- * @example .table("members").columns("name", "email").crud() // Show users from Project.members
7129
+ * @example .table("members").columns("name", "email").crud()
7494
7130
  */
7495
7131
  table(relationAttribute) {
7496
- return new DirectTableTabConfig(this.view, this.base, relationAttribute);
7132
+ return new TableTabConfig(this.view, {
7133
+ ...this.base,
7134
+ type: "table",
7135
+ source: { type: "relation", attribute: relationAttribute },
7136
+ columns: []
7137
+ });
7497
7138
  }
7498
7139
  /**
7499
- * Create an inverse table tab showing records from another object that have a relation to us
7140
+ * Create a table tab showing records from another object that have a relation to us
7500
7141
  *
7501
7142
  * Use this when another object has a relation attribute pointing to the current object.
7502
7143
  *
7503
7144
  * @param sourceObject - Name of the object that has the relation to us
7504
7145
  * @param relationAttribute - Name of the relation attribute on the source object
7505
- * @example .tableFrom("contacts", "company").columns("firstName", "lastName") // Show contacts where Contact.company = this
7146
+ * @example .tableFrom("contacts", "company").columns("firstName", "lastName")
7506
7147
  */
7507
7148
  tableFrom(sourceObject, relationAttribute) {
7508
- return new InverseTableTabConfig(this.view, this.base, sourceObject, relationAttribute);
7149
+ return new TableTabConfig(this.view, {
7150
+ ...this.base,
7151
+ type: "table",
7152
+ source: { type: "inverse", object: sourceObject, attribute: relationAttribute },
7153
+ columns: []
7154
+ });
7509
7155
  }
7510
7156
  /**
7511
7157
  * Create a custom tab with a component
@@ -7515,11 +7161,11 @@ var TabBuilder = class {
7515
7161
  return new CustomTabConfig(this.view, this.base, component);
7516
7162
  }
7517
7163
  /**
7518
- * Create a notes tab
7519
- * @example .notes().create().privateOnly()
7164
+ * Create a richtext tab for a richtext attribute
7165
+ * @example .richtext("content").titleAttribute("title")
7520
7166
  */
7521
- notes() {
7522
- return new NotesTabConfig(this.view, this.base);
7167
+ richtext(attribute) {
7168
+ return new RichtextTabConfig(this.view, this.base, attribute);
7523
7169
  }
7524
7170
  /**
7525
7171
  * Create an activity tab
@@ -7606,6 +7252,16 @@ var DetailViewBuilder = class {
7606
7252
  this.data.metadata = value;
7607
7253
  return this;
7608
7254
  }
7255
+ /**
7256
+ * Configure a side panel with flat attribute fields displayed alongside tab content.
7257
+ * Not available for modal layout.
7258
+ *
7259
+ * @example .sidePanel({ attributes: ["visibility", "linkedTo"] })
7260
+ */
7261
+ sidePanel(config) {
7262
+ this.data.sidePanel = config;
7263
+ return this;
7264
+ }
7609
7265
  /**
7610
7266
  * Start building a new tab
7611
7267
  */
@@ -7650,10 +7306,14 @@ var DetailViewBuilder = class {
7650
7306
  if (this.data.tabs[0].type !== "form") {
7651
7307
  throw new Error("[DetailViewBuilder] Modal views must have a form tab");
7652
7308
  }
7309
+ if (this.data.sidePanel) {
7310
+ throw new Error("[DetailViewBuilder] Modal views cannot have a side panel");
7311
+ }
7653
7312
  }
7654
7313
  const config = {
7655
7314
  layout: this.data.layout,
7656
- tabs: this.data.tabs
7315
+ tabs: this.data.tabs,
7316
+ sidePanel: this.data.sidePanel
7657
7317
  };
7658
7318
  return {
7659
7319
  name: this.data.name,
@@ -8012,8 +7672,8 @@ var WorkflowFormRowBuilder = class {
8012
7672
  id: `${this.rowData.id}-${slotId}-${attribute}`,
8013
7673
  slotId,
8014
7674
  attribute,
8015
- label: _optionalChain([options, 'optionalAccess', _153 => _153.label]),
8016
- required: _optionalChain([options, 'optionalAccess', _154 => _154.required])
7675
+ label: _optionalChain([options, 'optionalAccess', _148 => _148.label]),
7676
+ required: _optionalChain([options, 'optionalAccess', _149 => _149.required])
8017
7677
  };
8018
7678
  this.rowData.fields.push(field);
8019
7679
  return this;
@@ -8304,7 +7964,7 @@ var WorkflowBuilder = class {
8304
7964
  * @param options - Slot configuration
8305
7965
  */
8306
7966
  slot(id, objectName, options) {
8307
- if (_optionalChain([this, 'access', _155 => _155.data, 'access', _156 => _156.slots, 'optionalAccess', _157 => _157.some, 'call', _158 => _158((s) => s.id === id)])) {
7967
+ if (_optionalChain([this, 'access', _150 => _150.data, 'access', _151 => _151.slots, 'optionalAccess', _152 => _152.some, 'call', _153 => _153((s) => s.id === id)])) {
8308
7968
  throw new Error(`[WorkflowBuilder] Duplicate slot id: "${id}"`);
8309
7969
  }
8310
7970
  const slot = {
@@ -8315,7 +7975,7 @@ var WorkflowBuilder = class {
8315
7975
  color: options.color,
8316
7976
  icon: options.icon
8317
7977
  };
8318
- _optionalChain([this, 'access', _159 => _159.data, 'access', _160 => _160.slots, 'optionalAccess', _161 => _161.push, 'call', _162 => _162(slot)]);
7978
+ _optionalChain([this, 'access', _154 => _154.data, 'access', _155 => _155.slots, 'optionalAccess', _156 => _156.push, 'call', _157 => _157(slot)]);
8319
7979
  return this;
8320
7980
  }
8321
7981
  // ============================================================================
@@ -8447,7 +8107,7 @@ var WorkflowBuilder = class {
8447
8107
  }
8448
8108
  }
8449
8109
  validateSlotReferences() {
8450
- const slotIds = _nullishCoalesce(_optionalChain([this, 'access', _163 => _163.data, 'access', _164 => _164.slots, 'optionalAccess', _165 => _165.reduce, 'call', _166 => _166((set, s) => set.add(s.id), /* @__PURE__ */ new Set())]), () => ( /* @__PURE__ */ new Set()));
8110
+ const slotIds = _nullishCoalesce(_optionalChain([this, 'access', _158 => _158.data, 'access', _159 => _159.slots, 'optionalAccess', _160 => _160.reduce, 'call', _161 => _161((set, s) => set.add(s.id), /* @__PURE__ */ new Set())]), () => ( /* @__PURE__ */ new Set()));
8451
8111
  for (const node of Object.values(_nullishCoalesce(this.data.nodes, () => ( {})))) {
8452
8112
  if (node.type === "form") {
8453
8113
  const referencedSlots = /* @__PURE__ */ new Set();
@@ -8648,12 +8308,125 @@ function buildAuditChanges(oldValues, newValues, fieldsToCheck) {
8648
8308
  return changes;
8649
8309
  }
8650
8310
 
8311
+ // src/runtime/services/bilateral/bilateral-validation.service.ts
8312
+ var BilateralValidationService = class extends BaseService {
8313
+ constructor(adapter, schemaService) {
8314
+ super(adapter);
8315
+ this.schemaService = schemaService;
8316
+ }
8317
+ /**
8318
+ * Validate a bilateral relation configuration.
8319
+ *
8320
+ * Performs comprehensive checks:
8321
+ * 1. Verifies target object exists
8322
+ * 2. Verifies inverse attribute exists on target object
8323
+ * 3. Verifies inverse attribute targets the source object
8324
+ * 4. Detects invalid circular bilateral declarations
8325
+ *
8326
+ * @param sourceSchema - Schema containing the relation attribute
8327
+ * @param sourceAttr - Relation attribute to validate
8328
+ * @returns Validation result with errors if any
8329
+ */
8330
+ async validateBilateralRelation(sourceSchema, sourceAttr) {
8331
+ const errors = [];
8332
+ if (!(isBilateralRelation(sourceAttr) && sourceAttr.bilateral)) {
8333
+ return { valid: true, errors: [] };
8334
+ }
8335
+ const { object: targetObjectName, attribute: inverseAttrName } = sourceAttr.bilateral;
8336
+ let targetSchema = null;
8337
+ try {
8338
+ targetSchema = await this.schemaService.getObjectSchemaByName(targetObjectName);
8339
+ } catch (e12) {
8340
+ targetSchema = null;
8341
+ }
8342
+ if (!targetSchema) {
8343
+ errors.push({
8344
+ code: "INVERSE_ATTR_NOT_FOUND",
8345
+ message: `Target object "${targetObjectName}" not found`,
8346
+ context: {
8347
+ sourceObject: sourceSchema.name,
8348
+ sourceAttribute: sourceAttr.name,
8349
+ targetObject: targetObjectName,
8350
+ targetAttribute: inverseAttrName
8351
+ }
8352
+ });
8353
+ return { valid: false, errors };
8354
+ }
8355
+ const inverseAttr = targetSchema.attributes.find(
8356
+ (a) => a.name === inverseAttrName && a.type === "relation"
8357
+ );
8358
+ if (!inverseAttr) {
8359
+ errors.push({
8360
+ code: "INVERSE_ATTR_NOT_FOUND",
8361
+ message: `Inverse attribute "${inverseAttrName}" not found on "${targetObjectName}"`,
8362
+ context: {
8363
+ sourceObject: sourceSchema.name,
8364
+ sourceAttribute: sourceAttr.name,
8365
+ targetObject: targetObjectName,
8366
+ targetAttribute: inverseAttrName
8367
+ }
8368
+ });
8369
+ return { valid: false, errors };
8370
+ }
8371
+ const inverseTargetsSource = inverseAttr.targets.some(
8372
+ (target) => target.object === sourceSchema.name
8373
+ );
8374
+ if (!inverseTargetsSource) {
8375
+ errors.push({
8376
+ code: "INVERSE_TARGET_MISMATCH",
8377
+ message: `Inverse attribute doesn't target source object`,
8378
+ context: {
8379
+ sourceObject: sourceSchema.name,
8380
+ sourceAttribute: sourceAttr.name,
8381
+ targetObject: targetObjectName,
8382
+ targetAttribute: inverseAttrName
8383
+ }
8384
+ });
8385
+ }
8386
+ if (isBilateralRelation(inverseAttr)) {
8387
+ const inverseBilateral = inverseAttr.bilateral;
8388
+ if (inverseBilateral.object === sourceSchema.name && inverseBilateral.attribute !== sourceAttr.name) {
8389
+ errors.push({
8390
+ code: "CIRCULAR_BILATERAL",
8391
+ message: "Both sides declare bilateral but point to different attributes",
8392
+ context: {
8393
+ sourceObject: sourceSchema.name,
8394
+ sourceAttribute: sourceAttr.name,
8395
+ targetObject: targetObjectName,
8396
+ targetAttribute: inverseAttrName
8397
+ }
8398
+ });
8399
+ }
8400
+ }
8401
+ return { valid: errors.length === 0, errors };
8402
+ }
8403
+ /**
8404
+ * Validate all bilateral relations in a schema.
8405
+ *
8406
+ * Iterates through all relation attributes and validates each bilateral configuration.
8407
+ *
8408
+ * @param schema - Object schema to validate
8409
+ * @returns Combined validation result with all errors
8410
+ */
8411
+ async validateAllBilateralRelations(schema) {
8412
+ const allErrors = [];
8413
+ for (const attr of schema.attributes) {
8414
+ if (attr.type === "relation" && isBilateralRelation(attr)) {
8415
+ const result = await this.validateBilateralRelation(schema, attr);
8416
+ allErrors.push(...result.errors);
8417
+ }
8418
+ }
8419
+ return { valid: allErrors.length === 0, errors: allErrors };
8420
+ }
8421
+ };
8422
+
8651
8423
  // src/runtime/services/schema/object-schema.service.ts
8652
8424
  var ObjectSchemaService = class extends BaseService {
8653
8425
  constructor(adapter, nativeRegistry, options) {
8654
8426
  super(adapter);
8655
8427
  this.nativeRegistry = nativeRegistry;
8656
- this.auditService = _optionalChain([options, 'optionalAccess', _167 => _167.auditService]);
8428
+ this.auditService = _optionalChain([options, 'optionalAccess', _162 => _162.auditService]);
8429
+ this.bilateralValidationService = new BilateralValidationService(adapter, this);
8657
8430
  }
8658
8431
  /**
8659
8432
  * Create a new custom object.
@@ -8677,6 +8450,15 @@ var ObjectSchemaService = class extends BaseService {
8677
8450
  builder.attributes(definition.attributes);
8678
8451
  }
8679
8452
  const objectDef = builder.build();
8453
+ const bilateralValidation = await this.bilateralValidationService.validateAllBilateralRelations(objectDef);
8454
+ if (!bilateralValidation.valid) {
8455
+ const errorMessages = bilateralValidation.errors.map((e) => `${e.code}: ${e.message}`).join("; ");
8456
+ throw new SchemaError(
8457
+ `Bilateral relation validation failed: ${errorMessages}`,
8458
+ SchemaErrorCode.VALIDATION_FAILED,
8459
+ { errors: bilateralValidation.errors }
8460
+ );
8461
+ }
8680
8462
  const existing = await this.adapter.objects.findByName(objectDef.name);
8681
8463
  if (existing) {
8682
8464
  throw new Error(`Object with name "${objectDef.name}" already exists`);
@@ -8766,6 +8548,28 @@ var ObjectSchemaService = class extends BaseService {
8766
8548
  );
8767
8549
  }
8768
8550
  const config = await this.validateAttributeInput(attribute);
8551
+ if (attribute.type === "relation" && attribute.bilateral) {
8552
+ const tempSchema = await this.getObjectSchema(objectId);
8553
+ const tempAttr = {
8554
+ ...attribute,
8555
+ id: "temp-id",
8556
+ // Temporary ID for validation
8557
+ system: false,
8558
+ config
8559
+ };
8560
+ const validation = await this.bilateralValidationService.validateBilateralRelation(
8561
+ tempSchema,
8562
+ tempAttr
8563
+ );
8564
+ if (!validation.valid) {
8565
+ const errorMessages = validation.errors.map((e) => `${e.code}: ${e.message}`).join("; ");
8566
+ throw new SchemaError(
8567
+ `Bilateral relation validation failed: ${errorMessages}`,
8568
+ SchemaErrorCode.VALIDATION_FAILED,
8569
+ { errors: validation.errors }
8570
+ );
8571
+ }
8572
+ }
8769
8573
  const dbAttr = await this.adapter.attributes.create({
8770
8574
  objectId,
8771
8575
  name: attribute.name,
@@ -8866,7 +8670,7 @@ var ObjectSchemaService = class extends BaseService {
8866
8670
  resourceType: "attribute",
8867
8671
  resourceId: attributeId,
8868
8672
  resourceLabel: updatedDbAttr.label,
8869
- objectName: _optionalChain([dbObject, 'optionalAccess', _168 => _168.name]),
8673
+ objectName: _optionalChain([dbObject, 'optionalAccess', _163 => _163.name]),
8870
8674
  objectId: dbAttr.objectId,
8871
8675
  changes
8872
8676
  });
@@ -8899,7 +8703,7 @@ var ObjectSchemaService = class extends BaseService {
8899
8703
  );
8900
8704
  }
8901
8705
  const dbObject = await this.adapter.objects.findById(dbAttr.objectId);
8902
- if (_optionalChain([dbObject, 'optionalAccess', _169 => _169.labelExpression])) {
8706
+ if (_optionalChain([dbObject, 'optionalAccess', _164 => _164.labelExpression])) {
8903
8707
  const usedAttributes = extractAttributeNames(dbObject.labelExpression);
8904
8708
  if (usedAttributes.includes(dbAttr.name)) {
8905
8709
  throw new AttributeInUseError(dbAttr.name, "labelExpression");
@@ -8915,7 +8719,7 @@ var ObjectSchemaService = class extends BaseService {
8915
8719
  resourceType: "attribute",
8916
8720
  resourceId: attributeId,
8917
8721
  resourceLabel: dbAttr.label,
8918
- objectName: _optionalChain([dbObject, 'optionalAccess', _170 => _170.name]),
8722
+ objectName: _optionalChain([dbObject, 'optionalAccess', _165 => _165.name]),
8919
8723
  objectId: dbAttr.objectId
8920
8724
  });
8921
8725
  }
@@ -8930,9 +8734,9 @@ var ObjectSchemaService = class extends BaseService {
8930
8734
  async listAttributes(objectId, options) {
8931
8735
  const dbAttributes = await this.adapter.attributes.findByObjectId(objectId);
8932
8736
  let filtered = dbAttributes;
8933
- if (_optionalChain([options, 'optionalAccess', _171 => _171.systemOnly])) {
8737
+ if (_optionalChain([options, 'optionalAccess', _166 => _166.systemOnly])) {
8934
8738
  filtered = dbAttributes.filter((attr) => attr.system);
8935
- } else if (_optionalChain([options, 'optionalAccess', _172 => _172.customOnly])) {
8739
+ } else if (_optionalChain([options, 'optionalAccess', _167 => _167.customOnly])) {
8936
8740
  filtered = dbAttributes.filter((attr) => !attr.system);
8937
8741
  }
8938
8742
  return filtered.map((attr) => this.convertDBAttributeToAttribute(attr));
@@ -8968,14 +8772,14 @@ var ObjectSchemaService = class extends BaseService {
8968
8772
  pluralLabel: dbObject.pluralLabel,
8969
8773
  description: dbObject.description,
8970
8774
  labelExpression: dbObject.labelExpression,
8971
- icon: _optionalChain([dbObject, 'access', _173 => _173.metadata, 'optionalAccess', _174 => _174.icon])
8775
+ icon: _optionalChain([dbObject, 'access', _168 => _168.metadata, 'optionalAccess', _169 => _169.icon])
8972
8776
  };
8973
8777
  let metadata = dbObject.metadata;
8974
8778
  if (updates.icon !== void 0 || updates.metadata !== void 0) {
8975
8779
  metadata = {
8976
8780
  ...dbObject.metadata,
8977
8781
  ...updates.metadata,
8978
- icon: _nullishCoalesce(updates.icon, () => ( _optionalChain([dbObject, 'access', _175 => _175.metadata, 'optionalAccess', _176 => _176.icon])))
8782
+ icon: _nullishCoalesce(updates.icon, () => ( _optionalChain([dbObject, 'access', _170 => _170.metadata, 'optionalAccess', _171 => _171.icon])))
8979
8783
  };
8980
8784
  }
8981
8785
  const updatedDbObject = await this.adapter.objects.update(objectId, {
@@ -9190,7 +8994,59 @@ Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
9190
8994
  async buildObjectDefinition(dbObject) {
9191
8995
  const dbAttributes = await this.adapter.attributes.findByObjectId(dbObject.id);
9192
8996
  const baseDef = dbObject.system ? this.mergeNativeObject(dbObject, dbAttributes) : this.convertDBObjectToDefinition(dbObject, dbAttributes);
9193
- return this.withSystemAttributes(baseDef);
8997
+ const enriched = await this.enrichBilateralProperties(baseDef);
8998
+ return this.withSystemAttributes(enriched);
8999
+ }
9000
+ /**
9001
+ * Enrich bilateral relation attributes that don't own property definitions.
9002
+ * Copies `properties` from the canonical side (the one with `.qualifyWith()`)
9003
+ * and sets `storageOwner: false` so the storage layer knows direction.
9004
+ *
9005
+ * Uses direct DB lookups to avoid circular recursion through `getObjectSchema`.
9006
+ * @internal
9007
+ */
9008
+ async enrichBilateralProperties(def) {
9009
+ const toEnrich = def.attributes.filter(
9010
+ (attr) => attr.type === "relation" && !!attr.bilateral && !attr.properties
9011
+ );
9012
+ if (toEnrich.length === 0) return def;
9013
+ const enrichedMap = /* @__PURE__ */ new Map();
9014
+ for (const attr of toEnrich) {
9015
+ const { bilateral } = attr;
9016
+ if (!bilateral) continue;
9017
+ const inverseObject = await this.adapter.objects.findByName(bilateral.object);
9018
+ if (!inverseObject) continue;
9019
+ const inverseDbAttrs = await this.adapter.attributes.findByObjectId(inverseObject.id);
9020
+ const inverseDbAttr = inverseDbAttrs.find((a) => a.name === bilateral.attribute);
9021
+ if (!inverseDbAttr) continue;
9022
+ let properties = inverseDbAttr.config.properties;
9023
+ if (!properties) {
9024
+ const nativeObj = this.nativeRegistry.getByName(bilateral.object);
9025
+ const nativeAttr = _optionalChain([nativeObj, 'optionalAccess', _172 => _172.attributes, 'access', _173 => _173.find, 'call', _174 => _174((a) => a.name === bilateral.attribute)]);
9026
+ if (nativeAttr && "properties" in nativeAttr) {
9027
+ properties = nativeAttr.properties;
9028
+ }
9029
+ }
9030
+ if (properties) {
9031
+ enrichedMap.set(attr.name, {
9032
+ properties,
9033
+ bilateral: { ...bilateral, storageOwner: false }
9034
+ });
9035
+ }
9036
+ }
9037
+ if (enrichedMap.size === 0) return def;
9038
+ return {
9039
+ ...def,
9040
+ attributes: def.attributes.map((attr) => {
9041
+ const enrichment = enrichedMap.get(attr.name);
9042
+ if (!enrichment) return attr;
9043
+ return {
9044
+ ...attr,
9045
+ properties: enrichment.properties,
9046
+ bilateral: enrichment.bilateral
9047
+ };
9048
+ })
9049
+ };
9194
9050
  }
9195
9051
  /**
9196
9052
  * Append system attributes to an ObjectDefinition
@@ -9255,7 +9111,7 @@ Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
9255
9111
  label: dbObject.label,
9256
9112
  pluralLabel: dbObject.pluralLabel,
9257
9113
  description: dbObject.description,
9258
- icon: _optionalChain([dbObject, 'access', _177 => _177.metadata, 'optionalAccess', _178 => _178.icon]),
9114
+ icon: _optionalChain([dbObject, 'access', _175 => _175.metadata, 'optionalAccess', _176 => _176.icon]),
9259
9115
  labelExpression: dbObject.labelExpression,
9260
9116
  attributes,
9261
9117
  system: dbObject.system,
@@ -9355,7 +9211,7 @@ Reserved names: ${RESERVED_ATTRIBUTE_NAMES.join(", ")}`
9355
9211
  const hasRelationToTarget = attrs.some((attr) => {
9356
9212
  if (attr.type !== "relation") return false;
9357
9213
  const config = attr.config;
9358
- return _nullishCoalesce(_optionalChain([config, 'optionalAccess', _179 => _179.targets, 'optionalAccess', _180 => _180.some, 'call', _181 => _181((t) => t.object === targetObjectName)]), () => ( false));
9214
+ return _nullishCoalesce(_optionalChain([config, 'optionalAccess', _177 => _177.targets, 'optionalAccess', _178 => _178.some, 'call', _179 => _179((t) => t.object === targetObjectName)]), () => ( false));
9359
9215
  });
9360
9216
  if (hasRelationToTarget) {
9361
9217
  referencing.push(obj.name);
@@ -9433,7 +9289,7 @@ Native objects must have system=true. Did you forget to call .system() in your b
9433
9289
  const existing = this.objects.get(object2.name);
9434
9290
  throw new Error(
9435
9291
  `[NativeObjectRegistry] Duplicate object name "${object2.name}":
9436
- - Existing: "${_optionalChain([existing, 'optionalAccess', _182 => _182.label])}" (id: ${_optionalChain([existing, 'optionalAccess', _183 => _183.id])})
9292
+ - Existing: "${_optionalChain([existing, 'optionalAccess', _180 => _180.label])}" (id: ${_optionalChain([existing, 'optionalAccess', _181 => _181.id])})
9437
9293
  - New: "${object2.label}" (id: ${object2.id})
9438
9294
  Please use unique names for each native object.`
9439
9295
  );
@@ -9550,7 +9406,7 @@ var AuditService = class extends BaseService {
9550
9406
  this.isFlushing = false;
9551
9407
  /** Pending flush promise to allow waiting on concurrent flush */
9552
9408
  this.flushPromise = null;
9553
- if (_optionalChain([options, 'optionalAccess', _184 => _184.async]) && options.flushIntervalMs) {
9409
+ if (_optionalChain([options, 'optionalAccess', _182 => _182.async]) && options.flushIntervalMs) {
9554
9410
  this.startFlushTimer();
9555
9411
  }
9556
9412
  }
@@ -9747,7 +9603,7 @@ var AuditService = class extends BaseService {
9747
9603
  if (!this.adapter.audit) {
9748
9604
  return;
9749
9605
  }
9750
- if (_optionalChain([this, 'access', _185 => _185.options, 'optionalAccess', _186 => _186.async])) {
9606
+ if (_optionalChain([this, 'access', _183 => _183.options, 'optionalAccess', _184 => _184.async])) {
9751
9607
  this.buffer.push(entry);
9752
9608
  const batchSize = _nullishCoalesce(this.options.batchSize, () => ( 10));
9753
9609
  if (this.buffer.length >= batchSize) {
@@ -9761,7 +9617,7 @@ var AuditService = class extends BaseService {
9761
9617
  * Start the flush timer for async mode
9762
9618
  */
9763
9619
  startFlushTimer() {
9764
- const intervalMs = _nullishCoalesce(_optionalChain([this, 'access', _187 => _187.options, 'optionalAccess', _188 => _188.flushIntervalMs]), () => ( 1e3));
9620
+ const intervalMs = _nullishCoalesce(_optionalChain([this, 'access', _185 => _185.options, 'optionalAccess', _186 => _186.flushIntervalMs]), () => ( 1e3));
9765
9621
  this.flushTimer = setInterval(() => {
9766
9622
  this.flush().catch(() => {
9767
9623
  });
@@ -9785,40 +9641,337 @@ var AuditService = class extends BaseService {
9785
9641
  }
9786
9642
  };
9787
9643
 
9788
- // src/runtime/services/user/user.service.ts
9789
- var UserService = class extends BaseService {
9790
- constructor(adapter) {
9644
+ // src/runtime/services/bilateral/bilateral-sync.service.ts
9645
+ var browserStub4 = {
9646
+ getStore: () => void 0,
9647
+ run: (_store, callback) => callback()
9648
+ };
9649
+ var AsyncLocalStorageClass4 = null;
9650
+ if (typeof process !== "undefined" && _optionalChain([process, 'access', _187 => _187.versions, 'optionalAccess', _188 => _188.node])) {
9651
+ try {
9652
+ if (typeof _chunk3RG5ZIWIjs.__require !== "undefined") {
9653
+ const asyncHooks = _chunk3RG5ZIWIjs.__require.call(void 0, "async_hooks");
9654
+ AsyncLocalStorageClass4 = asyncHooks.AsyncLocalStorage;
9655
+ }
9656
+ } catch (e13) {
9657
+ try {
9658
+ const dynamicRequire = new Function(
9659
+ "m",
9660
+ 'return typeof require!=="undefined"?require(m):null'
9661
+ );
9662
+ const asyncHooks = dynamicRequire("node:async_hooks");
9663
+ if (asyncHooks) {
9664
+ AsyncLocalStorageClass4 = asyncHooks.AsyncLocalStorage;
9665
+ }
9666
+ } catch (e14) {
9667
+ }
9668
+ }
9669
+ }
9670
+ var bilateralSyncContext = null;
9671
+ function getSyncContext() {
9672
+ if (bilateralSyncContext !== null) {
9673
+ return bilateralSyncContext;
9674
+ }
9675
+ if (AsyncLocalStorageClass4) {
9676
+ bilateralSyncContext = new AsyncLocalStorageClass4();
9677
+ return bilateralSyncContext;
9678
+ }
9679
+ bilateralSyncContext = browserStub4;
9680
+ return bilateralSyncContext;
9681
+ }
9682
+ var BilateralSyncService = class extends BaseService {
9683
+ constructor(adapter, schemaService, relationPropertiesService) {
9791
9684
  super(adapter);
9685
+ this.schemaService = schemaService;
9686
+ this.relationPropertiesService = relationPropertiesService;
9792
9687
  }
9688
+ // ============================================================================
9689
+ // PUBLIC API
9690
+ // ============================================================================
9793
9691
  /**
9794
- * Validate all user attributes in the data.
9795
- *
9796
- * Uses batch fetching (findByIds) to avoid N+1 query pattern.
9797
- *
9798
- * @param schema - Object schema containing attribute definitions
9799
- * @param data - Record data to validate
9800
- * @returns Validation result with errors if any
9801
- *
9802
- * @example
9803
- * ```typescript
9804
- * const result = await userService.validateUsers(schema, {
9805
- * assignee: "user-123",
9806
- * watchers: ["user-456", "user-789"]
9807
- * });
9692
+ * Synchronize a bilateral relation after modification.
9808
9693
  *
9809
- * if (!result.valid) {
9810
- * console.log(result.errors);
9811
- * // [{ attribute: "assignee", message: "User not found", invalidIds: ["user-123"] }]
9812
- * }
9813
- * ```
9694
+ * @param sourceSchema - Schema of the object containing the relation
9695
+ * @param sourceRecordId - ID of the record being modified
9696
+ * @param attributeName - Name of the relation attribute
9697
+ * @param newValue - New value (ID, array of IDs, or hybrid format with properties)
9698
+ * @param oldValue - Old value (ID, array of IDs, or hybrid format with properties)
9814
9699
  */
9815
- async validateUsers(schema, data) {
9816
- const errors = [];
9817
- const userAttrs = schema.attributes.filter(
9818
- (attr) => attr.type === "user"
9700
+ async syncBilateralRelation(sourceSchema, sourceRecordId, attributeName, newValue, oldValue) {
9701
+ const attribute = sourceSchema.attributes.find(
9702
+ (a) => a.name === attributeName && a.type === "relation"
9819
9703
  );
9820
- if (userAttrs.length === 0) {
9821
- return { valid: true, errors: [] };
9704
+ if (!(attribute && isBilateralRelation(attribute))) {
9705
+ return;
9706
+ }
9707
+ const ctx = getSyncContext().getStore();
9708
+ const syncKey = `${sourceSchema.name}:${sourceRecordId}:${attributeName}`;
9709
+ if (_optionalChain([ctx, 'optionalAccess', _189 => _189.syncing, 'access', _190 => _190.has, 'call', _191 => _191(syncKey)])) {
9710
+ return;
9711
+ }
9712
+ await this.runWithSyncContext(syncKey, async () => {
9713
+ await this.performBilateralSync(sourceSchema, sourceRecordId, attribute, newValue, oldValue);
9714
+ });
9715
+ }
9716
+ // ============================================================================
9717
+ // PRIVATE METHODS
9718
+ // ============================================================================
9719
+ /**
9720
+ * Perform the bidirectional synchronization.
9721
+ * @private
9722
+ */
9723
+ async performBilateralSync(sourceSchema, sourceRecordId, sourceAttr, newValue, oldValue) {
9724
+ const bilateral = sourceAttr.bilateral;
9725
+ const targetSchema = await this.schemaService.getObjectSchemaByName(bilateral.object);
9726
+ if (!targetSchema) {
9727
+ throw new Error(`Target object "${bilateral.object}" not found`);
9728
+ }
9729
+ const inverseAttr = targetSchema.attributes.find(
9730
+ (a) => a.name === bilateral.attribute && a.type === "relation"
9731
+ );
9732
+ if (!inverseAttr) {
9733
+ throw new Error(
9734
+ `Inverse attribute "${bilateral.attribute}" not found on "${bilateral.object}"`
9735
+ );
9736
+ }
9737
+ const newData = this.extractRelationData(newValue);
9738
+ const oldData = this.extractRelationData(oldValue);
9739
+ const addedIds = newData.ids.filter((id) => !oldData.ids.includes(id));
9740
+ const removedIds = oldData.ids.filter((id) => !newData.ids.includes(id));
9741
+ const commonIds = newData.ids.filter((id) => oldData.ids.includes(id));
9742
+ await Promise.all([
9743
+ // Add new relations
9744
+ ...addedIds.map(
9745
+ (targetId) => this.addInverseRelation(
9746
+ targetId,
9747
+ inverseAttr,
9748
+ sourceRecordId,
9749
+ sourceSchema.name,
9750
+ sourceAttr.name,
9751
+ newData.properties.get(targetId)
9752
+ )
9753
+ ),
9754
+ // Remove deleted relations
9755
+ ...removedIds.map(
9756
+ (targetId) => this.removeInverseRelation(targetId, inverseAttr, sourceRecordId)
9757
+ ),
9758
+ // Update properties for common IDs
9759
+ ...commonIds.map(
9760
+ (targetId) => this.updateInverseRelationProperties(
9761
+ targetId,
9762
+ inverseAttr,
9763
+ sourceRecordId,
9764
+ sourceSchema.name,
9765
+ sourceAttr.name,
9766
+ newData.properties.get(targetId),
9767
+ oldData.properties.get(targetId)
9768
+ )
9769
+ )
9770
+ ]);
9771
+ }
9772
+ /**
9773
+ * Extract IDs and properties from hybrid relation value.
9774
+ * @private
9775
+ */
9776
+ extractRelationData(value) {
9777
+ const ids = [];
9778
+ const properties = /* @__PURE__ */ new Map();
9779
+ if (value === null || value === void 0) {
9780
+ return { ids, properties };
9781
+ }
9782
+ if (typeof value === "string") {
9783
+ ids.push(value);
9784
+ return { ids, properties };
9785
+ }
9786
+ if (!Array.isArray(value) && typeof value === "object" && "id" in value) {
9787
+ ids.push(value.id);
9788
+ if (value.props) {
9789
+ properties.set(value.id, value.props);
9790
+ }
9791
+ return { ids, properties };
9792
+ }
9793
+ if (Array.isArray(value)) {
9794
+ for (const item of value) {
9795
+ if (typeof item === "string") {
9796
+ ids.push(item);
9797
+ } else if (typeof item === "object" && item !== null && "id" in item) {
9798
+ ids.push(item.id);
9799
+ if (item.props) {
9800
+ properties.set(item.id, item.props);
9801
+ }
9802
+ }
9803
+ }
9804
+ }
9805
+ return { ids, properties };
9806
+ }
9807
+ /**
9808
+ * Add an ID to an inverse relation (with properties).
9809
+ * @private
9810
+ */
9811
+ async addInverseRelation(targetRecordId, inverseAttr, sourceRecordId, sourceObject, sourceAttribute, properties) {
9812
+ const targetRecord = await this.adapter.objectRecords.findById(targetRecordId);
9813
+ if (!targetRecord) {
9814
+ return;
9815
+ }
9816
+ const currentValue = targetRecord.values[inverseAttr.name];
9817
+ let newValue;
9818
+ if (inverseAttr.cardinality === "one") {
9819
+ newValue = sourceRecordId;
9820
+ } else {
9821
+ const currentArray = this.normalizeToArray(currentValue);
9822
+ if (currentArray.includes(sourceRecordId)) {
9823
+ return;
9824
+ }
9825
+ newValue = [...currentArray, sourceRecordId];
9826
+ }
9827
+ await this.adapter.objectRecords.update(targetRecordId, {
9828
+ [inverseAttr.name]: newValue
9829
+ });
9830
+ await this.invalidateTargetRecordCaches(targetRecordId, targetRecord.objectId);
9831
+ if (properties && Object.keys(properties).length > 0 && this.adapter.relationAttributes) {
9832
+ const sourceSchema = await this.schemaService.getObjectSchemaByName(sourceObject);
9833
+ if (sourceSchema) {
9834
+ await this.relationPropertiesService.syncRelationProperties(
9835
+ sourceSchema,
9836
+ sourceRecordId,
9837
+ sourceAttribute,
9838
+ [{ id: targetRecordId, props: properties }],
9839
+ this.adapter
9840
+ );
9841
+ }
9842
+ }
9843
+ }
9844
+ /**
9845
+ * Update properties of an existing inverse relation.
9846
+ * @private
9847
+ */
9848
+ async updateInverseRelationProperties(targetRecordId, _inverseAttr, sourceRecordId, sourceObject, sourceAttribute, newProperties, oldProperties) {
9849
+ if (JSON.stringify(newProperties) === JSON.stringify(oldProperties)) {
9850
+ return;
9851
+ }
9852
+ if (!this.adapter.relationAttributes) {
9853
+ return;
9854
+ }
9855
+ const sourceSchema = await this.schemaService.getObjectSchemaByName(sourceObject);
9856
+ if (!sourceSchema) {
9857
+ return;
9858
+ }
9859
+ if (newProperties && Object.keys(newProperties).length > 0) {
9860
+ await this.relationPropertiesService.syncRelationProperties(
9861
+ sourceSchema,
9862
+ sourceRecordId,
9863
+ sourceAttribute,
9864
+ [{ id: targetRecordId, props: newProperties }],
9865
+ this.adapter
9866
+ );
9867
+ } else {
9868
+ await this.adapter.relationAttributes.deleteBySource(
9869
+ sourceObject,
9870
+ sourceRecordId,
9871
+ sourceAttribute
9872
+ );
9873
+ }
9874
+ }
9875
+ /**
9876
+ * Remove an ID from an inverse relation.
9877
+ * @private
9878
+ */
9879
+ async removeInverseRelation(targetRecordId, inverseAttr, sourceRecordId) {
9880
+ const targetRecord = await this.adapter.objectRecords.findById(targetRecordId);
9881
+ if (!targetRecord) {
9882
+ return;
9883
+ }
9884
+ const currentValue = targetRecord.values[inverseAttr.name];
9885
+ let newValue;
9886
+ if (inverseAttr.cardinality === "one") {
9887
+ if (currentValue === sourceRecordId) {
9888
+ newValue = null;
9889
+ } else {
9890
+ return;
9891
+ }
9892
+ } else {
9893
+ const currentArray = this.normalizeToArray(currentValue);
9894
+ newValue = currentArray.filter((id) => id !== sourceRecordId);
9895
+ if (newValue.length === currentArray.length) {
9896
+ return;
9897
+ }
9898
+ }
9899
+ await this.adapter.objectRecords.update(targetRecordId, {
9900
+ [inverseAttr.name]: newValue
9901
+ });
9902
+ await this.invalidateTargetRecordCaches(targetRecordId, targetRecord.objectId);
9903
+ }
9904
+ /**
9905
+ * Normalize a relation value to an array of IDs.
9906
+ * @private
9907
+ */
9908
+ normalizeToArray(value) {
9909
+ if (value === null || value === void 0) return [];
9910
+ if (typeof value === "string") return [value];
9911
+ if (Array.isArray(value)) return value;
9912
+ return [];
9913
+ }
9914
+ /**
9915
+ * Invalidate caches for a target record after bilateral update.
9916
+ * Mirrors RecordService.invalidateRecordCaches to ensure consistency.
9917
+ * @private
9918
+ */
9919
+ async invalidateTargetRecordCaches(recordId, objectId) {
9920
+ await this.invalidateCache(cacheKeys.record(this.tenantId, recordId));
9921
+ await this.invalidateLists("allRecordLists", objectId);
9922
+ await this.invalidateLists("allSearchResults", objectId);
9923
+ await this.invalidateCachePattern(cacheKeys.allGlobalSearch(this.tenantId));
9924
+ await this.invalidateCachePattern(cacheKeys.resolvedRelationsByRecord(this.tenantId, recordId));
9925
+ }
9926
+ /**
9927
+ * Execute a function with sync context.
9928
+ * @private
9929
+ */
9930
+ async runWithSyncContext(syncKey, fn) {
9931
+ const storage = getSyncContext();
9932
+ const existingCtx = storage.getStore();
9933
+ const ctx = {
9934
+ syncing: new Set(_nullishCoalesce(_optionalChain([existingCtx, 'optionalAccess', _192 => _192.syncing]), () => ( [])))
9935
+ };
9936
+ ctx.syncing.add(syncKey);
9937
+ return await storage.run(ctx, fn);
9938
+ }
9939
+ };
9940
+
9941
+ // src/runtime/services/user/user.service.ts
9942
+ var UserService = class extends BaseService {
9943
+ constructor(adapter) {
9944
+ super(adapter);
9945
+ }
9946
+ /**
9947
+ * Validate all user attributes in the data.
9948
+ *
9949
+ * Uses batch fetching (findByIds) to avoid N+1 query pattern.
9950
+ *
9951
+ * @param schema - Object schema containing attribute definitions
9952
+ * @param data - Record data to validate
9953
+ * @returns Validation result with errors if any
9954
+ *
9955
+ * @example
9956
+ * ```typescript
9957
+ * const result = await userService.validateUsers(schema, {
9958
+ * assignee: "user-123",
9959
+ * watchers: ["user-456", "user-789"]
9960
+ * });
9961
+ *
9962
+ * if (!result.valid) {
9963
+ * console.log(result.errors);
9964
+ * // [{ attribute: "assignee", message: "User not found", invalidIds: ["user-123"] }]
9965
+ * }
9966
+ * ```
9967
+ */
9968
+ async validateUsers(schema, data) {
9969
+ const errors = [];
9970
+ const userAttrs = schema.attributes.filter(
9971
+ (attr) => attr.type === "user"
9972
+ );
9973
+ if (userAttrs.length === 0) {
9974
+ return { valid: true, errors: [] };
9822
9975
  }
9823
9976
  const allIds = /* @__PURE__ */ new Set();
9824
9977
  const attrIdMap = /* @__PURE__ */ new Map();
@@ -9869,7 +10022,7 @@ var UserService = class extends BaseService {
9869
10022
  if (roleErrors.length > 0) {
9870
10023
  errors.push({
9871
10024
  attribute: attrName,
9872
- message: `Users do not have required role for ${attr.label}. Allowed roles: ${_optionalChain([attr, 'access', _189 => _189.allowedRoles, 'optionalAccess', _190 => _190.join, 'call', _191 => _191(", ")])}`,
10025
+ message: `Users do not have required role for ${attr.label}. Allowed roles: ${_optionalChain([attr, 'access', _193 => _193.allowedRoles, 'optionalAccess', _194 => _194.join, 'call', _195 => _195(", ")])}`,
9873
10026
  invalidIds: roleErrors
9874
10027
  });
9875
10028
  }
@@ -10185,13 +10338,367 @@ async function recalculateParentRollups(record, schema, ctx) {
10185
10338
  }
10186
10339
  }
10187
10340
 
10341
+ // src/runtime/services/record/relation-properties.service.ts
10342
+
10343
+ var RelationPropertiesService = class extends BaseService {
10344
+ constructor(adapter) {
10345
+ super(adapter);
10346
+ }
10347
+ // ============================================================================
10348
+ // PUBLIC API
10349
+ // ============================================================================
10350
+ /**
10351
+ * Get relation properties for a given attribute.
10352
+ *
10353
+ * Supports bidirectional relations: searches for properties in both directions
10354
+ * (forward: from_object/from_id → to_id, and inverse: to_id → from_id).
10355
+ *
10356
+ * This ensures that qualified properties are SHARED between both directions
10357
+ * of a bilateral relation, as they are stored in a single row in relation_attributes.
10358
+ *
10359
+ * @param objectName - Source object name
10360
+ * @param recordId - Source record ID
10361
+ * @param attributeName - Relation attribute name
10362
+ * @param targetIds - Array of target record IDs
10363
+ * @returns Map of target ID → properties
10364
+ *
10365
+ * @example
10366
+ * ```typescript
10367
+ * // Properties stored as: contacts/A/companies → X with { role: "CEO" }
10368
+ *
10369
+ * // Read from Contact A → Company X
10370
+ * const propsFromContact = await service.getRelationProperties(
10371
+ * "contacts", "A", "companies", ["X"]
10372
+ * );
10373
+ * // → Map { "X" => { role: "CEO" } }
10374
+ *
10375
+ * // Read from Company X → Contact A (inverse)
10376
+ * const propsFromCompany = await service.getRelationProperties(
10377
+ * "companies", "X", "contacts", ["A"]
10378
+ * );
10379
+ * // → Map { "A" => { role: "CEO" } } (same properties!)
10380
+ * ```
10381
+ */
10382
+ async getRelationProperties(objectName, recordId, attributeName, targetIds) {
10383
+ const properties = /* @__PURE__ */ new Map();
10384
+ if (!this.adapter.relationAttributes) {
10385
+ return properties;
10386
+ }
10387
+ const forwardProps = await this.adapter.relationAttributes.findBySource(
10388
+ objectName,
10389
+ recordId,
10390
+ attributeName
10391
+ );
10392
+ for (const prop of forwardProps) {
10393
+ if (targetIds.includes(prop.toId)) {
10394
+ properties.set(prop.toId, prop.properties);
10395
+ }
10396
+ }
10397
+ const inverseProps = await this.adapter.relationAttributes.findByTarget(recordId);
10398
+ for (const prop of inverseProps) {
10399
+ if (targetIds.includes(prop.fromId) && !properties.has(prop.fromId)) {
10400
+ properties.set(prop.fromId, prop.properties);
10401
+ }
10402
+ }
10403
+ return properties;
10404
+ }
10405
+ /**
10406
+ * Batch enrich records with qualified relation properties.
10407
+ *
10408
+ * Detects qualified attributes in the schema and fetches their properties
10409
+ * using batch queries (1 query per qualified attribute, not per record).
10410
+ * Returns records with values in hybrid format `{ id, props }`.
10411
+ *
10412
+ * For bilateral relations, also checks the inverse direction.
10413
+ *
10414
+ * @param records - Records to enrich
10415
+ * @param schema - Object schema
10416
+ * @returns Records with relation values enriched with properties
10417
+ */
10418
+ async enrichRecordsBatch(records, schema) {
10419
+ if (!this.adapter.relationAttributes) return records;
10420
+ if (records.length === 0) return records;
10421
+ const qualifiedAttrs = schema.attributes.filter(
10422
+ (a) => a.type === "relation" && (!!a.properties || !!a.bilateral)
10423
+ );
10424
+ if (qualifiedAttrs.length === 0) return records;
10425
+ const recordIds = records.map((r) => r.id);
10426
+ const relationAttrsRepo = this.adapter.relationAttributes;
10427
+ await Promise.all(
10428
+ qualifiedAttrs.map(async (attr) => {
10429
+ const forwardRows = await relationAttrsRepo.findBySourceBatch(
10430
+ schema.name,
10431
+ recordIds,
10432
+ attr.name
10433
+ );
10434
+ const inverseRows = attr.bilateral ? await relationAttrsRepo.findByTargetBatch(recordIds) : [];
10435
+ const byRecord = /* @__PURE__ */ new Map();
10436
+ for (const row of forwardRows) {
10437
+ let recordMap = byRecord.get(row.fromId);
10438
+ if (!recordMap) {
10439
+ recordMap = /* @__PURE__ */ new Map();
10440
+ byRecord.set(row.fromId, recordMap);
10441
+ }
10442
+ recordMap.set(row.toId, row.properties);
10443
+ }
10444
+ for (const row of inverseRows) {
10445
+ let recordMap = byRecord.get(row.toId);
10446
+ if (!recordMap) {
10447
+ recordMap = /* @__PURE__ */ new Map();
10448
+ byRecord.set(row.toId, recordMap);
10449
+ }
10450
+ if (!recordMap.has(row.fromId)) {
10451
+ recordMap.set(row.fromId, row.properties);
10452
+ }
10453
+ }
10454
+ for (const record of records) {
10455
+ const propsForRecord = byRecord.get(record.id);
10456
+ if (!propsForRecord) continue;
10457
+ const value = record.values[attr.name];
10458
+ if (Array.isArray(value)) {
10459
+ record.values = {
10460
+ ...record.values,
10461
+ [attr.name]: value.map((id) => {
10462
+ const props = propsForRecord.get(id);
10463
+ return props ? { id, props } : id;
10464
+ })
10465
+ };
10466
+ } else if (typeof value === "string") {
10467
+ const props = propsForRecord.get(value);
10468
+ if (props) {
10469
+ record.values = {
10470
+ ...record.values,
10471
+ [attr.name]: { id: value, props }
10472
+ };
10473
+ }
10474
+ }
10475
+ }
10476
+ })
10477
+ );
10478
+ return records;
10479
+ }
10480
+ /**
10481
+ * Normalize relation values for storage in object_records table.
10482
+ *
10483
+ * Extracts IDs from hybrid format ({ id, props }) and returns legacy format (string[] or string).
10484
+ * This ensures object_records.values only contains IDs, while properties are in relation_attributes.
10485
+ *
10486
+ * @param schema - Object schema
10487
+ * @param data - Record data with hybrid relation values
10488
+ * @returns Data with relation values normalized to ID-only format
10489
+ */
10490
+ normalizeRelationValuesForStorage(schema, data) {
10491
+ const normalized = { ...data };
10492
+ for (const attr of schema.attributes) {
10493
+ if (attr.type !== "relation") {
10494
+ continue;
10495
+ }
10496
+ const value = data[attr.name];
10497
+ if (value === null || value === void 0) {
10498
+ continue;
10499
+ }
10500
+ if (attr.cardinality === "many" && Array.isArray(value)) {
10501
+ normalized[attr.name] = value.map((item) => {
10502
+ if (typeof item === "string") return item;
10503
+ if (typeof item === "object" && item !== null && "id" in item) {
10504
+ return item.id;
10505
+ }
10506
+ return item;
10507
+ });
10508
+ } else if (typeof value === "object" && value !== null && "id" in value) {
10509
+ normalized[attr.name] = value.id;
10510
+ }
10511
+ }
10512
+ return normalized;
10513
+ }
10514
+ /**
10515
+ * Synchronize relation properties for a given attribute.
10516
+ *
10517
+ * Handles:
10518
+ * - Format normalization (legacy → new)
10519
+ * - Validation of properties
10520
+ * - Upsert for present IDs
10521
+ * - Delete for absent IDs
10522
+ *
10523
+ * @param schema - Object schema
10524
+ * @param recordId - Source record ID
10525
+ * @param attributeName - Relation attribute name
10526
+ * @param relationValue - Relation value (hybrid format)
10527
+ * @param adapter - Database adapter
10528
+ */
10529
+ async syncRelationProperties(schema, recordId, attributeName, relationValue, adapter) {
10530
+ const attribute = schema.attributes.find((a) => a.name === attributeName);
10531
+ if (!attribute || attribute.type !== "relation") {
10532
+ return;
10533
+ }
10534
+ const normalized = this.normalizeRelationValue(relationValue);
10535
+ const propertySchema = this.getPropertySchema(attribute);
10536
+ const hasPropsInInput = normalized.some((item) => item.props !== void 0);
10537
+ const hasSchema = Boolean(propertySchema);
10538
+ const canSync = hasSchema || hasPropsInInput;
10539
+ if (!canSync) {
10540
+ return;
10541
+ }
10542
+ if (propertySchema) {
10543
+ for (const item of normalized) {
10544
+ if (item.props && Object.keys(item.props).length > 0) {
10545
+ this.validateProperties(propertySchema, item.props);
10546
+ }
10547
+ }
10548
+ }
10549
+ const shouldStoreAsInverse = _optionalChain([attribute, 'access', _196 => _196.bilateral, 'optionalAccess', _197 => _197.storageOwner]) === false;
10550
+ let storageFromObject = schema.name;
10551
+ let storageFromAttribute = attributeName;
10552
+ if (shouldStoreAsInverse && attribute.bilateral) {
10553
+ storageFromObject = attribute.bilateral.object;
10554
+ storageFromAttribute = attribute.bilateral.attribute;
10555
+ }
10556
+ let existing;
10557
+ if (adapter.relationAttributes) {
10558
+ if (shouldStoreAsInverse) {
10559
+ const results = await Promise.all(
10560
+ normalized.map(
10561
+ (item) => _optionalChain([adapter, 'access', _198 => _198.relationAttributes, 'optionalAccess', _199 => _199.findBySource, 'call', _200 => _200(
10562
+ storageFromObject,
10563
+ item.id,
10564
+ storageFromAttribute
10565
+ )])
10566
+ )
10567
+ );
10568
+ existing = results.filter((r) => r !== void 0).flat().filter((r) => r.toId === recordId);
10569
+ } else {
10570
+ existing = await adapter.relationAttributes.findBySource(
10571
+ schema.name,
10572
+ recordId,
10573
+ attributeName
10574
+ );
10575
+ }
10576
+ }
10577
+ const hasChanges = existing && existing.length > 0;
10578
+ const toUpsert = normalized.filter((item) => {
10579
+ return item.props !== void 0 && Object.keys(item.props).length > 0;
10580
+ });
10581
+ if (!adapter.relationAttributes) {
10582
+ return;
10583
+ }
10584
+ if (hasChanges && existing) {
10585
+ if (shouldStoreAsInverse) {
10586
+ for (const item of normalized) {
10587
+ await adapter.relationAttributes.deleteBySourceAndTarget(
10588
+ storageFromObject,
10589
+ item.id,
10590
+ storageFromAttribute,
10591
+ recordId
10592
+ );
10593
+ }
10594
+ } else {
10595
+ await adapter.relationAttributes.deleteBySource(schema.name, recordId, attributeName);
10596
+ }
10597
+ }
10598
+ if (toUpsert.length > 0) {
10599
+ const inputs = toUpsert.map((item) => {
10600
+ if (shouldStoreAsInverse) {
10601
+ return {
10602
+ fromObject: storageFromObject,
10603
+ fromId: item.id,
10604
+ fromAttribute: storageFromAttribute,
10605
+ toId: recordId,
10606
+ properties: _nullishCoalesce(item.props, () => ( {})),
10607
+ updatedBy: _nullishCoalesce(this.userId, () => ( void 0)),
10608
+ createdBy: _nullishCoalesce(this.userId, () => ( void 0))
10609
+ };
10610
+ }
10611
+ return {
10612
+ fromObject: schema.name,
10613
+ fromId: recordId,
10614
+ fromAttribute: attributeName,
10615
+ toId: item.id,
10616
+ properties: _nullishCoalesce(item.props, () => ( {})),
10617
+ updatedBy: _nullishCoalesce(this.userId, () => ( void 0)),
10618
+ createdBy: _nullishCoalesce(this.userId, () => ( void 0))
10619
+ };
10620
+ });
10621
+ await adapter.relationAttributes.upsertBatch(inputs);
10622
+ }
10623
+ }
10624
+ /**
10625
+ * Validate relation properties against PropertySchema.
10626
+ *
10627
+ * Uses Zod for runtime validation based on PropertyAttribute types.
10628
+ *
10629
+ * @param propertySchema - Schema defining allowed properties
10630
+ * @param properties - Properties to validate
10631
+ * @throws {z.ZodError} if validation fails
10632
+ */
10633
+ validateProperties(propertySchema, properties) {
10634
+ const schema = this.buildZodSchema(propertySchema);
10635
+ schema.parse(properties);
10636
+ }
10637
+ // ============================================================================
10638
+ // PRIVATE HELPERS
10639
+ // ============================================================================
10640
+ /**
10641
+ * Get PropertySchema for a relation attribute.
10642
+ *
10643
+ * For bilateral relations without .qualifyWith(), returns undefined.
10644
+ * Properties will be stored/retrieved but not validated on the inverse side.
10645
+ */
10646
+ getPropertySchema(attribute) {
10647
+ return attribute.properties;
10648
+ }
10649
+ /**
10650
+ * Normalize relation value to unified internal format.
10651
+ *
10652
+ * Converts:
10653
+ * - string[] → Array<{ id, props?: undefined }>
10654
+ * - string → [{ id, props?: undefined }]
10655
+ * - null → []
10656
+ * - Array<{ id, props }> → Array<{ id, props }> (passthrough)
10657
+ * - { id, props } → [{ id, props }] (single to array)
10658
+ */
10659
+ normalizeRelationValue(value) {
10660
+ if (value === null || value === void 0) {
10661
+ return [];
10662
+ }
10663
+ if (typeof value === "string") {
10664
+ return [{ id: value }];
10665
+ }
10666
+ if (!Array.isArray(value) && typeof value === "object" && "id" in value) {
10667
+ return [value];
10668
+ }
10669
+ if (Array.isArray(value)) {
10670
+ return value.map((item) => {
10671
+ if (typeof item === "string") {
10672
+ return { id: item };
10673
+ }
10674
+ return item;
10675
+ });
10676
+ }
10677
+ return [];
10678
+ }
10679
+ /**
10680
+ * Build Zod schema from PropertySchema definition.
10681
+ *
10682
+ * Reuses createFormAttributeValidator() to avoid code duplication with validators.ts.
10683
+ * This validator handles null/undefined values correctly for optional fields.
10684
+ */
10685
+ buildZodSchema(propertySchema) {
10686
+ const shape = {};
10687
+ for (const def of propertySchema.definitions) {
10688
+ shape[def.name] = _chunkU4AB53AMjs.createFormAttributeValidator.call(void 0, def);
10689
+ }
10690
+ return _zod.z.object(shape);
10691
+ }
10692
+ };
10693
+
10188
10694
  // src/runtime/services/record/query.service.ts
10189
10695
  var RecordQueryService = class extends BaseService {
10190
10696
  constructor(adapter, schemaService, options) {
10191
10697
  super(adapter);
10192
10698
  this.schemaService = schemaService;
10193
10699
  this.options = options;
10194
- this.policyRegistry = _optionalChain([options, 'optionalAccess', _192 => _192.policyRegistry]) === null ? null : _nullishCoalesce(_optionalChain([options, 'optionalAccess', _193 => _193.policyRegistry]), () => ( defaultPolicyRegistry));
10700
+ this.policyRegistry = _optionalChain([options, 'optionalAccess', _201 => _201.policyRegistry]) === null ? null : _nullishCoalesce(_optionalChain([options, 'optionalAccess', _202 => _202.policyRegistry]), () => ( defaultPolicyRegistry));
10701
+ this.relationPropertiesService = new RelationPropertiesService(adapter);
10195
10702
  }
10196
10703
  // ============================================================================
10197
10704
  // LIST
@@ -10241,12 +10748,12 @@ var RecordQueryService = class extends BaseService {
10241
10748
  * Internal list query execution
10242
10749
  */
10243
10750
  async executeListQuery(schema, objectId, options) {
10244
- if (_optionalChain([this, 'access', _194 => _194.options, 'optionalAccess', _195 => _195.permissionService]) && this.userId) {
10751
+ if (_optionalChain([this, 'access', _203 => _203.options, 'optionalAccess', _204 => _204.permissionService]) && this.userId) {
10245
10752
  await checkPermission(this.options.permissionService, this.userId, schema.name, "read");
10246
10753
  }
10247
- const policy = _optionalChain([options, 'optionalAccess', _196 => _196.skipPolicyFilter]) ? void 0 : getPolicy(this.policyRegistry, this.userId, schema.name);
10754
+ const policy = _optionalChain([options, 'optionalAccess', _205 => _205.skipPolicyFilter]) ? void 0 : getPolicy(this.policyRegistry, this.userId, schema.name);
10248
10755
  let effectiveOptions = options;
10249
- if (_optionalChain([policy, 'optionalAccess', _197 => _197.applyListFilter]) && this.userId) {
10756
+ if (_optionalChain([policy, 'optionalAccess', _206 => _206.applyListFilter]) && this.userId) {
10250
10757
  const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
10251
10758
  effectiveOptions = policy.applyListFilter(ctx, options);
10252
10759
  }
@@ -10256,10 +10763,10 @@ var RecordQueryService = class extends BaseService {
10256
10763
  );
10257
10764
  let filteredRecords = result.records;
10258
10765
  let effectiveTotal = result.total;
10259
- if (_optionalChain([policy, 'optionalAccess', _198 => _198.canAccessRecord]) && this.userId) {
10766
+ if (_optionalChain([policy, 'optionalAccess', _207 => _207.canAccessRecord]) && this.userId) {
10260
10767
  const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
10261
- const requestedLimit = _nullishCoalesce(_optionalChain([effectiveOptions, 'optionalAccess', _199 => _199.limit]), () => ( 20));
10262
- const requestedOffset = _nullishCoalesce(_optionalChain([effectiveOptions, 'optionalAccess', _200 => _200.offset]), () => ( 0));
10768
+ const requestedLimit = _nullishCoalesce(_optionalChain([effectiveOptions, 'optionalAccess', _208 => _208.limit]), () => ( 20));
10769
+ const requestedOffset = _nullishCoalesce(_optionalChain([effectiveOptions, 'optionalAccess', _209 => _209.offset]), () => ( 0));
10263
10770
  const overfetchMultiplier = 5;
10264
10771
  const batchSize = requestedLimit * overfetchMultiplier;
10265
10772
  const maxScanRecords = 1e4;
@@ -10281,7 +10788,7 @@ var RecordQueryService = class extends BaseService {
10281
10788
  exhausted = true;
10282
10789
  break;
10283
10790
  }
10284
- const filtered = batch.records.filter((record) => _optionalChain([policy, 'access', _201 => _201.canAccessRecord, 'optionalCall', _202 => _202(ctx, record)]));
10791
+ const filtered = batch.records.filter((record) => _optionalChain([policy, 'access', _210 => _210.canAccessRecord, 'optionalCall', _211 => _211(ctx, record)]));
10285
10792
  collected.push(...filtered);
10286
10793
  dbOffset += batch.records.length;
10287
10794
  totalScanned += batch.records.length;
@@ -10293,14 +10800,11 @@ var RecordQueryService = class extends BaseService {
10293
10800
  effectiveTotal = exhausted ? collected.length : Math.max(collected.length, result.total);
10294
10801
  filteredRecords = collected.slice(requestedOffset, requestedOffset + requestedLimit);
10295
10802
  }
10296
- if (_optionalChain([options, 'optionalAccess', _203 => _203.include]) && options.include.length > 0) {
10297
- filteredRecords = await this.includeRelationsWithProperties(
10298
- filteredRecords,
10299
- schema,
10300
- options.include
10301
- );
10302
- }
10303
- if (!_optionalChain([options, 'optionalAccess', _204 => _204.skipFormulas])) {
10803
+ filteredRecords = await this.relationPropertiesService.enrichRecordsBatch(
10804
+ filteredRecords,
10805
+ schema
10806
+ );
10807
+ if (!_optionalChain([options, 'optionalAccess', _212 => _212.skipFormulas])) {
10304
10808
  return {
10305
10809
  records: enrichRecordsWithFormulas(filteredRecords, schema),
10306
10810
  total: effectiveTotal
@@ -10360,459 +10864,119 @@ var RecordQueryService = class extends BaseService {
10360
10864
  * Internal search query execution
10361
10865
  */
10362
10866
  async executeSearchQuery(schema, objectId, query, options) {
10363
- if (_optionalChain([this, 'access', _205 => _205.options, 'optionalAccess', _206 => _206.permissionService]) && this.userId) {
10867
+ if (_optionalChain([this, 'access', _213 => _213.options, 'optionalAccess', _214 => _214.permissionService]) && this.userId) {
10364
10868
  await checkPermission(this.options.permissionService, this.userId, schema.name, "read");
10365
10869
  }
10366
10870
  const result = await runWithSchemaContext(
10367
10871
  [schema],
10368
10872
  () => this.adapter.objectRecords.search(objectId, query, options)
10369
10873
  );
10370
- if (!_optionalChain([options, 'optionalAccess', _207 => _207.skipFormulas])) {
10874
+ const enrichedRecords = await this.relationPropertiesService.enrichRecordsBatch(
10875
+ result.records,
10876
+ schema
10877
+ );
10878
+ if (!_optionalChain([options, 'optionalAccess', _215 => _215.skipFormulas])) {
10371
10879
  return {
10372
- records: enrichRecordsWithFormulas(result.records, schema),
10880
+ records: enrichRecordsWithFormulas(enrichedRecords, schema),
10373
10881
  total: result.total
10374
10882
  };
10375
10883
  }
10376
- return result;
10377
- }
10378
- // ============================================================================
10379
- // INCLUDE RELATIONS WITH PROPERTIES
10380
- // ============================================================================
10381
- /**
10382
- * Include relation properties in records.
10383
- *
10384
- * For each requested relation attribute:
10385
- * - If attribute has properties → Fetch from relation_attributes and return hybrid format
10386
- * - If attribute has NO properties → Return legacy format (string[] or string)
10387
- *
10388
- * Uses batch loading to avoid N+1 queries.
10389
- *
10390
- * @param records - Records to enrich with relation properties
10391
- * @param schema - Object schema
10392
- * @param includes - Array of relation attribute names to include
10393
- * @returns Records enriched with relation properties in hybrid format
10394
- * @private
10395
- */
10396
- async includeRelationsWithProperties(records, schema, includes) {
10397
- if (records.length === 0 || includes.length === 0) {
10398
- return records;
10399
- }
10400
- for (const includeName of includes) {
10401
- const attr = schema.attributes.find((a) => a.name === includeName);
10402
- if (!attr || attr.type !== "relation") {
10403
- continue;
10404
- }
10405
- if (attr.properties && this.adapter.relationAttributes) {
10406
- const recordIds = records.map((r) => r.id);
10407
- const relationAttributesRepo = this.adapter.relationAttributes;
10408
- const allRelationProps = await Promise.all(
10409
- recordIds.map(
10410
- (recordId) => relationAttributesRepo.findBySource(schema.name, recordId, includeName)
10411
- )
10412
- );
10413
- const propsByRecord = /* @__PURE__ */ new Map();
10414
- allRelationProps.forEach((props, index) => {
10415
- const recordId = recordIds[index];
10416
- const propsMap = /* @__PURE__ */ new Map();
10417
- for (const prop of props) {
10418
- propsMap.set(prop.toId, prop.properties);
10419
- }
10420
- propsByRecord.set(recordId, propsMap);
10421
- });
10422
- for (const record of records) {
10423
- const currentValue = record.values[includeName];
10424
- const propsMap = propsByRecord.get(record.id);
10425
- if (!currentValue) {
10426
- continue;
10427
- }
10428
- if (!propsMap) {
10429
- continue;
10430
- }
10431
- if (attr.cardinality === "many" && Array.isArray(currentValue)) {
10432
- record.values[includeName] = currentValue.map((id) => {
10433
- if (typeof id === "string") {
10434
- const props = propsMap.get(id);
10435
- return props ? { id, props } : { id };
10436
- }
10437
- return id;
10438
- });
10439
- } else if (attr.cardinality === "one") {
10440
- const id = typeof currentValue === "string" ? currentValue : null;
10441
- if (id) {
10442
- const props = propsMap.get(id);
10443
- record.values[includeName] = props ? { id, props } : { id };
10444
- }
10445
- }
10446
- }
10447
- }
10448
- }
10449
- return records;
10450
- }
10451
- };
10452
-
10453
- // src/runtime/services/record/record-resolver.service.ts
10454
- var RecordResolverService = class extends BaseService {
10455
- constructor(adapter) {
10456
- super(adapter);
10457
- }
10458
- // ============================================================================
10459
- // CACHED RECORD ACCESS
10460
- // ============================================================================
10461
- /**
10462
- * Find a record by ID with caching.
10463
- *
10464
- * Uses the shared record cache for optimal performance.
10465
- * Delegates to findByIds for consistent cache handling.
10466
- *
10467
- * @param id - Record ID
10468
- * @returns Record or null if not found
10469
- */
10470
- async findById(id) {
10471
- if (!id) return null;
10472
- const results = await this.findByIds([id]);
10473
- return _nullishCoalesce(results[0], () => ( null));
10474
- }
10475
- /**
10476
- * Find multiple records by IDs with caching.
10477
- *
10478
- * Each record is cached individually for reuse across services.
10479
- * Only fetches records not already in cache.
10480
- *
10481
- * @param ids - Record IDs to fetch
10482
- * @returns Array of found records (missing IDs are not included)
10483
- */
10484
- async findByIds(ids) {
10485
- if (!ids || ids.length === 0) {
10486
- return [];
10487
- }
10488
- const uniqueIds = [...new Set(ids)];
10489
- return this.cachedByMany(
10490
- "record",
10491
- uniqueIds,
10492
- (missingIds) => this.adapter.objectRecords.findByIds(missingIds),
10493
- (record) => record.id,
10494
- cacheTtl.records
10495
- );
10496
- }
10497
- // ============================================================================
10498
- // FACTORY METHODS
10499
- // ============================================================================
10500
- /**
10501
- * Create a RelationLabelResolver callback for computeLabelWithRelations.
10502
- *
10503
- * Used by RelationService.resolveLabel() and ObjectSchemaService.
10504
- *
10505
- * @returns Callback that resolves record IDs to their labels (cached)
10506
- */
10507
- createRelationLabelResolver() {
10508
- return async (ids) => {
10509
- const records = await this.findByIds(ids);
10510
- return new Map(records.map((r) => [r.id, r.label]));
10511
- };
10512
- }
10513
- /**
10514
- * Create a LabelResolver interface for label computation helpers.
10515
- *
10516
- * Used by RecordService for computing record labels.
10517
- *
10518
- * @param relationService - RelationService for resolving relation display labels
10519
- * @returns LabelResolver interface with cached record fetching
10520
- */
10521
- createLabelResolver(relationService) {
10522
10884
  return {
10523
- resolveRelationIds: (ids, attrId) => relationService.resolveIds(ids, attrId),
10524
- findRecordLabels: (ids) => this.findByIds(ids)
10525
- };
10526
- }
10527
- /**
10528
- * Create a RollupCascadeContext for rollup recalculation.
10529
- *
10530
- * Used by RecordService after create/update/delete operations.
10531
- *
10532
- * @param rollupService - RollupService for recalculating rollups
10533
- * @param schemaService - ObjectSchemaService for fetching schemas
10534
- * @returns Context with cached record fetching
10535
- */
10536
- createRollupContext(rollupService, schemaService) {
10537
- return {
10538
- rollupService,
10539
- schemaService,
10540
- findRecordsByIds: (ids) => this.findByIds(ids)
10541
- };
10542
- }
10543
- };
10544
-
10545
- // src/runtime/services/record/relation-properties.service.ts
10546
-
10547
- var RelationPropertiesService = class extends BaseService {
10548
- constructor(adapter) {
10549
- super(adapter);
10550
- }
10551
- // ============================================================================
10552
- // PUBLIC API
10553
- // ============================================================================
10554
- /**
10555
- * Normalize relation values for storage in object_records table.
10556
- *
10557
- * Extracts IDs from hybrid format ({ id, props }) and returns legacy format (string[] or string).
10558
- * This ensures object_records.values only contains IDs, while properties are in relation_attributes.
10559
- *
10560
- * @param schema - Object schema
10561
- * @param data - Record data with hybrid relation values
10562
- * @returns Data with relation values normalized to ID-only format
10563
- */
10564
- normalizeRelationValuesForStorage(schema, data) {
10565
- const normalized = { ...data };
10566
- for (const attr of schema.attributes) {
10567
- if (attr.type !== "relation" || !attr.properties) {
10568
- continue;
10569
- }
10570
- const value = data[attr.name];
10571
- if (value === null || value === void 0) {
10572
- continue;
10573
- }
10574
- if (attr.cardinality === "many" && Array.isArray(value)) {
10575
- normalized[attr.name] = value.map((item) => {
10576
- if (typeof item === "string") return item;
10577
- if (typeof item === "object" && item !== null && "id" in item) {
10578
- return item.id;
10579
- }
10580
- return item;
10581
- });
10582
- } else if (typeof value === "object" && value !== null && "id" in value) {
10583
- normalized[attr.name] = value.id;
10584
- }
10585
- }
10586
- return normalized;
10885
+ records: enrichedRecords,
10886
+ total: result.total
10887
+ };
10888
+ }
10889
+ };
10890
+
10891
+ // src/runtime/services/record/record-resolver.service.ts
10892
+ var RecordResolverService = class extends BaseService {
10893
+ constructor(adapter) {
10894
+ super(adapter);
10587
10895
  }
10896
+ // ============================================================================
10897
+ // CACHED RECORD ACCESS
10898
+ // ============================================================================
10588
10899
  /**
10589
- * Synchronize relation properties for a given attribute.
10900
+ * Find a record by ID with caching.
10590
10901
  *
10591
- * Handles:
10592
- * - Format normalization (legacy new)
10593
- * - Validation of properties
10594
- * - Upsert for present IDs
10595
- * - Delete for absent IDs
10902
+ * Uses the shared record cache for optimal performance.
10903
+ * Delegates to findByIds for consistent cache handling.
10596
10904
  *
10597
- * @param schema - Object schema
10598
- * @param recordId - Source record ID
10599
- * @param attributeName - Relation attribute name
10600
- * @param relationValue - Relation value (hybrid format)
10601
- * @param adapter - Database adapter
10905
+ * @param id - Record ID
10906
+ * @returns Record or null if not found
10602
10907
  */
10603
- async syncRelationProperties(schema, recordId, attributeName, relationValue, adapter) {
10604
- const attribute = schema.attributes.find((a) => a.name === attributeName);
10605
- if (!attribute || attribute.type !== "relation") {
10606
- return;
10607
- }
10608
- if (!attribute.properties) {
10609
- return;
10610
- }
10611
- const normalized = this.normalizeRelationValue(relationValue);
10612
- for (const item of normalized) {
10613
- if (item.props) {
10614
- this.validateProperties(attribute.properties, item.props);
10615
- }
10616
- }
10617
- const existing = await _optionalChain([adapter, 'access', _208 => _208.relationAttributes, 'optionalAccess', _209 => _209.findBySource, 'call', _210 => _210(
10618
- schema.name,
10619
- recordId,
10620
- attributeName
10621
- )]);
10622
- const existingIds = new Set((_nullishCoalesce(existing, () => ( []))).map((r) => r.toId));
10623
- const newIds = new Set(normalized.map((item) => item.id));
10624
- const toUpsert = normalized.filter((item) => item.props !== void 0);
10625
- const toDelete = Array.from(existingIds).filter((id) => !newIds.has(id));
10626
- if (toUpsert.length > 0 && adapter.relationAttributes) {
10627
- const inputs = toUpsert.map((item) => ({
10628
- fromObject: schema.name,
10629
- fromId: recordId,
10630
- fromAttribute: attributeName,
10631
- toId: item.id,
10632
- properties: _nullishCoalesce(item.props, () => ( {})),
10633
- updatedBy: _nullishCoalesce(this.userId, () => ( void 0)),
10634
- createdBy: _nullishCoalesce(this.userId, () => ( void 0))
10635
- }));
10636
- await adapter.relationAttributes.upsertBatch(inputs);
10637
- }
10638
- if (toDelete.length > 0 && adapter.relationAttributes && existing) {
10639
- for (const toId of toDelete) {
10640
- const relation2 = existing.find((r) => r.toId === toId);
10641
- if (relation2) {
10642
- }
10643
- }
10644
- await adapter.relationAttributes.deleteBySource(schema.name, recordId, attributeName);
10645
- if (toUpsert.length > 0) {
10646
- const inputs = toUpsert.map((item) => ({
10647
- fromObject: schema.name,
10648
- fromId: recordId,
10649
- fromAttribute: attributeName,
10650
- toId: item.id,
10651
- properties: _nullishCoalesce(item.props, () => ( {})),
10652
- updatedBy: _nullishCoalesce(this.userId, () => ( void 0)),
10653
- createdBy: _nullishCoalesce(this.userId, () => ( void 0))
10654
- }));
10655
- await adapter.relationAttributes.upsertBatch(inputs);
10656
- }
10657
- }
10908
+ async findById(id) {
10909
+ if (!id) return null;
10910
+ const results = await this.findByIds([id]);
10911
+ return _nullishCoalesce(results[0], () => ( null));
10658
10912
  }
10659
10913
  /**
10660
- * Validate relation properties against PropertySchema.
10914
+ * Find multiple records by IDs with caching.
10661
10915
  *
10662
- * Uses Zod for runtime validation based on PropertyDefinition types.
10916
+ * Each record is cached individually for reuse across services.
10917
+ * Only fetches records not already in cache.
10663
10918
  *
10664
- * @param propertySchema - Schema defining allowed properties
10665
- * @param properties - Properties to validate
10666
- * @throws {z.ZodError} if validation fails
10919
+ * @param ids - Record IDs to fetch
10920
+ * @returns Array of found records (missing IDs are not included)
10667
10921
  */
10668
- validateProperties(propertySchema, properties) {
10669
- const schema = this.buildZodSchema(propertySchema);
10670
- schema.parse(properties);
10922
+ async findByIds(ids) {
10923
+ if (!ids || ids.length === 0) {
10924
+ return [];
10925
+ }
10926
+ const uniqueIds = [...new Set(ids)];
10927
+ return this.cachedByMany(
10928
+ "record",
10929
+ uniqueIds,
10930
+ (missingIds) => this.adapter.objectRecords.findByIds(missingIds),
10931
+ (record) => record.id,
10932
+ cacheTtl.records
10933
+ );
10671
10934
  }
10672
10935
  // ============================================================================
10673
- // PRIVATE HELPERS
10936
+ // FACTORY METHODS
10674
10937
  // ============================================================================
10675
10938
  /**
10676
- * Normalize relation value to unified internal format.
10939
+ * Create a RelationLabelResolver callback for computeLabelWithRelations.
10677
10940
  *
10678
- * Converts:
10679
- * - string[] → Array<{ id, props?: undefined }>
10680
- * - string → [{ id, props?: undefined }]
10681
- * - null → []
10682
- * - Array<{ id, props }> → Array<{ id, props }> (passthrough)
10683
- * - { id, props } → [{ id, props }] (single to array)
10941
+ * Used by RelationService.resolveLabel() and ObjectSchemaService.
10684
10942
  *
10685
- * @param value - Relation value in hybrid format
10686
- * @returns Normalized array of relation items
10687
- * @private
10943
+ * @returns Callback that resolves record IDs to their labels (cached)
10688
10944
  */
10689
- normalizeRelationValue(value) {
10690
- if (value === null || value === void 0) {
10691
- return [];
10692
- }
10693
- if (typeof value === "string") {
10694
- return [{ id: value }];
10695
- }
10696
- if (!Array.isArray(value) && typeof value === "object" && "id" in value) {
10697
- return [value];
10698
- }
10699
- if (Array.isArray(value)) {
10700
- return value.map((item) => {
10701
- if (typeof item === "string") {
10702
- return { id: item };
10703
- }
10704
- return item;
10705
- });
10706
- }
10707
- return [];
10945
+ createRelationLabelResolver() {
10946
+ return async (ids) => {
10947
+ const records = await this.findByIds(ids);
10948
+ return new Map(records.map((r) => [r.id, r.label]));
10949
+ };
10708
10950
  }
10709
10951
  /**
10710
- * Build Zod schema from PropertySchema definition.
10952
+ * Create a LabelResolver interface for label computation helpers.
10711
10953
  *
10712
- * Dynamically generates validation schema based on PropertyDefinition types.
10954
+ * Used by RecordService for computing record labels.
10713
10955
  *
10714
- * @param propertySchema - PropertySchema with definitions
10715
- * @returns Zod schema for validation
10716
- * @private
10956
+ * @param relationService - RelationService for resolving relation display labels
10957
+ * @returns LabelResolver interface with cached record fetching
10717
10958
  */
10718
- buildZodSchema(propertySchema) {
10719
- const shape = {};
10720
- for (const def of propertySchema.definitions) {
10721
- let fieldSchema = this.buildFieldSchema(def);
10722
- if (!def.required) {
10723
- fieldSchema = fieldSchema.optional();
10724
- }
10725
- shape[def.name] = fieldSchema;
10726
- }
10727
- return _zod.z.object(shape);
10959
+ createLabelResolver(relationService) {
10960
+ return {
10961
+ resolveRelationIds: (ids, attrId) => relationService.resolveIds(ids, attrId),
10962
+ findRecordLabels: (ids) => this.findByIds(ids)
10963
+ };
10728
10964
  }
10729
10965
  /**
10730
- * Build Zod schema for a single property field.
10966
+ * Create a RollupCascadeContext for rollup recalculation.
10731
10967
  *
10732
- * @param def - PropertyDefinition
10733
- * @returns Zod schema for the field
10734
- * @private
10968
+ * Used by RecordService after create/update/delete operations.
10969
+ *
10970
+ * @param rollupService - RollupService for recalculating rollups
10971
+ * @param schemaService - ObjectSchemaService for fetching schemas
10972
+ * @returns Context with cached record fetching
10735
10973
  */
10736
- buildFieldSchema(def) {
10737
- switch (def.type) {
10738
- case "text":
10739
- case "textarea": {
10740
- let schema = _zod.z.string();
10741
- if (def.minLength !== void 0) {
10742
- schema = schema.min(def.minLength);
10743
- }
10744
- if (def.maxLength !== void 0) {
10745
- schema = schema.max(def.maxLength);
10746
- }
10747
- if (def.type === "text" && def.pattern) {
10748
- schema = schema.regex(new RegExp(def.pattern));
10749
- }
10750
- return schema;
10751
- }
10752
- case "number": {
10753
- let schema = _zod.z.number();
10754
- if (def.min !== void 0) {
10755
- schema = schema.min(def.min);
10756
- }
10757
- if (def.max !== void 0) {
10758
- schema = schema.max(def.max);
10759
- }
10760
- if (def.integer) {
10761
- schema = schema.int();
10762
- }
10763
- return schema;
10764
- }
10765
- case "checkbox": {
10766
- return _zod.z.boolean();
10767
- }
10768
- case "date": {
10769
- const schema = _zod.z.string().datetime();
10770
- return schema;
10771
- }
10772
- case "phone": {
10773
- return _zod.z.string();
10774
- }
10775
- case "currency": {
10776
- let schema = _zod.z.number();
10777
- if (def.min !== void 0) {
10778
- schema = schema.min(def.min);
10779
- }
10780
- if (def.max !== void 0) {
10781
- schema = schema.max(def.max);
10782
- }
10783
- return schema;
10784
- }
10785
- case "status":
10786
- case "select": {
10787
- const validValues = def.options.map((opt) => opt.value);
10788
- return _zod.z.enum(validValues);
10789
- }
10790
- case "multiselect": {
10791
- const validValues = def.options.map((opt) => opt.value);
10792
- let schema = _zod.z.array(_zod.z.enum(validValues));
10793
- if (def.maxSelections !== void 0) {
10794
- schema = schema.max(def.maxSelections);
10795
- }
10796
- return schema;
10797
- }
10798
- case "rating": {
10799
- let schema = _zod.z.number().int();
10800
- if (def.max !== void 0) {
10801
- schema = schema.max(def.max);
10802
- }
10803
- return schema.min(0);
10804
- }
10805
- case "location": {
10806
- return _zod.z.object({
10807
- address: _zod.z.string().optional(),
10808
- lat: _zod.z.number().optional(),
10809
- lng: _zod.z.number().optional()
10810
- });
10811
- }
10812
- default: {
10813
- return _zod.z.unknown();
10814
- }
10815
- }
10974
+ createRollupContext(rollupService, schemaService) {
10975
+ return {
10976
+ rollupService,
10977
+ schemaService,
10978
+ findRecordsByIds: (ids) => this.findByIds(ids)
10979
+ };
10816
10980
  }
10817
10981
  };
10818
10982
 
@@ -10823,6 +10987,7 @@ var RelationService = class extends BaseService {
10823
10987
  this.schemaService = new ObjectSchemaService(adapter, nativeRegistry);
10824
10988
  this.queryService = options.queryService;
10825
10989
  this.recordResolver = options.recordResolver;
10990
+ this.relationPropertiesService = options.relationPropertiesService;
10826
10991
  }
10827
10992
  /**
10828
10993
  * Set the query service after construction.
@@ -10893,7 +11058,7 @@ var RelationService = class extends BaseService {
10893
11058
  }
10894
11059
  const isUniversal = attr.targets.length === 1 && attr.targets[0].object === RELATION_TARGET_ANY;
10895
11060
  const validObjectIds = isUniversal ? null : await this.getValidObjectIds(attr.targets);
10896
- if (!isUniversal && _optionalChain([validObjectIds, 'optionalAccess', _211 => _211.size]) === 0) {
11061
+ if (!isUniversal && _optionalChain([validObjectIds, 'optionalAccess', _216 => _216.size]) === 0) {
10897
11062
  errors.push({
10898
11063
  attribute: attr.name,
10899
11064
  message: `No valid target objects found for ${attr.label}`
@@ -10946,10 +11111,10 @@ var RelationService = class extends BaseService {
10946
11111
  for (const target of targets) {
10947
11112
  try {
10948
11113
  const objectSchema = await this.schemaService.getObjectSchemaByName(target.object);
10949
- if (_optionalChain([objectSchema, 'optionalAccess', _212 => _212.id])) {
11114
+ if (_optionalChain([objectSchema, 'optionalAccess', _217 => _217.id])) {
10950
11115
  objectIds.add(objectSchema.id);
10951
11116
  }
10952
- } catch (e12) {
11117
+ } catch (e15) {
10953
11118
  }
10954
11119
  }
10955
11120
  return objectIds;
@@ -11015,7 +11180,7 @@ var RelationService = class extends BaseService {
11015
11180
  const targetResults = await Promise.all(
11016
11181
  filteredTargets.map(async (target) => {
11017
11182
  const objectSchema = await this.schemaService.getObjectSchemaByName(target.object);
11018
- if (!_optionalChain([objectSchema, 'optionalAccess', _213 => _213.id])) return { options: [], total: 0 };
11183
+ if (!_optionalChain([objectSchema, 'optionalAccess', _218 => _218.id])) return { options: [], total: 0 };
11019
11184
  const objectId = objectSchema.id;
11020
11185
  const result = query ? await queryService.searchRecords(objectId, query, queryOptions) : await queryService.listRecords(objectId, queryOptions);
11021
11186
  const options = await Promise.all(
@@ -11172,8 +11337,8 @@ var RelationService = class extends BaseService {
11172
11337
  continue;
11173
11338
  }
11174
11339
  const attribute = attributeMap.get(attributeId);
11175
- const targetConfig = _optionalChain([attribute, 'optionalAccess', _214 => _214.targets, 'optionalAccess', _215 => _215.find, 'call', _216 => _216((t) => t.object === objectSchema.name)]);
11176
- const customTemplate = _optionalChain([targetConfig, 'optionalAccess', _217 => _217.displayTemplate]);
11340
+ const targetConfig = _optionalChain([attribute, 'optionalAccess', _219 => _219.targets, 'optionalAccess', _220 => _220.find, 'call', _221 => _221((t) => t.object === objectSchema.name)]);
11341
+ const customTemplate = _optionalChain([targetConfig, 'optionalAccess', _222 => _222.displayTemplate]);
11177
11342
  const label = await this.resolveLabel(record, objectSchema, customTemplate);
11178
11343
  resolved.push({
11179
11344
  _compositeId: compositeId,
@@ -11190,20 +11355,33 @@ var RelationService = class extends BaseService {
11190
11355
  /**
11191
11356
  * Resolve the display label for a record.
11192
11357
  * Uses custom template if provided, otherwise falls back to pre-computed label.
11358
+ *
11359
+ * Preserves `{{ props.X }}` tokens for client-side substitution using a sentinel approach:
11360
+ * tokens are replaced with null-byte sentinels before template rendering, then restored after.
11361
+ * This lets `computeLabelWithRelations` resolve target fields while keeping prop placeholders intact.
11193
11362
  */
11194
11363
  async resolveLabel(record, objectSchema, customTemplate) {
11195
- if (customTemplate) {
11196
- return computeLabelWithRelations(
11197
- customTemplate,
11198
- record.values,
11199
- objectSchema.attributes,
11200
- async (nestedIds) => {
11201
- const linkedRecords = await this.recordResolver.findByIds(nestedIds);
11202
- return new Map(linkedRecords.map((r) => [r.id, r.label]));
11203
- }
11204
- );
11364
+ if (!customTemplate) return record.label;
11365
+ const preserved = [];
11366
+ let i = 0;
11367
+ const safeTemplate = customTemplate.replace(/\{\{\s*props\.\w+[^}]*\}\}/g, (match) => {
11368
+ const key = `\0PROP${i++}\0`;
11369
+ preserved.push([key, match]);
11370
+ return key;
11371
+ });
11372
+ let label = await computeLabelWithRelations(
11373
+ safeTemplate,
11374
+ record.values,
11375
+ objectSchema.attributes,
11376
+ async (nestedIds) => {
11377
+ const linkedRecords = await this.recordResolver.findByIds(nestedIds);
11378
+ return new Map(linkedRecords.map((r) => [r.id, r.label]));
11379
+ }
11380
+ );
11381
+ for (const [key, token] of preserved) {
11382
+ label = label.replace(key, token);
11205
11383
  }
11206
- return record.label;
11384
+ return label;
11207
11385
  }
11208
11386
  /**
11209
11387
  * Find a relation attribute by ID.
@@ -11323,14 +11501,14 @@ var RollupService = class extends BaseService {
11323
11501
  const sourceSchema = getSchemaByNameFromContext(sourceObjectName);
11324
11502
  let sourceObjectId;
11325
11503
  let reverseRelationAttrName;
11326
- if (_optionalChain([sourceSchema, 'optionalAccess', _218 => _218.id])) {
11504
+ if (_optionalChain([sourceSchema, 'optionalAccess', _223 => _223.id])) {
11327
11505
  sourceObjectId = sourceSchema.id;
11328
11506
  const reverseRelationAttr = sourceSchema.attributes.find((attr) => {
11329
11507
  if (attr.type !== "relation") return false;
11330
11508
  const relationConfig = attr;
11331
- return _optionalChain([relationConfig, 'optionalAccess', _219 => _219.targets, 'optionalAccess', _220 => _220.some, 'call', _221 => _221((t) => t.object === schema.name)]);
11509
+ return _optionalChain([relationConfig, 'optionalAccess', _224 => _224.targets, 'optionalAccess', _225 => _225.some, 'call', _226 => _226((t) => t.object === schema.name)]);
11332
11510
  });
11333
- reverseRelationAttrName = _optionalChain([reverseRelationAttr, 'optionalAccess', _222 => _222.name]);
11511
+ reverseRelationAttrName = _optionalChain([reverseRelationAttr, 'optionalAccess', _227 => _227.name]);
11334
11512
  } else {
11335
11513
  const sourceObject = await this.adapter.objects.findByName(sourceObjectName);
11336
11514
  if (!sourceObject) {
@@ -11341,9 +11519,9 @@ var RollupService = class extends BaseService {
11341
11519
  const reverseRelationAttr = sourceAttributes.find((attr) => {
11342
11520
  if (attr.type !== "relation") return false;
11343
11521
  const relationConfig = attr.config;
11344
- return _optionalChain([relationConfig, 'optionalAccess', _223 => _223.targets, 'optionalAccess', _224 => _224.some, 'call', _225 => _225((t) => t.object === schema.name)]);
11522
+ return _optionalChain([relationConfig, 'optionalAccess', _228 => _228.targets, 'optionalAccess', _229 => _229.some, 'call', _230 => _230((t) => t.object === schema.name)]);
11345
11523
  });
11346
- reverseRelationAttrName = _optionalChain([reverseRelationAttr, 'optionalAccess', _226 => _226.name]);
11524
+ reverseRelationAttrName = _optionalChain([reverseRelationAttr, 'optionalAccess', _231 => _231.name]);
11347
11525
  }
11348
11526
  if (!reverseRelationAttrName) {
11349
11527
  return { value: null, recordCount: 0 };
@@ -11599,13 +11777,13 @@ var RollupService = class extends BaseService {
11599
11777
  if (!obj) continue;
11600
11778
  for (const rollupDbAttr of rollupAttrs) {
11601
11779
  const rollupConfig = rollupDbAttr.config;
11602
- if (!_optionalChain([rollupConfig, 'optionalAccess', _227 => _227.relationAttribute])) continue;
11780
+ if (!_optionalChain([rollupConfig, 'optionalAccess', _232 => _232.relationAttribute])) continue;
11603
11781
  const relationAttr = attributes.find(
11604
11782
  (a) => a.type === "relation" && a.name === rollupConfig.relationAttribute
11605
11783
  );
11606
11784
  if (!relationAttr) continue;
11607
11785
  const relationConfig = relationAttr.config;
11608
- const targetsChangedObject = _optionalChain([relationConfig, 'optionalAccess', _228 => _228.targets, 'optionalAccess', _229 => _229.some, 'call', _230 => _230(
11786
+ const targetsChangedObject = _optionalChain([relationConfig, 'optionalAccess', _233 => _233.targets, 'optionalAccess', _234 => _234.some, 'call', _235 => _235(
11609
11787
  (t) => t.object === changedSchema.name
11610
11788
  )]);
11611
11789
  if (!targetsChangedObject) continue;
@@ -11630,11 +11808,11 @@ var RecordService = class extends BaseService {
11630
11808
  constructor(adapter, options) {
11631
11809
  super(adapter);
11632
11810
  this.schemaService = new ObjectSchemaService(adapter, registry, {
11633
- auditService: _optionalChain([options, 'optionalAccess', _231 => _231.auditService])
11811
+ auditService: _optionalChain([options, 'optionalAccess', _236 => _236.auditService])
11634
11812
  });
11635
- this.permissionService = _optionalChain([options, 'optionalAccess', _232 => _232.permissionService]);
11636
- this.auditService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _233 => _233.auditService]), () => ( (adapter.audit ? new AuditService(adapter) : void 0)));
11637
- this.policyRegistry = _optionalChain([options, 'optionalAccess', _234 => _234.policyRegistry]) === null ? null : _nullishCoalesce(_optionalChain([options, 'optionalAccess', _235 => _235.policyRegistry]), () => ( defaultPolicyRegistry));
11813
+ this.permissionService = _optionalChain([options, 'optionalAccess', _237 => _237.permissionService]);
11814
+ this.auditService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _238 => _238.auditService]), () => ( (adapter.audit ? new AuditService(adapter) : void 0)));
11815
+ this.policyRegistry = _optionalChain([options, 'optionalAccess', _239 => _239.policyRegistry]) === null ? null : _nullishCoalesce(_optionalChain([options, 'optionalAccess', _240 => _240.policyRegistry]), () => ( defaultPolicyRegistry));
11638
11816
  this.recordResolver = new RecordResolverService(adapter);
11639
11817
  this.queryService = new RecordQueryService(adapter, this.schemaService, {
11640
11818
  permissionService: this.permissionService,
@@ -11649,7 +11827,12 @@ var RecordService = class extends BaseService {
11649
11827
  recordResolver: this.recordResolver
11650
11828
  });
11651
11829
  this.userService = new UserService(adapter);
11652
- this.hookRegistry = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _236 => _236.hookRegistry]), () => ( new NoopHookRegistry()));
11830
+ this.hookRegistry = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _241 => _241.hookRegistry]), () => ( new NoopHookRegistry()));
11831
+ this.bilateralSyncService = new BilateralSyncService(
11832
+ adapter,
11833
+ this.schemaService,
11834
+ this.relationPropertiesService
11835
+ );
11653
11836
  this.labelResolver = this.recordResolver.createLabelResolver(this.relationService);
11654
11837
  this.rollupContext = this.recordResolver.createRollupContext(
11655
11838
  this.rollupService,
@@ -11684,25 +11867,25 @@ var RecordService = class extends BaseService {
11684
11867
  schema,
11685
11868
  this.tenantId,
11686
11869
  dataWithDefaults,
11687
- _optionalChain([options, 'optionalAccess', _237 => _237.hookMetadata])
11870
+ _optionalChain([options, 'optionalAccess', _242 => _242.hookMetadata])
11688
11871
  );
11689
- if (!_optionalChain([options, 'optionalAccess', _238 => _238.skipHooks])) {
11872
+ if (!_optionalChain([options, 'optionalAccess', _243 => _243.skipHooks])) {
11690
11873
  await this.hookRegistry.execute("beforeCreate", schema.name, hookCtx);
11691
11874
  }
11692
11875
  const normalizedData = this.relationPropertiesService.normalizeRelationValuesForStorage(
11693
11876
  schema,
11694
11877
  dataWithDefaults
11695
11878
  );
11696
- if (_optionalChain([options, 'optionalAccess', _239 => _239.validate]) !== false) {
11697
- if (_optionalChain([options, 'optionalAccess', _240 => _240.allowDraft])) {
11879
+ if (_optionalChain([options, 'optionalAccess', _244 => _244.validate]) !== false) {
11880
+ if (_optionalChain([options, 'optionalAccess', _245 => _245.allowDraft])) {
11698
11881
  _chunkU4AB53AMjs.validateDraftOrThrow.call(void 0, schema, normalizedData);
11699
11882
  } else {
11700
11883
  _chunkU4AB53AMjs.validateObjectOrThrow.call(void 0, schema, normalizedData);
11701
11884
  }
11702
- if (!_optionalChain([options, 'optionalAccess', _241 => _241.skipRelationValidation])) {
11885
+ if (!_optionalChain([options, 'optionalAccess', _246 => _246.skipRelationValidation])) {
11703
11886
  await this.relationService.validateRelationsOrThrow(schema, normalizedData);
11704
11887
  }
11705
- if (!_optionalChain([options, 'optionalAccess', _242 => _242.skipUserValidation])) {
11888
+ if (!_optionalChain([options, 'optionalAccess', _247 => _247.skipUserValidation])) {
11706
11889
  await this.userService.validateUsersOrThrow(schema, normalizedData);
11707
11890
  }
11708
11891
  }
@@ -11713,22 +11896,39 @@ var RecordService = class extends BaseService {
11713
11896
  data: normalizedData,
11714
11897
  label,
11715
11898
  completionStatus,
11716
- metadata: _optionalChain([options, 'optionalAccess', _243 => _243.metadata]),
11899
+ metadata: _optionalChain([options, 'optionalAccess', _248 => _248.metadata]),
11717
11900
  createdBy: this.userId
11718
11901
  });
11719
11902
  for (const [attrName, value] of Object.entries(dataWithDefaults)) {
11720
11903
  const attr = schema.attributes.find((a) => a.name === attrName);
11721
- if (_optionalChain([attr, 'optionalAccess', _244 => _244.type]) === "relation" && attr.properties) {
11722
- await this.relationPropertiesService.syncRelationProperties(
11904
+ if (_optionalChain([attr, 'optionalAccess', _249 => _249.type]) === "relation") {
11905
+ const hasProperties2 = attr.properties !== void 0;
11906
+ const isBilateral = isBilateralRelation(attr);
11907
+ if (hasProperties2 || isBilateral) {
11908
+ await this.relationPropertiesService.syncRelationProperties(
11909
+ schema,
11910
+ record.id,
11911
+ attrName,
11912
+ value,
11913
+ this.adapter
11914
+ );
11915
+ }
11916
+ }
11917
+ }
11918
+ for (const [attrName, value] of Object.entries(normalizedData)) {
11919
+ const attr = schema.attributes.find((a) => a.name === attrName);
11920
+ if (_optionalChain([attr, 'optionalAccess', _250 => _250.type]) === "relation" && isBilateralRelation(attr)) {
11921
+ await this.bilateralSyncService.syncBilateralRelation(
11723
11922
  schema,
11724
11923
  record.id,
11725
11924
  attrName,
11726
11925
  value,
11727
- this.adapter
11926
+ null
11927
+ // oldValue is null for create
11728
11928
  );
11729
11929
  }
11730
11930
  }
11731
- if (!_optionalChain([options, 'optionalAccess', _245 => _245.skipHooks])) {
11931
+ if (!_optionalChain([options, 'optionalAccess', _251 => _251.skipHooks])) {
11732
11932
  const afterCtx = {
11733
11933
  ...hookCtx,
11734
11934
  recordId: record.id,
@@ -11746,12 +11946,8 @@ var RecordService = class extends BaseService {
11746
11946
  objectId: schema.id,
11747
11947
  recordId: record.id,
11748
11948
  recordLabel: record.label,
11749
- metadata: _optionalChain([options, 'optionalAccess', _246 => _246.hookMetadata])
11750
- }).catch((err) => {
11751
- console.error(
11752
- "Audit log failed (record.created):",
11753
- err instanceof Error ? err.message : err
11754
- );
11949
+ metadata: _optionalChain([options, 'optionalAccess', _252 => _252.hookMetadata])
11950
+ }).catch(() => {
11755
11951
  });
11756
11952
  }
11757
11953
  return record;
@@ -11772,7 +11968,7 @@ var RecordService = class extends BaseService {
11772
11968
  return null;
11773
11969
  }
11774
11970
  const schema = await this.schemaService.getObjectSchema(record.objectId);
11775
- if (!_optionalChain([options, 'optionalAccess', _247 => _247.skipPolicyCheck])) {
11971
+ if (!_optionalChain([options, 'optionalAccess', _253 => _253.skipPolicyCheck])) {
11776
11972
  const policy = getPolicy(this.policyRegistry, this.userId, schema.name);
11777
11973
  if (policy) {
11778
11974
  const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
@@ -11782,10 +11978,11 @@ var RecordService = class extends BaseService {
11782
11978
  }
11783
11979
  }
11784
11980
  let enrichedRecord = record;
11785
- if (!_optionalChain([options, 'optionalAccess', _248 => _248.skipFormulas])) {
11981
+ if (!_optionalChain([options, 'optionalAccess', _254 => _254.skipFormulas])) {
11786
11982
  enrichedRecord = enrichWithFormulas(record, schema);
11787
11983
  }
11788
- if (_optionalChain([options, 'optionalAccess', _249 => _249.includeSchema])) {
11984
+ enrichedRecord = await this.enrichRelationProperties(enrichedRecord, schema);
11985
+ if (_optionalChain([options, 'optionalAccess', _255 => _255.includeSchema])) {
11789
11986
  const recordWithSchema = enrichedRecord;
11790
11987
  recordWithSchema.schema = schema;
11791
11988
  return recordWithSchema;
@@ -11835,7 +12032,7 @@ var RecordService = class extends BaseService {
11835
12032
  if (oldVal !== null && newVal !== null && typeof oldVal === "object" && typeof newVal === "object") {
11836
12033
  try {
11837
12034
  return JSON.stringify(oldVal) !== JSON.stringify(newVal);
11838
- } catch (e13) {
12035
+ } catch (e16) {
11839
12036
  return true;
11840
12037
  }
11841
12038
  }
@@ -11847,9 +12044,9 @@ var RecordService = class extends BaseService {
11847
12044
  existing,
11848
12045
  mergedData,
11849
12046
  changedAttributes,
11850
- _optionalChain([options, 'optionalAccess', _250 => _250.hookMetadata])
12047
+ _optionalChain([options, 'optionalAccess', _256 => _256.hookMetadata])
11851
12048
  );
11852
- if (!_optionalChain([options, 'optionalAccess', _251 => _251.skipHooks])) {
12049
+ if (!_optionalChain([options, 'optionalAccess', _257 => _257.skipHooks])) {
11853
12050
  await this.hookRegistry.execute("beforeUpdate", schema.name, hookCtx);
11854
12051
  }
11855
12052
  const hookModifiedValues = {};
@@ -11864,16 +12061,16 @@ var RecordService = class extends BaseService {
11864
12061
  dataToUpdate
11865
12062
  );
11866
12063
  const normalizedMergedData = { ...existing.values, ...normalizedUpdate };
11867
- if (_optionalChain([options, 'optionalAccess', _252 => _252.validate]) !== false) {
11868
- if (_optionalChain([options, 'optionalAccess', _253 => _253.partial])) {
12064
+ if (_optionalChain([options, 'optionalAccess', _258 => _258.validate]) !== false) {
12065
+ if (_optionalChain([options, 'optionalAccess', _259 => _259.partial])) {
11869
12066
  _chunkU4AB53AMjs.validateDraftOrThrow.call(void 0, schema, normalizedMergedData);
11870
12067
  } else {
11871
12068
  _chunkU4AB53AMjs.validateObjectOrThrow.call(void 0, schema, normalizedMergedData);
11872
12069
  }
11873
- if (!_optionalChain([options, 'optionalAccess', _254 => _254.skipRelationValidation])) {
12070
+ if (!_optionalChain([options, 'optionalAccess', _260 => _260.skipRelationValidation])) {
11874
12071
  await this.relationService.validateRelationsOrThrow(schema, normalizedUpdate);
11875
12072
  }
11876
- if (!_optionalChain([options, 'optionalAccess', _255 => _255.skipUserValidation])) {
12073
+ if (!_optionalChain([options, 'optionalAccess', _261 => _261.skipUserValidation])) {
11877
12074
  await this.userService.validateUsersOrThrow(schema, normalizedUpdate);
11878
12075
  }
11879
12076
  }
@@ -11886,7 +12083,7 @@ var RecordService = class extends BaseService {
11886
12083
  __lastUpdatedBy: this.userId,
11887
12084
  __expectedUpdatedAt: existing.updatedAt instanceof Date ? existing.updatedAt.toISOString() : existing.updatedAt
11888
12085
  };
11889
- if (_optionalChain([options, 'optionalAccess', _256 => _256.metadata]) !== void 0) {
12086
+ if (_optionalChain([options, 'optionalAccess', _262 => _262.metadata]) !== void 0) {
11890
12087
  const existingMetadata = _nullishCoalesce(existing.metadata, () => ( {}));
11891
12088
  const mergedMetadata = { ...existingMetadata, ...options.metadata };
11892
12089
  const cleanedMetadata = Object.fromEntries(
@@ -11894,21 +12091,45 @@ var RecordService = class extends BaseService {
11894
12091
  );
11895
12092
  updatePayload.__metadata = cleanedMetadata;
11896
12093
  }
12094
+ const bilateralOldValues = {};
12095
+ for (const attrName of Object.keys(normalizedUpdate)) {
12096
+ const attr = schema.attributes.find((a) => a.name === attrName);
12097
+ if (_optionalChain([attr, 'optionalAccess', _263 => _263.type]) === "relation" && isBilateralRelation(attr)) {
12098
+ bilateralOldValues[attrName] = existing.values[attrName];
12099
+ }
12100
+ }
11897
12101
  const updated = await this.adapter.objectRecords.update(recordId, updatePayload);
11898
12102
  await this.invalidateRecordCaches(recordId, existing.objectId);
11899
12103
  for (const [attrName, value] of Object.entries(dataToUpdate)) {
11900
12104
  const attr = schema.attributes.find((a) => a.name === attrName);
11901
- if (_optionalChain([attr, 'optionalAccess', _257 => _257.type]) === "relation" && attr.properties) {
11902
- await this.relationPropertiesService.syncRelationProperties(
12105
+ if (_optionalChain([attr, 'optionalAccess', _264 => _264.type]) === "relation") {
12106
+ const hasProperties2 = attr.properties !== void 0;
12107
+ const isBilateral = isBilateralRelation(attr);
12108
+ if (hasProperties2 || isBilateral) {
12109
+ await this.relationPropertiesService.syncRelationProperties(
12110
+ schema,
12111
+ recordId,
12112
+ attrName,
12113
+ value,
12114
+ this.adapter
12115
+ );
12116
+ }
12117
+ }
12118
+ }
12119
+ for (const [attrName, value] of Object.entries(normalizedUpdate)) {
12120
+ const attr = schema.attributes.find((a) => a.name === attrName);
12121
+ if (_optionalChain([attr, 'optionalAccess', _265 => _265.type]) === "relation" && isBilateralRelation(attr)) {
12122
+ const oldValue = bilateralOldValues[attrName];
12123
+ await this.bilateralSyncService.syncBilateralRelation(
11903
12124
  schema,
11904
12125
  recordId,
11905
12126
  attrName,
11906
12127
  value,
11907
- this.adapter
12128
+ oldValue
11908
12129
  );
11909
12130
  }
11910
12131
  }
11911
- if (!_optionalChain([options, 'optionalAccess', _258 => _258.skipHooks])) {
12132
+ if (!_optionalChain([options, 'optionalAccess', _266 => _266.skipHooks])) {
11912
12133
  const afterCtx = {
11913
12134
  ...hookCtx,
11914
12135
  record: updated
@@ -11923,7 +12144,7 @@ var RecordService = class extends BaseService {
11923
12144
  if (this.auditService && this.userId && allChangedAttributes.length > 0) {
11924
12145
  const changes = allChangedAttributes.map((attr) => ({
11925
12146
  field: attr,
11926
- oldValue: _optionalChain([hookCtx, 'access', _259 => _259.oldValues, 'optionalAccess', _260 => _260[attr]]),
12147
+ oldValue: _optionalChain([hookCtx, 'access', _267 => _267.oldValues, 'optionalAccess', _268 => _268[attr]]),
11927
12148
  newValue: hookCtx.newValues[attr]
11928
12149
  }));
11929
12150
  this.auditService.logRecordAction({
@@ -11934,12 +12155,8 @@ var RecordService = class extends BaseService {
11934
12155
  recordId: updated.id,
11935
12156
  recordLabel: updated.label,
11936
12157
  changes,
11937
- metadata: _optionalChain([options, 'optionalAccess', _261 => _261.hookMetadata])
11938
- }).catch((err) => {
11939
- console.error(
11940
- "Audit log failed (record.updated):",
11941
- err instanceof Error ? err.message : err
11942
- );
12158
+ metadata: _optionalChain([options, 'optionalAccess', _269 => _269.hookMetadata])
12159
+ }).catch(() => {
11943
12160
  });
11944
12161
  }
11945
12162
  return updated;
@@ -11969,22 +12186,35 @@ var RecordService = class extends BaseService {
11969
12186
  const ctx = buildPolicyContext(schema.name, this.userId, this.tenantId);
11970
12187
  checkRecordDeleteOrThrow(policy, record, ctx);
11971
12188
  }
11972
- if (_optionalChain([options, 'optionalAccess', _262 => _262.checkSystem]) && schema.system) {
12189
+ if (_optionalChain([options, 'optionalAccess', _270 => _270.checkSystem]) && schema.system) {
11973
12190
  throw new ProtectedResourceError("object", schema.name, "delete");
11974
12191
  }
11975
- if (!_optionalChain([options, 'optionalAccess', _263 => _263.skipReferenceCheck])) {
12192
+ if (!_optionalChain([options, 'optionalAccess', _271 => _271.skipReferenceCheck])) {
11976
12193
  const references = await this.adapter.objectRecords.countRecordsReferencingId(recordId);
11977
12194
  if (references.length > 0) {
11978
12195
  throw new RecordReferencedError(recordId, references);
11979
12196
  }
11980
12197
  }
11981
- const hookCtx = createContextForDelete(schema, this.tenantId, record, _optionalChain([options, 'optionalAccess', _264 => _264.hookMetadata]));
11982
- if (!_optionalChain([options, 'optionalAccess', _265 => _265.skipHooks])) {
12198
+ const hookCtx = createContextForDelete(schema, this.tenantId, record, _optionalChain([options, 'optionalAccess', _272 => _272.hookMetadata]));
12199
+ if (!_optionalChain([options, 'optionalAccess', _273 => _273.skipHooks])) {
11983
12200
  await this.hookRegistry.execute("beforeDelete", schema.name, hookCtx);
11984
12201
  }
12202
+ for (const attr of schema.attributes) {
12203
+ if (attr.type === "relation" && isBilateralRelation(attr)) {
12204
+ const currentValue = record.values[attr.name];
12205
+ await this.bilateralSyncService.syncBilateralRelation(
12206
+ schema,
12207
+ recordId,
12208
+ attr.name,
12209
+ null,
12210
+ // newValue is null
12211
+ currentValue
12212
+ );
12213
+ }
12214
+ }
11985
12215
  await this.adapter.objectRecords.delete(recordId);
11986
12216
  await this.invalidateRecordCaches(recordId, record.objectId);
11987
- if (!_optionalChain([options, 'optionalAccess', _266 => _266.skipHooks])) {
12217
+ if (!_optionalChain([options, 'optionalAccess', _274 => _274.skipHooks])) {
11988
12218
  await this.hookRegistry.execute("afterDelete", schema.name, hookCtx);
11989
12219
  }
11990
12220
  await recalculateParentRollups(record, schema, this.rollupContext);
@@ -11996,12 +12226,8 @@ var RecordService = class extends BaseService {
11996
12226
  objectId: schema.id,
11997
12227
  recordId: record.id,
11998
12228
  recordLabel: record.label,
11999
- metadata: _optionalChain([options, 'optionalAccess', _267 => _267.hookMetadata])
12000
- }).catch((err) => {
12001
- console.error(
12002
- "Audit log failed (record.deleted):",
12003
- err instanceof Error ? err.message : err
12004
- );
12229
+ metadata: _optionalChain([options, 'optionalAccess', _275 => _275.hookMetadata])
12230
+ }).catch(() => {
12005
12231
  });
12006
12232
  }
12007
12233
  }
@@ -12061,13 +12287,13 @@ var RecordService = class extends BaseService {
12061
12287
  this.tenantId
12062
12288
  );
12063
12289
  await checkPermission(this.permissionService, this.userId, schema.name, "update");
12064
- const hookCtx = createContextForRestore(schema, this.tenantId, record, _optionalChain([options, 'optionalAccess', _268 => _268.hookMetadata]));
12065
- if (!_optionalChain([options, 'optionalAccess', _269 => _269.skipHooks])) {
12290
+ const hookCtx = createContextForRestore(schema, this.tenantId, record, _optionalChain([options, 'optionalAccess', _276 => _276.hookMetadata]));
12291
+ if (!_optionalChain([options, 'optionalAccess', _277 => _277.skipHooks])) {
12066
12292
  await this.hookRegistry.execute("beforeRestore", schema.name, hookCtx);
12067
12293
  }
12068
12294
  const restored = await this.adapter.objectRecords.restore(recordId);
12069
12295
  await this.invalidateRecordCaches(recordId, record.objectId);
12070
- if (!_optionalChain([options, 'optionalAccess', _270 => _270.skipHooks])) {
12296
+ if (!_optionalChain([options, 'optionalAccess', _278 => _278.skipHooks])) {
12071
12297
  const afterCtx = {
12072
12298
  ...hookCtx,
12073
12299
  record: restored
@@ -12082,12 +12308,8 @@ var RecordService = class extends BaseService {
12082
12308
  objectId: schema.id,
12083
12309
  recordId: restored.id,
12084
12310
  recordLabel: restored.label,
12085
- metadata: _optionalChain([options, 'optionalAccess', _271 => _271.hookMetadata])
12086
- }).catch((err) => {
12087
- console.error(
12088
- "Audit log failed (record.restored):",
12089
- err instanceof Error ? err.message : err
12090
- );
12311
+ metadata: _optionalChain([options, 'optionalAccess', _279 => _279.hookMetadata])
12312
+ }).catch(() => {
12091
12313
  });
12092
12314
  }
12093
12315
  return restored;
@@ -12095,6 +12317,60 @@ var RecordService = class extends BaseService {
12095
12317
  // ============================================================================
12096
12318
  // PRIVATE HELPERS
12097
12319
  // ============================================================================
12320
+ /**
12321
+ * Enrich relation attributes with their properties (for qualified relations).
12322
+ *
12323
+ * Transforms simple ID arrays into hybrid format { id, props } when properties exist.
12324
+ *
12325
+ * @param record - Record to enrich
12326
+ * @param schema - Object schema
12327
+ * @returns Enriched record with relation properties loaded
12328
+ * @private
12329
+ */
12330
+ async enrichRelationProperties(record, schema) {
12331
+ const enrichedValues = { ...record.values };
12332
+ for (const attr of schema.attributes) {
12333
+ if (attr.type !== "relation") {
12334
+ continue;
12335
+ }
12336
+ const hasProperties2 = attr.properties && attr.properties.definitions.length > 0;
12337
+ const isBilateral = isBilateralRelation(attr);
12338
+ const shouldEnrich = hasProperties2 || isBilateral;
12339
+ if (!shouldEnrich) {
12340
+ continue;
12341
+ }
12342
+ const value = record.values[attr.name];
12343
+ if (value === null || value === void 0) {
12344
+ continue;
12345
+ }
12346
+ const isMany = attr.cardinality === "many";
12347
+ const targetIds = isMany ? value : [value];
12348
+ if (!targetIds || targetIds.length === 0) {
12349
+ continue;
12350
+ }
12351
+ const propsMap = await this.relationPropertiesService.getRelationProperties(
12352
+ schema.name,
12353
+ record.id,
12354
+ attr.name,
12355
+ targetIds
12356
+ );
12357
+ if (isMany) {
12358
+ const hybridArray = targetIds.map((id) => {
12359
+ const props = propsMap.get(id);
12360
+ return props ? { id, props } : id;
12361
+ });
12362
+ enrichedValues[attr.name] = hybridArray;
12363
+ } else {
12364
+ const id = targetIds[0];
12365
+ const props = propsMap.get(id);
12366
+ enrichedValues[attr.name] = props ? { id, props } : id;
12367
+ }
12368
+ }
12369
+ return {
12370
+ ...record,
12371
+ values: enrichedValues
12372
+ };
12373
+ }
12098
12374
  /**
12099
12375
  * Invalidate all caches related to a record (record cache + lists + global search)
12100
12376
  * @private
@@ -12463,7 +12739,7 @@ var DocumentRendererService = class {
12463
12739
  throw new StorageDownloadNotSupportedError();
12464
12740
  }
12465
12741
  let storagePath = fileId;
12466
- if (_optionalChain([this, 'access', _272 => _272.options, 'optionalAccess', _273 => _273.filesRepository])) {
12742
+ if (_optionalChain([this, 'access', _280 => _280.options, 'optionalAccess', _281 => _281.filesRepository])) {
12467
12743
  const file2 = await this.options.filesRepository.findById(fileId);
12468
12744
  if (!file2) {
12469
12745
  throw new Error(`Template file not found: ${fileId}`);
@@ -12481,8 +12757,8 @@ var DocumentRendererService = class {
12481
12757
  for (const field of fields) {
12482
12758
  const rawValue = getContextValue(context, field.contextPath);
12483
12759
  const attrInfo = await this.getAttributeInfo(field.contextPath, workflow2);
12484
- if (_optionalChain([attrInfo, 'optionalAccess', _274 => _274.attribute])) {
12485
- if (attrInfo.attribute.type === "relation" && rawValue && _optionalChain([this, 'access', _275 => _275.options, 'optionalAccess', _276 => _276.relationService])) {
12760
+ if (_optionalChain([attrInfo, 'optionalAccess', _282 => _282.attribute])) {
12761
+ if (attrInfo.attribute.type === "relation" && rawValue && _optionalChain([this, 'access', _283 => _283.options, 'optionalAccess', _284 => _284.relationService])) {
12486
12762
  const ids = Array.isArray(rawValue) ? rawValue : [rawValue];
12487
12763
  const stringIds = ids.filter((id) => typeof id === "string");
12488
12764
  if (stringIds.length > 0) {
@@ -12503,7 +12779,7 @@ var DocumentRendererService = class {
12503
12779
  resolved.set(field.id, this.formatValueSimple(rawValue, field.fallback));
12504
12780
  }
12505
12781
  }
12506
- if (relationBatch.length > 0 && _optionalChain([this, 'access', _277 => _277.options, 'optionalAccess', _278 => _278.relationService])) {
12782
+ if (relationBatch.length > 0 && _optionalChain([this, 'access', _285 => _285.options, 'optionalAccess', _286 => _286.relationService])) {
12507
12783
  try {
12508
12784
  const batchResult = await this.options.relationService.resolveIdsBatch(
12509
12785
  relationBatch.map((r) => ({ attributeId: r.attributeId, ids: r.ids }))
@@ -12512,12 +12788,12 @@ var DocumentRendererService = class {
12512
12788
  const options = _nullishCoalesce(batchResult[attributeId], () => ( []));
12513
12789
  const labels = options.map((o) => o.label);
12514
12790
  const field = fields.find((f) => f.id === fieldId);
12515
- resolved.set(fieldId, labels.join(", ") || _optionalChain([field, 'optionalAccess', _279 => _279.fallback]) || "");
12791
+ resolved.set(fieldId, labels.join(", ") || _optionalChain([field, 'optionalAccess', _287 => _287.fallback]) || "");
12516
12792
  }
12517
- } catch (e14) {
12793
+ } catch (e17) {
12518
12794
  for (const { fieldId, ids } of relationBatch) {
12519
12795
  const field = fields.find((f) => f.id === fieldId);
12520
- resolved.set(fieldId, ids.join(", ") || _optionalChain([field, 'optionalAccess', _280 => _280.fallback]) || "");
12796
+ resolved.set(fieldId, ids.join(", ") || _optionalChain([field, 'optionalAccess', _288 => _288.fallback]) || "");
12521
12797
  }
12522
12798
  }
12523
12799
  }
@@ -12528,7 +12804,7 @@ var DocumentRendererService = class {
12528
12804
  * Parses paths like "slots.client.firstName" to find the attribute definition
12529
12805
  */
12530
12806
  async getAttributeInfo(contextPath, workflow2) {
12531
- const schemaService = _optionalChain([this, 'access', _281 => _281.options, 'optionalAccess', _282 => _282.schemaService]);
12807
+ const schemaService = _optionalChain([this, 'access', _289 => _289.options, 'optionalAccess', _290 => _290.schemaService]);
12532
12808
  if (!schemaService) {
12533
12809
  return null;
12534
12810
  }
@@ -12541,7 +12817,7 @@ var DocumentRendererService = class {
12541
12817
  }
12542
12818
  const slotId = parts[1];
12543
12819
  const attributeName = parts[2];
12544
- const slot = _optionalChain([workflow2, 'access', _283 => _283.slots, 'optionalAccess', _284 => _284.find, 'call', _285 => _285((s) => s.id === slotId)]);
12820
+ const slot = _optionalChain([workflow2, 'access', _291 => _291.slots, 'optionalAccess', _292 => _292.find, 'call', _293 => _293((s) => s.id === slotId)]);
12545
12821
  if (!slot) {
12546
12822
  return null;
12547
12823
  }
@@ -12550,7 +12826,7 @@ var DocumentRendererService = class {
12550
12826
  try {
12551
12827
  schema = await schemaService.getObjectSchemaByName(slot.objectName);
12552
12828
  this.schemaCache.set(slot.objectName, schema);
12553
- } catch (e15) {
12829
+ } catch (e18) {
12554
12830
  return null;
12555
12831
  }
12556
12832
  }
@@ -12740,7 +13016,7 @@ var DocumentProcessingHook = class extends BaseService {
12740
13016
  const pendingIds = [];
12741
13017
  for (const [nodeId, doc] of Object.entries(context.documents)) {
12742
13018
  const metadata = doc.metadata;
12743
- if (_optionalChain([metadata, 'optionalAccess', _286 => _286.status]) === "pending") {
13019
+ if (_optionalChain([metadata, 'optionalAccess', _294 => _294.status]) === "pending") {
12744
13020
  pendingIds.push(nodeId);
12745
13021
  }
12746
13022
  }
@@ -12791,12 +13067,12 @@ var DocumentProcessingHook = class extends BaseService {
12791
13067
  }
12792
13068
  for (const slotId of targetSlotIds) {
12793
13069
  try {
12794
- const recordId = _optionalChain([context, 'access', _287 => _287.createdRecordIds, 'optionalAccess', _288 => _288[slotId]]);
13070
+ const recordId = _optionalChain([context, 'access', _295 => _295.createdRecordIds, 'optionalAccess', _296 => _296[slotId]]);
12795
13071
  if (!recordId) {
12796
13072
  continue;
12797
13073
  }
12798
- const slotDef = _optionalChain([workflow2, 'access', _289 => _289.slots, 'optionalAccess', _290 => _290.find, 'call', _291 => _291((s) => s.id === slotId)]);
12799
- const objectName = _optionalChain([slotDef, 'optionalAccess', _292 => _292.objectName]);
13074
+ const slotDef = _optionalChain([workflow2, 'access', _297 => _297.slots, 'optionalAccess', _298 => _298.find, 'call', _299 => _299((s) => s.id === slotId)]);
13075
+ const objectName = _optionalChain([slotDef, 'optionalAccess', _300 => _300.objectName]);
12800
13076
  if (!objectName) {
12801
13077
  continue;
12802
13078
  }
@@ -12813,14 +13089,14 @@ var DocumentProcessingHook = class extends BaseService {
12813
13089
  attachedDocumentIds.push(result.document.id);
12814
13090
  const record = await recordService.getRecord(recordId);
12815
13091
  if (record) {
12816
- const attachments = _nullishCoalesce(_optionalChain([record, 'access', _293 => _293.values, 'optionalAccess', _294 => _294.attachments]), () => ( []));
13092
+ const attachments = _nullishCoalesce(_optionalChain([record, 'access', _301 => _301.values, 'optionalAccess', _302 => _302.attachments]), () => ( []));
12817
13093
  await recordService.updateRecord(
12818
13094
  recordId,
12819
13095
  { attachments: [...attachments, result.document.id] },
12820
13096
  { partial: true }
12821
13097
  );
12822
13098
  }
12823
- } catch (e16) {
13099
+ } catch (e19) {
12824
13100
  }
12825
13101
  }
12826
13102
  return attachedDocumentIds;
@@ -13079,7 +13355,7 @@ var WorkflowAccessGrantService = class extends BaseService {
13079
13355
  * Check if a specific token has been revoked.
13080
13356
  */
13081
13357
  isTokenRevoked(dbGrant, jti) {
13082
- return _nullishCoalesce(_optionalChain([dbGrant, 'access', _295 => _295.revoked_token_jtis, 'optionalAccess', _296 => _296.includes, 'call', _297 => _297(jti)]), () => ( false));
13358
+ return _nullishCoalesce(_optionalChain([dbGrant, 'access', _303 => _303.revoked_token_jtis, 'optionalAccess', _304 => _304.includes, 'call', _305 => _305(jti)]), () => ( false));
13083
13359
  }
13084
13360
  /**
13085
13361
  * Validate access token payload against the grant.
@@ -13131,10 +13407,10 @@ var WorkflowInstanceService = class extends BaseService {
13131
13407
  constructor(adapter, workflowService, options) {
13132
13408
  super(adapter);
13133
13409
  this.workflowService = workflowService;
13134
- this.executorRegistry = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _298 => _298.executorRegistry]), () => ( getDefaultExecutorRegistry()));
13135
- this.schemaService = _optionalChain([options, 'optionalAccess', _299 => _299.schemaService]);
13136
- this.recordService = _optionalChain([options, 'optionalAccess', _300 => _300.recordService]);
13137
- this.documentProcessingHook = _optionalChain([options, 'optionalAccess', _301 => _301.documentProcessingHook]);
13410
+ this.executorRegistry = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _306 => _306.executorRegistry]), () => ( getDefaultExecutorRegistry()));
13411
+ this.schemaService = _optionalChain([options, 'optionalAccess', _307 => _307.schemaService]);
13412
+ this.recordService = _optionalChain([options, 'optionalAccess', _308 => _308.recordService]);
13413
+ this.documentProcessingHook = _optionalChain([options, 'optionalAccess', _309 => _309.documentProcessingHook]);
13138
13414
  }
13139
13415
  /**
13140
13416
  * Start a new workflow instance
@@ -13316,7 +13592,7 @@ var WorkflowInstanceService = class extends BaseService {
13316
13592
  if (!this.adapter.workflowInstances) {
13317
13593
  return { instances: [], total: 0 };
13318
13594
  }
13319
- if (_optionalChain([options, 'optionalAccess', _302 => _302.workflowName])) {
13595
+ if (_optionalChain([options, 'optionalAccess', _310 => _310.workflowName])) {
13320
13596
  const allDbInstances = await this.adapter.workflowInstances.findByWorkflowName(
13321
13597
  options.workflowName,
13322
13598
  { status: options.status }
@@ -13330,11 +13606,11 @@ var WorkflowInstanceService = class extends BaseService {
13330
13606
  return { instances: instances2, total: total2 };
13331
13607
  }
13332
13608
  const { instances: dbInstances, total } = await this.adapter.workflowInstances.list({
13333
- limit: _optionalChain([options, 'optionalAccess', _303 => _303.limit]),
13334
- offset: _optionalChain([options, 'optionalAccess', _304 => _304.offset])
13609
+ limit: _optionalChain([options, 'optionalAccess', _311 => _311.limit]),
13610
+ offset: _optionalChain([options, 'optionalAccess', _312 => _312.offset])
13335
13611
  });
13336
13612
  let instances = dbInstances.map((db) => this.convertDBInstanceToInstance(db));
13337
- if (_optionalChain([options, 'optionalAccess', _305 => _305.status])) {
13613
+ if (_optionalChain([options, 'optionalAccess', _313 => _313.status])) {
13338
13614
  instances = instances.filter((i) => i.status === options.status);
13339
13615
  }
13340
13616
  instances = await this.markExpiredInstances(instances);
@@ -13355,9 +13631,9 @@ var WorkflowInstanceService = class extends BaseService {
13355
13631
  return { instances: [], total: 0 };
13356
13632
  }
13357
13633
  const { instances: dbInstances, total } = await this.adapter.workflowInstances.findByRecordInSlots(objectName, recordId, {
13358
- status: _optionalChain([options, 'optionalAccess', _306 => _306.status]),
13359
- limit: _optionalChain([options, 'optionalAccess', _307 => _307.limit]),
13360
- offset: _optionalChain([options, 'optionalAccess', _308 => _308.offset])
13634
+ status: _optionalChain([options, 'optionalAccess', _314 => _314.status]),
13635
+ limit: _optionalChain([options, 'optionalAccess', _315 => _315.limit]),
13636
+ offset: _optionalChain([options, 'optionalAccess', _316 => _316.offset])
13361
13637
  });
13362
13638
  const instances = dbInstances.map((db) => this.convertDBInstanceToInstance(db));
13363
13639
  return { instances, total };
@@ -13423,13 +13699,13 @@ var WorkflowInstanceService = class extends BaseService {
13423
13699
  try {
13424
13700
  const schemas = await Promise.all(
13425
13701
  current.workflowSnapshot.slots.map(
13426
- (slot) => _optionalChain([this, 'access', _309 => _309.schemaService, 'optionalAccess', _310 => _310.getObjectSchemaByName, 'call', _311 => _311(slot.objectName)])
13702
+ (slot) => _optionalChain([this, 'access', _317 => _317.schemaService, 'optionalAccess', _318 => _318.getObjectSchemaByName, 'call', _319 => _319(slot.objectName)])
13427
13703
  )
13428
13704
  );
13429
13705
  objectDefinitions = schemas.filter(
13430
13706
  (s) => s !== void 0
13431
13707
  );
13432
- } catch (e17) {
13708
+ } catch (e20) {
13433
13709
  }
13434
13710
  }
13435
13711
  const executorContext = {
@@ -13688,9 +13964,9 @@ var WorkflowInstanceService = class extends BaseService {
13688
13964
  */
13689
13965
  async snapshotRecord(recordId) {
13690
13966
  try {
13691
- const record = await _optionalChain([this, 'access', _312 => _312.recordService, 'optionalAccess', _313 => _313.getRecord, 'call', _314 => _314(recordId, { skipPolicyCheck: true })]);
13692
- return _optionalChain([record, 'optionalAccess', _315 => _315.values]);
13693
- } catch (e18) {
13967
+ const record = await _optionalChain([this, 'access', _320 => _320.recordService, 'optionalAccess', _321 => _321.getRecord, 'call', _322 => _322(recordId, { skipPolicyCheck: true })]);
13968
+ return _optionalChain([record, 'optionalAccess', _323 => _323.values]);
13969
+ } catch (e21) {
13694
13970
  return void 0;
13695
13971
  }
13696
13972
  }
@@ -13708,18 +13984,18 @@ var WorkflowInstanceService = class extends BaseService {
13708
13984
  for (const op of [...operations].reverse()) {
13709
13985
  try {
13710
13986
  if (op.operation === "create") {
13711
- await _optionalChain([this, 'access', _316 => _316.recordService, 'optionalAccess', _317 => _317.deleteRecord, 'call', _318 => _318(op.recordId, {
13987
+ await _optionalChain([this, 'access', _324 => _324.recordService, 'optionalAccess', _325 => _325.deleteRecord, 'call', _326 => _326(op.recordId, {
13712
13988
  skipHooks: true,
13713
13989
  skipReferenceCheck: true
13714
13990
  })]);
13715
13991
  rolledBack.push(op.slotId);
13716
13992
  } else if (op.operation === "update" && op.previousData) {
13717
- await _optionalChain([this, 'access', _319 => _319.recordService, 'optionalAccess', _320 => _320.updateRecord, 'call', _321 => _321(op.recordId, op.previousData, {
13993
+ await _optionalChain([this, 'access', _327 => _327.recordService, 'optionalAccess', _328 => _328.updateRecord, 'call', _329 => _329(op.recordId, op.previousData, {
13718
13994
  partial: false
13719
13995
  })]);
13720
13996
  rolledBack.push(op.slotId);
13721
13997
  }
13722
- } catch (e19) {
13998
+ } catch (e22) {
13723
13999
  }
13724
14000
  }
13725
14001
  return rolledBack;
@@ -13838,7 +14114,7 @@ var WorkflowInstanceService = class extends BaseService {
13838
14114
  if (!this.adapter.workflowInstances) {
13839
14115
  return;
13840
14116
  }
13841
- const currentVersion = _nullishCoalesce(_optionalChain([instance, 'access', _322 => _322.context, 'access', _323 => _323.variables, 'optionalAccess', _324 => _324.__version]), () => ( 0));
14117
+ const currentVersion = _nullishCoalesce(_optionalChain([instance, 'access', _330 => _330.context, 'access', _331 => _331.variables, 'optionalAccess', _332 => _332.__version]), () => ( 0));
13842
14118
  const nextVersion = currentVersion + 1;
13843
14119
  const instanceWithVersion = {
13844
14120
  ...instance,
@@ -14119,7 +14395,7 @@ var WorkflowRelationService = class extends BaseService {
14119
14395
  if (attr.type !== "relation") continue;
14120
14396
  for (const slot of slots) {
14121
14397
  const slotData = context.slots[slot.id];
14122
- const slotRecordId = _optionalChain([slotData, 'optionalAccess', _325 => _325.id]);
14398
+ const slotRecordId = _optionalChain([slotData, 'optionalAccess', _333 => _333.id]);
14123
14399
  if (!slotRecordId) continue;
14124
14400
  const targetsSlotObject = attr.targets.some(
14125
14401
  (t) => t.object === slot.objectName
@@ -14187,7 +14463,7 @@ var WorkflowService = class extends BaseService {
14187
14463
  if (Array.isArray(options)) {
14188
14464
  this.systemWorkflows = new Map(options.map((w) => [w.name, w]));
14189
14465
  } else {
14190
- this.systemWorkflows = new Map((_nullishCoalesce(_optionalChain([options, 'optionalAccess', _326 => _326.systemWorkflows]), () => ( []))).map((w) => [w.name, w]));
14466
+ this.systemWorkflows = new Map((_nullishCoalesce(_optionalChain([options, 'optionalAccess', _334 => _334.systemWorkflows]), () => ( []))).map((w) => [w.name, w]));
14191
14467
  }
14192
14468
  }
14193
14469
  // ============================================================================
@@ -14485,7 +14761,7 @@ var WorkflowService = class extends BaseService {
14485
14761
  var UserProfileService = class extends BaseService {
14486
14762
  constructor(adapter, options) {
14487
14763
  super(adapter);
14488
- this.auditService = _optionalChain([options, 'optionalAccess', _327 => _327.auditService]);
14764
+ this.auditService = _optionalChain([options, 'optionalAccess', _335 => _335.auditService]);
14489
14765
  }
14490
14766
  // ============================================================================
14491
14767
  // CACHE MANAGEMENT
@@ -14648,7 +14924,7 @@ var UserProfileService = class extends BaseService {
14648
14924
  */
14649
14925
  async deleteProfile(profileId, options) {
14650
14926
  const profile = await this.getProfileOrThrow(profileId);
14651
- if (_optionalChain([options, 'optionalAccess', _328 => _328.checkAdmin])) {
14927
+ if (_optionalChain([options, 'optionalAccess', _336 => _336.checkAdmin])) {
14652
14928
  if (profile.role === "admin") {
14653
14929
  const adminCount = await this.adapter.userProfiles.countByRole("admin");
14654
14930
  if (adminCount <= 1) {
@@ -14723,7 +14999,7 @@ var UserProfileService = class extends BaseService {
14723
14999
  */
14724
15000
  async hasRole(profileId, role) {
14725
15001
  const profile = await this.getProfile(profileId);
14726
- return _optionalChain([profile, 'optionalAccess', _329 => _329.role]) === role;
15002
+ return _optionalChain([profile, 'optionalAccess', _337 => _337.role]) === role;
14727
15003
  }
14728
15004
  /**
14729
15005
  * Check if user is admin
@@ -15157,7 +15433,7 @@ var DocumentTemplateService = class extends BaseService {
15157
15433
  * Includes both system templates and tenant-specific templates.
15158
15434
  */
15159
15435
  async listTemplates(options) {
15160
- if (_optionalChain([options, 'optionalAccess', _330 => _330.systemOnly])) {
15436
+ if (_optionalChain([options, 'optionalAccess', _338 => _338.systemOnly])) {
15161
15437
  return SYSTEM_TEMPLATES;
15162
15438
  }
15163
15439
  const templates = [...SYSTEM_TEMPLATES];
@@ -15240,8 +15516,8 @@ var DocumentTemplateService = class extends BaseService {
15240
15516
  var DocumentService = class extends BaseService {
15241
15517
  constructor(adapter, options) {
15242
15518
  super(adapter);
15243
- this.templateService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _331 => _331.templateService]), () => ( new DocumentTemplateService(adapter)));
15244
- this.fileService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _332 => _332.fileService]), () => ( null));
15519
+ this.templateService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _339 => _339.templateService]), () => ( new DocumentTemplateService(adapter)));
15520
+ this.fileService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _340 => _340.fileService]), () => ( null));
15245
15521
  }
15246
15522
  // ============================================================================
15247
15523
  // CREATE
@@ -15492,7 +15768,7 @@ var DocumentService = class extends BaseService {
15492
15768
  */
15493
15769
  async isComplete(documentId) {
15494
15770
  const document2 = await this.getDocument(documentId);
15495
- return _optionalChain([document2, 'optionalAccess', _333 => _333.status]) !== "draft";
15771
+ return _optionalChain([document2, 'optionalAccess', _341 => _341.status]) !== "draft";
15496
15772
  }
15497
15773
  /**
15498
15774
  * Get document with its template and slots.
@@ -15750,7 +16026,7 @@ var DocumentProcessingService = class extends BaseService {
15750
16026
  type: "signature",
15751
16027
  provider: this.config.signatureAdapter.name,
15752
16028
  input: { signers, ...options },
15753
- expiresAt: _optionalChain([options, 'optionalAccess', _334 => _334.expiresAt])
16029
+ expiresAt: _optionalChain([options, 'optionalAccess', _342 => _342.expiresAt])
15754
16030
  });
15755
16031
  return job;
15756
16032
  }
@@ -15907,7 +16183,7 @@ var DocumentProcessingService = class extends BaseService {
15907
16183
  }
15908
16184
  const document2 = await this.documentService.getDocumentOrThrow(documentId);
15909
16185
  const template = await this.templateService.getTemplateOrThrow(document2.templateId);
15910
- if (!_optionalChain([template, 'access', _335 => _335.autoProcessing, 'optionalAccess', _336 => _336.identityVerification, 'optionalAccess', _337 => _337.enabled])) {
16186
+ if (!_optionalChain([template, 'access', _343 => _343.autoProcessing, 'optionalAccess', _344 => _344.identityVerification, 'optionalAccess', _345 => _345.enabled])) {
15911
16187
  throw new Error("Identity verification is not enabled for this document type");
15912
16188
  }
15913
16189
  const job = await this.adapter.documentJobs.create({
@@ -15993,13 +16269,13 @@ var DocumentProcessingService = class extends BaseService {
15993
16269
  const template = await this.templateService.getTemplateOrThrow(document2.templateId);
15994
16270
  const slots = await this.documentService.getSlots(documentId);
15995
16271
  const jobs = [];
15996
- if (_optionalChain([template, 'access', _338 => _338.autoProcessing, 'optionalAccess', _339 => _339.ocr, 'optionalAccess', _340 => _340.enabled]) && this.config.ocrAdapter) {
16272
+ if (_optionalChain([template, 'access', _346 => _346.autoProcessing, 'optionalAccess', _347 => _347.ocr, 'optionalAccess', _348 => _348.enabled]) && this.config.ocrAdapter) {
15997
16273
  for (const slot of slots) {
15998
16274
  const job = await this.processOcr(documentId, slot.slotName);
15999
16275
  jobs.push(job);
16000
16276
  }
16001
16277
  }
16002
- if (_optionalChain([template, 'access', _341 => _341.autoProcessing, 'optionalAccess', _342 => _342.identityVerification, 'optionalAccess', _343 => _343.enabled]) && this.config.identityAdapter) {
16278
+ if (_optionalChain([template, 'access', _349 => _349.autoProcessing, 'optionalAccess', _350 => _350.identityVerification, 'optionalAccess', _351 => _351.enabled]) && this.config.identityAdapter) {
16003
16279
  const job = await this.verifyIdentity(documentId);
16004
16280
  jobs.push(job);
16005
16281
  }
@@ -16070,15 +16346,15 @@ var DocumentProcessingService = class extends BaseService {
16070
16346
  return {
16071
16347
  ocr: {
16072
16348
  available: !!this.config.ocrAdapter,
16073
- provider: _optionalChain([this, 'access', _344 => _344.config, 'access', _345 => _345.ocrAdapter, 'optionalAccess', _346 => _346.name])
16349
+ provider: _optionalChain([this, 'access', _352 => _352.config, 'access', _353 => _353.ocrAdapter, 'optionalAccess', _354 => _354.name])
16074
16350
  },
16075
16351
  signature: {
16076
16352
  available: !!this.config.signatureAdapter,
16077
- provider: _optionalChain([this, 'access', _347 => _347.config, 'access', _348 => _348.signatureAdapter, 'optionalAccess', _349 => _349.name])
16353
+ provider: _optionalChain([this, 'access', _355 => _355.config, 'access', _356 => _356.signatureAdapter, 'optionalAccess', _357 => _357.name])
16078
16354
  },
16079
16355
  identityVerification: {
16080
16356
  available: !!this.config.identityAdapter,
16081
- provider: _optionalChain([this, 'access', _350 => _350.config, 'access', _351 => _351.identityAdapter, 'optionalAccess', _352 => _352.name])
16357
+ provider: _optionalChain([this, 'access', _358 => _358.config, 'access', _359 => _359.identityAdapter, 'optionalAccess', _360 => _360.name])
16082
16358
  }
16083
16359
  };
16084
16360
  }
@@ -16088,7 +16364,7 @@ var DocumentProcessingService = class extends BaseService {
16088
16364
  var FileService = class extends BaseService {
16089
16365
  constructor(adapter, options) {
16090
16366
  super(adapter);
16091
- this.auditService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _353 => _353.auditService]), () => ( (adapter.audit ? new AuditService(adapter) : void 0)));
16367
+ this.auditService = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _361 => _361.auditService]), () => ( (adapter.audit ? new AuditService(adapter) : void 0)));
16092
16368
  }
16093
16369
  // ============================================================================
16094
16370
  // UPLOAD (requires StorageAdapter)
@@ -16227,7 +16503,7 @@ var FileService = class extends BaseService {
16227
16503
  */
16228
16504
  async getFile(fileId) {
16229
16505
  const file2 = await this.adapter.files.findById(fileId);
16230
- if (_optionalChain([file2, 'optionalAccess', _354 => _354.deletedAt])) {
16506
+ if (_optionalChain([file2, 'optionalAccess', _362 => _362.deletedAt])) {
16231
16507
  return null;
16232
16508
  }
16233
16509
  return file2;
@@ -16289,12 +16565,12 @@ var FileService = class extends BaseService {
16289
16565
  */
16290
16566
  async deleteFile(fileId, options) {
16291
16567
  const file2 = await this.getFileOrThrow(fileId);
16292
- if (_optionalChain([options, 'optionalAccess', _355 => _355.checkOwnership]) && options.userId) {
16568
+ if (_optionalChain([options, 'optionalAccess', _363 => _363.checkOwnership]) && options.userId) {
16293
16569
  if (file2.uploadedBy !== options.userId) {
16294
16570
  throw new Error("You can only delete files you uploaded");
16295
16571
  }
16296
16572
  }
16297
- if (_optionalChain([options, 'optionalAccess', _356 => _356.hard])) {
16573
+ if (_optionalChain([options, 'optionalAccess', _364 => _364.hard])) {
16298
16574
  await this.adapter.files.hardDelete(fileId);
16299
16575
  } else {
16300
16576
  await this.adapter.files.delete(fileId);
@@ -16325,7 +16601,7 @@ var FileService = class extends BaseService {
16325
16601
  }
16326
16602
  const file2 = await this.getFileOrThrow(fileId);
16327
16603
  await this.adapter.storage.delete(file2.storagePath);
16328
- if (_optionalChain([options, 'optionalAccess', _357 => _357.hard])) {
16604
+ if (_optionalChain([options, 'optionalAccess', _365 => _365.hard])) {
16329
16605
  await this.adapter.files.hardDelete(fileId);
16330
16606
  } else {
16331
16607
  await this.adapter.files.delete(fileId);
@@ -16351,15 +16627,15 @@ var FileService = class extends BaseService {
16351
16627
  const fileResults = await Promise.all(fileIds.map((id) => this.getFile(id)));
16352
16628
  const files = fileResults.filter((f) => f !== null);
16353
16629
  if (files.length === 0) return;
16354
- if (_optionalChain([options, 'optionalAccess', _358 => _358.deleteFromStorage]) && this.adapter.storage) {
16630
+ if (_optionalChain([options, 'optionalAccess', _366 => _366.deleteFromStorage]) && this.adapter.storage) {
16355
16631
  const BATCH_SIZE = 10;
16356
16632
  for (let i = 0; i < files.length; i += BATCH_SIZE) {
16357
16633
  const batch = files.slice(i, i + BATCH_SIZE);
16358
- await Promise.all(batch.map((file2) => _optionalChain([this, 'access', _359 => _359.adapter, 'access', _360 => _360.storage, 'optionalAccess', _361 => _361.delete, 'call', _362 => _362(file2.storagePath)])));
16634
+ await Promise.all(batch.map((file2) => _optionalChain([this, 'access', _367 => _367.adapter, 'access', _368 => _368.storage, 'optionalAccess', _369 => _369.delete, 'call', _370 => _370(file2.storagePath)])));
16359
16635
  }
16360
16636
  }
16361
16637
  const idsToDelete = files.map((f) => f.id);
16362
- if (_optionalChain([options, 'optionalAccess', _363 => _363.hard])) {
16638
+ if (_optionalChain([options, 'optionalAccess', _371 => _371.hard])) {
16363
16639
  await Promise.all(idsToDelete.map((id) => this.adapter.files.hardDelete(id)));
16364
16640
  } else {
16365
16641
  await Promise.all(idsToDelete.map((id) => this.adapter.files.delete(id)));
@@ -16367,12 +16643,12 @@ var FileService = class extends BaseService {
16367
16643
  if (this.auditService && this.userId) {
16368
16644
  await Promise.all(
16369
16645
  files.map(
16370
- (file2) => _optionalChain([this, 'access', _364 => _364.auditService, 'optionalAccess', _365 => _365.logFileAction, 'call', _366 => _366({
16646
+ (file2) => _optionalChain([this, 'access', _372 => _372.auditService, 'optionalAccess', _373 => _373.logFileAction, 'call', _374 => _374({
16371
16647
  action: "file.deleted",
16372
16648
  actorId: _nullishCoalesce(this.userId, () => ( "")),
16373
16649
  fileId: file2.id,
16374
16650
  fileName: file2.name,
16375
- metadata: { deletedFromStorage: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _367 => _367.deleteFromStorage]), () => ( false)) }
16651
+ metadata: { deletedFromStorage: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _375 => _375.deleteFromStorage]), () => ( false)) }
16376
16652
  })])
16377
16653
  )
16378
16654
  );
@@ -16450,7 +16726,7 @@ var FileService = class extends BaseService {
16450
16726
  if (!file2) {
16451
16727
  return false;
16452
16728
  }
16453
- if (_optionalChain([options, 'optionalAccess', _368 => _368.isAdmin])) {
16729
+ if (_optionalChain([options, 'optionalAccess', _376 => _376.isAdmin])) {
16454
16730
  return true;
16455
16731
  }
16456
16732
  if (file2.visibility === "public") {
@@ -16460,7 +16736,7 @@ var FileService = class extends BaseService {
16460
16736
  return true;
16461
16737
  }
16462
16738
  if (file2.visibility === "restricted") {
16463
- return _nullishCoalesce(_optionalChain([file2, 'access', _369 => _369.allowedUsers, 'optionalAccess', _370 => _370.includes, 'call', _371 => _371(userId)]), () => ( false));
16739
+ return _nullishCoalesce(_optionalChain([file2, 'access', _377 => _377.allowedUsers, 'optionalAccess', _378 => _378.includes, 'call', _379 => _379(userId)]), () => ( false));
16464
16740
  }
16465
16741
  return false;
16466
16742
  }
@@ -16555,7 +16831,7 @@ function withTimeout(promise, ms, label) {
16555
16831
  var GeocodingService = class {
16556
16832
  constructor(adapter, options) {
16557
16833
  this.adapter = adapter;
16558
- this.timeoutMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _372 => _372.timeoutMs]), () => ( GEOCODING_TIMEOUT_MS));
16834
+ this.timeoutMs = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _380 => _380.timeoutMs]), () => ( GEOCODING_TIMEOUT_MS));
16559
16835
  }
16560
16836
  /**
16561
16837
  * Search for address suggestions as the user types
@@ -16615,9 +16891,9 @@ var GlobalSearchService = class extends BaseService {
16615
16891
  "search",
16616
16892
  { query: query.trim(), ...options },
16617
16893
  () => this.adapter.objectRecords.globalSearch(query.trim(), {
16618
- limit: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _373 => _373.limit]), () => ( 20)),
16619
- offset: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _374 => _374.offset]), () => ( 0)),
16620
- objectNames: _optionalChain([options, 'optionalAccess', _375 => _375.objectNames])
16894
+ limit: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _381 => _381.limit]), () => ( 20)),
16895
+ offset: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _382 => _382.offset]), () => ( 0)),
16896
+ objectNames: _optionalChain([options, 'optionalAccess', _383 => _383.objectNames])
16621
16897
  })
16622
16898
  );
16623
16899
  }
@@ -16638,8 +16914,8 @@ var GlobalSearchService = class extends BaseService {
16638
16914
  "grouped",
16639
16915
  { query: query.trim(), ...options },
16640
16916
  () => this.adapter.objectRecords.globalSearchGrouped(query.trim(), {
16641
- objectNames: _optionalChain([options, 'optionalAccess', _376 => _376.objectNames]),
16642
- limitPerGroup: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _377 => _377.limitPerGroup]), () => ( 5))
16917
+ objectNames: _optionalChain([options, 'optionalAccess', _384 => _384.objectNames]),
16918
+ limitPerGroup: _nullishCoalesce(_optionalChain([options, 'optionalAccess', _385 => _385.limitPerGroup]), () => ( 5))
16643
16919
  })
16644
16920
  );
16645
16921
  }
@@ -16656,7 +16932,7 @@ var PermissionService = class extends BaseService {
16656
16932
  }
16657
16933
  this.permissionsRepo = adapter.permissions;
16658
16934
  this.permissionCache = _nullishCoalesce(adapter.cache, () => ( new NoopCacheAdapter()));
16659
- this.auditService = _optionalChain([options, 'optionalAccess', _378 => _378.auditService]);
16935
+ this.auditService = _optionalChain([options, 'optionalAccess', _386 => _386.auditService]);
16660
16936
  }
16661
16937
  // ============================================================================
16662
16938
  // PERMISSION CHECKS
@@ -16675,11 +16951,11 @@ var PermissionService = class extends BaseService {
16675
16951
  return true;
16676
16952
  }
16677
16953
  const wildcardPerms = permissions.objectPermissions["*"];
16678
- if (_optionalChain([wildcardPerms, 'optionalAccess', _379 => _379.includes, 'call', _380 => _380(action)])) {
16954
+ if (_optionalChain([wildcardPerms, 'optionalAccess', _387 => _387.includes, 'call', _388 => _388(action)])) {
16679
16955
  return true;
16680
16956
  }
16681
16957
  const objectPerms = permissions.objectPermissions[objectName];
16682
- return _nullishCoalesce(_optionalChain([objectPerms, 'optionalAccess', _381 => _381.includes, 'call', _382 => _382(action)]), () => ( false));
16958
+ return _nullishCoalesce(_optionalChain([objectPerms, 'optionalAccess', _389 => _389.includes, 'call', _390 => _390(action)]), () => ( false));
16683
16959
  }
16684
16960
  /**
16685
16961
  * Check if user can access an object, throw ForbiddenError if not.
@@ -16734,12 +17010,12 @@ var PermissionService = class extends BaseService {
16734
17010
  if (permissions.isAdmin) {
16735
17011
  return true;
16736
17012
  }
16737
- const wildcardPerms = _optionalChain([permissions, 'access', _383 => _383.systemPermissions, 'optionalAccess', _384 => _384["*"]]);
16738
- if (_optionalChain([wildcardPerms, 'optionalAccess', _385 => _385.includes, 'call', _386 => _386(action)])) {
17013
+ const wildcardPerms = _optionalChain([permissions, 'access', _391 => _391.systemPermissions, 'optionalAccess', _392 => _392["*"]]);
17014
+ if (_optionalChain([wildcardPerms, 'optionalAccess', _393 => _393.includes, 'call', _394 => _394(action)])) {
16739
17015
  return true;
16740
17016
  }
16741
- const resourcePerms = _optionalChain([permissions, 'access', _387 => _387.systemPermissions, 'optionalAccess', _388 => _388[resource]]);
16742
- return _nullishCoalesce(_optionalChain([resourcePerms, 'optionalAccess', _389 => _389.includes, 'call', _390 => _390(action)]), () => ( false));
17017
+ const resourcePerms = _optionalChain([permissions, 'access', _395 => _395.systemPermissions, 'optionalAccess', _396 => _396[resource]]);
17018
+ return _nullishCoalesce(_optionalChain([resourcePerms, 'optionalAccess', _397 => _397.includes, 'call', _398 => _398(action)]), () => ( false));
16743
17019
  }
16744
17020
  /**
16745
17021
  * Check if user can access a system resource, throw ForbiddenError if not.
@@ -16768,8 +17044,8 @@ var PermissionService = class extends BaseService {
16768
17044
  if (permissions.isAdmin) {
16769
17045
  return { canRead: true, canCreate: true, canUpdate: true, canDelete: true };
16770
17046
  }
16771
- const wildcardPerms = _nullishCoalesce(_optionalChain([permissions, 'access', _391 => _391.systemPermissions, 'optionalAccess', _392 => _392["*"]]), () => ( []));
16772
- const resourcePerms = _nullishCoalesce(_optionalChain([permissions, 'access', _393 => _393.systemPermissions, 'optionalAccess', _394 => _394[resource]]), () => ( []));
17047
+ const wildcardPerms = _nullishCoalesce(_optionalChain([permissions, 'access', _399 => _399.systemPermissions, 'optionalAccess', _400 => _400["*"]]), () => ( []));
17048
+ const resourcePerms = _nullishCoalesce(_optionalChain([permissions, 'access', _401 => _401.systemPermissions, 'optionalAccess', _402 => _402[resource]]), () => ( []));
16773
17049
  const allPerms = /* @__PURE__ */ new Set([...wildcardPerms, ...resourcePerms]);
16774
17050
  return {
16775
17051
  canRead: allPerms.has("read"),
@@ -16912,7 +17188,7 @@ var PermissionService = class extends BaseService {
16912
17188
  action: "role.updated",
16913
17189
  actorId: this.userId,
16914
17190
  roleId,
16915
- roleLabel: _nullishCoalesce(_optionalChain([role, 'optionalAccess', _395 => _395.label]), () => ( roleId)),
17191
+ roleLabel: _nullishCoalesce(_optionalChain([role, 'optionalAccess', _403 => _403.label]), () => ( roleId)),
16916
17192
  metadata: { permissionsUpdated: true, permissionCount: permissions.length }
16917
17193
  });
16918
17194
  }
@@ -16942,7 +17218,7 @@ var PermissionService = class extends BaseService {
16942
17218
  action: "role.assigned",
16943
17219
  actorId: this.userId,
16944
17220
  roleId,
16945
- roleLabel: _nullishCoalesce(_optionalChain([role, 'optionalAccess', _396 => _396.label]), () => ( roleId)),
17221
+ roleLabel: _nullishCoalesce(_optionalChain([role, 'optionalAccess', _404 => _404.label]), () => ( roleId)),
16946
17222
  targetUserId: userProfileId
16947
17223
  });
16948
17224
  }
@@ -16960,7 +17236,7 @@ var PermissionService = class extends BaseService {
16960
17236
  action: "role.revoked",
16961
17237
  actorId: this.userId,
16962
17238
  roleId,
16963
- roleLabel: _nullishCoalesce(_optionalChain([role, 'optionalAccess', _397 => _397.label]), () => ( roleId)),
17239
+ roleLabel: _nullishCoalesce(_optionalChain([role, 'optionalAccess', _405 => _405.label]), () => ( roleId)),
16964
17240
  targetUserId: userProfileId
16965
17241
  });
16966
17242
  }
@@ -17259,14 +17535,6 @@ var ViewService = class extends BaseService {
17259
17535
  type: "activity",
17260
17536
  order: 1
17261
17537
  });
17262
- tabs.push({
17263
- id: "notes",
17264
- name: "notes",
17265
- label: "Notes",
17266
- type: "notes",
17267
- order: 2,
17268
- allowCreate: true
17269
- });
17270
17538
  const hasDocuments = object2.attributes.some((attr) => attr.type === "document");
17271
17539
  if (hasDocuments) {
17272
17540
  tabs.push({
@@ -17443,7 +17711,7 @@ var ViewService = class extends BaseService {
17443
17711
  dbView.objectName,
17444
17712
  dbView.type,
17445
17713
  objectDefinition,
17446
- dbView.type === "detail" ? _nullishCoalesce(_optionalChain([dbView, 'access', _398 => _398.config, 'optionalAccess', _399 => _399.layout]), () => ( "page")) : void 0
17714
+ dbView.type === "detail" ? _nullishCoalesce(_optionalChain([dbView, 'access', _406 => _406.config, 'optionalAccess', _407 => _407.layout]), () => ( "page")) : void 0
17447
17715
  );
17448
17716
  const newConfig = generated.config;
17449
17717
  const updated = await this.adapter.views.update(viewId, { config: newConfig });
@@ -18312,20 +18580,4 @@ var NoopGeocodingAdapter = class {
18312
18580
 
18313
18581
 
18314
18582
 
18315
-
18316
-
18317
-
18318
-
18319
-
18320
-
18321
-
18322
-
18323
-
18324
-
18325
-
18326
-
18327
-
18328
-
18329
-
18330
-
18331
- exports.IDENTITY_PROPERTIES = IDENTITY_PROPERTIES; exports.BEHAVIOR_PROPERTIES = BEHAVIOR_PROPERTIES; exports.PRESENTATION_PROPERTIES = PRESENTATION_PROPERTIES; exports.isIdentityProperty = isIdentityProperty; exports.isBehaviorProperty = isBehaviorProperty; exports.isPresentationProperty = isPresentationProperty; exports.RELATION_TARGET_ANY = RELATION_TARGET_ANY; exports.isUniversalRelation = isUniversalRelation; exports.RecordReferencedError = RecordReferencedError; exports.AttributeInUseError = AttributeInUseError; exports.ObjectReferencedError = ObjectReferencedError; exports.getErrorMessage = getErrorMessage; exports.NoopGeocodingAdapter = NoopGeocodingAdapter; exports.SYSTEM_FIELD_NAMES = SYSTEM_FIELD_NAMES; exports.RESERVED_ATTRIBUTE_NAMES = RESERVED_ATTRIBUTE_NAMES; exports.PolicyViolationError = PolicyViolationError; exports.FORBIDDEN_PROPERTY_TYPES = FORBIDDEN_PROPERTY_TYPES; exports.SYSTEM_ATTRIBUTES = SYSTEM_ATTRIBUTES; exports.getSystemAttributeList = getSystemAttributeList; exports.isSystemAttribute = isSystemAttribute; exports.isSystemAttributeObject = isSystemAttributeObject; exports.isSimpleFormNode = isSimpleFormNode; exports.isAdvancedFormNode = isAdvancedFormNode; exports.isStartNode = isStartNode; exports.isFormNode = isFormNode; exports.isConditionNode = isConditionNode; exports.isDocumentNode = isDocumentNode; exports.isEndNode = isEndNode; exports.getNodeOutputs = getNodeOutputs; exports.isConditionRule = isConditionRule; exports.isConditionGroup = isConditionGroup; exports.eq = eq; exports.neq = neq; exports.and = and; exports.or = or; exports.inValues = inValues; exports.isWorkflowDefinition = isWorkflowDefinition; exports.isWorkflowPublished = isWorkflowPublished; exports.isSystemWorkflow = isSystemWorkflow; exports.isInstanceTerminal = isInstanceTerminal; exports.isInstanceWaiting = isInstanceWaiting; exports.canResumeInstance = canResumeInstance; exports.createStartTransition = createStartTransition; exports.isInvitationValid = isInvitationValid; exports.isInvitationAccepted = isInvitationAccepted; exports.isInvitationExpired = isInvitationExpired; exports.isGrantValid = isGrantValid; exports.isGrantRevoked = isGrantRevoked; exports.isTokenRevoked = isTokenRevoked; exports.isGrantExpired = isGrantExpired; exports.canAccessNode = canAccessNode; exports.createEmptyContext = createEmptyContext; exports.getContextValue = getContextValue; exports.setContextValue = setContextValue; exports.DEFAULT_THEME = DEFAULT_THEME; exports.mergeWithDefaults = mergeWithDefaults; exports.generateCssVariables = generateCssVariables; exports.isInstanceEvent = isInstanceEvent; exports.isNodeEvent = isNodeEvent; exports.isInvitationOrGrantEvent = isInvitationOrGrantEvent; exports.ConditionOperatorSchema = ConditionOperatorSchema; exports.ConditionRuleSchema = ConditionRuleSchema; exports.ConditionGroupSchema = ConditionGroupSchema; exports.StartNodeSchema = StartNodeSchema; exports.FormFieldRefSchema = FormFieldRefSchema; exports.FlowRowFieldSchema = FlowRowFieldSchema; exports.FlowRowSchema = FlowRowSchema; exports.FormNodeSchema = FormNodeSchema; exports.ConditionNodeSchema = ConditionNodeSchema; exports.DocumentNodeSchema = DocumentNodeSchema; exports.EndNodeSchema = EndNodeSchema; exports.WorkflowNodeSchema = WorkflowNodeSchema; exports.SlotModeSchema = SlotModeSchema; exports.WorkflowSlotSchema = WorkflowSlotSchema; exports.AuthMethodSchema = AuthMethodSchema; exports.ShareStatusSchema = ShareStatusSchema; exports.WorkflowShareSchema = WorkflowShareSchema; exports.CreateShareInputSchema = CreateShareInputSchema; exports.NodePositionSchema = NodePositionSchema; exports.ViewportSchema = ViewportSchema; exports.WorkflowLayoutSchema = WorkflowLayoutSchema; exports.ThemeColorsSchema = ThemeColorsSchema; exports.ThemeLogoSchema = ThemeLogoSchema; exports.WorkflowThemeSchema = WorkflowThemeSchema; exports.WorkflowConfigSchema = WorkflowConfigSchema; exports.WorkflowStatusSchema = WorkflowStatusSchema; exports.WorkflowDefinitionSchema = WorkflowDefinitionSchema; exports.isEmpty = isEmpty; exports.isNotEmpty = isNotEmpty; exports.toUndefinedIfEmpty = toUndefinedIfEmpty; exports.hasProperties = hasProperties; exports.EMPTY_VALUE_PLACEHOLDER = EMPTY_VALUE_PLACEHOLDER; exports.formatAttributeValue = formatAttributeValue; exports.SchemaErrorCode = SchemaErrorCode; exports.SchemaError = SchemaError; exports.NotFoundError = NotFoundError; exports.ObjectNotFoundError = ObjectNotFoundError; exports.AttributeNotFoundError = AttributeNotFoundError; exports.RecordNotFoundError = RecordNotFoundError; exports.UserProfileNotFoundError = UserProfileNotFoundError; exports.FileNotFoundError = FileNotFoundError; exports.ValidationError = ValidationError; exports.ProtectedResourceError = ProtectedResourceError; exports.SyncError = SyncError; exports.NotSystemObjectError = NotSystemObjectError; exports.DuplicateError = DuplicateError; exports.isSchemaError = isSchemaError; exports.isNotFoundError = isNotFoundError; exports.isValidationError = isValidationError; exports.isProtectedResourceError = isProtectedResourceError; exports.ForbiddenError = ForbiddenError; exports.ProtectedRoleError = ProtectedRoleError; exports.RoleNotFoundError = RoleNotFoundError; exports.isForbiddenError = isForbiddenError; exports.ConcurrentModificationError = ConcurrentModificationError; exports.PropertySchemaBuilder = PropertySchemaBuilder; exports.PropertyTypeBuilder = PropertyTypeBuilder; exports.BasePropertyBuilder = BasePropertyBuilder; exports.TextPropertyBuilder = TextPropertyBuilder; exports.TextareaPropertyBuilder = TextareaPropertyBuilder; exports.NumberPropertyBuilder = NumberPropertyBuilder; exports.CheckboxPropertyBuilder = CheckboxPropertyBuilder; exports.DatePropertyBuilder = DatePropertyBuilder; exports.PhonePropertyBuilder = PhonePropertyBuilder; exports.CurrencyPropertyBuilder = CurrencyPropertyBuilder; exports.StatusPropertyBuilder = StatusPropertyBuilder; exports.SelectPropertyBuilder = SelectPropertyBuilder; exports.MultiselectPropertyBuilder = MultiselectPropertyBuilder; exports.RatingPropertyBuilder = RatingPropertyBuilder; exports.LocationPropertyBuilder = LocationPropertyBuilder; exports.validatePropertyType = validatePropertyType; exports.text = text; exports.textarea = textarea; exports.richtext = richtext; exports.number = number; exports.checkbox = checkbox; exports.date = date; exports.phone = phone; exports.currency = currency; exports.status = status; exports.select = select; exports.multiselect = multiselect; exports.location = location; exports.file = file; exports.user = user; exports.relation = relation; exports.rating = rating; exports.formula = formula; exports.rollup = rollup; exports.document = document; exports.ObjectBuilder = ObjectBuilder; exports.object = object; exports.GroupBuilder = GroupBuilder; exports.DirectTableTabConfig = DirectTableTabConfig; exports.InverseTableTabConfig = InverseTableTabConfig; exports.CustomTabConfig = CustomTabConfig; exports.NotesTabConfig = NotesTabConfig; exports.ActivityTabConfig = ActivityTabConfig; exports.FlowsTabConfig = FlowsTabConfig; exports.DocumentsTabConfig = DocumentsTabConfig; exports.TabBuilder = TabBuilder; exports.DetailViewBuilder = DetailViewBuilder; exports.ViewBuilder = ViewBuilder; exports.detailView = detailView; exports.view = view; exports.ListViewBuilder = ListViewBuilder; exports.ListViewTabConfigBuilder = ListViewTabConfigBuilder; exports.listView = listView; exports.group = group; exports.WorkflowFormRowBuilder = WorkflowFormRowBuilder; exports.WorkflowFormBuilder = WorkflowFormBuilder; exports.WorkflowSimpleFormBuilder = WorkflowSimpleFormBuilder; exports.WorkflowConditionBuilder = WorkflowConditionBuilder; exports.WorkflowEndBuilder = WorkflowEndBuilder; exports.WorkflowStartBuilder = WorkflowStartBuilder; exports.WorkflowBuilder = WorkflowBuilder; exports.workflow = workflow; exports.registry = registry; exports.SYSTEM_TEMPLATE_IDS = SYSTEM_TEMPLATE_IDS; exports.FRENCH_ID_CARD = FRENCH_ID_CARD; exports.PASSPORT = PASSPORT; exports.DRIVING_LICENSE = DRIVING_LICENSE; exports.PROOF_OF_ADDRESS = PROOF_OF_ADDRESS; exports.SIGNABLE_CONTRACT = SIGNABLE_CONTRACT; exports.GENERIC_DOCUMENT = GENERIC_DOCUMENT; exports.SYSTEM_TEMPLATES = SYSTEM_TEMPLATES; exports.getSystemTemplate = getSystemTemplate; exports.isSystemTemplate = isSystemTemplate; exports.WorkflowJwtService = WorkflowJwtService; exports.hashOptions = hashOptions; exports.cacheKeys = cacheKeys; exports.cacheTtl = cacheTtl; exports.defaultTtl = defaultTtl; exports.NoopCacheAdapter = NoopCacheAdapter; exports.formatRecord = formatRecord; exports.formatRecords = formatRecords; exports.createDefaultState = createDefaultState; exports.SHORTCUT_TO_FILTER_OPERATOR = SHORTCUT_TO_FILTER_OPERATOR; exports.QueryNoResultError = QueryNoResultError; exports.QueryMultipleResultsError = QueryMultipleResultsError; exports.TenantContextError = TenantContextError; exports.FeatureFlagsContextError = FeatureFlagsContextError; exports.isFeatureEnabled = isFeatureEnabled; exports.getFeatureValue = getFeatureValue; exports.getFeatureFlags = getFeatureFlags; exports.tryGetFeatureValue = tryGetFeatureValue; exports.hasFeatureFlagsContext = hasFeatureFlagsContext; exports.runWithFeatureFlags = runWithFeatureFlags; exports.withFeatureFlags = withFeatureFlags; exports.getSchemaFromContext = getSchemaFromContext; exports.getSchemaByNameFromContext = getSchemaByNameFromContext; exports.hasSchemaContext = hasSchemaContext; exports.getSchemaContext = getSchemaContext; exports.addSchemaToContext = addSchemaToContext; exports.runWithSchemaContext = runWithSchemaContext; exports.runWithMergedSchemaContext = runWithMergedSchemaContext; exports.getContext = getContext2; exports.getTenantId = getTenantId; exports.getUserId = getUserId; exports.hasContext = hasContext; exports.runWithContext = runWithContext; exports.withTenantContext = withTenantContext; exports.QueryBuilder = QueryBuilder; exports.createQueryBuilder = createQueryBuilder; exports.evaluateCondition = evaluateCondition; exports.evaluate = evaluate; exports.evaluateWithTrace = evaluateWithTrace; exports.ExecutorRegistry = ExecutorRegistry; exports.success = success; exports.wait = wait; exports.complete = complete; exports.error = error; exports.ConditionExecutor = ConditionExecutor; exports.DocumentExecutor = DocumentExecutor; exports.EndExecutor = EndExecutor; exports.FormExecutor = FormExecutor; exports.StartExecutor = StartExecutor; exports.createDefaultExecutorRegistry = createDefaultExecutorRegistry; exports.getDefaultExecutorRegistry = getDefaultExecutorRegistry; exports.evaluateFormula = evaluateFormula; exports.evaluateFormulaWithResult = evaluateFormulaWithResult; exports.formatFormulaResult = formatFormulaResult; exports.evaluateFormulaAttribute = evaluateFormulaAttribute; exports.validateFormulaExpression = validateFormulaExpression; exports.extractFormulaVariables = extractFormulaVariables; exports.extractRelationReferences = extractRelationReferences; exports.extractRelationNames = extractRelationNames; exports.hasRelationReferences = hasRelationReferences; exports.flattenRelationsForEval = flattenRelationsForEval; exports.evaluateFormulaWithRelations = evaluateFormulaWithRelations; exports.evaluateFormulaAttributeWithRelations = evaluateFormulaAttributeWithRelations; exports.InvalidPathError = InvalidPathError; exports.MaxDepthExceededError = MaxDepthExceededError; exports.parsePath = parsePath; exports.validatePath = validatePath; exports.pathHasManyCardinality = pathHasManyCardinality; exports.getPathDepth = getPathDepth; exports.getTargetAttributeName = getTargetAttributeName; exports.getRelationPath = getRelationPath; exports.traversePath = traversePath; exports.resolveSingleValue = resolveSingleValue; exports.resolveMultiplePaths = resolveMultiplePaths; exports.NoopHookRegistry = NoopHookRegistry; exports.DEFAULT_LABEL_FALLBACK = DEFAULT_LABEL_FALLBACK; exports.renderLabelExpression = renderLabelExpression; exports.isLabelExpression = isLabelExpression; exports.extractAttributeNames = extractAttributeNames; exports.enrichValuesForDisplay = enrichValuesForDisplay; exports.enrichValuesWithSelectLabels = enrichValuesWithSelectLabels; exports.extractRelationIds = extractRelationIds; exports.computeLabelWithRelations = computeLabelWithRelations; exports.createMockAdapter = createMockAdapter; exports.PolicyRegistry = PolicyRegistry; exports.defaultPolicyRegistry = defaultPolicyRegistry; exports.notesPolicy = notesPolicy; exports.BaseService = BaseService; exports.BaseRepository = BaseRepository; exports.SchemaContextAwareRepository = SchemaContextAwareRepository; exports.buildAuditChanges = buildAuditChanges; exports.ObjectSchemaService = ObjectSchemaService; exports.AuditService = AuditService; exports.UserService = UserService; exports.applyDefaultValues = applyDefaultValues; exports.checkPermission = checkPermission; exports.getPolicy = getPolicy; exports.buildPolicyContext = buildPolicyContext; exports.checkRecordAccess = checkRecordAccess; exports.checkRecordModifyOrThrow = checkRecordModifyOrThrow; exports.checkRecordDeleteOrThrow = checkRecordDeleteOrThrow; exports.checkSharedObjectWriteAccess = checkSharedObjectWriteAccess; exports.computeLabel = computeLabel; exports.enrichWithFormulas = enrichWithFormulas; exports.enrichRecordsWithFormulas = enrichRecordsWithFormulas; exports.createContextForCreate = createContextForCreate; exports.createContextForUpdate = createContextForUpdate; exports.createContextForDelete = createContextForDelete; exports.createContextForRestore = createContextForRestore; exports.recalculateParentRollups = recalculateParentRollups; exports.RecordQueryService = RecordQueryService; exports.RecordResolverService = RecordResolverService; exports.RelationPropertiesService = RelationPropertiesService; exports.RelationService = RelationService; exports.RollupService = RollupService; exports.RecordService = RecordService; exports.FormulaResolverService = FormulaResolverService; exports.RollupScheduler = RollupScheduler; exports.DocumentRenderError = DocumentRenderError; exports.StorageDownloadNotSupportedError = StorageDownloadNotSupportedError; exports.DocumentRendererService = DocumentRendererService; exports.DocumentProcessingHook = DocumentProcessingHook; exports.GrantNotFoundError = GrantNotFoundError; exports.GrantExpiredError = GrantExpiredError; exports.GrantRevokedError = GrantRevokedError; exports.TokenRevokedError = TokenRevokedError; exports.WorkflowAccessGrantService = WorkflowAccessGrantService; exports.WorkflowInstanceService = WorkflowInstanceService; exports.InvitationNotFoundError = InvitationNotFoundError; exports.InvitationExpiredError = InvitationExpiredError; exports.InvitationAlreadyAcceptedError = InvitationAlreadyAcceptedError; exports.InvitationRevokedError = InvitationRevokedError; exports.WorkflowInvitationService = WorkflowInvitationService; exports.WorkflowRelationService = WorkflowRelationService; exports.WorkflowService = WorkflowService; exports.UserProfileService = UserProfileService; exports.DocumentGenerationTemplateNotFoundError = DocumentGenerationTemplateNotFoundError; exports.DocumentGenerationNotConfiguredError = DocumentGenerationNotConfiguredError; exports.DocumentGenerationService = DocumentGenerationService; exports.DocumentTemplateService = DocumentTemplateService; exports.DocumentService = DocumentService; exports.DocumentProcessingService = DocumentProcessingService; exports.FileService = FileService; exports.GeocodingService = GeocodingService; exports.GlobalSearchService = GlobalSearchService; exports.PermissionService = PermissionService; exports.ViewService = ViewService; exports.seedRegistryViews = seedRegistryViews; exports.syncNativeViews = syncNativeViews; exports.verifyRegistryViewsSeeded = verifyRegistryViewsSeeded; exports.verifyNativeViewsSync = verifyNativeViewsSync; exports.getViewSeedPreview = getViewSeedPreview; exports.getViewSyncPreview = getViewSyncPreview; exports.syncNativeObjects = syncNativeObjects; exports.verifyNativeObjectsSync = verifyNativeObjectsSync; exports.getSyncPreview = getSyncPreview; exports.syncAll = syncAll;
18583
+ exports.IDENTITY_PROPERTIES = IDENTITY_PROPERTIES; exports.BEHAVIOR_PROPERTIES = BEHAVIOR_PROPERTIES; exports.PRESENTATION_PROPERTIES = PRESENTATION_PROPERTIES; exports.isIdentityProperty = isIdentityProperty; exports.isBehaviorProperty = isBehaviorProperty; exports.isPresentationProperty = isPresentationProperty; exports.RELATION_TARGET_ANY = RELATION_TARGET_ANY; exports.isUniversalRelation = isUniversalRelation; exports.isBilateralRelation = isBilateralRelation; exports.inferInverseCardinality = inferInverseCardinality; exports.RecordReferencedError = RecordReferencedError; exports.AttributeInUseError = AttributeInUseError; exports.ObjectReferencedError = ObjectReferencedError; exports.getErrorMessage = getErrorMessage; exports.NoopGeocodingAdapter = NoopGeocodingAdapter; exports.SYSTEM_FIELD_NAMES = SYSTEM_FIELD_NAMES; exports.RESERVED_ATTRIBUTE_NAMES = RESERVED_ATTRIBUTE_NAMES; exports.PolicyViolationError = PolicyViolationError; exports.FORBIDDEN_PROPERTY_TYPES = FORBIDDEN_PROPERTY_TYPES; exports.SYSTEM_ATTRIBUTES = SYSTEM_ATTRIBUTES; exports.getSystemAttributeList = getSystemAttributeList; exports.isSystemAttribute = isSystemAttribute; exports.isSystemAttributeObject = isSystemAttributeObject; exports.isSimpleFormNode = isSimpleFormNode; exports.isAdvancedFormNode = isAdvancedFormNode; exports.isStartNode = isStartNode; exports.isFormNode = isFormNode; exports.isConditionNode = isConditionNode; exports.isDocumentNode = isDocumentNode; exports.isEndNode = isEndNode; exports.getNodeOutputs = getNodeOutputs; exports.isConditionRule = isConditionRule; exports.isConditionGroup = isConditionGroup; exports.eq = eq; exports.neq = neq; exports.and = and; exports.or = or; exports.inValues = inValues; exports.isWorkflowDefinition = isWorkflowDefinition; exports.isWorkflowPublished = isWorkflowPublished; exports.isSystemWorkflow = isSystemWorkflow; exports.isInstanceTerminal = isInstanceTerminal; exports.isInstanceWaiting = isInstanceWaiting; exports.canResumeInstance = canResumeInstance; exports.createStartTransition = createStartTransition; exports.isInvitationValid = isInvitationValid; exports.isInvitationAccepted = isInvitationAccepted; exports.isInvitationExpired = isInvitationExpired; exports.isGrantValid = isGrantValid; exports.isGrantRevoked = isGrantRevoked; exports.isTokenRevoked = isTokenRevoked; exports.isGrantExpired = isGrantExpired; exports.canAccessNode = canAccessNode; exports.createEmptyContext = createEmptyContext; exports.getContextValue = getContextValue; exports.setContextValue = setContextValue; exports.DEFAULT_THEME = DEFAULT_THEME; exports.mergeWithDefaults = mergeWithDefaults; exports.generateCssVariables = generateCssVariables; exports.isInstanceEvent = isInstanceEvent; exports.isNodeEvent = isNodeEvent; exports.isInvitationOrGrantEvent = isInvitationOrGrantEvent; exports.ConditionOperatorSchema = ConditionOperatorSchema; exports.ConditionRuleSchema = ConditionRuleSchema; exports.ConditionGroupSchema = ConditionGroupSchema; exports.StartNodeSchema = StartNodeSchema; exports.FormFieldRefSchema = FormFieldRefSchema; exports.FlowRowFieldSchema = FlowRowFieldSchema; exports.FlowRowSchema = FlowRowSchema; exports.FormNodeSchema = FormNodeSchema; exports.ConditionNodeSchema = ConditionNodeSchema; exports.DocumentNodeSchema = DocumentNodeSchema; exports.EndNodeSchema = EndNodeSchema; exports.WorkflowNodeSchema = WorkflowNodeSchema; exports.SlotModeSchema = SlotModeSchema; exports.WorkflowSlotSchema = WorkflowSlotSchema; exports.AuthMethodSchema = AuthMethodSchema; exports.ShareStatusSchema = ShareStatusSchema; exports.WorkflowShareSchema = WorkflowShareSchema; exports.CreateShareInputSchema = CreateShareInputSchema; exports.NodePositionSchema = NodePositionSchema; exports.ViewportSchema = ViewportSchema; exports.WorkflowLayoutSchema = WorkflowLayoutSchema; exports.ThemeColorsSchema = ThemeColorsSchema; exports.ThemeLogoSchema = ThemeLogoSchema; exports.WorkflowThemeSchema = WorkflowThemeSchema; exports.WorkflowConfigSchema = WorkflowConfigSchema; exports.WorkflowStatusSchema = WorkflowStatusSchema; exports.WorkflowDefinitionSchema = WorkflowDefinitionSchema; exports.isEmpty = isEmpty; exports.isNotEmpty = isNotEmpty; exports.toUndefinedIfEmpty = toUndefinedIfEmpty; exports.hasProperties = hasProperties; exports.EMPTY_VALUE_PLACEHOLDER = EMPTY_VALUE_PLACEHOLDER; exports.formatAttributeValue = formatAttributeValue; exports.DEFAULT_LABEL_FALLBACK = DEFAULT_LABEL_FALLBACK; exports.renderLabelExpression = renderLabelExpression; exports.isLabelExpression = isLabelExpression; exports.extractAttributeNames = extractAttributeNames; exports.enrichValuesForDisplay = enrichValuesForDisplay; exports.enrichValuesWithSelectLabels = enrichValuesWithSelectLabels; exports.extractRelationIds = extractRelationIds; exports.computeLabelWithRelations = computeLabelWithRelations; exports.SchemaErrorCode = SchemaErrorCode; exports.SchemaError = SchemaError; exports.NotFoundError = NotFoundError; exports.ObjectNotFoundError = ObjectNotFoundError; exports.AttributeNotFoundError = AttributeNotFoundError; exports.RecordNotFoundError = RecordNotFoundError; exports.UserProfileNotFoundError = UserProfileNotFoundError; exports.FileNotFoundError = FileNotFoundError; exports.ValidationError = ValidationError; exports.ProtectedResourceError = ProtectedResourceError; exports.SyncError = SyncError; exports.NotSystemObjectError = NotSystemObjectError; exports.DuplicateError = DuplicateError; exports.isSchemaError = isSchemaError; exports.isNotFoundError = isNotFoundError; exports.isValidationError = isValidationError; exports.isProtectedResourceError = isProtectedResourceError; exports.ForbiddenError = ForbiddenError; exports.ProtectedRoleError = ProtectedRoleError; exports.RoleNotFoundError = RoleNotFoundError; exports.isForbiddenError = isForbiddenError; exports.ConcurrentModificationError = ConcurrentModificationError; exports.text = text; exports.textarea = textarea; exports.richtext = richtext; exports.number = number; exports.checkbox = checkbox; exports.date = date; exports.phone = phone; exports.currency = currency; exports.status = status; exports.select = select; exports.multiselect = multiselect; exports.location = location; exports.file = file; exports.user = user; exports.relation = relation; exports.rating = rating; exports.formula = formula; exports.rollup = rollup; exports.document = document; exports.ObjectBuilder = ObjectBuilder; exports.object = object; exports.GroupBuilder = GroupBuilder; exports.TableTabConfig = TableTabConfig; exports.CustomTabConfig = CustomTabConfig; exports.RichtextTabConfig = RichtextTabConfig; exports.ActivityTabConfig = ActivityTabConfig; exports.FlowsTabConfig = FlowsTabConfig; exports.DocumentsTabConfig = DocumentsTabConfig; exports.TabBuilder = TabBuilder; exports.DetailViewBuilder = DetailViewBuilder; exports.ViewBuilder = ViewBuilder; exports.detailView = detailView; exports.view = view; exports.ListViewBuilder = ListViewBuilder; exports.ListViewTabConfigBuilder = ListViewTabConfigBuilder; exports.listView = listView; exports.group = group; exports.WorkflowFormRowBuilder = WorkflowFormRowBuilder; exports.WorkflowFormBuilder = WorkflowFormBuilder; exports.WorkflowSimpleFormBuilder = WorkflowSimpleFormBuilder; exports.WorkflowConditionBuilder = WorkflowConditionBuilder; exports.WorkflowEndBuilder = WorkflowEndBuilder; exports.WorkflowStartBuilder = WorkflowStartBuilder; exports.WorkflowBuilder = WorkflowBuilder; exports.workflow = workflow; exports.registry = registry; exports.SYSTEM_TEMPLATE_IDS = SYSTEM_TEMPLATE_IDS; exports.FRENCH_ID_CARD = FRENCH_ID_CARD; exports.PASSPORT = PASSPORT; exports.DRIVING_LICENSE = DRIVING_LICENSE; exports.PROOF_OF_ADDRESS = PROOF_OF_ADDRESS; exports.SIGNABLE_CONTRACT = SIGNABLE_CONTRACT; exports.GENERIC_DOCUMENT = GENERIC_DOCUMENT; exports.SYSTEM_TEMPLATES = SYSTEM_TEMPLATES; exports.getSystemTemplate = getSystemTemplate; exports.isSystemTemplate = isSystemTemplate; exports.WorkflowJwtService = WorkflowJwtService; exports.hashOptions = hashOptions; exports.cacheKeys = cacheKeys; exports.cacheTtl = cacheTtl; exports.defaultTtl = defaultTtl; exports.NoopCacheAdapter = NoopCacheAdapter; exports.formatRecord = formatRecord; exports.formatRecords = formatRecords; exports.createDefaultState = createDefaultState; exports.SHORTCUT_TO_FILTER_OPERATOR = SHORTCUT_TO_FILTER_OPERATOR; exports.QueryNoResultError = QueryNoResultError; exports.QueryMultipleResultsError = QueryMultipleResultsError; exports.TenantContextError = TenantContextError; exports.FeatureFlagsContextError = FeatureFlagsContextError; exports.isFeatureEnabled = isFeatureEnabled; exports.getFeatureValue = getFeatureValue; exports.getFeatureFlags = getFeatureFlags; exports.tryGetFeatureValue = tryGetFeatureValue; exports.hasFeatureFlagsContext = hasFeatureFlagsContext; exports.runWithFeatureFlags = runWithFeatureFlags; exports.withFeatureFlags = withFeatureFlags; exports.getSchemaFromContext = getSchemaFromContext; exports.getSchemaByNameFromContext = getSchemaByNameFromContext; exports.hasSchemaContext = hasSchemaContext; exports.getSchemaContext = getSchemaContext; exports.addSchemaToContext = addSchemaToContext; exports.runWithSchemaContext = runWithSchemaContext; exports.runWithMergedSchemaContext = runWithMergedSchemaContext; exports.getContext = getContext2; exports.getTenantId = getTenantId; exports.getUserId = getUserId; exports.hasContext = hasContext; exports.runWithContext = runWithContext; exports.withTenantContext = withTenantContext; exports.QueryBuilder = QueryBuilder; exports.createQueryBuilder = createQueryBuilder; exports.evaluateCondition = evaluateCondition; exports.evaluate = evaluate; exports.evaluateWithTrace = evaluateWithTrace; exports.ExecutorRegistry = ExecutorRegistry; exports.success = success; exports.wait = wait; exports.complete = complete; exports.error = error; exports.ConditionExecutor = ConditionExecutor; exports.DocumentExecutor = DocumentExecutor; exports.EndExecutor = EndExecutor; exports.FormExecutor = FormExecutor; exports.StartExecutor = StartExecutor; exports.createDefaultExecutorRegistry = createDefaultExecutorRegistry; exports.getDefaultExecutorRegistry = getDefaultExecutorRegistry; exports.evaluateFormula = evaluateFormula; exports.evaluateFormulaWithResult = evaluateFormulaWithResult; exports.formatFormulaResult = formatFormulaResult; exports.evaluateFormulaAttribute = evaluateFormulaAttribute; exports.validateFormulaExpression = validateFormulaExpression; exports.extractFormulaVariables = extractFormulaVariables; exports.extractRelationReferences = extractRelationReferences; exports.extractRelationNames = extractRelationNames; exports.hasRelationReferences = hasRelationReferences; exports.flattenRelationsForEval = flattenRelationsForEval; exports.evaluateFormulaWithRelations = evaluateFormulaWithRelations; exports.evaluateFormulaAttributeWithRelations = evaluateFormulaAttributeWithRelations; exports.InvalidPathError = InvalidPathError; exports.MaxDepthExceededError = MaxDepthExceededError; exports.parsePath = parsePath; exports.validatePath = validatePath; exports.pathHasManyCardinality = pathHasManyCardinality; exports.getPathDepth = getPathDepth; exports.getTargetAttributeName = getTargetAttributeName; exports.getRelationPath = getRelationPath; exports.traversePath = traversePath; exports.resolveSingleValue = resolveSingleValue; exports.resolveMultiplePaths = resolveMultiplePaths; exports.NoopHookRegistry = NoopHookRegistry; exports.createMockAdapter = createMockAdapter; exports.PolicyRegistry = PolicyRegistry; exports.defaultPolicyRegistry = defaultPolicyRegistry; exports.BaseService = BaseService; exports.BaseRepository = BaseRepository; exports.SchemaContextAwareRepository = SchemaContextAwareRepository; exports.buildAuditChanges = buildAuditChanges; exports.ObjectSchemaService = ObjectSchemaService; exports.AuditService = AuditService; exports.UserService = UserService; exports.applyDefaultValues = applyDefaultValues; exports.checkPermission = checkPermission; exports.getPolicy = getPolicy; exports.buildPolicyContext = buildPolicyContext; exports.checkRecordAccess = checkRecordAccess; exports.checkRecordModifyOrThrow = checkRecordModifyOrThrow; exports.checkRecordDeleteOrThrow = checkRecordDeleteOrThrow; exports.checkSharedObjectWriteAccess = checkSharedObjectWriteAccess; exports.computeLabel = computeLabel; exports.enrichWithFormulas = enrichWithFormulas; exports.enrichRecordsWithFormulas = enrichRecordsWithFormulas; exports.createContextForCreate = createContextForCreate; exports.createContextForUpdate = createContextForUpdate; exports.createContextForDelete = createContextForDelete; exports.createContextForRestore = createContextForRestore; exports.recalculateParentRollups = recalculateParentRollups; exports.RelationPropertiesService = RelationPropertiesService; exports.RecordQueryService = RecordQueryService; exports.RecordResolverService = RecordResolverService; exports.RelationService = RelationService; exports.RollupService = RollupService; exports.RecordService = RecordService; exports.FormulaResolverService = FormulaResolverService; exports.RollupScheduler = RollupScheduler; exports.DocumentRenderError = DocumentRenderError; exports.StorageDownloadNotSupportedError = StorageDownloadNotSupportedError; exports.DocumentRendererService = DocumentRendererService; exports.DocumentProcessingHook = DocumentProcessingHook; exports.GrantNotFoundError = GrantNotFoundError; exports.GrantExpiredError = GrantExpiredError; exports.GrantRevokedError = GrantRevokedError; exports.TokenRevokedError = TokenRevokedError; exports.WorkflowAccessGrantService = WorkflowAccessGrantService; exports.WorkflowInstanceService = WorkflowInstanceService; exports.InvitationNotFoundError = InvitationNotFoundError; exports.InvitationExpiredError = InvitationExpiredError; exports.InvitationAlreadyAcceptedError = InvitationAlreadyAcceptedError; exports.InvitationRevokedError = InvitationRevokedError; exports.WorkflowInvitationService = WorkflowInvitationService; exports.WorkflowRelationService = WorkflowRelationService; exports.WorkflowService = WorkflowService; exports.UserProfileService = UserProfileService; exports.DocumentGenerationTemplateNotFoundError = DocumentGenerationTemplateNotFoundError; exports.DocumentGenerationNotConfiguredError = DocumentGenerationNotConfiguredError; exports.DocumentGenerationService = DocumentGenerationService; exports.DocumentTemplateService = DocumentTemplateService; exports.DocumentService = DocumentService; exports.DocumentProcessingService = DocumentProcessingService; exports.FileService = FileService; exports.GeocodingService = GeocodingService; exports.GlobalSearchService = GlobalSearchService; exports.PermissionService = PermissionService; exports.ViewService = ViewService; exports.seedRegistryViews = seedRegistryViews; exports.syncNativeViews = syncNativeViews; exports.verifyRegistryViewsSeeded = verifyRegistryViewsSeeded; exports.verifyNativeViewsSync = verifyNativeViewsSync; exports.getViewSeedPreview = getViewSeedPreview; exports.getViewSyncPreview = getViewSyncPreview; exports.syncNativeObjects = syncNativeObjects; exports.verifyNativeObjectsSync = verifyNativeObjectsSync; exports.getSyncPreview = getSyncPreview; exports.syncAll = syncAll;