linkgress-orm 0.1.17 → 0.1.18
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 +86 -24
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js +104 -124
- package/dist/entity/db-context.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/query/cte-builder.d.ts +1 -1
- package/dist/query/cte-builder.js +8 -8
- package/dist/query/cte-builder.js.map +1 -1
- package/dist/query/grouped-query.d.ts +8 -0
- package/dist/query/grouped-query.d.ts.map +1 -1
- package/dist/query/grouped-query.js +12 -0
- package/dist/query/grouped-query.js.map +1 -1
- package/dist/query/query-builder.d.ts +13 -2
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +72 -26
- package/dist/query/query-builder.js.map +1 -1
- package/package.json +3 -2
|
@@ -27,6 +27,7 @@ var FieldType;
|
|
|
27
27
|
FieldType[FieldType["FIELD_REF_MAPPER"] = 6] = "FIELD_REF_MAPPER";
|
|
28
28
|
FieldType[FieldType["FIELD_REF_NO_MAPPER"] = 7] = "FIELD_REF_NO_MAPPER";
|
|
29
29
|
FieldType[FieldType["SIMPLE"] = 8] = "SIMPLE";
|
|
30
|
+
FieldType[FieldType["COLLECTION_SINGLE"] = 9] = "COLLECTION_SINGLE";
|
|
30
31
|
})(FieldType || (FieldType = {}));
|
|
31
32
|
/**
|
|
32
33
|
* Performance utility: Get column name map from schema, using cached version if available
|
|
@@ -175,7 +176,7 @@ class QueryBuilder {
|
|
|
175
176
|
* Multiple where() calls are chained with AND logic
|
|
176
177
|
*/
|
|
177
178
|
where(condition) {
|
|
178
|
-
const mockRow = this.
|
|
179
|
+
const mockRow = this._createMockRow();
|
|
179
180
|
const newCondition = condition(mockRow);
|
|
180
181
|
if (this.whereCond) {
|
|
181
182
|
this.whereCond = (0, conditions_1.and)(this.whereCond, newCondition);
|
|
@@ -194,8 +195,9 @@ class QueryBuilder {
|
|
|
194
195
|
}
|
|
195
196
|
/**
|
|
196
197
|
* Create mock row for analysis
|
|
198
|
+
* @internal - Also used by DbEntityTable.props() to avoid code duplication
|
|
197
199
|
*/
|
|
198
|
-
|
|
200
|
+
_createMockRow() {
|
|
199
201
|
// Performance: Return cached mock if available
|
|
200
202
|
if (this._cachedMockRow) {
|
|
201
203
|
return this._cachedMockRow;
|
|
@@ -293,7 +295,7 @@ class QueryBuilder {
|
|
|
293
295
|
const rightAlias = `${rightSchema.name}_${this.joinCounter}`;
|
|
294
296
|
const newJoinCounter = this.joinCounter + 1;
|
|
295
297
|
// Create mock rows for condition evaluation
|
|
296
|
-
const mockLeft = this.
|
|
298
|
+
const mockLeft = this._createMockRow();
|
|
297
299
|
const mockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
298
300
|
const joinCondition = condition(mockLeft, mockRight);
|
|
299
301
|
// Add the join to the list
|
|
@@ -306,7 +308,7 @@ class QueryBuilder {
|
|
|
306
308
|
}];
|
|
307
309
|
// Store schemas for creating fresh mocks in the selector
|
|
308
310
|
const leftSchema = this.schema;
|
|
309
|
-
const createLeftMock = () => this.
|
|
311
|
+
const createLeftMock = () => this._createMockRow();
|
|
310
312
|
const createRightMock = () => this.createMockRowForTable(rightSchema, rightAlias);
|
|
311
313
|
// Create a selector wrapper that generates fresh mocks and calls the user's selector
|
|
312
314
|
const wrappedSelector = (row) => {
|
|
@@ -342,7 +344,7 @@ class QueryBuilder {
|
|
|
342
344
|
const rightAlias = `${rightSchema.name}_${this.joinCounter}`;
|
|
343
345
|
const newJoinCounter = this.joinCounter + 1;
|
|
344
346
|
// Create mock rows for condition evaluation
|
|
345
|
-
const mockLeft = this.
|
|
347
|
+
const mockLeft = this._createMockRow();
|
|
346
348
|
const mockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
347
349
|
const joinCondition = condition(mockLeft, mockRight);
|
|
348
350
|
// Add the join to the list
|
|
@@ -355,7 +357,7 @@ class QueryBuilder {
|
|
|
355
357
|
}];
|
|
356
358
|
// Store schemas for creating fresh mocks in the selector
|
|
357
359
|
const leftSchema = this.schema;
|
|
358
|
-
const createLeftMock = () => this.
|
|
360
|
+
const createLeftMock = () => this._createMockRow();
|
|
359
361
|
const createRightMock = () => this.createMockRowForTable(rightSchema, rightAlias);
|
|
360
362
|
// Create a selector wrapper that generates fresh mocks and calls the user's selector
|
|
361
363
|
const wrappedSelector = (row) => {
|
|
@@ -441,7 +443,7 @@ class QueryBuilder {
|
|
|
441
443
|
return this;
|
|
442
444
|
}
|
|
443
445
|
orderBy(selector) {
|
|
444
|
-
const mockRow = this.
|
|
446
|
+
const mockRow = this._createMockRow();
|
|
445
447
|
const result = selector(mockRow);
|
|
446
448
|
(0, query_utils_1.parseOrderBy)(result, this.orderByFields);
|
|
447
449
|
return this;
|
|
@@ -497,7 +499,7 @@ class SelectQueryBuilder {
|
|
|
497
499
|
* Note: The row parameter represents the selected shape (after select())
|
|
498
500
|
*/
|
|
499
501
|
where(condition) {
|
|
500
|
-
const mockRow = this.
|
|
502
|
+
const mockRow = this._createMockRow();
|
|
501
503
|
// Apply the selector to get the selected shape that the user sees in the WHERE condition
|
|
502
504
|
const selectedMock = this.selector(mockRow);
|
|
503
505
|
// Wrap in proxy - for WHERE, we preserve original column names
|
|
@@ -545,7 +547,7 @@ class SelectQueryBuilder {
|
|
|
545
547
|
return this;
|
|
546
548
|
}
|
|
547
549
|
orderBy(selector) {
|
|
548
|
-
const mockRow = this.
|
|
550
|
+
const mockRow = this._createMockRow();
|
|
549
551
|
const selectedMock = this.selector(mockRow);
|
|
550
552
|
// Wrap selectedMock in a proxy that returns FieldRefs for property access
|
|
551
553
|
const fieldRefProxy = this.createFieldRefProxy(selectedMock);
|
|
@@ -577,7 +579,7 @@ class SelectQueryBuilder {
|
|
|
577
579
|
leftJoinSubquery(subquery, alias, condition, selector) {
|
|
578
580
|
const newJoinCounter = this.joinCounter + 1;
|
|
579
581
|
// Create mock for the current selection (left side)
|
|
580
|
-
const mockRow = this.
|
|
582
|
+
const mockRow = this._createMockRow();
|
|
581
583
|
const mockLeftSelection = this.selector(mockRow);
|
|
582
584
|
// Create mock for the subquery result (right side)
|
|
583
585
|
// For subqueries, we create a mock based on the result type
|
|
@@ -621,7 +623,7 @@ class SelectQueryBuilder {
|
|
|
621
623
|
const newJoinCounter = this.joinCounter + 1;
|
|
622
624
|
// Create mock for the current selection (left side)
|
|
623
625
|
// IMPORTANT: We call the selector with the mock row to get a result that contains FieldRef objects
|
|
624
|
-
const mockRow = this.
|
|
626
|
+
const mockRow = this._createMockRow();
|
|
625
627
|
const mockLeftSelection = this.selector(mockRow);
|
|
626
628
|
// The mockLeftSelection now contains FieldRef objects (with __fieldName, __dbColumnName, __tableAlias)
|
|
627
629
|
// These FieldRef objects preserve the table context
|
|
@@ -652,7 +654,7 @@ class SelectQueryBuilder {
|
|
|
652
654
|
leftJoinCte(cte, condition, selector) {
|
|
653
655
|
const newJoinCounter = this.joinCounter + 1;
|
|
654
656
|
// Create mock for the current selection (left side)
|
|
655
|
-
const mockRow = this.
|
|
657
|
+
const mockRow = this._createMockRow();
|
|
656
658
|
const mockLeftSelection = this.selector(mockRow);
|
|
657
659
|
// Create mock for the CTE columns (right side)
|
|
658
660
|
const mockRight = this.createMockRowForCte(cte);
|
|
@@ -685,7 +687,7 @@ class SelectQueryBuilder {
|
|
|
685
687
|
innerJoinSubquery(subquery, alias, condition, selector) {
|
|
686
688
|
const newJoinCounter = this.joinCounter + 1;
|
|
687
689
|
// Create mock for the current selection (left side)
|
|
688
|
-
const mockRow = this.
|
|
690
|
+
const mockRow = this._createMockRow();
|
|
689
691
|
const mockLeftSelection = this.selector(mockRow);
|
|
690
692
|
// Create mock for the subquery result (right side)
|
|
691
693
|
const mockRight = this.createMockRowForSubquery(alias, subquery);
|
|
@@ -727,7 +729,7 @@ class SelectQueryBuilder {
|
|
|
727
729
|
const rightAlias = `${rightSchema.name}_${this.joinCounter}`;
|
|
728
730
|
const newJoinCounter = this.joinCounter + 1;
|
|
729
731
|
// Create mock for the current selection (left side)
|
|
730
|
-
const mockRow = this.
|
|
732
|
+
const mockRow = this._createMockRow();
|
|
731
733
|
const mockLeftSelection = this.selector(mockRow);
|
|
732
734
|
// Create mock for the right table
|
|
733
735
|
const mockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
@@ -936,13 +938,13 @@ class SelectQueryBuilder {
|
|
|
936
938
|
// If selector is provided, apply it to determine the field
|
|
937
939
|
let fieldToAggregate;
|
|
938
940
|
if (selector) {
|
|
939
|
-
const mockRow = this.
|
|
941
|
+
const mockRow = this._createMockRow();
|
|
940
942
|
const mockSelection = this.selector(mockRow);
|
|
941
943
|
fieldToAggregate = selector(mockSelection);
|
|
942
944
|
}
|
|
943
945
|
else {
|
|
944
946
|
// No selector - use the current selection
|
|
945
|
-
const mockRow = this.
|
|
947
|
+
const mockRow = this._createMockRow();
|
|
946
948
|
fieldToAggregate = this.selector(mockRow);
|
|
947
949
|
}
|
|
948
950
|
// Build aggregation query
|
|
@@ -967,13 +969,13 @@ class SelectQueryBuilder {
|
|
|
967
969
|
// If selector is provided, apply it to determine the field
|
|
968
970
|
let fieldToAggregate;
|
|
969
971
|
if (selector) {
|
|
970
|
-
const mockRow = this.
|
|
972
|
+
const mockRow = this._createMockRow();
|
|
971
973
|
const mockSelection = this.selector(mockRow);
|
|
972
974
|
fieldToAggregate = selector(mockSelection);
|
|
973
975
|
}
|
|
974
976
|
else {
|
|
975
977
|
// No selector - use the current selection
|
|
976
|
-
const mockRow = this.
|
|
978
|
+
const mockRow = this._createMockRow();
|
|
977
979
|
fieldToAggregate = this.selector(mockRow);
|
|
978
980
|
}
|
|
979
981
|
// Build aggregation query
|
|
@@ -998,13 +1000,13 @@ class SelectQueryBuilder {
|
|
|
998
1000
|
// If selector is provided, apply it to determine the field
|
|
999
1001
|
let fieldToAggregate;
|
|
1000
1002
|
if (selector) {
|
|
1001
|
-
const mockRow = this.
|
|
1003
|
+
const mockRow = this._createMockRow();
|
|
1002
1004
|
const mockSelection = this.selector(mockRow);
|
|
1003
1005
|
fieldToAggregate = selector(mockSelection);
|
|
1004
1006
|
}
|
|
1005
1007
|
else {
|
|
1006
1008
|
// No selector - use the current selection
|
|
1007
|
-
const mockRow = this.
|
|
1009
|
+
const mockRow = this._createMockRow();
|
|
1008
1010
|
fieldToAggregate = this.selector(mockRow);
|
|
1009
1011
|
}
|
|
1010
1012
|
// Build aggregation query
|
|
@@ -1052,7 +1054,7 @@ class SelectQueryBuilder {
|
|
|
1052
1054
|
executor: this.executor,
|
|
1053
1055
|
}));
|
|
1054
1056
|
// Analyze the selector to extract nested queries
|
|
1055
|
-
const mockRow = tracer.trace('createMockRow', () => this.
|
|
1057
|
+
const mockRow = tracer.trace('createMockRow', () => this._createMockRow());
|
|
1056
1058
|
const selectionResult = tracer.trace('evaluateSelector', () => this.selector(mockRow));
|
|
1057
1059
|
// Check if we're using temp table strategy and have collections
|
|
1058
1060
|
const collections = tracer.trace('detectCollections', () => this.detectCollections(selectionResult));
|
|
@@ -1319,7 +1321,7 @@ class SelectQueryBuilder {
|
|
|
1319
1321
|
const baseSelection = {};
|
|
1320
1322
|
const collectionNames = new Set(collections.map(c => c.name));
|
|
1321
1323
|
// Always ensure we have the primary key in the base selection with a known alias
|
|
1322
|
-
const mockRow = this.
|
|
1324
|
+
const mockRow = this._createMockRow();
|
|
1323
1325
|
baseSelection['__pk_id'] = mockRow.id; // Add primary key with a known alias
|
|
1324
1326
|
for (const [key, value] of Object.entries(selection)) {
|
|
1325
1327
|
if (!collectionNames.has(key)) {
|
|
@@ -1413,6 +1415,10 @@ class SelectQueryBuilder {
|
|
|
1413
1415
|
// Array aggregation
|
|
1414
1416
|
return [];
|
|
1415
1417
|
}
|
|
1418
|
+
else if (builder.isSingleResult()) {
|
|
1419
|
+
// firstOrDefault() - single item
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1416
1422
|
else {
|
|
1417
1423
|
// JSONB aggregation (object array)
|
|
1418
1424
|
return [];
|
|
@@ -1588,7 +1594,7 @@ class SelectQueryBuilder {
|
|
|
1588
1594
|
return { sql, columns };
|
|
1589
1595
|
}
|
|
1590
1596
|
// Selector function - extract selected columns
|
|
1591
|
-
const mockRow = this.
|
|
1597
|
+
const mockRow = this._createMockRow();
|
|
1592
1598
|
const selectedMock = this.selector(mockRow);
|
|
1593
1599
|
const selection = returning(selectedMock);
|
|
1594
1600
|
if (typeof selection === 'object' && selection !== null) {
|
|
@@ -1647,8 +1653,9 @@ class SelectQueryBuilder {
|
|
|
1647
1653
|
}
|
|
1648
1654
|
/**
|
|
1649
1655
|
* Create mock row for analysis
|
|
1656
|
+
* @internal
|
|
1650
1657
|
*/
|
|
1651
|
-
|
|
1658
|
+
_createMockRow() {
|
|
1652
1659
|
const mock = {};
|
|
1653
1660
|
const tableAlias = this.schema.name;
|
|
1654
1661
|
// Performance: Use pre-computed column name map if available
|
|
@@ -2436,9 +2443,18 @@ class SelectQueryBuilder {
|
|
|
2436
2443
|
}
|
|
2437
2444
|
else {
|
|
2438
2445
|
const isArrayAgg = value && typeof value === 'object' && 'isArrayAggregation' in value && value.isArrayAggregation();
|
|
2446
|
+
const isSingleResult = value instanceof CollectionQueryBuilder && value.isSingleResult();
|
|
2439
2447
|
if (isArrayAgg) {
|
|
2440
2448
|
fieldConfigs.push({ key, type: 2 /* FieldType.COLLECTION_ARRAY */, value });
|
|
2441
2449
|
}
|
|
2450
|
+
else if (isSingleResult) {
|
|
2451
|
+
fieldConfigs.push({
|
|
2452
|
+
key,
|
|
2453
|
+
type: 9 /* FieldType.COLLECTION_SINGLE */,
|
|
2454
|
+
value,
|
|
2455
|
+
collectionBuilder: value
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2442
2458
|
else {
|
|
2443
2459
|
fieldConfigs.push({
|
|
2444
2460
|
key,
|
|
@@ -2549,6 +2565,18 @@ class SelectQueryBuilder {
|
|
|
2549
2565
|
}
|
|
2550
2566
|
break;
|
|
2551
2567
|
}
|
|
2568
|
+
case 9 /* FieldType.COLLECTION_SINGLE */: {
|
|
2569
|
+
// firstOrDefault() - return first item or null
|
|
2570
|
+
const items = rawValue || [];
|
|
2571
|
+
if (config.collectionBuilder && items.length > 0) {
|
|
2572
|
+
const transformedItems = this.transformCollectionItems(items, config.collectionBuilder);
|
|
2573
|
+
result[key] = transformedItems[0] ?? null;
|
|
2574
|
+
}
|
|
2575
|
+
else {
|
|
2576
|
+
result[key] = items[0] ?? null;
|
|
2577
|
+
}
|
|
2578
|
+
break;
|
|
2579
|
+
}
|
|
2552
2580
|
case 4 /* FieldType.CTE_AGGREGATION */: {
|
|
2553
2581
|
const items = rawValue || [];
|
|
2554
2582
|
if (config.innerMetadata && !disableMappers) {
|
|
@@ -2851,7 +2879,7 @@ class SelectQueryBuilder {
|
|
|
2851
2879
|
executor: this.executor,
|
|
2852
2880
|
};
|
|
2853
2881
|
// Analyze the selector to extract nested queries
|
|
2854
|
-
const mockRow = this.
|
|
2882
|
+
const mockRow = this._createMockRow();
|
|
2855
2883
|
const selectionResult = this.selector(mockRow);
|
|
2856
2884
|
// Build the query
|
|
2857
2885
|
const { sql } = this.buildQuery(selectionResult, context);
|
|
@@ -2862,7 +2890,7 @@ class SelectQueryBuilder {
|
|
|
2862
2890
|
// For table subqueries, preserve the selection metadata (includes SqlFragments with mappers)
|
|
2863
2891
|
let selectionMetadata;
|
|
2864
2892
|
if (mode === 'table') {
|
|
2865
|
-
const mockRow = this.
|
|
2893
|
+
const mockRow = this._createMockRow();
|
|
2866
2894
|
selectionMetadata = this.selector(mockRow);
|
|
2867
2895
|
}
|
|
2868
2896
|
// Extract outer field refs from the WHERE condition
|
|
@@ -3282,6 +3310,18 @@ class CollectionQueryBuilder {
|
|
|
3282
3310
|
// At runtime, this is still a CollectionQueryBuilder, but TypeScript sees it as CollectionResult
|
|
3283
3311
|
return this;
|
|
3284
3312
|
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Get first item from collection or null if empty
|
|
3315
|
+
* Automatically applies LIMIT 1 and returns a single item instead of array
|
|
3316
|
+
*/
|
|
3317
|
+
firstOrDefault(name) {
|
|
3318
|
+
if (name) {
|
|
3319
|
+
this.asName = name;
|
|
3320
|
+
}
|
|
3321
|
+
this.limitValue = 1;
|
|
3322
|
+
this.isMarkedAsList = false; // Single item, not a list
|
|
3323
|
+
return this;
|
|
3324
|
+
}
|
|
3285
3325
|
/**
|
|
3286
3326
|
* Get target table schema
|
|
3287
3327
|
*/
|
|
@@ -3300,6 +3340,12 @@ class CollectionQueryBuilder {
|
|
|
3300
3340
|
isScalarAggregation() {
|
|
3301
3341
|
return this.aggregationType !== undefined;
|
|
3302
3342
|
}
|
|
3343
|
+
/**
|
|
3344
|
+
* Check if this is a single item result (firstOrDefault)
|
|
3345
|
+
*/
|
|
3346
|
+
isSingleResult() {
|
|
3347
|
+
return !this.isMarkedAsList && this.limitValue === 1;
|
|
3348
|
+
}
|
|
3303
3349
|
/**
|
|
3304
3350
|
* Get the aggregation type
|
|
3305
3351
|
*/
|