@pol-studios/db 1.0.54 → 1.0.56
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/{DataLayerContext-C7cJtiO8.d.ts → DataLayerContext-BYZtDD0g.d.ts} +1 -1
- package/dist/auth/context.js +6 -4
- package/dist/auth/hooks.js +7 -5
- package/dist/auth/index.js +7 -5
- package/dist/{chunk-FIAXWEBK.js → chunk-4EO55YV2.js} +10 -7
- package/dist/chunk-4EO55YV2.js.map +1 -0
- package/dist/{chunk-DP3YEVSX.js → chunk-6SDH7M7J.js} +26 -10
- package/dist/chunk-6SDH7M7J.js.map +1 -0
- package/dist/{chunk-UJWETW36.js → chunk-AKIRHA4Q.js} +527 -418
- package/dist/chunk-AKIRHA4Q.js.map +1 -0
- package/dist/{chunk-2XS2PM62.js → chunk-DDL63KLQ.js} +388 -107
- package/dist/chunk-DDL63KLQ.js.map +1 -0
- package/dist/{chunk-YA6MUTA7.js → chunk-FI6JAD5G.js} +3 -3
- package/dist/{chunk-WQLIGVQR.js → chunk-GWYTROSD.js} +98 -1
- package/dist/chunk-GWYTROSD.js.map +1 -0
- package/dist/chunk-JOULSXOI.js +415 -0
- package/dist/chunk-JOULSXOI.js.map +1 -0
- package/dist/{chunk-OKYHI6JG.js → chunk-LF3V3ERS.js} +3 -3
- package/dist/{chunk-FMYXG4VN.js → chunk-MEBT5YHA.js} +2 -2
- package/dist/{chunk-BZSAPFFB.js → chunk-N4KK5G5T.js} +116 -18
- package/dist/chunk-N4KK5G5T.js.map +1 -0
- package/dist/chunk-QYAFI34Q.js +64 -0
- package/dist/chunk-QYAFI34Q.js.map +1 -0
- package/dist/{chunk-3Q74DK5K.js → chunk-VYFAMTHI.js} +2 -2
- package/dist/chunk-W7PERM66.js +215 -0
- package/dist/chunk-W7PERM66.js.map +1 -0
- package/dist/{chunk-ZGQ7Q4ZU.js → chunk-WM25QE7E.js} +2 -2
- package/dist/{chunk-HZIVE5AZ.js → chunk-YRIPM2AN.js} +253 -338
- package/dist/chunk-YRIPM2AN.js.map +1 -0
- package/dist/chunk-YUX6RGLZ.js +1858 -0
- package/dist/chunk-YUX6RGLZ.js.map +1 -0
- package/dist/{chunk-Z3EJX3VG.js → chunk-Z456IHCB.js} +3 -3
- package/dist/core/index.d.ts +24 -1
- package/dist/{executor-YJw4m7Q7.d.ts → executor-D15yjeMo.d.ts} +20 -0
- package/dist/hooks/index.d.ts +3 -3
- package/dist/hooks/index.js +4 -2
- package/dist/{index-jVYdTeWx.d.ts → index-CFUuTzXO.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +16 -14
- package/dist/index.native.d.ts +62 -8
- package/dist/index.native.js +16 -14
- package/dist/index.web.d.ts +10 -9
- package/dist/index.web.js +30 -19
- package/dist/index.web.js.map +1 -1
- package/dist/mutation/index.js +3 -3
- package/dist/parser/index.js +3 -3
- package/dist/powersync-bridge/index.d.ts +1 -1
- package/dist/query/index.d.ts +4 -83
- package/dist/query/index.js +17 -7
- package/dist/realtime/index.d.ts +80 -1
- package/dist/realtime/index.js +14 -12
- package/dist/realtime/index.js.map +1 -1
- package/dist/select-parser-BAV7fOaM.d.ts +144 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +4 -4
- package/dist/{useDbCount-DHLJzmkO.d.ts → useDbCount-Ckb-FhZk.d.ts} +1 -1
- package/dist/{useResolveFeedback-B0UcYWVI.d.ts → useResolveFeedback-CuUkdHoR.d.ts} +13 -29
- package/dist/with-auth/index.js +9 -7
- package/dist/with-auth/index.js.map +1 -1
- package/package.json +9 -4
- package/dist/chunk-2XS2PM62.js.map +0 -1
- package/dist/chunk-BZSAPFFB.js.map +0 -1
- package/dist/chunk-CTRY7JDP.js +0 -4112
- package/dist/chunk-CTRY7JDP.js.map +0 -1
- package/dist/chunk-DP3YEVSX.js.map +0 -1
- package/dist/chunk-FIAXWEBK.js.map +0 -1
- package/dist/chunk-HZIVE5AZ.js.map +0 -1
- package/dist/chunk-INEUG6MC.js +0 -521
- package/dist/chunk-INEUG6MC.js.map +0 -1
- package/dist/chunk-UJWETW36.js.map +0 -1
- package/dist/chunk-WQLIGVQR.js.map +0 -1
- /package/dist/{chunk-YA6MUTA7.js.map → chunk-FI6JAD5G.js.map} +0 -0
- /package/dist/{chunk-OKYHI6JG.js.map → chunk-LF3V3ERS.js.map} +0 -0
- /package/dist/{chunk-FMYXG4VN.js.map → chunk-MEBT5YHA.js.map} +0 -0
- /package/dist/{chunk-3Q74DK5K.js.map → chunk-VYFAMTHI.js.map} +0 -0
- /package/dist/{chunk-ZGQ7Q4ZU.js.map → chunk-WM25QE7E.js.map} +0 -0
- /package/dist/{chunk-Z3EJX3VG.js.map → chunk-Z456IHCB.js.map} +0 -0
|
@@ -1,133 +1,211 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import {
|
|
2
|
+
parseSelect
|
|
3
|
+
} from "./chunk-W7PERM66.js";
|
|
4
|
+
|
|
5
|
+
// src/query/result-joiner.ts
|
|
6
|
+
var ResultJoiner = class {
|
|
7
|
+
/**
|
|
8
|
+
* Join related data onto base records.
|
|
9
|
+
*
|
|
10
|
+
* @param baseRecords - The base table records
|
|
11
|
+
* @param relatedRecords - Records from the related table
|
|
12
|
+
* @param relationship - The relationship definition
|
|
13
|
+
* @param relationName - The property name to use for the relation
|
|
14
|
+
* @returns Base records with related data attached
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // One-to-many: EquipmentUnit -> EquipmentFixture[]
|
|
18
|
+
* const joiner = new ResultJoiner();
|
|
19
|
+
* const result = joiner.join(
|
|
20
|
+
* equipmentUnits,
|
|
21
|
+
* equipmentFixtures,
|
|
22
|
+
* { type: "one-to-many", foreignKey: "equipmentUnitId", referencedColumn: "id", ... },
|
|
23
|
+
* "EquipmentFixture"
|
|
24
|
+
* );
|
|
25
|
+
* // Each equipmentUnit now has EquipmentFixture: [...]
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Many-to-one: EquipmentUnit -> ProjectDatabase
|
|
29
|
+
* const result = joiner.join(
|
|
30
|
+
* equipmentUnits,
|
|
31
|
+
* projectDatabases,
|
|
32
|
+
* { type: "many-to-one", foreignKey: "projectDatabaseId", referencedColumn: "id", ... },
|
|
33
|
+
* "ProjectDatabase"
|
|
34
|
+
* );
|
|
35
|
+
* // Each equipmentUnit now has ProjectDatabase: {...} or null
|
|
36
|
+
*/
|
|
37
|
+
join(baseRecords, relatedRecords, relationship, relationName) {
|
|
38
|
+
if (baseRecords.length === 0) {
|
|
39
|
+
return baseRecords;
|
|
40
|
+
}
|
|
41
|
+
if (relationship.type === "one-to-many") {
|
|
42
|
+
const groupedByFK = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const r of relatedRecords) {
|
|
44
|
+
const fkValue = r[relationship.foreignKey];
|
|
45
|
+
if (!groupedByFK.has(fkValue)) groupedByFK.set(fkValue, []);
|
|
46
|
+
groupedByFK.get(fkValue).push(r);
|
|
17
47
|
}
|
|
18
|
-
|
|
48
|
+
for (const base of baseRecords) {
|
|
49
|
+
const baseId = base[relationship.referencedColumn];
|
|
50
|
+
base[relationName] = groupedByFK.get(baseId) ?? [];
|
|
51
|
+
}
|
|
52
|
+
return baseRecords;
|
|
19
53
|
} else {
|
|
20
|
-
|
|
54
|
+
const relatedMap = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const r of relatedRecords) {
|
|
56
|
+
const refValue = r[relationship.referencedColumn];
|
|
57
|
+
if (refValue !== null && refValue !== void 0) {
|
|
58
|
+
relatedMap.set(refValue, r);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const base of baseRecords) {
|
|
62
|
+
const fkValue = base[relationship.foreignKey];
|
|
63
|
+
base[relationName] = fkValue != null ? relatedMap.get(fkValue) ?? null : null;
|
|
64
|
+
}
|
|
65
|
+
return baseRecords;
|
|
21
66
|
}
|
|
22
67
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
relations: []
|
|
58
|
-
};
|
|
59
|
-
const tokens = tokenizeTopLevel(trimmed);
|
|
60
|
-
for (const token of tokens) {
|
|
61
|
-
const trimmedToken = token.trim();
|
|
62
|
-
if (!trimmedToken) {
|
|
63
|
-
continue;
|
|
68
|
+
/**
|
|
69
|
+
* Recursively join nested relations onto records.
|
|
70
|
+
*
|
|
71
|
+
* @param baseRecords - The base records
|
|
72
|
+
* @param relationData - Map of relation names to join data
|
|
73
|
+
* @returns Base records with all nested relations attached
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* // Join EquipmentFixture and ProjectDatabase onto EquipmentUnit
|
|
77
|
+
* // where EquipmentFixture has its own nested Organization relation
|
|
78
|
+
* const result = joiner.joinNested(equipmentUnits, new Map([
|
|
79
|
+
* ["EquipmentFixture", {
|
|
80
|
+
* records: fixtures,
|
|
81
|
+
* relationship: { type: "one-to-many", ... },
|
|
82
|
+
* nestedRelations: new Map()
|
|
83
|
+
* }],
|
|
84
|
+
* ["ProjectDatabase", {
|
|
85
|
+
* records: projects,
|
|
86
|
+
* relationship: { type: "many-to-one", ... },
|
|
87
|
+
* nestedRelations: new Map([
|
|
88
|
+
* ["Organization", { records: orgs, relationship: ..., nestedRelations: new Map() }]
|
|
89
|
+
* ])
|
|
90
|
+
* }]
|
|
91
|
+
* ]));
|
|
92
|
+
*/
|
|
93
|
+
joinNested(baseRecords, relationData) {
|
|
94
|
+
let result = [...baseRecords];
|
|
95
|
+
const entries = Array.from(relationData.entries());
|
|
96
|
+
for (const [relationName, data] of entries) {
|
|
97
|
+
let relatedRecords = data.records;
|
|
98
|
+
if (data.nestedRelations.size > 0) {
|
|
99
|
+
relatedRecords = this.joinNested(relatedRecords, data.nestedRelations);
|
|
100
|
+
}
|
|
101
|
+
result = this.join(result, relatedRecords, data.relationship, relationName);
|
|
64
102
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (result.columns !== "*") {
|
|
82
|
-
result.columns.push(column);
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Group records by a key field.
|
|
107
|
+
* Useful for preparing data before joining.
|
|
108
|
+
*
|
|
109
|
+
* @param records - Records to group
|
|
110
|
+
* @param keyField - Field to group by
|
|
111
|
+
* @returns Map of key values to arrays of records
|
|
112
|
+
*/
|
|
113
|
+
groupBy(records, keyField) {
|
|
114
|
+
const groups = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const record of records) {
|
|
116
|
+
const key = record[keyField];
|
|
117
|
+
if (!groups.has(key)) {
|
|
118
|
+
groups.set(key, []);
|
|
83
119
|
}
|
|
120
|
+
groups.get(key).push(record);
|
|
84
121
|
}
|
|
122
|
+
return groups;
|
|
85
123
|
}
|
|
86
|
-
|
|
87
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Index records by a unique key field.
|
|
126
|
+
* Useful for many-to-one lookups.
|
|
127
|
+
*
|
|
128
|
+
* @param records - Records to index
|
|
129
|
+
* @param keyField - Field to index by (should be unique)
|
|
130
|
+
* @returns Map of key values to single records
|
|
131
|
+
*/
|
|
132
|
+
indexBy(records, keyField) {
|
|
133
|
+
const index = /* @__PURE__ */ new Map();
|
|
134
|
+
for (const record of records) {
|
|
135
|
+
const key = record[keyField];
|
|
136
|
+
if (key !== null && key !== void 0) {
|
|
137
|
+
index.set(key, record);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return index;
|
|
88
141
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Extract unique values for a field from records.
|
|
144
|
+
* Useful for building IN clauses for related queries.
|
|
145
|
+
*
|
|
146
|
+
* @param records - Records to extract from
|
|
147
|
+
* @param field - Field to extract
|
|
148
|
+
* @returns Array of unique non-null values
|
|
149
|
+
*/
|
|
150
|
+
extractUniqueValues(records, field) {
|
|
151
|
+
const values = /* @__PURE__ */ new Set();
|
|
152
|
+
for (const record of records) {
|
|
153
|
+
const value = record[field];
|
|
154
|
+
if (value !== null && value !== void 0) {
|
|
155
|
+
values.add(value);
|
|
101
156
|
}
|
|
102
157
|
}
|
|
158
|
+
return Array.from(values);
|
|
103
159
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Remove a relation property from records (for cleanup).
|
|
162
|
+
*
|
|
163
|
+
* @param records - Records to process
|
|
164
|
+
* @param relationName - Relation property to remove
|
|
165
|
+
* @returns Records without the specified property
|
|
166
|
+
*/
|
|
167
|
+
removeRelation(records, relationName) {
|
|
168
|
+
return records.map((record) => {
|
|
169
|
+
const {
|
|
170
|
+
[relationName]: _removed,
|
|
171
|
+
...rest
|
|
172
|
+
} = record;
|
|
173
|
+
return rest;
|
|
108
174
|
});
|
|
109
|
-
if (rel.alias) {
|
|
110
|
-
parts.push(`${rel.alias}:${rel.name}(${innerStr})`);
|
|
111
|
-
} else {
|
|
112
|
-
parts.push(`${rel.name}(${innerStr})`);
|
|
113
|
-
}
|
|
114
175
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Flatten a nested relation into the parent record.
|
|
178
|
+
* Useful for flattening many-to-one relations.
|
|
179
|
+
*
|
|
180
|
+
* @param records - Records with nested relation
|
|
181
|
+
* @param relationName - Name of the relation to flatten
|
|
182
|
+
* @param prefix - Prefix for flattened field names
|
|
183
|
+
* @returns Records with flattened relation fields
|
|
184
|
+
*/
|
|
185
|
+
flattenRelation(records, relationName, prefix = "") {
|
|
186
|
+
return records.map((record) => {
|
|
187
|
+
const relation = record[relationName];
|
|
188
|
+
const {
|
|
189
|
+
[relationName]: _removed,
|
|
190
|
+
...rest
|
|
191
|
+
} = record;
|
|
192
|
+
if (!relation) {
|
|
193
|
+
return rest;
|
|
194
|
+
}
|
|
195
|
+
const flattenedRelation = {};
|
|
196
|
+
for (const [key, value] of Object.entries(relation)) {
|
|
197
|
+
const fieldName = prefix ? `${prefix}_${key}` : `${relationName}_${key}`;
|
|
198
|
+
flattenedRelation[fieldName] = value;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
...rest,
|
|
202
|
+
...flattenedRelation
|
|
203
|
+
};
|
|
204
|
+
});
|
|
120
205
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return parsed.relations.map((rel) => rel.alias ?? rel.name);
|
|
125
|
-
}
|
|
126
|
-
function hasRelation(parsed, relationName) {
|
|
127
|
-
return parsed.relations.some((rel) => rel.name === relationName || rel.alias === relationName);
|
|
128
|
-
}
|
|
129
|
-
function getRelationSelect(parsed, relationName) {
|
|
130
|
-
return parsed.relations.find((rel) => rel.name === relationName || rel.alias === relationName);
|
|
206
|
+
};
|
|
207
|
+
function createResultJoiner() {
|
|
208
|
+
return new ResultJoiner();
|
|
131
209
|
}
|
|
132
210
|
|
|
133
211
|
// src/query/relationship-resolver.ts
|
|
@@ -655,213 +733,8 @@ function createSQLBuilder() {
|
|
|
655
733
|
return new SQLBuilder();
|
|
656
734
|
}
|
|
657
735
|
|
|
658
|
-
// src/query/result-joiner.ts
|
|
659
|
-
var ResultJoiner = class {
|
|
660
|
-
/**
|
|
661
|
-
* Join related data onto base records.
|
|
662
|
-
*
|
|
663
|
-
* @param baseRecords - The base table records
|
|
664
|
-
* @param relatedRecords - Records from the related table
|
|
665
|
-
* @param relationship - The relationship definition
|
|
666
|
-
* @param relationName - The property name to use for the relation
|
|
667
|
-
* @returns Base records with related data attached
|
|
668
|
-
*
|
|
669
|
-
* @example
|
|
670
|
-
* // One-to-many: EquipmentUnit -> EquipmentFixture[]
|
|
671
|
-
* const joiner = new ResultJoiner();
|
|
672
|
-
* const result = joiner.join(
|
|
673
|
-
* equipmentUnits,
|
|
674
|
-
* equipmentFixtures,
|
|
675
|
-
* { type: "one-to-many", foreignKey: "equipmentUnitId", referencedColumn: "id", ... },
|
|
676
|
-
* "EquipmentFixture"
|
|
677
|
-
* );
|
|
678
|
-
* // Each equipmentUnit now has EquipmentFixture: [...]
|
|
679
|
-
*
|
|
680
|
-
* @example
|
|
681
|
-
* // Many-to-one: EquipmentUnit -> ProjectDatabase
|
|
682
|
-
* const result = joiner.join(
|
|
683
|
-
* equipmentUnits,
|
|
684
|
-
* projectDatabases,
|
|
685
|
-
* { type: "many-to-one", foreignKey: "projectDatabaseId", referencedColumn: "id", ... },
|
|
686
|
-
* "ProjectDatabase"
|
|
687
|
-
* );
|
|
688
|
-
* // Each equipmentUnit now has ProjectDatabase: {...} or null
|
|
689
|
-
*/
|
|
690
|
-
join(baseRecords, relatedRecords, relationship, relationName) {
|
|
691
|
-
if (baseRecords.length === 0) {
|
|
692
|
-
return baseRecords;
|
|
693
|
-
}
|
|
694
|
-
if (relationship.type === "one-to-many") {
|
|
695
|
-
const groupedByFK = /* @__PURE__ */ new Map();
|
|
696
|
-
for (const r of relatedRecords) {
|
|
697
|
-
const fkValue = r[relationship.foreignKey];
|
|
698
|
-
if (!groupedByFK.has(fkValue)) groupedByFK.set(fkValue, []);
|
|
699
|
-
groupedByFK.get(fkValue).push(r);
|
|
700
|
-
}
|
|
701
|
-
for (const base of baseRecords) {
|
|
702
|
-
const baseId = base[relationship.referencedColumn];
|
|
703
|
-
base[relationName] = groupedByFK.get(baseId) ?? [];
|
|
704
|
-
}
|
|
705
|
-
return baseRecords;
|
|
706
|
-
} else {
|
|
707
|
-
const relatedMap = /* @__PURE__ */ new Map();
|
|
708
|
-
for (const r of relatedRecords) {
|
|
709
|
-
const refValue = r[relationship.referencedColumn];
|
|
710
|
-
if (refValue !== null && refValue !== void 0) {
|
|
711
|
-
relatedMap.set(refValue, r);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
for (const base of baseRecords) {
|
|
715
|
-
const fkValue = base[relationship.foreignKey];
|
|
716
|
-
base[relationName] = fkValue != null ? relatedMap.get(fkValue) ?? null : null;
|
|
717
|
-
}
|
|
718
|
-
return baseRecords;
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Recursively join nested relations onto records.
|
|
723
|
-
*
|
|
724
|
-
* @param baseRecords - The base records
|
|
725
|
-
* @param relationData - Map of relation names to join data
|
|
726
|
-
* @returns Base records with all nested relations attached
|
|
727
|
-
*
|
|
728
|
-
* @example
|
|
729
|
-
* // Join EquipmentFixture and ProjectDatabase onto EquipmentUnit
|
|
730
|
-
* // where EquipmentFixture has its own nested Organization relation
|
|
731
|
-
* const result = joiner.joinNested(equipmentUnits, new Map([
|
|
732
|
-
* ["EquipmentFixture", {
|
|
733
|
-
* records: fixtures,
|
|
734
|
-
* relationship: { type: "one-to-many", ... },
|
|
735
|
-
* nestedRelations: new Map()
|
|
736
|
-
* }],
|
|
737
|
-
* ["ProjectDatabase", {
|
|
738
|
-
* records: projects,
|
|
739
|
-
* relationship: { type: "many-to-one", ... },
|
|
740
|
-
* nestedRelations: new Map([
|
|
741
|
-
* ["Organization", { records: orgs, relationship: ..., nestedRelations: new Map() }]
|
|
742
|
-
* ])
|
|
743
|
-
* }]
|
|
744
|
-
* ]));
|
|
745
|
-
*/
|
|
746
|
-
joinNested(baseRecords, relationData) {
|
|
747
|
-
let result = [...baseRecords];
|
|
748
|
-
const entries = Array.from(relationData.entries());
|
|
749
|
-
for (const [relationName, data] of entries) {
|
|
750
|
-
let relatedRecords = data.records;
|
|
751
|
-
if (data.nestedRelations.size > 0) {
|
|
752
|
-
relatedRecords = this.joinNested(relatedRecords, data.nestedRelations);
|
|
753
|
-
}
|
|
754
|
-
result = this.join(result, relatedRecords, data.relationship, relationName);
|
|
755
|
-
}
|
|
756
|
-
return result;
|
|
757
|
-
}
|
|
758
|
-
/**
|
|
759
|
-
* Group records by a key field.
|
|
760
|
-
* Useful for preparing data before joining.
|
|
761
|
-
*
|
|
762
|
-
* @param records - Records to group
|
|
763
|
-
* @param keyField - Field to group by
|
|
764
|
-
* @returns Map of key values to arrays of records
|
|
765
|
-
*/
|
|
766
|
-
groupBy(records, keyField) {
|
|
767
|
-
const groups = /* @__PURE__ */ new Map();
|
|
768
|
-
for (const record of records) {
|
|
769
|
-
const key = record[keyField];
|
|
770
|
-
if (!groups.has(key)) {
|
|
771
|
-
groups.set(key, []);
|
|
772
|
-
}
|
|
773
|
-
groups.get(key).push(record);
|
|
774
|
-
}
|
|
775
|
-
return groups;
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Index records by a unique key field.
|
|
779
|
-
* Useful for many-to-one lookups.
|
|
780
|
-
*
|
|
781
|
-
* @param records - Records to index
|
|
782
|
-
* @param keyField - Field to index by (should be unique)
|
|
783
|
-
* @returns Map of key values to single records
|
|
784
|
-
*/
|
|
785
|
-
indexBy(records, keyField) {
|
|
786
|
-
const index = /* @__PURE__ */ new Map();
|
|
787
|
-
for (const record of records) {
|
|
788
|
-
const key = record[keyField];
|
|
789
|
-
if (key !== null && key !== void 0) {
|
|
790
|
-
index.set(key, record);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
return index;
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* Extract unique values for a field from records.
|
|
797
|
-
* Useful for building IN clauses for related queries.
|
|
798
|
-
*
|
|
799
|
-
* @param records - Records to extract from
|
|
800
|
-
* @param field - Field to extract
|
|
801
|
-
* @returns Array of unique non-null values
|
|
802
|
-
*/
|
|
803
|
-
extractUniqueValues(records, field) {
|
|
804
|
-
const values = /* @__PURE__ */ new Set();
|
|
805
|
-
for (const record of records) {
|
|
806
|
-
const value = record[field];
|
|
807
|
-
if (value !== null && value !== void 0) {
|
|
808
|
-
values.add(value);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
return Array.from(values);
|
|
812
|
-
}
|
|
813
|
-
/**
|
|
814
|
-
* Remove a relation property from records (for cleanup).
|
|
815
|
-
*
|
|
816
|
-
* @param records - Records to process
|
|
817
|
-
* @param relationName - Relation property to remove
|
|
818
|
-
* @returns Records without the specified property
|
|
819
|
-
*/
|
|
820
|
-
removeRelation(records, relationName) {
|
|
821
|
-
return records.map((record) => {
|
|
822
|
-
const {
|
|
823
|
-
[relationName]: _removed,
|
|
824
|
-
...rest
|
|
825
|
-
} = record;
|
|
826
|
-
return rest;
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
/**
|
|
830
|
-
* Flatten a nested relation into the parent record.
|
|
831
|
-
* Useful for flattening many-to-one relations.
|
|
832
|
-
*
|
|
833
|
-
* @param records - Records with nested relation
|
|
834
|
-
* @param relationName - Name of the relation to flatten
|
|
835
|
-
* @param prefix - Prefix for flattened field names
|
|
836
|
-
* @returns Records with flattened relation fields
|
|
837
|
-
*/
|
|
838
|
-
flattenRelation(records, relationName, prefix = "") {
|
|
839
|
-
return records.map((record) => {
|
|
840
|
-
const relation = record[relationName];
|
|
841
|
-
const {
|
|
842
|
-
[relationName]: _removed,
|
|
843
|
-
...rest
|
|
844
|
-
} = record;
|
|
845
|
-
if (!relation) {
|
|
846
|
-
return rest;
|
|
847
|
-
}
|
|
848
|
-
const flattenedRelation = {};
|
|
849
|
-
for (const [key, value] of Object.entries(relation)) {
|
|
850
|
-
const fieldName = prefix ? `${prefix}_${key}` : `${relationName}_${key}`;
|
|
851
|
-
flattenedRelation[fieldName] = value;
|
|
852
|
-
}
|
|
853
|
-
return {
|
|
854
|
-
...rest,
|
|
855
|
-
...flattenedRelation
|
|
856
|
-
};
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
};
|
|
860
|
-
function createResultJoiner() {
|
|
861
|
-
return new ResultJoiner();
|
|
862
|
-
}
|
|
863
|
-
|
|
864
736
|
// src/query/executor.ts
|
|
737
|
+
var MAX_RELATION_CACHE_SIZE = 100;
|
|
865
738
|
var QueryExecutor = class {
|
|
866
739
|
constructor(db, schema) {
|
|
867
740
|
this.db = db;
|
|
@@ -1133,7 +1006,7 @@ var QueryExecutor = class {
|
|
|
1133
1006
|
return idCache.get(column);
|
|
1134
1007
|
};
|
|
1135
1008
|
const relationResults = await Promise.all(relations.map(async (relation) => {
|
|
1136
|
-
const resolved = this.resolveRelationCached(parentTable, relation.name);
|
|
1009
|
+
const resolved = relation.explicitFk ? this.buildExplicitFkRelationship(parentTable, relation) : this.resolveRelationCached(parentTable, relation.name);
|
|
1137
1010
|
if (!resolved) {
|
|
1138
1011
|
console.warn(`Could not resolve relationship: ${parentTable} -> ${relation.name}`);
|
|
1139
1012
|
return {
|
|
@@ -1153,8 +1026,9 @@ var QueryExecutor = class {
|
|
|
1153
1026
|
}
|
|
1154
1027
|
const relQuery = this.buildRelatedQuery(relation, resolved, parentIds);
|
|
1155
1028
|
let records = await this.db.getAll(relQuery.sql, relQuery.params);
|
|
1029
|
+
const nestedParentTable = relation.explicitFk ? this.getTableNameFromQualified(relation.explicitFk.targetTable) : relation.name;
|
|
1156
1030
|
if (relation.relations.length > 0 && records.length > 0) {
|
|
1157
|
-
records = await this.queryAndJoinRelations(
|
|
1031
|
+
records = await this.queryAndJoinRelations(nestedParentTable, records, relation.relations);
|
|
1158
1032
|
}
|
|
1159
1033
|
return {
|
|
1160
1034
|
relation,
|
|
@@ -1186,6 +1060,43 @@ var QueryExecutor = class {
|
|
|
1186
1060
|
}
|
|
1187
1061
|
return result;
|
|
1188
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Build a ResolvedRelationship from an explicit FK specification.
|
|
1065
|
+
* This bypasses schema lookup and constructs the relationship directly.
|
|
1066
|
+
*
|
|
1067
|
+
* @param parentTable - The parent table name
|
|
1068
|
+
* @param relation - The relation with explicitFk specification
|
|
1069
|
+
* @returns ResolvedRelationship for the explicit FK
|
|
1070
|
+
*/
|
|
1071
|
+
buildExplicitFkRelationship(parentTable, relation) {
|
|
1072
|
+
const explicitFk = relation.explicitFk;
|
|
1073
|
+
const targetTable = this.getTableNameFromQualified(explicitFk.targetTable);
|
|
1074
|
+
return {
|
|
1075
|
+
type: "many-to-one",
|
|
1076
|
+
fromTable: parentTable,
|
|
1077
|
+
toTable: targetTable,
|
|
1078
|
+
foreignKey: explicitFk.sourceField,
|
|
1079
|
+
referencedColumn: explicitFk.targetColumn ?? "id"
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Extract the table name from a potentially schema-qualified identifier.
|
|
1084
|
+
* For PowerSync, tables are flattened to a single namespace, so we need
|
|
1085
|
+
* to handle both "core.Profile" -> "CoreProfile" (PowerSync alias) and
|
|
1086
|
+
* simple table names.
|
|
1087
|
+
*
|
|
1088
|
+
* @param qualifiedName - Table name, optionally schema-qualified (e.g., "core.Profile")
|
|
1089
|
+
* @returns The table name to use in queries
|
|
1090
|
+
*/
|
|
1091
|
+
getTableNameFromQualified(qualifiedName) {
|
|
1092
|
+
if (!qualifiedName.includes(".")) {
|
|
1093
|
+
return qualifiedName;
|
|
1094
|
+
}
|
|
1095
|
+
const [schema, ...tableParts] = qualifiedName.split(".");
|
|
1096
|
+
const tableName = tableParts.join(".");
|
|
1097
|
+
const pascalSchema = schema.charAt(0).toUpperCase() + schema.slice(1);
|
|
1098
|
+
return `${pascalSchema}${tableName}`;
|
|
1099
|
+
}
|
|
1189
1100
|
/**
|
|
1190
1101
|
* Get parent IDs needed for a relation query.
|
|
1191
1102
|
*/
|
|
@@ -1204,7 +1115,8 @@ var QueryExecutor = class {
|
|
|
1204
1115
|
buildRelatedQuery(relation, resolved, parentIds) {
|
|
1205
1116
|
const filterColumn = resolved.type === "one-to-many" ? resolved.foreignKey : resolved.referencedColumn;
|
|
1206
1117
|
const uniqueIds = Array.from(new Set(parentIds));
|
|
1207
|
-
|
|
1118
|
+
const tableName = relation.explicitFk ? resolved.toTable : relation.name;
|
|
1119
|
+
return this.builder.buildRelationQuery(tableName, filterColumn, uniqueIds, relation.columns);
|
|
1208
1120
|
}
|
|
1209
1121
|
/**
|
|
1210
1122
|
* Get the relationship resolver (for advanced use).
|
|
@@ -1226,6 +1138,7 @@ var QueryExecutor = class {
|
|
|
1226
1138
|
}
|
|
1227
1139
|
/**
|
|
1228
1140
|
* Resolve a relationship with caching.
|
|
1141
|
+
* Uses true LRU eviction: accessed items are moved to the end of the Map.
|
|
1229
1142
|
*
|
|
1230
1143
|
* @param fromTable - The source table
|
|
1231
1144
|
* @param toTable - The target table/relation name
|
|
@@ -1233,10 +1146,19 @@ var QueryExecutor = class {
|
|
|
1233
1146
|
*/
|
|
1234
1147
|
resolveRelationCached(fromTable, toTable) {
|
|
1235
1148
|
const key = `${fromTable}:${toTable}`;
|
|
1236
|
-
if (
|
|
1237
|
-
this.relationCache.
|
|
1238
|
-
|
|
1239
|
-
|
|
1149
|
+
if (this.relationCache.has(key)) {
|
|
1150
|
+
const value = this.relationCache.get(key);
|
|
1151
|
+
this.relationCache.delete(key);
|
|
1152
|
+
this.relationCache.set(key, value);
|
|
1153
|
+
return value;
|
|
1154
|
+
}
|
|
1155
|
+
if (this.relationCache.size >= MAX_RELATION_CACHE_SIZE) {
|
|
1156
|
+
const firstKey = this.relationCache.keys().next().value;
|
|
1157
|
+
if (firstKey) this.relationCache.delete(firstKey);
|
|
1158
|
+
}
|
|
1159
|
+
const resolved = this.resolver.resolve(fromTable, toTable);
|
|
1160
|
+
this.relationCache.set(key, resolved);
|
|
1161
|
+
return resolved;
|
|
1240
1162
|
}
|
|
1241
1163
|
};
|
|
1242
1164
|
function createQueryExecutor(db, schema) {
|
|
@@ -1244,20 +1166,13 @@ function createQueryExecutor(db, schema) {
|
|
|
1244
1166
|
}
|
|
1245
1167
|
|
|
1246
1168
|
export {
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
stringifySelect,
|
|
1250
|
-
extractColumnNames,
|
|
1251
|
-
extractRelationNames,
|
|
1252
|
-
hasRelation,
|
|
1253
|
-
getRelationSelect,
|
|
1169
|
+
ResultJoiner,
|
|
1170
|
+
createResultJoiner,
|
|
1254
1171
|
RelationshipResolver,
|
|
1255
1172
|
createRelationshipResolver,
|
|
1256
1173
|
SQLBuilder,
|
|
1257
1174
|
createSQLBuilder,
|
|
1258
|
-
ResultJoiner,
|
|
1259
|
-
createResultJoiner,
|
|
1260
1175
|
QueryExecutor,
|
|
1261
1176
|
createQueryExecutor
|
|
1262
1177
|
};
|
|
1263
|
-
//# sourceMappingURL=chunk-
|
|
1178
|
+
//# sourceMappingURL=chunk-YRIPM2AN.js.map
|