@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.
Files changed (77) hide show
  1. package/dist/{DataLayerContext-C7cJtiO8.d.ts → DataLayerContext-BYZtDD0g.d.ts} +1 -1
  2. package/dist/auth/context.js +6 -4
  3. package/dist/auth/hooks.js +7 -5
  4. package/dist/auth/index.js +7 -5
  5. package/dist/{chunk-FIAXWEBK.js → chunk-4EO55YV2.js} +10 -7
  6. package/dist/chunk-4EO55YV2.js.map +1 -0
  7. package/dist/{chunk-DP3YEVSX.js → chunk-6SDH7M7J.js} +26 -10
  8. package/dist/chunk-6SDH7M7J.js.map +1 -0
  9. package/dist/{chunk-UJWETW36.js → chunk-AKIRHA4Q.js} +527 -418
  10. package/dist/chunk-AKIRHA4Q.js.map +1 -0
  11. package/dist/{chunk-2XS2PM62.js → chunk-DDL63KLQ.js} +388 -107
  12. package/dist/chunk-DDL63KLQ.js.map +1 -0
  13. package/dist/{chunk-YA6MUTA7.js → chunk-FI6JAD5G.js} +3 -3
  14. package/dist/{chunk-WQLIGVQR.js → chunk-GWYTROSD.js} +98 -1
  15. package/dist/chunk-GWYTROSD.js.map +1 -0
  16. package/dist/chunk-JOULSXOI.js +415 -0
  17. package/dist/chunk-JOULSXOI.js.map +1 -0
  18. package/dist/{chunk-OKYHI6JG.js → chunk-LF3V3ERS.js} +3 -3
  19. package/dist/{chunk-FMYXG4VN.js → chunk-MEBT5YHA.js} +2 -2
  20. package/dist/{chunk-BZSAPFFB.js → chunk-N4KK5G5T.js} +116 -18
  21. package/dist/chunk-N4KK5G5T.js.map +1 -0
  22. package/dist/chunk-QYAFI34Q.js +64 -0
  23. package/dist/chunk-QYAFI34Q.js.map +1 -0
  24. package/dist/{chunk-3Q74DK5K.js → chunk-VYFAMTHI.js} +2 -2
  25. package/dist/chunk-W7PERM66.js +215 -0
  26. package/dist/chunk-W7PERM66.js.map +1 -0
  27. package/dist/{chunk-ZGQ7Q4ZU.js → chunk-WM25QE7E.js} +2 -2
  28. package/dist/{chunk-HZIVE5AZ.js → chunk-YRIPM2AN.js} +253 -338
  29. package/dist/chunk-YRIPM2AN.js.map +1 -0
  30. package/dist/chunk-YUX6RGLZ.js +1858 -0
  31. package/dist/chunk-YUX6RGLZ.js.map +1 -0
  32. package/dist/{chunk-Z3EJX3VG.js → chunk-Z456IHCB.js} +3 -3
  33. package/dist/core/index.d.ts +24 -1
  34. package/dist/{executor-YJw4m7Q7.d.ts → executor-D15yjeMo.d.ts} +20 -0
  35. package/dist/hooks/index.d.ts +3 -3
  36. package/dist/hooks/index.js +4 -2
  37. package/dist/{index-jVYdTeWx.d.ts → index-CFUuTzXO.d.ts} +1 -1
  38. package/dist/index.d.ts +5 -5
  39. package/dist/index.js +16 -14
  40. package/dist/index.native.d.ts +62 -8
  41. package/dist/index.native.js +16 -14
  42. package/dist/index.web.d.ts +10 -9
  43. package/dist/index.web.js +30 -19
  44. package/dist/index.web.js.map +1 -1
  45. package/dist/mutation/index.js +3 -3
  46. package/dist/parser/index.js +3 -3
  47. package/dist/powersync-bridge/index.d.ts +1 -1
  48. package/dist/query/index.d.ts +4 -83
  49. package/dist/query/index.js +17 -7
  50. package/dist/realtime/index.d.ts +80 -1
  51. package/dist/realtime/index.js +14 -12
  52. package/dist/realtime/index.js.map +1 -1
  53. package/dist/select-parser-BAV7fOaM.d.ts +144 -0
  54. package/dist/types/index.d.ts +3 -3
  55. package/dist/types/index.js +4 -4
  56. package/dist/{useDbCount-DHLJzmkO.d.ts → useDbCount-Ckb-FhZk.d.ts} +1 -1
  57. package/dist/{useResolveFeedback-B0UcYWVI.d.ts → useResolveFeedback-CuUkdHoR.d.ts} +13 -29
  58. package/dist/with-auth/index.js +9 -7
  59. package/dist/with-auth/index.js.map +1 -1
  60. package/package.json +9 -4
  61. package/dist/chunk-2XS2PM62.js.map +0 -1
  62. package/dist/chunk-BZSAPFFB.js.map +0 -1
  63. package/dist/chunk-CTRY7JDP.js +0 -4112
  64. package/dist/chunk-CTRY7JDP.js.map +0 -1
  65. package/dist/chunk-DP3YEVSX.js.map +0 -1
  66. package/dist/chunk-FIAXWEBK.js.map +0 -1
  67. package/dist/chunk-HZIVE5AZ.js.map +0 -1
  68. package/dist/chunk-INEUG6MC.js +0 -521
  69. package/dist/chunk-INEUG6MC.js.map +0 -1
  70. package/dist/chunk-UJWETW36.js.map +0 -1
  71. package/dist/chunk-WQLIGVQR.js.map +0 -1
  72. /package/dist/{chunk-YA6MUTA7.js.map → chunk-FI6JAD5G.js.map} +0 -0
  73. /package/dist/{chunk-OKYHI6JG.js.map → chunk-LF3V3ERS.js.map} +0 -0
  74. /package/dist/{chunk-FMYXG4VN.js.map → chunk-MEBT5YHA.js.map} +0 -0
  75. /package/dist/{chunk-3Q74DK5K.js.map → chunk-VYFAMTHI.js.map} +0 -0
  76. /package/dist/{chunk-ZGQ7Q4ZU.js.map → chunk-WM25QE7E.js.map} +0 -0
  77. /package/dist/{chunk-Z3EJX3VG.js.map → chunk-Z456IHCB.js.map} +0 -0
@@ -1,133 +1,211 @@
1
- // src/query/select-parser.ts
2
- function tokenizeTopLevel(input) {
3
- const tokens = [];
4
- let current = "";
5
- let depth = 0;
6
- for (const char of input) {
7
- if (char === "(") {
8
- depth++;
9
- current += char;
10
- } else if (char === ")") {
11
- depth--;
12
- current += char;
13
- } else if (char === "," && depth === 0) {
14
- const trimmed2 = current.trim();
15
- if (trimmed2) {
16
- tokens.push(trimmed2);
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
- current = "";
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
- current += char;
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
- const trimmed = current.trim();
24
- if (trimmed) {
25
- tokens.push(trimmed);
26
- }
27
- return tokens;
28
- }
29
- function parseColumnToken(token) {
30
- const aliasMatch = token.match(/^(\w+):(\w+)$/);
31
- if (aliasMatch) {
32
- return {
33
- name: aliasMatch[2],
34
- alias: aliasMatch[1]
35
- };
36
- }
37
- return {
38
- name: token
39
- };
40
- }
41
- function parseSelect(select) {
42
- const trimmed = select.trim();
43
- if (trimmed === "*") {
44
- return {
45
- columns: "*",
46
- relations: []
47
- };
48
- }
49
- if (!trimmed) {
50
- return {
51
- columns: "*",
52
- relations: []
53
- };
54
- }
55
- const result = {
56
- columns: [],
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
- const relationMatch = trimmedToken.match(/^(?:(\w+):)?(\w+)\((.+)\)$/);
66
- if (relationMatch) {
67
- const alias = relationMatch[1];
68
- const name = relationMatch[2];
69
- const innerSelect = relationMatch[3];
70
- const innerParsed = parseSelect(innerSelect);
71
- result.relations.push({
72
- name,
73
- alias,
74
- columns: innerParsed.columns,
75
- relations: innerParsed.relations
76
- });
77
- } else if (trimmedToken === "*") {
78
- result.columns = "*";
79
- } else {
80
- const column = parseColumnToken(trimmedToken);
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
- if (Array.isArray(result.columns) && result.columns.length === 0) {
87
- result.columns = "*";
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
- return result;
90
- }
91
- function stringifySelect(parsed) {
92
- const parts = [];
93
- if (parsed.columns === "*") {
94
- parts.push("*");
95
- } else {
96
- for (const col of parsed.columns) {
97
- if (col.alias) {
98
- parts.push(`${col.alias}:${col.name}`);
99
- } else {
100
- parts.push(col.name);
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
- for (const rel of parsed.relations) {
105
- const innerStr = stringifySelect({
106
- columns: rel.columns,
107
- relations: rel.relations
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
- return parts.join(", ");
116
- }
117
- function extractColumnNames(parsed) {
118
- if (parsed.columns === "*") {
119
- return "*";
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
- return parsed.columns.map((col) => col.name);
122
- }
123
- function extractRelationNames(parsed) {
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(relation.name, records, relation.relations);
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
- return this.builder.buildRelationQuery(relation.name, filterColumn, uniqueIds, relation.columns);
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 (!this.relationCache.has(key)) {
1237
- this.relationCache.set(key, this.resolver.resolve(fromTable, toTable));
1238
- }
1239
- return this.relationCache.get(key);
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
- tokenizeTopLevel,
1248
- parseSelect,
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-HZIVE5AZ.js.map
1178
+ //# sourceMappingURL=chunk-YRIPM2AN.js.map