@pol-studios/db 1.0.55 → 1.0.57
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 +4 -2
- package/dist/auth/hooks.js +5 -3
- package/dist/auth/index.js +5 -3
- package/dist/{chunk-7YGDT46S.js → chunk-4EO55YV2.js} +10 -7
- package/dist/chunk-4EO55YV2.js.map +1 -0
- package/dist/{chunk-WILXD5X3.js → chunk-6SDH7M7J.js} +25 -9
- package/dist/chunk-6SDH7M7J.js.map +1 -0
- package/dist/{chunk-OC6S4XFL.js → chunk-DDL63KLQ.js} +385 -104
- package/dist/chunk-DDL63KLQ.js.map +1 -0
- package/dist/{chunk-WSKBZIEI.js → chunk-GWYTROSD.js} +95 -1
- package/dist/chunk-GWYTROSD.js.map +1 -0
- package/dist/{chunk-BRFFTGVJ.js → chunk-MEBT5YHA.js} +2 -2
- package/dist/chunk-QYAFI34Q.js +64 -0
- package/dist/chunk-QYAFI34Q.js.map +1 -0
- package/dist/{chunk-AA3VUWTP.js → chunk-VSJKGPRI.js} +12 -8
- package/dist/chunk-VSJKGPRI.js.map +1 -0
- package/dist/{chunk-AKCNWHXV.js → chunk-VYFAMTHI.js} +2 -2
- package/dist/chunk-W7PERM66.js +215 -0
- package/dist/chunk-W7PERM66.js.map +1 -0
- package/dist/{chunk-HZIVE5AZ.js → chunk-YRIPM2AN.js} +253 -338
- package/dist/chunk-YRIPM2AN.js.map +1 -0
- 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 +10 -8
- package/dist/index.native.d.ts +6 -6
- package/dist/index.native.js +10 -8
- package/dist/index.web.d.ts +10 -9
- package/dist/index.web.js +23 -12
- package/dist/index.web.js.map +1 -1
- package/dist/powersync-bridge/index.d.ts +1 -1
- package/dist/query/index.d.ts +4 -83
- package/dist/query/index.js +13 -3
- package/dist/realtime/index.d.ts +80 -1
- package/dist/realtime/index.js +11 -9
- 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/{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 +7 -5
- package/dist/with-auth/index.js.map +1 -1
- package/package.json +5 -2
- package/dist/chunk-7YGDT46S.js.map +0 -1
- package/dist/chunk-AA3VUWTP.js.map +0 -1
- package/dist/chunk-HZIVE5AZ.js.map +0 -1
- package/dist/chunk-OC6S4XFL.js.map +0 -1
- package/dist/chunk-WILXD5X3.js.map +0 -1
- package/dist/chunk-WSKBZIEI.js.map +0 -1
- /package/dist/{chunk-BRFFTGVJ.js.map → chunk-MEBT5YHA.js.map} +0 -0
- /package/dist/{chunk-AKCNWHXV.js.map → chunk-VYFAMTHI.js.map} +0 -0
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useDataLayerCore,
|
|
7
7
|
useDataLayerStatus,
|
|
8
8
|
useDbQuery
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GWYTROSD.js";
|
|
10
10
|
import {
|
|
11
11
|
getSupabaseUrl
|
|
12
12
|
} from "./chunk-GC3TBUWE.js";
|
|
@@ -1239,4 +1239,4 @@ export {
|
|
|
1239
1239
|
useSyncControl,
|
|
1240
1240
|
useOnlineStatus
|
|
1241
1241
|
};
|
|
1242
|
-
//# sourceMappingURL=chunk-
|
|
1242
|
+
//# sourceMappingURL=chunk-VYFAMTHI.js.map
|
|
@@ -0,0 +1,215 @@
|
|
|
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);
|
|
17
|
+
}
|
|
18
|
+
current = "";
|
|
19
|
+
} else {
|
|
20
|
+
current += char;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
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;
|
|
64
|
+
}
|
|
65
|
+
const explicitFkMatch = trimmedToken.match(/^(\w+)!(\w+):([\w.]+)\((.+)\)$/);
|
|
66
|
+
if (explicitFkMatch) {
|
|
67
|
+
const [, alias, sourceField, targetTable, innerSelect] = explicitFkMatch;
|
|
68
|
+
const innerParsed = parseSelect(innerSelect);
|
|
69
|
+
result.relations.push({
|
|
70
|
+
name: targetTable,
|
|
71
|
+
// Use target table as the relation name
|
|
72
|
+
alias,
|
|
73
|
+
columns: innerParsed.columns,
|
|
74
|
+
relations: innerParsed.relations,
|
|
75
|
+
explicitFk: {
|
|
76
|
+
sourceField,
|
|
77
|
+
targetTable
|
|
78
|
+
// targetColumn defaults to "id" - handled at resolution time
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const relationMatch = trimmedToken.match(/^(?:(\w+):)?(\w+)\((.+)\)$/);
|
|
84
|
+
if (relationMatch) {
|
|
85
|
+
const alias = relationMatch[1];
|
|
86
|
+
const name = relationMatch[2];
|
|
87
|
+
const innerSelect = relationMatch[3];
|
|
88
|
+
const innerParsed = parseSelect(innerSelect);
|
|
89
|
+
result.relations.push({
|
|
90
|
+
name,
|
|
91
|
+
alias,
|
|
92
|
+
columns: innerParsed.columns,
|
|
93
|
+
relations: innerParsed.relations
|
|
94
|
+
});
|
|
95
|
+
} else if (trimmedToken === "*") {
|
|
96
|
+
result.columns = "*";
|
|
97
|
+
} else {
|
|
98
|
+
const column = parseColumnToken(trimmedToken);
|
|
99
|
+
if (result.columns !== "*") {
|
|
100
|
+
result.columns.push(column);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(result.columns) && result.columns.length === 0) {
|
|
105
|
+
result.columns = "*";
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
function stringifySelect(parsed) {
|
|
110
|
+
const parts = [];
|
|
111
|
+
if (parsed.columns === "*") {
|
|
112
|
+
parts.push("*");
|
|
113
|
+
} else {
|
|
114
|
+
for (const col of parsed.columns) {
|
|
115
|
+
if (col.alias) {
|
|
116
|
+
parts.push(`${col.alias}:${col.name}`);
|
|
117
|
+
} else {
|
|
118
|
+
parts.push(col.name);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const rel of parsed.relations) {
|
|
123
|
+
const innerStr = stringifySelect({
|
|
124
|
+
columns: rel.columns,
|
|
125
|
+
relations: rel.relations
|
|
126
|
+
});
|
|
127
|
+
if (rel.explicitFk) {
|
|
128
|
+
parts.push(`${rel.alias}!${rel.explicitFk.sourceField}:${rel.explicitFk.targetTable}(${innerStr})`);
|
|
129
|
+
} else if (rel.alias) {
|
|
130
|
+
parts.push(`${rel.alias}:${rel.name}(${innerStr})`);
|
|
131
|
+
} else {
|
|
132
|
+
parts.push(`${rel.name}(${innerStr})`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return parts.join(", ");
|
|
136
|
+
}
|
|
137
|
+
function extractColumnNames(parsed) {
|
|
138
|
+
if (parsed.columns === "*") {
|
|
139
|
+
return "*";
|
|
140
|
+
}
|
|
141
|
+
return parsed.columns.map((col) => col.name);
|
|
142
|
+
}
|
|
143
|
+
function extractRelationNames(parsed) {
|
|
144
|
+
return parsed.relations.map((rel) => rel.alias ?? rel.name);
|
|
145
|
+
}
|
|
146
|
+
function hasRelation(parsed, relationName) {
|
|
147
|
+
return parsed.relations.some((rel) => rel.name === relationName || rel.alias === relationName);
|
|
148
|
+
}
|
|
149
|
+
function getRelationSelect(parsed, relationName) {
|
|
150
|
+
return parsed.relations.find((rel) => rel.name === relationName || rel.alias === relationName);
|
|
151
|
+
}
|
|
152
|
+
function hasExplicitFk(parsed) {
|
|
153
|
+
for (const relation of parsed.relations) {
|
|
154
|
+
if (relation.explicitFk) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
if (relation.relations.length > 0 && hasExplicitFk({
|
|
158
|
+
columns: relation.columns,
|
|
159
|
+
relations: relation.relations
|
|
160
|
+
})) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
function selectHasExplicitFkSyntax(select) {
|
|
167
|
+
return /\w+!\w+:[\w.]+\(/.test(select);
|
|
168
|
+
}
|
|
169
|
+
function extractExplicitFkRelations(parsed) {
|
|
170
|
+
const results = [];
|
|
171
|
+
function collectFromRelations(relations) {
|
|
172
|
+
for (const relation of relations) {
|
|
173
|
+
if (relation.explicitFk) {
|
|
174
|
+
results.push({
|
|
175
|
+
alias: relation.alias ?? relation.name,
|
|
176
|
+
sourceField: relation.explicitFk.sourceField,
|
|
177
|
+
targetTable: relation.explicitFk.targetTable,
|
|
178
|
+
targetColumn: relation.explicitFk.targetColumn ?? "id"
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (relation.relations.length > 0) {
|
|
182
|
+
collectFromRelations(relation.relations);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
collectFromRelations(parsed.relations);
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
function extractExplicitFkTables(select) {
|
|
190
|
+
if (!selectHasExplicitFkSyntax(select)) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const parsed = parseSelect(select);
|
|
194
|
+
const explicitFks = extractExplicitFkRelations(parsed);
|
|
195
|
+
const uniqueTables = /* @__PURE__ */ new Set();
|
|
196
|
+
for (const fk of explicitFks) {
|
|
197
|
+
uniqueTables.add(fk.targetTable);
|
|
198
|
+
}
|
|
199
|
+
return Array.from(uniqueTables);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export {
|
|
203
|
+
tokenizeTopLevel,
|
|
204
|
+
parseSelect,
|
|
205
|
+
stringifySelect,
|
|
206
|
+
extractColumnNames,
|
|
207
|
+
extractRelationNames,
|
|
208
|
+
hasRelation,
|
|
209
|
+
getRelationSelect,
|
|
210
|
+
hasExplicitFk,
|
|
211
|
+
selectHasExplicitFkSyntax,
|
|
212
|
+
extractExplicitFkRelations,
|
|
213
|
+
extractExplicitFkTables
|
|
214
|
+
};
|
|
215
|
+
//# sourceMappingURL=chunk-W7PERM66.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/query/select-parser.ts"],"sourcesContent":["/**\n * Select String Parser\n *\n * Parses Supabase PostgREST select syntax into an AST for local SQLite processing.\n *\n * Supported Syntax:\n * - \"*\" -> All columns\n * - \"id, name, status\" -> Specific columns\n * - \"*, RelatedTable(*)\" -> All columns + relation\n * - \"id, RelatedTable(id, name)\" -> Specific + relation with specific columns\n * - \"*, Parent(*, Grandparent(name))\" -> Nested relations\n * - \"*, items:RelatedTable(*)\" -> Aliased relation\n * - \"aliasName:columnName\" -> Aliased column\n */\n\nimport type { ParsedSelect, SelectColumn, SelectRelation } from \"../core/types\";\n\n/**\n * Tokenize a string at the top level, respecting parentheses nesting.\n * Splits by comma but keeps nested parentheses intact.\n *\n * @example\n * tokenizeTopLevel(\"id, name, Parent(*, Child(name))\")\n * // Returns: [\"id\", \"name\", \"Parent(*, Child(name))\"]\n */\nfunction tokenizeTopLevel(input: string): string[] {\n const tokens: string[] = [];\n let current = \"\";\n let depth = 0;\n for (const char of input) {\n if (char === \"(\") {\n depth++;\n current += char;\n } else if (char === \")\") {\n depth--;\n current += char;\n } else if (char === \",\" && depth === 0) {\n const trimmed = current.trim();\n if (trimmed) {\n tokens.push(trimmed);\n }\n current = \"\";\n } else {\n current += char;\n }\n }\n\n // Add the last token\n const trimmed = current.trim();\n if (trimmed) {\n tokens.push(trimmed);\n }\n return tokens;\n}\n\n/**\n * Parse a single column token that may have an alias.\n *\n * @example\n * parseColumnToken(\"aliasName:columnName\") -> { name: \"columnName\", alias: \"aliasName\" }\n * parseColumnToken(\"columnName\") -> { name: \"columnName\" }\n */\nfunction parseColumnToken(token: string): SelectColumn {\n // Check for alias format: alias:column\n const aliasMatch = token.match(/^(\\w+):(\\w+)$/);\n if (aliasMatch) {\n return {\n name: aliasMatch[2],\n alias: aliasMatch[1]\n };\n }\n return {\n name: token\n };\n}\n\n/**\n * Parse a Supabase PostgREST select string into an AST.\n *\n * @param select - The select string (e.g., \"*, RelatedTable(*)\")\n * @returns Parsed select AST\n *\n * @example\n * parseSelect(\"*\")\n * // Returns: { columns: \"*\", relations: [] }\n *\n * parseSelect(\"id, name, status\")\n * // Returns: {\n * // columns: [{ name: \"id\" }, { name: \"name\" }, { name: \"status\" }],\n * // relations: []\n * // }\n *\n * parseSelect(\"*, EquipmentFixture(*)\")\n * // Returns: {\n * // columns: \"*\",\n * // relations: [{ name: \"EquipmentFixture\", columns: \"*\", relations: [] }]\n * // }\n *\n * parseSelect(\"id, name, ProjectDatabase(name, Organization(*))\")\n * // Returns: {\n * // columns: [{ name: \"id\" }, { name: \"name\" }],\n * // relations: [{\n * // name: \"ProjectDatabase\",\n * // columns: [{ name: \"name\" }],\n * // relations: [{ name: \"Organization\", columns: \"*\", relations: [] }]\n * // }]\n * // }\n */\nexport function parseSelect(select: string): ParsedSelect {\n const trimmed = select.trim();\n\n // Handle simple wildcard\n if (trimmed === \"*\") {\n return {\n columns: \"*\",\n relations: []\n };\n }\n\n // Handle empty string\n if (!trimmed) {\n return {\n columns: \"*\",\n relations: []\n };\n }\n const result: ParsedSelect = {\n columns: [],\n relations: []\n };\n\n // Tokenize at top level (respecting parentheses)\n const tokens = tokenizeTopLevel(trimmed);\n for (const token of tokens) {\n const trimmedToken = token.trim();\n if (!trimmedToken) {\n continue;\n }\n\n // Check if it's an explicit FK relation: Alias!sourceField:targetTable(...)\n // Pattern: Word!word:word.word(...) or Word!word:word(...)\n const explicitFkMatch = trimmedToken.match(/^(\\w+)!(\\w+):([\\w.]+)\\((.+)\\)$/);\n if (explicitFkMatch) {\n const [, alias, sourceField, targetTable, innerSelect] = explicitFkMatch;\n\n // Recursively parse inner select\n const innerParsed = parseSelect(innerSelect);\n result.relations.push({\n name: targetTable,\n // Use target table as the relation name\n alias,\n columns: innerParsed.columns,\n relations: innerParsed.relations,\n explicitFk: {\n sourceField,\n targetTable\n // targetColumn defaults to \"id\" - handled at resolution time\n }\n });\n continue;\n }\n\n // Check if it's a standard relation: Name(...) or alias:Name(...)\n // Regex: optional alias followed by table name and parentheses with content\n const relationMatch = trimmedToken.match(/^(?:(\\w+):)?(\\w+)\\((.+)\\)$/);\n if (relationMatch) {\n const alias = relationMatch[1];\n const name = relationMatch[2];\n const innerSelect = relationMatch[3];\n\n // Recursively parse inner select\n const innerParsed = parseSelect(innerSelect);\n result.relations.push({\n name,\n alias,\n columns: innerParsed.columns,\n relations: innerParsed.relations\n });\n } else if (trimmedToken === \"*\") {\n // Wildcard - all columns\n result.columns = \"*\";\n } else {\n // It's a column, possibly with alias\n const column = parseColumnToken(trimmedToken);\n\n // Only add to columns array if we haven't set \"*\" already\n if (result.columns !== \"*\") {\n (result.columns as SelectColumn[]).push(column);\n }\n }\n }\n\n // If no explicit columns were added but we have relations, default to \"*\"\n if (Array.isArray(result.columns) && result.columns.length === 0) {\n result.columns = \"*\";\n }\n return result;\n}\n\n/**\n * Convert a parsed select back to a string representation (for debugging)\n */\nexport function stringifySelect(parsed: ParsedSelect): string {\n const parts: string[] = [];\n\n // Add columns\n if (parsed.columns === \"*\") {\n parts.push(\"*\");\n } else {\n for (const col of parsed.columns) {\n if (col.alias) {\n parts.push(`${col.alias}:${col.name}`);\n } else {\n parts.push(col.name);\n }\n }\n }\n\n // Add relations\n for (const rel of parsed.relations) {\n const innerStr = stringifySelect({\n columns: rel.columns,\n relations: rel.relations\n });\n if (rel.explicitFk) {\n // Explicit FK syntax: Alias!sourceField:targetTable(...)\n parts.push(`${rel.alias}!${rel.explicitFk.sourceField}:${rel.explicitFk.targetTable}(${innerStr})`);\n } else if (rel.alias) {\n parts.push(`${rel.alias}:${rel.name}(${innerStr})`);\n } else {\n parts.push(`${rel.name}(${innerStr})`);\n }\n }\n return parts.join(\", \");\n}\n\n/**\n * Extract all column names from a parsed select (for building SQL queries)\n * Does not include relation names.\n */\nexport function extractColumnNames(parsed: ParsedSelect): string[] | \"*\" {\n if (parsed.columns === \"*\") {\n return \"*\";\n }\n return parsed.columns.map(col => col.name);\n}\n\n/**\n * Extract all relation names from a parsed select\n */\nexport function extractRelationNames(parsed: ParsedSelect): string[] {\n return parsed.relations.map(rel => rel.alias ?? rel.name);\n}\n\n/**\n * Check if a select string references a specific relation\n */\nexport function hasRelation(parsed: ParsedSelect, relationName: string): boolean {\n return parsed.relations.some(rel => rel.name === relationName || rel.alias === relationName);\n}\n\n/**\n * Get the select configuration for a specific relation\n */\nexport function getRelationSelect(parsed: ParsedSelect, relationName: string): SelectRelation | undefined {\n return parsed.relations.find(rel => rel.name === relationName || rel.alias === relationName);\n}\n\n/**\n * Check if a parsed select contains any explicit FK relations.\n * This is used to determine if QueryExecutor should be used instead of PostgREST.\n *\n * @param parsed - The parsed select statement\n * @returns true if any relation uses explicit FK syntax\n */\nexport function hasExplicitFk(parsed: ParsedSelect): boolean {\n for (const relation of parsed.relations) {\n if (relation.explicitFk) {\n return true;\n }\n // Check nested relations recursively\n if (relation.relations.length > 0 && hasExplicitFk({\n columns: relation.columns,\n relations: relation.relations\n })) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Check if a select string contains explicit FK syntax.\n * Quick check without full parsing for performance.\n *\n * @param select - The select string\n * @returns true if the string contains explicit FK syntax (! followed by field:table)\n */\nexport function selectHasExplicitFkSyntax(select: string): boolean {\n // Quick regex check for the explicit FK pattern: word!word:word\n return /\\w+!\\w+:[\\w.]+\\(/.test(select);\n}\n\n/**\n * Information about an explicit FK relation extracted from a select string.\n * Used for setting up realtime subscriptions on related tables.\n */\nexport interface ExtractedExplicitFk {\n /** The alias used for this relation in the select string (e.g., \"Sender\") */\n alias: string;\n /** The FK column on the parent table (e.g., \"senderId\") */\n sourceField: string;\n /** The target table, optionally schema-qualified (e.g., \"core.Profile\") */\n targetTable: string;\n /** Column to match in target table, defaults to \"id\" */\n targetColumn: string;\n}\n\n/**\n * Extract all explicit FK relations from a parsed select statement.\n * Recursively traverses the relation tree to find all explicit FK specs.\n *\n * @param parsed - The parsed select statement\n * @returns Array of extracted explicit FK relations\n *\n * @example\n * const parsed = parseSelect(\"*, Sender!senderId:core.Profile(*), Recipient!recipientId:core.Profile(*)\");\n * const explicitFks = extractExplicitFkRelations(parsed);\n * // Returns:\n * // [\n * // { alias: \"Sender\", sourceField: \"senderId\", targetTable: \"core.Profile\", targetColumn: \"id\" },\n * // { alias: \"Recipient\", sourceField: \"recipientId\", targetTable: \"core.Profile\", targetColumn: \"id\" }\n * // ]\n */\nexport function extractExplicitFkRelations(parsed: ParsedSelect): ExtractedExplicitFk[] {\n const results: ExtractedExplicitFk[] = [];\n function collectFromRelations(relations: SelectRelation[]): void {\n for (const relation of relations) {\n if (relation.explicitFk) {\n results.push({\n alias: relation.alias ?? relation.name,\n sourceField: relation.explicitFk.sourceField,\n targetTable: relation.explicitFk.targetTable,\n targetColumn: relation.explicitFk.targetColumn ?? \"id\"\n });\n }\n // Recursively check nested relations\n if (relation.relations.length > 0) {\n collectFromRelations(relation.relations);\n }\n }\n }\n collectFromRelations(parsed.relations);\n return results;\n}\n\n/**\n * Extract all unique target table names from explicit FK relations in a select string.\n * This is useful for setting up realtime watchers on related tables.\n *\n * @param select - The select string (e.g., \"*, Sender!senderId:core.Profile(*)\")\n * @returns Array of unique target table names\n *\n * @example\n * extractExplicitFkTables(\"*, Sender!senderId:core.Profile(*), Recipient!recipientId:core.Profile(*)\")\n * // Returns: [\"core.Profile\"]\n *\n * extractExplicitFkTables(\"*, Author!authorId:User(*), Editor!editorId:Admin(*)\")\n * // Returns: [\"User\", \"Admin\"]\n */\nexport function extractExplicitFkTables(select: string): string[] {\n // Quick check - if no explicit FK syntax, return empty array\n if (!selectHasExplicitFkSyntax(select)) {\n return [];\n }\n const parsed = parseSelect(select);\n const explicitFks = extractExplicitFkRelations(parsed);\n\n // Extract unique table names\n const uniqueTables = new Set<string>();\n for (const fk of explicitFks) {\n uniqueTables.add(fk.targetTable);\n }\n return Array.from(uniqueTables);\n}\n\n// Export the helper function for testing\nexport { tokenizeTopLevel };"],"mappings":"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","names":["trimmed"]}
|