linkgress-orm 0.4.66 → 0.4.68

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.
@@ -16,6 +16,7 @@ const collection_strategy_factory_1 = require("./collection-strategy.factory");
16
16
  const union_builder_1 = require("./union-builder");
17
17
  const future_query_1 = require("./future-query");
18
18
  const mock_row_cache_1 = require("./mock-row-cache");
19
+ const navigation_path_cache_1 = require("./navigation-path-cache");
19
20
  const join_utils_1 = require("./join-utils");
20
21
  /**
21
22
  * Field type categories for optimized result transformation
@@ -79,6 +80,39 @@ const MOCK_ROW_NAV_CACHE = Symbol('linkgressMockNavCache');
79
80
  const navigationPathSignature = (path) => path
80
81
  .map(step => `${step.alias}:${step.targetTable}:${(step.foreignKeys ?? []).join('+')}:${(step.matches ?? []).join('+')}:${step.isMandatory ? 1 : 0}:${step.sourceAlias ?? ''}`)
81
82
  .join('>');
83
+ /**
84
+ * Whether `value` is a reference mock row minted by `ReferenceQueryBuilder.createMockTargetRow`.
85
+ * Those rows INHERIT their column/relation getters from a shared prototype (see MockRowCache),
86
+ * so `Object.getPrototypeOf(row) !== Object.prototype` and own-property APIs see no columns —
87
+ * the row's own state slots are the reliable marker.
88
+ */
89
+ const isReferenceMockRow = (value) => value != null && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, MOCK_ROW_FIELD_REFS);
90
+ /** `Object.getOwnPropertyDescriptor` that walks the prototype chain (stops before Object.prototype). */
91
+ const findPropertyDescriptor = (value, key) => {
92
+ let current = value;
93
+ while (current != null && current !== Object.prototype) {
94
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
95
+ if (descriptor) {
96
+ return descriptor;
97
+ }
98
+ current = Object.getPrototypeOf(current);
99
+ }
100
+ return undefined;
101
+ };
102
+ /**
103
+ * First enumerable key (own or inherited) backed by a getter — the "is this a mock row" probe
104
+ * shared by the selection resolvers. Root mocks define their getters as own properties,
105
+ * reference mocks inherit them; column getters are enumerable on both, so the first
106
+ * enumerable getter is always a column and `row[key]` yields its FieldRef.
107
+ */
108
+ const findFirstGetterKey = (value) => {
109
+ for (const key in value) {
110
+ if (findPropertyDescriptor(value, key)?.get != null) {
111
+ return key;
112
+ }
113
+ }
114
+ return undefined;
115
+ };
82
116
  /**
83
117
  * Performance utility: Get target schema for a relation, using cached version if available
84
118
  */
@@ -1702,7 +1736,10 @@ class SelectQueryBuilder {
1702
1736
  // tryBuildFlatNestedSelect flattens. Class instances (collections, SqlFragment,
1703
1737
  // Subquery) and collection-result markers must not be walked.
1704
1738
  const proto = Object.getPrototypeOf(value);
1705
- if ((proto === Object.prototype || proto === null) && !('__collectionResult' in value)) {
1739
+ // Reference mock rows inherit their getters from a shared prototype (see
1740
+ // createMockTargetRow) yet must keep being walked exactly like the plain-object mocks
1741
+ // they replaced.
1742
+ if ((proto === Object.prototype || proto === null || isReferenceMockRow(value)) && !('__collectionResult' in value)) {
1706
1743
  this.collectJsonRowRevivals(value, pathPrefix ? `${pathPrefix}__${key}` : `__nested__${key}`, revivals, textColumns);
1707
1744
  }
1708
1745
  }
@@ -4097,19 +4134,15 @@ ${joinClauses.join('\n')}`;
4097
4134
  continue;
4098
4135
  }
4099
4136
  }
4100
- // Check if it's a mock object with property descriptors (navigation property mock)
4101
- const props = Object.getOwnPropertyNames(value);
4102
- if (props.length > 0) {
4103
- const firstProp = props[0];
4104
- const descriptor = Object.getOwnPropertyDescriptor(value, firstProp);
4105
- if (descriptor && descriptor.get) {
4137
+ // Check if it's a mock object with getter-backed properties (navigation property
4138
+ // mock). The getters may be OWN (root mocks) or INHERITED from the shared
4139
+ // prototype (reference mocks) — findFirstGetterKey walks both.
4140
+ {
4141
+ const tableAlias = findFirstGetterKey(value);
4142
+ if (tableAlias) {
4106
4143
  // This object has getter properties - likely a navigation mock
4107
4144
  // Try to determine if this is a reference navigation by checking the schema relations
4108
- const tableAlias = Object.keys(value).find(k => {
4109
- const desc = Object.getOwnPropertyDescriptor(value, k);
4110
- return desc && desc.get && typeof desc.get === 'function';
4111
- });
4112
- if (tableAlias) {
4145
+ {
4113
4146
  // Try to get the first property to check if it has __tableAlias
4114
4147
  try {
4115
4148
  const firstValue = value[tableAlias];
@@ -4761,15 +4794,12 @@ ${joinClauses.join('\n')}`;
4761
4794
  // These are treated as SIMPLE because the actual value comes from json_build_object in the row
4762
4795
  // The navigation mock is just a placeholder - actual data processing happens via FieldType.SIMPLE
4763
4796
  if (value && typeof value === 'object' && !('__dbColumnName' in value) && !('__fieldName' in value) && !('__collectionResult' in value) && !('__isAggregationArray' in value)) {
4764
- const props = Object.getOwnPropertyNames(value);
4765
- if (props.length > 0) {
4766
- const descriptor = Object.getOwnPropertyDescriptor(value, props[0]);
4767
- if (descriptor && descriptor.get) {
4768
- // Navigation mock - treat as simple, data will come from row via json_build_object
4769
- // If row has no data, convertValue will return undefined
4770
- fieldConfigs.push({ key, type: 8 /* FieldType.SIMPLE */, value });
4771
- continue;
4772
- }
4797
+ // Getter-backed row (own getters on a root mock, inherited ones on a reference mock).
4798
+ if (findFirstGetterKey(value) != null) {
4799
+ // Navigation mock - treat as simple, data will come from row via json_build_object
4800
+ // If row has no data, convertValue will return undefined
4801
+ fieldConfigs.push({ key, type: 8 /* FieldType.SIMPLE */, value });
4802
+ continue;
4773
4803
  }
4774
4804
  }
4775
4805
  // Collection types
@@ -5598,20 +5628,24 @@ class ReferenceQueryBuilder {
5598
5628
  */
5599
5629
  createMockTargetRow() {
5600
5630
  if (this.targetTableSchema) {
5601
- // Descriptor-level cache — see MockRowCache's doc. Everything the descriptors close
5602
- // over is fully determined by (target schema object identity, relationName,
5603
- // sourceAlias, navigation-path content), so rows with the same signature can share
5604
- // one prebuilt PropertyDescriptorMap. The cross-row cache is OPT-IN via the static
5605
- // switch (MockRowCache.setEnabledthe host app flips it from its own config):
5606
- // with it off, each row gets a FRESH descriptor set (the pre-0.4.66 memory profile
5607
- // nothing retained beyond the row's lifetime), while the per-row FieldRef and
5608
- // navigation slots below stay enabled either way (they are row-scoped).
5609
- const descriptors = mock_row_cache_1.MockRowCache.getOrBuild(`${this.targetTable}|${this.relationName}|${this.sourceAlias ?? ''}|${navigationPathSignature(this.navigationPath)}`, () => this.buildMockRowDescriptors());
5610
- const mock = {
5611
- [MOCK_ROW_FIELD_REFS]: {},
5612
- [MOCK_ROW_NAV_CACHE]: {},
5613
- };
5614
- Object.defineProperties(mock, descriptors);
5631
+ // Prototype-level cache — see MockRowCache's doc. Everything the getters close over
5632
+ // is fully determined by (target schema object identity, relationName, sourceAlias,
5633
+ // navigation-path content), so rows with the same signature can share one prebuilt
5634
+ // PROTOTYPE carrying every column/relation getter; a new row is then `Object.create`
5635
+ // plus its two own state slots O(1) instead of one property definition per column.
5636
+ // The cross-row cache is OPT-IN via the static switch (MockRowCache.setEnabled the
5637
+ // host app flips it from its own config): with it off, each row gets a FRESH
5638
+ // prototype (the pre-0.4.66 memory profile nothing retained beyond the row's
5639
+ // lifetime), while the per-row FieldRef and navigation slots stay row-scoped either
5640
+ // way (the shared getters read them through `this`).
5641
+ //
5642
+ // Consumers must not probe these rows with OWN-property APIs (`Object.keys`,
5643
+ // `getOwnPropertyNames`, `getOwnPropertyDescriptor` on the row itself) — the getters
5644
+ // are inherited. Use `isReferenceMockRow` / `findFirstGetterKey` instead.
5645
+ const prototype = mock_row_cache_1.MockRowCache.getOrBuild(`${this.targetTable}|${this.relationName}|${this.sourceAlias ?? ''}|${navigationPathSignature(this.navigationPath)}`, () => Object.defineProperties({}, this.buildMockRowDescriptors()));
5646
+ const mock = Object.create(prototype);
5647
+ mock[MOCK_ROW_FIELD_REFS] = {};
5648
+ mock[MOCK_ROW_NAV_CACHE] = {};
5615
5649
  return mock;
5616
5650
  }
5617
5651
  else {
@@ -6491,6 +6525,19 @@ class CollectionQueryBuilder {
6491
6525
  * Uses BFS to find the shortest path through the schema graph
6492
6526
  */
6493
6527
  findNavigationPath(targetAlias, joinedSchemas, _startSchema) {
6528
+ if (!this.schemaRegistry) {
6529
+ return [];
6530
+ }
6531
+ // Memoised per registry — the BFS below depends only on the target alias and the ORDERED
6532
+ // joined alias→table pairs (order decides which of several equal-length paths wins), so
6533
+ // the same query shape resolves to the same path on every build. See NavigationPathCache.
6534
+ const signature = `${targetAlias}|${[...joinedSchemas]
6535
+ .map(([alias, schema]) => `${alias}:${schema.schema ?? ''}.${schema.name}`)
6536
+ .join(',')}`;
6537
+ return navigation_path_cache_1.NavigationPathCache.getOrBuild(this.schemaRegistry, signature, () => this.computeNavigationPath(targetAlias, joinedSchemas));
6538
+ }
6539
+ /** The uncached BFS behind {@link findNavigationPath}. */
6540
+ computeNavigationPath(targetAlias, joinedSchemas) {
6494
6541
  if (!this.schemaRegistry) {
6495
6542
  return [];
6496
6543
  }