linkgress-orm 0.4.15 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entity/db-context.d.ts +12 -3
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js +15 -5
- package/dist/entity/db-context.js.map +1 -1
- package/dist/entity/entity-base.d.ts +24 -0
- package/dist/entity/entity-base.d.ts.map +1 -1
- package/dist/entity/entity-base.js +15 -0
- package/dist/entity/entity-base.js.map +1 -1
- package/dist/entity/entity-builder.d.ts +34 -1
- package/dist/entity/entity-builder.d.ts.map +1 -1
- package/dist/entity/entity-builder.js +79 -15
- package/dist/entity/entity-builder.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/migration/db-schema-manager.d.ts +20 -0
- package/dist/migration/db-schema-manager.d.ts.map +1 -1
- package/dist/migration/db-schema-manager.js +115 -9
- package/dist/migration/db-schema-manager.js.map +1 -1
- package/dist/migration/migration-scaffold.d.ts +2 -0
- package/dist/migration/migration-scaffold.d.ts.map +1 -1
- package/dist/migration/migration-scaffold.js +21 -4
- package/dist/migration/migration-scaffold.js.map +1 -1
- package/dist/query/collection-strategy.interface.d.ts +9 -0
- package/dist/query/collection-strategy.interface.d.ts.map +1 -1
- package/dist/query/conditions.d.ts +20 -0
- package/dist/query/conditions.d.ts.map +1 -1
- package/dist/query/conditions.js +51 -1
- package/dist/query/conditions.js.map +1 -1
- package/dist/query/query-builder.d.ts +8 -0
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +82 -12
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/strategies/cte-collection-strategy.d.ts.map +1 -1
- package/dist/query/strategies/cte-collection-strategy.js +9 -3
- package/dist/query/strategies/cte-collection-strategy.js.map +1 -1
- package/dist/query/strategies/lateral-collection-strategy.d.ts.map +1 -1
- package/dist/query/strategies/lateral-collection-strategy.js +21 -9
- package/dist/query/strategies/lateral-collection-strategy.js.map +1 -1
- package/dist/query/strategies/temptable-collection-strategy.d.ts.map +1 -1
- package/dist/query/strategies/temptable-collection-strategy.js +9 -3
- package/dist/query/strategies/temptable-collection-strategy.js.map +1 -1
- package/dist/query/subquery.d.ts +22 -18
- package/dist/query/subquery.d.ts.map +1 -1
- package/dist/query/subquery.js +36 -32
- package/dist/query/subquery.js.map +1 -1
- package/dist/schema/column-builder.d.ts +7 -0
- package/dist/schema/column-builder.d.ts.map +1 -1
- package/dist/schema/column-builder.js +7 -0
- package/dist/schema/column-builder.js.map +1 -1
- package/dist/schema/table-builder.d.ts +9 -0
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js.map +1 -1
- package/dist/types/collation-builder.d.ts +35 -0
- package/dist/types/collation-builder.d.ts.map +1 -0
- package/dist/types/collation-builder.js +47 -0
- package/dist/types/collation-builder.js.map +1 -0
- package/package.json +1 -1
|
@@ -4272,6 +4272,13 @@ ${joinClauses.join('\n')}`;
|
|
|
4272
4272
|
* Applies custom mappers to fields within the nested collection result.
|
|
4273
4273
|
*/
|
|
4274
4274
|
transformNestedCollectionValue(value, nestedInfo, schemaRegistry) {
|
|
4275
|
+
// Flattened lists (toNumberList/toStringList) return a primitive array directly
|
|
4276
|
+
// from the driver — no per-element object transformation applies. Iterating with
|
|
4277
|
+
// `for (const key in item)` on a number/string would yield an empty object, so
|
|
4278
|
+
// short-circuit here before touching any elements.
|
|
4279
|
+
if (nestedInfo.flattenResultType) {
|
|
4280
|
+
return value;
|
|
4281
|
+
}
|
|
4275
4282
|
const nestedSchema = schemaRegistry.get(nestedInfo.targetTable);
|
|
4276
4283
|
if (!nestedSchema?.columnMetadataCache) {
|
|
4277
4284
|
return value; // No schema info, return as-is
|
|
@@ -5158,6 +5165,16 @@ class CollectionQueryBuilder {
|
|
|
5158
5165
|
getSchemaRegistry() {
|
|
5159
5166
|
return this.schemaRegistry;
|
|
5160
5167
|
}
|
|
5168
|
+
/**
|
|
5169
|
+
* Get the navigation path leading to this collection.
|
|
5170
|
+
* Non-empty when this collection was reached through a reference chain
|
|
5171
|
+
* (e.g. `cdc.discountCode!.discount!.discountProducts`). The outer collection
|
|
5172
|
+
* builder needs these joins emitted in its own FROM so the nested aggregation's
|
|
5173
|
+
* correlation key (e.g. `discount.id`) resolves.
|
|
5174
|
+
*/
|
|
5175
|
+
getNavigationPath() {
|
|
5176
|
+
return this.navigationPath;
|
|
5177
|
+
}
|
|
5161
5178
|
/**
|
|
5162
5179
|
* Check if this collection uses array aggregation (for flattened results)
|
|
5163
5180
|
*/
|
|
@@ -5280,7 +5297,20 @@ class CollectionQueryBuilder {
|
|
|
5280
5297
|
this.addNavigationJoinForFieldRef(fieldRef, joins, currentSourceAlias, currentSchema, allTableAliases);
|
|
5281
5298
|
}
|
|
5282
5299
|
}
|
|
5283
|
-
else if (value
|
|
5300
|
+
else if (value instanceof CollectionQueryBuilder) {
|
|
5301
|
+
// A nested collection that was reached via a reference chain (e.g.
|
|
5302
|
+
// `cdc.discountCode!.discount!.discountProducts`) carries the chain as its
|
|
5303
|
+
// navigationPath. The nested aggregation will correlate on the last alias's
|
|
5304
|
+
// PK (e.g. `discount.id`), so the outer collection must emit those joins
|
|
5305
|
+
// in its own FROM for the correlation to resolve.
|
|
5306
|
+
const nestedPath = value.getNavigationPath();
|
|
5307
|
+
for (const step of nestedPath) {
|
|
5308
|
+
if (!joins.some(j => j.alias === step.alias)) {
|
|
5309
|
+
joins.push(step);
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
5284
5314
|
// Recursively check nested objects
|
|
5285
5315
|
collectFromSelection(value);
|
|
5286
5316
|
}
|
|
@@ -5533,9 +5563,22 @@ class CollectionQueryBuilder {
|
|
|
5533
5563
|
if (nestedResult.tableName && (nestedResult.isCTE || nestedResult.joinClause)) {
|
|
5534
5564
|
let nestedJoinClause;
|
|
5535
5565
|
if (nestedResult.isCTE) {
|
|
5536
|
-
// CTE strategy: join by parent_id
|
|
5537
|
-
//
|
|
5538
|
-
|
|
5566
|
+
// CTE strategy: join by parent_id. When the nested collection was reached
|
|
5567
|
+
// through a reference chain (e.g. `cdc.discountCode!.discount!.discountProducts`),
|
|
5568
|
+
// the CTE aggregates on the chain's terminal PK (here `discount.id`), not on
|
|
5569
|
+
// the outer collection's own PK — and the outer's target table may not even
|
|
5570
|
+
// have an `id` column (junction tables like `cart_discount_codes`). The
|
|
5571
|
+
// intermediate joins are emitted into the outer's FROM via detectNavigationJoins
|
|
5572
|
+
// picking up the nested path.
|
|
5573
|
+
const nestedPath = field.getNavigationPath();
|
|
5574
|
+
if (nestedPath.length > 0) {
|
|
5575
|
+
const lastStep = nestedPath[nestedPath.length - 1];
|
|
5576
|
+
const pk = lastStep.matches?.[0] || 'id';
|
|
5577
|
+
nestedJoinClause = `LEFT JOIN "${nestedResult.tableName}" ON "${lastStep.alias}"."${pk}" = "${nestedResult.tableName}".parent_id`;
|
|
5578
|
+
}
|
|
5579
|
+
else {
|
|
5580
|
+
nestedJoinClause = `LEFT JOIN "${nestedResult.tableName}" ON "${this.targetTable}"."id" = "${nestedResult.tableName}".parent_id`;
|
|
5581
|
+
}
|
|
5539
5582
|
}
|
|
5540
5583
|
else {
|
|
5541
5584
|
// LATERAL strategy: use the provided join clause (contains full LATERAL subquery)
|
|
@@ -5553,6 +5596,7 @@ class CollectionQueryBuilder {
|
|
|
5553
5596
|
targetTable: field.getTargetTable(),
|
|
5554
5597
|
selectedFieldConfigs: field.getSelectedFieldConfigs(),
|
|
5555
5598
|
isSingleResult: field.isSingleResult(),
|
|
5599
|
+
flattenResultType: field.getFlattenResultType(),
|
|
5556
5600
|
},
|
|
5557
5601
|
};
|
|
5558
5602
|
}
|
|
@@ -5576,6 +5620,7 @@ class CollectionQueryBuilder {
|
|
|
5576
5620
|
targetTable: field.getTargetTable(),
|
|
5577
5621
|
selectedFieldConfigs: field.getSelectedFieldConfigs(),
|
|
5578
5622
|
isSingleResult: field.isSingleResult(),
|
|
5623
|
+
flattenResultType: field.getFlattenResultType(),
|
|
5579
5624
|
},
|
|
5580
5625
|
};
|
|
5581
5626
|
}
|
|
@@ -5613,10 +5658,14 @@ class CollectionQueryBuilder {
|
|
|
5613
5658
|
return { alias, expression };
|
|
5614
5659
|
}
|
|
5615
5660
|
};
|
|
5661
|
+
// Evaluate the user's selector exactly once per buildCTE call. Downstream steps
|
|
5662
|
+
// (field collection, aggregate-expression discovery, navigation-join detection) all
|
|
5663
|
+
// need to walk the same selection, and re-invoking the selector is expensive
|
|
5664
|
+
// (rebuilds proxy mocks and any nested CollectionQueryBuilder instances).
|
|
5665
|
+
const selectorResult = this.selector ? this.selector(this.createMockItem()) : undefined;
|
|
5616
5666
|
// Step 1: Build field selection configuration
|
|
5617
5667
|
if (this.selector) {
|
|
5618
|
-
const
|
|
5619
|
-
const selectedFields = this.selector(mockItem);
|
|
5668
|
+
const selectedFields = selectorResult;
|
|
5620
5669
|
// Check if the selector returns a FieldRef directly (single field selection like p => p.title)
|
|
5621
5670
|
if (typeof selectedFields === 'object' && selectedFields !== null && '__dbColumnName' in selectedFields) {
|
|
5622
5671
|
// Single field selection - use the field name as both alias and expression
|
|
@@ -5629,6 +5678,10 @@ class CollectionQueryBuilder {
|
|
|
5629
5678
|
propertyName: fieldName,
|
|
5630
5679
|
});
|
|
5631
5680
|
}
|
|
5681
|
+
else if (selectedFields instanceof CollectionQueryBuilder || selectedFields instanceof conditions_1.SqlFragment) {
|
|
5682
|
+
// Selector returns a scalar subquery (e.g. .sum(row => other.count())) or a raw fragment.
|
|
5683
|
+
// No per-column fields to collect — the aggregate argument lives on aggregateExpression.
|
|
5684
|
+
}
|
|
5632
5685
|
else {
|
|
5633
5686
|
// Object selection - extract each field (with support for nested objects)
|
|
5634
5687
|
for (const [alias, field] of Object.entries(selectedFields)) {
|
|
@@ -5706,6 +5759,7 @@ class CollectionQueryBuilder {
|
|
|
5706
5759
|
// Step 4: Determine aggregation type and field
|
|
5707
5760
|
let aggregationType;
|
|
5708
5761
|
let aggregateField;
|
|
5762
|
+
let aggregateExpression;
|
|
5709
5763
|
let arrayField;
|
|
5710
5764
|
let defaultValue;
|
|
5711
5765
|
if (this.aggregationType) {
|
|
@@ -5713,11 +5767,23 @@ class CollectionQueryBuilder {
|
|
|
5713
5767
|
aggregationType = this.aggregationType.toLowerCase();
|
|
5714
5768
|
// For aggregations other than COUNT and EXISTS, determine which field to aggregate
|
|
5715
5769
|
if (this.aggregationType !== 'COUNT' && this.aggregationType !== 'EXISTS' && this.selector) {
|
|
5716
|
-
const
|
|
5717
|
-
const selectedField = this.selector(mockItem);
|
|
5770
|
+
const selectedField = selectorResult;
|
|
5718
5771
|
if (typeof selectedField === 'object' && selectedField !== null && '__dbColumnName' in selectedField) {
|
|
5719
5772
|
aggregateField = selectedField.__dbColumnName;
|
|
5720
5773
|
}
|
|
5774
|
+
else if (selectedField instanceof CollectionQueryBuilder) {
|
|
5775
|
+
// Selector returns a nested collection (e.g., sum(row => other.where(...).count())).
|
|
5776
|
+
// We need a *scalar* SQL expression to feed into the aggregate — so force the nested
|
|
5777
|
+
// build to emit a correlated subquery (lateral's scalar form) rather than a CTE/temp
|
|
5778
|
+
// table that can't be composed inside SUM(...). Outer strategies (CTE, temp table,
|
|
5779
|
+
// lateral) then wrap this expression with the aggregate function via
|
|
5780
|
+
// config.aggregateExpression.
|
|
5781
|
+
const nestedCtx = { ...context, collectionStrategy: 'lateral' };
|
|
5782
|
+
const nestedResult = selectedField.buildCTE(nestedCtx, client);
|
|
5783
|
+
context.cteCounter = nestedCtx.cteCounter;
|
|
5784
|
+
context.paramCounter = nestedCtx.paramCounter;
|
|
5785
|
+
aggregateExpression = nestedResult.selectExpression || nestedResult.sql;
|
|
5786
|
+
}
|
|
5721
5787
|
}
|
|
5722
5788
|
// Set default value based on aggregation type
|
|
5723
5789
|
if (aggregationType === 'count') {
|
|
@@ -5748,10 +5814,13 @@ class CollectionQueryBuilder {
|
|
|
5748
5814
|
}
|
|
5749
5815
|
// Step 5: Detect navigation joins from the selected fields
|
|
5750
5816
|
const navigationJoins = [];
|
|
5751
|
-
if (this.selector && this.targetTableSchema) {
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5817
|
+
if (this.selector && this.targetTableSchema && selectorResult !== undefined) {
|
|
5818
|
+
// A CollectionQueryBuilder summand already had its navigation joins built via the
|
|
5819
|
+
// recursive buildCTE above; detectNavigationJoins would iterate its own properties
|
|
5820
|
+
// as if they were fields and produce nothing useful. Skip the walk in that case.
|
|
5821
|
+
if (!(selectorResult instanceof CollectionQueryBuilder)) {
|
|
5822
|
+
this.detectNavigationJoins(selectorResult, navigationJoins, this.targetTable, this.targetTableSchema);
|
|
5823
|
+
}
|
|
5755
5824
|
}
|
|
5756
5825
|
// Step 5b: Merge navigation path joins (for intermediate tables in navigation chains)
|
|
5757
5826
|
// These joins are needed when accessing a collection through a chain like:
|
|
@@ -5782,6 +5851,7 @@ class CollectionQueryBuilder {
|
|
|
5782
5851
|
isSingleResult: this.isSingleResult(), // For firstOrDefault() - returns single object instead of array
|
|
5783
5852
|
aggregationType,
|
|
5784
5853
|
aggregateField,
|
|
5854
|
+
aggregateExpression,
|
|
5785
5855
|
arrayField,
|
|
5786
5856
|
defaultValue,
|
|
5787
5857
|
// Use the reserved counter for LATERAL strategy, otherwise increment as before
|