@pol-studios/db 1.0.9 → 1.0.10
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/auth/context.js +21 -12786
- package/dist/auth/context.js.map +1 -1
- package/dist/auth/guards.js +12 -7640
- package/dist/auth/guards.js.map +1 -1
- package/dist/auth/hooks.js +25 -10591
- package/dist/auth/hooks.js.map +1 -1
- package/dist/auth/index.js +43 -13008
- package/dist/auth/index.js.map +1 -1
- package/dist/canvas-75Y7XMF3.js +1541 -0
- package/dist/canvas-75Y7XMF3.js.map +1 -0
- package/dist/chunk-2IFGILT3.js +532 -0
- package/dist/chunk-2IFGILT3.js.map +1 -0
- package/dist/chunk-3M2U6TXH.js +928 -0
- package/dist/chunk-3M2U6TXH.js.map +1 -0
- package/dist/chunk-3PJTNH2L.js +2778 -0
- package/dist/chunk-3PJTNH2L.js.map +1 -0
- package/dist/chunk-5ZYAEGCJ.js +416 -0
- package/dist/chunk-5ZYAEGCJ.js.map +1 -0
- package/dist/chunk-7HG6G25H.js +710 -0
- package/dist/chunk-7HG6G25H.js.map +1 -0
- package/dist/chunk-7XT7K4QT.js +2687 -0
- package/dist/chunk-7XT7K4QT.js.map +1 -0
- package/dist/chunk-AWFMICFV.js +158 -0
- package/dist/chunk-AWFMICFV.js.map +1 -0
- package/dist/chunk-BRTW7CO5.js +1467 -0
- package/dist/chunk-BRTW7CO5.js.map +1 -0
- package/dist/chunk-EL45Z26M.js +4194 -0
- package/dist/chunk-EL45Z26M.js.map +1 -0
- package/dist/chunk-ERGF2FCE.js +903 -0
- package/dist/chunk-ERGF2FCE.js.map +1 -0
- package/dist/chunk-GK7B66LY.js +135 -0
- package/dist/chunk-GK7B66LY.js.map +1 -0
- package/dist/chunk-GQI6WJGI.js +172 -0
- package/dist/chunk-GQI6WJGI.js.map +1 -0
- package/dist/chunk-H6365JPC.js +1858 -0
- package/dist/chunk-H6365JPC.js.map +1 -0
- package/dist/chunk-J4ZVCXZ4.js +1 -0
- package/dist/chunk-J4ZVCXZ4.js.map +1 -0
- package/dist/chunk-JUVE3DWY.js +433 -0
- package/dist/chunk-JUVE3DWY.js.map +1 -0
- package/dist/chunk-O3K7R32P.js +7555 -0
- package/dist/chunk-O3K7R32P.js.map +1 -0
- package/dist/chunk-P4UZ7IXC.js +42 -0
- package/dist/chunk-P4UZ7IXC.js.map +1 -0
- package/dist/chunk-SEY5UO2T.js +89 -0
- package/dist/chunk-SEY5UO2T.js.map +1 -0
- package/dist/chunk-USJYMRUO.js +86 -0
- package/dist/chunk-USJYMRUO.js.map +1 -0
- package/dist/chunk-XX3IWSPM.js +189 -0
- package/dist/chunk-XX3IWSPM.js.map +1 -0
- package/dist/chunk-Y3INY2CS.js +14 -0
- package/dist/chunk-Y3INY2CS.js.map +1 -0
- package/dist/chunk-ZTSBF536.js +1927 -0
- package/dist/chunk-ZTSBF536.js.map +1 -0
- package/dist/client/index.js +13 -141
- package/dist/client/index.js.map +1 -1
- package/dist/dist-NDNRSNOG.js +521 -0
- package/dist/dist-NDNRSNOG.js.map +1 -0
- package/dist/gen/index.js +186 -1280
- package/dist/gen/index.js.map +1 -1
- package/dist/hooks/index.js +21 -8694
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.js +403 -47848
- package/dist/index.js.map +1 -1
- package/dist/index.native.js +400 -25048
- package/dist/index.native.js.map +1 -1
- package/dist/index.web.js +576 -43769
- package/dist/index.web.js.map +1 -1
- package/dist/mutation/index.js +44 -4675
- package/dist/mutation/index.js.map +1 -1
- package/dist/parser/index.js +45 -3697
- package/dist/parser/index.js.map +1 -1
- package/dist/pdf-3TIGQRLA.js +20336 -0
- package/dist/pdf-3TIGQRLA.js.map +1 -0
- package/dist/query/index.js +31 -13175
- package/dist/query/index.js.map +1 -1
- package/dist/realtime/index.js +45 -12431
- package/dist/realtime/index.js.map +1 -1
- package/dist/types/index.js +9 -0
- package/package.json +3 -3
|
@@ -0,0 +1,1927 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSupabaseUrl
|
|
3
|
+
} from "./chunk-Y3INY2CS.js";
|
|
4
|
+
import {
|
|
5
|
+
PostgrestParser
|
|
6
|
+
} from "./chunk-JUVE3DWY.js";
|
|
7
|
+
import {
|
|
8
|
+
isNullOrWhitespace,
|
|
9
|
+
isUsable,
|
|
10
|
+
omit
|
|
11
|
+
} from "./chunk-O3K7R32P.js";
|
|
12
|
+
import {
|
|
13
|
+
encode,
|
|
14
|
+
useQuery
|
|
15
|
+
} from "./chunk-H6365JPC.js";
|
|
16
|
+
import {
|
|
17
|
+
generateUUID,
|
|
18
|
+
useSupabase
|
|
19
|
+
} from "./chunk-AWFMICFV.js";
|
|
20
|
+
|
|
21
|
+
// src/query/select-parser.ts
|
|
22
|
+
function tokenizeTopLevel(input) {
|
|
23
|
+
const tokens = [];
|
|
24
|
+
let current = "";
|
|
25
|
+
let depth = 0;
|
|
26
|
+
for (const char of input) {
|
|
27
|
+
if (char === "(") {
|
|
28
|
+
depth++;
|
|
29
|
+
current += char;
|
|
30
|
+
} else if (char === ")") {
|
|
31
|
+
depth--;
|
|
32
|
+
current += char;
|
|
33
|
+
} else if (char === "," && depth === 0) {
|
|
34
|
+
const trimmed2 = current.trim();
|
|
35
|
+
if (trimmed2) {
|
|
36
|
+
tokens.push(trimmed2);
|
|
37
|
+
}
|
|
38
|
+
current = "";
|
|
39
|
+
} else {
|
|
40
|
+
current += char;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const trimmed = current.trim();
|
|
44
|
+
if (trimmed) {
|
|
45
|
+
tokens.push(trimmed);
|
|
46
|
+
}
|
|
47
|
+
return tokens;
|
|
48
|
+
}
|
|
49
|
+
function parseColumnToken(token) {
|
|
50
|
+
const aliasMatch = token.match(/^(\w+):(\w+)$/);
|
|
51
|
+
if (aliasMatch) {
|
|
52
|
+
return {
|
|
53
|
+
name: aliasMatch[2],
|
|
54
|
+
alias: aliasMatch[1]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return { name: token };
|
|
58
|
+
}
|
|
59
|
+
function parseSelect(select) {
|
|
60
|
+
const trimmed = select.trim();
|
|
61
|
+
if (trimmed === "*") {
|
|
62
|
+
return { columns: "*", relations: [] };
|
|
63
|
+
}
|
|
64
|
+
if (!trimmed) {
|
|
65
|
+
return { columns: "*", relations: [] };
|
|
66
|
+
}
|
|
67
|
+
const result = {
|
|
68
|
+
columns: [],
|
|
69
|
+
relations: []
|
|
70
|
+
};
|
|
71
|
+
const tokens = tokenizeTopLevel(trimmed);
|
|
72
|
+
for (const token of tokens) {
|
|
73
|
+
const trimmedToken = token.trim();
|
|
74
|
+
if (!trimmedToken) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const relationMatch = trimmedToken.match(/^(?:(\w+):)?(\w+)\((.+)\)$/);
|
|
78
|
+
if (relationMatch) {
|
|
79
|
+
const alias = relationMatch[1];
|
|
80
|
+
const name = relationMatch[2];
|
|
81
|
+
const innerSelect = relationMatch[3];
|
|
82
|
+
const innerParsed = parseSelect(innerSelect);
|
|
83
|
+
result.relations.push({
|
|
84
|
+
name,
|
|
85
|
+
alias,
|
|
86
|
+
columns: innerParsed.columns,
|
|
87
|
+
relations: innerParsed.relations
|
|
88
|
+
});
|
|
89
|
+
} else if (trimmedToken === "*") {
|
|
90
|
+
result.columns = "*";
|
|
91
|
+
} else {
|
|
92
|
+
const column = parseColumnToken(trimmedToken);
|
|
93
|
+
if (result.columns !== "*") {
|
|
94
|
+
result.columns.push(column);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(result.columns) && result.columns.length === 0) {
|
|
99
|
+
result.columns = "*";
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
function stringifySelect(parsed) {
|
|
104
|
+
const parts = [];
|
|
105
|
+
if (parsed.columns === "*") {
|
|
106
|
+
parts.push("*");
|
|
107
|
+
} else {
|
|
108
|
+
for (const col of parsed.columns) {
|
|
109
|
+
if (col.alias) {
|
|
110
|
+
parts.push(`${col.alias}:${col.name}`);
|
|
111
|
+
} else {
|
|
112
|
+
parts.push(col.name);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const rel of parsed.relations) {
|
|
117
|
+
const innerStr = stringifySelect({
|
|
118
|
+
columns: rel.columns,
|
|
119
|
+
relations: rel.relations
|
|
120
|
+
});
|
|
121
|
+
if (rel.alias) {
|
|
122
|
+
parts.push(`${rel.alias}:${rel.name}(${innerStr})`);
|
|
123
|
+
} else {
|
|
124
|
+
parts.push(`${rel.name}(${innerStr})`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return parts.join(", ");
|
|
128
|
+
}
|
|
129
|
+
function extractColumnNames(parsed) {
|
|
130
|
+
if (parsed.columns === "*") {
|
|
131
|
+
return "*";
|
|
132
|
+
}
|
|
133
|
+
return parsed.columns.map((col) => col.name);
|
|
134
|
+
}
|
|
135
|
+
function extractRelationNames(parsed) {
|
|
136
|
+
return parsed.relations.map((rel) => rel.alias ?? rel.name);
|
|
137
|
+
}
|
|
138
|
+
function hasRelation(parsed, relationName) {
|
|
139
|
+
return parsed.relations.some((rel) => rel.name === relationName || rel.alias === relationName);
|
|
140
|
+
}
|
|
141
|
+
function getRelationSelect(parsed, relationName) {
|
|
142
|
+
return parsed.relations.find(
|
|
143
|
+
(rel) => rel.name === relationName || rel.alias === relationName
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/query/relationship-resolver.ts
|
|
148
|
+
var RelationshipResolver = class {
|
|
149
|
+
constructor(schema) {
|
|
150
|
+
this.schema = schema;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolve a relationship from one table to another.
|
|
154
|
+
*
|
|
155
|
+
* This handles both:
|
|
156
|
+
* - Forward relationships: This table has a FK to the related table (many-to-one)
|
|
157
|
+
* - Reverse relationships: Related table has a FK to this table (one-to-many)
|
|
158
|
+
*
|
|
159
|
+
* @param fromTable - The table we're querying from
|
|
160
|
+
* @param relationName - The name of the related table
|
|
161
|
+
* @returns The resolved relationship or null if not found
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* // Forward relationship (many-to-one)
|
|
165
|
+
* // EquipmentUnit.projectDatabaseId -> ProjectDatabase.id
|
|
166
|
+
* resolver.resolve("EquipmentUnit", "ProjectDatabase")
|
|
167
|
+
* // Returns: {
|
|
168
|
+
* // type: "many-to-one",
|
|
169
|
+
* // fromTable: "EquipmentUnit",
|
|
170
|
+
* // toTable: "ProjectDatabase",
|
|
171
|
+
* // foreignKey: "projectDatabaseId",
|
|
172
|
+
* // referencedColumn: "id"
|
|
173
|
+
* // }
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* // Reverse relationship (one-to-many)
|
|
177
|
+
* // EquipmentUnit <- EquipmentFixture.equipmentUnitId
|
|
178
|
+
* resolver.resolve("EquipmentUnit", "EquipmentFixture")
|
|
179
|
+
* // Returns: {
|
|
180
|
+
* // type: "one-to-many",
|
|
181
|
+
* // fromTable: "EquipmentUnit",
|
|
182
|
+
* // toTable: "EquipmentFixture",
|
|
183
|
+
* // foreignKey: "equipmentUnitId",
|
|
184
|
+
* // referencedColumn: "id"
|
|
185
|
+
* // }
|
|
186
|
+
*/
|
|
187
|
+
resolve(fromTable, relationName) {
|
|
188
|
+
const tableSchema = this.getTableSchema(fromTable);
|
|
189
|
+
if (!tableSchema) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
for (const rel of tableSchema.relationships) {
|
|
193
|
+
if (rel.referencedTable === relationName) {
|
|
194
|
+
return {
|
|
195
|
+
type: "many-to-one",
|
|
196
|
+
fromTable,
|
|
197
|
+
toTable: relationName,
|
|
198
|
+
foreignKey: rel.foreignKey,
|
|
199
|
+
referencedColumn: rel.referencedColumn
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const relatedTableSchema = this.getTableSchema(relationName);
|
|
204
|
+
if (relatedTableSchema) {
|
|
205
|
+
for (const rel of relatedTableSchema.relationships) {
|
|
206
|
+
if (rel.referencedTable === fromTable) {
|
|
207
|
+
return {
|
|
208
|
+
type: "one-to-many",
|
|
209
|
+
fromTable,
|
|
210
|
+
toTable: relationName,
|
|
211
|
+
foreignKey: rel.foreignKey,
|
|
212
|
+
referencedColumn: rel.referencedColumn
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get all forward relationships for a table (many-to-one).
|
|
221
|
+
* These are relationships where this table has a foreign key.
|
|
222
|
+
*/
|
|
223
|
+
getForwardRelationships(tableName) {
|
|
224
|
+
const tableSchema = this.getTableSchema(tableName);
|
|
225
|
+
if (!tableSchema) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
return tableSchema.relationships.map((rel) => ({
|
|
229
|
+
type: "many-to-one",
|
|
230
|
+
fromTable: tableName,
|
|
231
|
+
toTable: rel.referencedTable,
|
|
232
|
+
foreignKey: rel.foreignKey,
|
|
233
|
+
referencedColumn: rel.referencedColumn
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get all reverse relationships for a table (one-to-many).
|
|
238
|
+
* These are relationships where other tables have FKs pointing to this table.
|
|
239
|
+
*/
|
|
240
|
+
getReverseRelationships(tableName) {
|
|
241
|
+
const results = [];
|
|
242
|
+
for (const schemaName of Object.keys(this.schema.schemas)) {
|
|
243
|
+
const schemaDefinition = this.schema.schemas[schemaName];
|
|
244
|
+
if (!schemaDefinition) continue;
|
|
245
|
+
for (const [otherTableName, otherTableSchema] of Object.entries(
|
|
246
|
+
schemaDefinition.tables
|
|
247
|
+
)) {
|
|
248
|
+
if (otherTableName === tableName) continue;
|
|
249
|
+
for (const rel of otherTableSchema.relationships) {
|
|
250
|
+
if (rel.referencedTable === tableName) {
|
|
251
|
+
results.push({
|
|
252
|
+
type: "one-to-many",
|
|
253
|
+
fromTable: tableName,
|
|
254
|
+
toTable: otherTableName,
|
|
255
|
+
foreignKey: rel.foreignKey,
|
|
256
|
+
referencedColumn: rel.referencedColumn
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return results;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Get all relationships for a table (both directions).
|
|
266
|
+
*/
|
|
267
|
+
getAllRelationships(tableName) {
|
|
268
|
+
return [
|
|
269
|
+
...this.getForwardRelationships(tableName),
|
|
270
|
+
...this.getReverseRelationships(tableName)
|
|
271
|
+
];
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Check if a relationship exists between two tables.
|
|
275
|
+
*/
|
|
276
|
+
hasRelationship(fromTable, toTable) {
|
|
277
|
+
return this.resolve(fromTable, toTable) !== null;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get the table schema from the database schema.
|
|
281
|
+
* Searches across all schema namespaces (public, core, etc.).
|
|
282
|
+
*/
|
|
283
|
+
getTableSchema(tableName) {
|
|
284
|
+
for (const schemaName of Object.keys(this.schema.schemas)) {
|
|
285
|
+
const schemaDefinition = this.schema.schemas[schemaName];
|
|
286
|
+
if (!schemaDefinition) continue;
|
|
287
|
+
if (schemaDefinition.tables[tableName]) {
|
|
288
|
+
return schemaDefinition.tables[tableName];
|
|
289
|
+
}
|
|
290
|
+
if (schemaDefinition.views && schemaDefinition.views[tableName]) {
|
|
291
|
+
return schemaDefinition.views[tableName];
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get the primary key column for a table.
|
|
298
|
+
* Defaults to "id" if not explicitly found.
|
|
299
|
+
*/
|
|
300
|
+
getPrimaryKey(tableName) {
|
|
301
|
+
const tableSchema = this.getTableSchema(tableName);
|
|
302
|
+
if (!tableSchema) {
|
|
303
|
+
return "id";
|
|
304
|
+
}
|
|
305
|
+
const idColumn = tableSchema.columns.find((col) => col.name === "id");
|
|
306
|
+
if (idColumn) {
|
|
307
|
+
return "id";
|
|
308
|
+
}
|
|
309
|
+
if (tableSchema.columns.length > 0) {
|
|
310
|
+
return tableSchema.columns[0].name;
|
|
311
|
+
}
|
|
312
|
+
return "id";
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get all column names for a table.
|
|
316
|
+
*/
|
|
317
|
+
getColumnNames(tableName) {
|
|
318
|
+
const tableSchema = this.getTableSchema(tableName);
|
|
319
|
+
if (!tableSchema) {
|
|
320
|
+
return [];
|
|
321
|
+
}
|
|
322
|
+
return tableSchema.columns.map((col) => col.name);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Check if a table exists in the schema.
|
|
326
|
+
*/
|
|
327
|
+
hasTable(tableName) {
|
|
328
|
+
return this.getTableSchema(tableName) !== null;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get all table names in the schema.
|
|
332
|
+
*/
|
|
333
|
+
getAllTableNames() {
|
|
334
|
+
const tables = [];
|
|
335
|
+
for (const schemaName of Object.keys(this.schema.schemas)) {
|
|
336
|
+
const schemaDefinition = this.schema.schemas[schemaName];
|
|
337
|
+
if (!schemaDefinition) continue;
|
|
338
|
+
tables.push(...Object.keys(schemaDefinition.tables));
|
|
339
|
+
}
|
|
340
|
+
return tables;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
function createRelationshipResolver(schema) {
|
|
344
|
+
return new RelationshipResolver(schema);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// src/query/sql-builder.ts
|
|
348
|
+
function isWhereOperator(value) {
|
|
349
|
+
if (value === null || typeof value !== "object") {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
const obj = value;
|
|
353
|
+
return "in" in obj || "gt" in obj || "gte" in obj || "lt" in obj || "lte" in obj || "like" in obj || "is" in obj || "neq" in obj || "notIn" in obj;
|
|
354
|
+
}
|
|
355
|
+
var SQLBuilder = class {
|
|
356
|
+
/**
|
|
357
|
+
* Build a SELECT query for a single table.
|
|
358
|
+
*
|
|
359
|
+
* @param table - Table name
|
|
360
|
+
* @param columns - Columns to select ("*" or array of column definitions)
|
|
361
|
+
* @param options - Query options (where, orderBy, limit, offset)
|
|
362
|
+
* @returns Built query with SQL and parameters
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* const builder = new SQLBuilder();
|
|
366
|
+
* const query = builder.build("EquipmentUnit", "*", {
|
|
367
|
+
* where: { status: "active", projectDatabaseId: 123 },
|
|
368
|
+
* orderBy: [{ field: "name", direction: "asc" }],
|
|
369
|
+
* limit: 10
|
|
370
|
+
* });
|
|
371
|
+
* // query.sql: SELECT * FROM "EquipmentUnit" WHERE "status" = ? AND "projectDatabaseId" = ? ORDER BY "name" ASC LIMIT ?
|
|
372
|
+
* // query.params: ["active", 123, 10]
|
|
373
|
+
*/
|
|
374
|
+
build(table, columns, options = {}) {
|
|
375
|
+
const params = [];
|
|
376
|
+
let columnList;
|
|
377
|
+
if (columns === "*") {
|
|
378
|
+
columnList = "*";
|
|
379
|
+
} else if (columns.length === 0) {
|
|
380
|
+
columnList = "*";
|
|
381
|
+
} else {
|
|
382
|
+
columnList = columns.map((c) => {
|
|
383
|
+
if (c.alias) {
|
|
384
|
+
return `"${c.name}" AS "${c.alias}"`;
|
|
385
|
+
}
|
|
386
|
+
return `"${c.name}"`;
|
|
387
|
+
}).join(", ");
|
|
388
|
+
}
|
|
389
|
+
let sql = `SELECT ${columnList} FROM "${table}"`;
|
|
390
|
+
if (options.where && Object.keys(options.where).length > 0) {
|
|
391
|
+
const whereClauses = [];
|
|
392
|
+
for (const [field, value] of Object.entries(options.where)) {
|
|
393
|
+
if (value === null) {
|
|
394
|
+
whereClauses.push(`"${field}" IS NULL`);
|
|
395
|
+
} else if (isWhereOperator(value)) {
|
|
396
|
+
const operatorClauses = this.buildOperatorClauses(field, value, params);
|
|
397
|
+
whereClauses.push(...operatorClauses);
|
|
398
|
+
} else {
|
|
399
|
+
whereClauses.push(`"${field}" = ?`);
|
|
400
|
+
params.push(value);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (whereClauses.length > 0) {
|
|
404
|
+
sql += ` WHERE ${whereClauses.join(" AND ")}`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (options.orderBy && options.orderBy.length > 0) {
|
|
408
|
+
const orderClauses = options.orderBy.map(
|
|
409
|
+
(o) => `"${o.field}" ${o.direction.toUpperCase()}`
|
|
410
|
+
);
|
|
411
|
+
sql += ` ORDER BY ${orderClauses.join(", ")}`;
|
|
412
|
+
}
|
|
413
|
+
if (options.limit !== void 0) {
|
|
414
|
+
sql += ` LIMIT ?`;
|
|
415
|
+
params.push(options.limit);
|
|
416
|
+
}
|
|
417
|
+
if (options.offset !== void 0) {
|
|
418
|
+
sql += ` OFFSET ?`;
|
|
419
|
+
params.push(options.offset);
|
|
420
|
+
}
|
|
421
|
+
return { sql, params };
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Build WHERE clauses from operator objects.
|
|
425
|
+
*/
|
|
426
|
+
buildOperatorClauses(field, operators, params) {
|
|
427
|
+
const clauses = [];
|
|
428
|
+
if ("in" in operators && operators.in !== void 0) {
|
|
429
|
+
if (operators.in.length === 0) {
|
|
430
|
+
clauses.push("1 = 0");
|
|
431
|
+
} else {
|
|
432
|
+
const placeholders = operators.in.map(() => "?").join(", ");
|
|
433
|
+
clauses.push(`"${field}" IN (${placeholders})`);
|
|
434
|
+
params.push(...operators.in);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if ("notIn" in operators && operators.notIn !== void 0) {
|
|
438
|
+
if (operators.notIn.length === 0) {
|
|
439
|
+
} else {
|
|
440
|
+
const placeholders = operators.notIn.map(() => "?").join(", ");
|
|
441
|
+
clauses.push(`"${field}" NOT IN (${placeholders})`);
|
|
442
|
+
params.push(...operators.notIn);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if ("gt" in operators && operators.gt !== void 0) {
|
|
446
|
+
clauses.push(`"${field}" > ?`);
|
|
447
|
+
params.push(operators.gt);
|
|
448
|
+
}
|
|
449
|
+
if ("gte" in operators && operators.gte !== void 0) {
|
|
450
|
+
clauses.push(`"${field}" >= ?`);
|
|
451
|
+
params.push(operators.gte);
|
|
452
|
+
}
|
|
453
|
+
if ("lt" in operators && operators.lt !== void 0) {
|
|
454
|
+
clauses.push(`"${field}" < ?`);
|
|
455
|
+
params.push(operators.lt);
|
|
456
|
+
}
|
|
457
|
+
if ("lte" in operators && operators.lte !== void 0) {
|
|
458
|
+
clauses.push(`"${field}" <= ?`);
|
|
459
|
+
params.push(operators.lte);
|
|
460
|
+
}
|
|
461
|
+
if ("like" in operators && operators.like !== void 0) {
|
|
462
|
+
clauses.push(`"${field}" LIKE ?`);
|
|
463
|
+
const pattern = operators.like.includes("%") ? operators.like : `%${operators.like}%`;
|
|
464
|
+
params.push(pattern);
|
|
465
|
+
}
|
|
466
|
+
if ("is" in operators && operators.is === null) {
|
|
467
|
+
clauses.push(`"${field}" IS NULL`);
|
|
468
|
+
}
|
|
469
|
+
if ("neq" in operators && operators.neq !== void 0) {
|
|
470
|
+
if (operators.neq === null) {
|
|
471
|
+
clauses.push(`"${field}" IS NOT NULL`);
|
|
472
|
+
} else {
|
|
473
|
+
clauses.push(`"${field}" != ?`);
|
|
474
|
+
params.push(operators.neq);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return clauses;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Build a query to fetch related records by foreign key.
|
|
481
|
+
*
|
|
482
|
+
* @param table - The related table name
|
|
483
|
+
* @param foreignKey - The FK column to match against
|
|
484
|
+
* @param parentIds - Array of parent IDs to fetch related records for
|
|
485
|
+
* @param columns - Columns to select
|
|
486
|
+
* @returns Built query
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* // Fetch all EquipmentFixtures for given EquipmentUnit IDs
|
|
490
|
+
* const query = builder.buildRelationQuery(
|
|
491
|
+
* "EquipmentFixture",
|
|
492
|
+
* "equipmentUnitId",
|
|
493
|
+
* ["uuid-1", "uuid-2"],
|
|
494
|
+
* "*"
|
|
495
|
+
* );
|
|
496
|
+
* // query.sql: SELECT * FROM "EquipmentFixture" WHERE "equipmentUnitId" IN (?, ?)
|
|
497
|
+
* // query.params: ["uuid-1", "uuid-2"]
|
|
498
|
+
*/
|
|
499
|
+
buildRelationQuery(table, foreignKey, parentIds, columns) {
|
|
500
|
+
if (parentIds.length === 0) {
|
|
501
|
+
return {
|
|
502
|
+
sql: `SELECT ${columns === "*" ? "*" : this.buildColumnList(columns, foreignKey)} FROM "${table}" WHERE 1 = 0`,
|
|
503
|
+
params: []
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
let columnList;
|
|
507
|
+
if (columns === "*") {
|
|
508
|
+
columnList = "*";
|
|
509
|
+
} else {
|
|
510
|
+
columnList = this.buildColumnList(columns, foreignKey);
|
|
511
|
+
}
|
|
512
|
+
const placeholders = parentIds.map(() => "?").join(", ");
|
|
513
|
+
const sql = `SELECT ${columnList} FROM "${table}" WHERE "${foreignKey}" IN (${placeholders})`;
|
|
514
|
+
return { sql, params: [...parentIds] };
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Build column list ensuring the foreign key is included.
|
|
518
|
+
*/
|
|
519
|
+
buildColumnList(columns, foreignKey) {
|
|
520
|
+
const colNames = columns.map((c) => c.name);
|
|
521
|
+
const columnList = columns.map((c) => {
|
|
522
|
+
if (c.alias) {
|
|
523
|
+
return `"${c.name}" AS "${c.alias}"`;
|
|
524
|
+
}
|
|
525
|
+
return `"${c.name}"`;
|
|
526
|
+
});
|
|
527
|
+
if (!colNames.includes(foreignKey)) {
|
|
528
|
+
columnList.unshift(`"${foreignKey}"`);
|
|
529
|
+
}
|
|
530
|
+
return columnList.join(", ");
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Build a SELECT query for a single record by ID.
|
|
534
|
+
*
|
|
535
|
+
* @param table - Table name
|
|
536
|
+
* @param id - Record ID
|
|
537
|
+
* @param columns - Columns to select
|
|
538
|
+
* @param idColumn - Name of the ID column (defaults to "id")
|
|
539
|
+
* @returns Built query
|
|
540
|
+
*/
|
|
541
|
+
buildByIdQuery(table, id, columns = "*", idColumn = "id") {
|
|
542
|
+
let columnList;
|
|
543
|
+
if (columns === "*") {
|
|
544
|
+
columnList = "*";
|
|
545
|
+
} else if (columns.length === 0) {
|
|
546
|
+
columnList = "*";
|
|
547
|
+
} else {
|
|
548
|
+
columnList = columns.map((c) => c.alias ? `"${c.name}" AS "${c.alias}"` : `"${c.name}"`).join(", ");
|
|
549
|
+
}
|
|
550
|
+
const sql = `SELECT ${columnList} FROM "${table}" WHERE "${idColumn}" = ? LIMIT 1`;
|
|
551
|
+
return { sql, params: [id] };
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Build an INSERT query.
|
|
555
|
+
*
|
|
556
|
+
* @param table - Table name
|
|
557
|
+
* @param data - Record data to insert
|
|
558
|
+
* @returns Built query
|
|
559
|
+
*/
|
|
560
|
+
buildInsertQuery(table, data) {
|
|
561
|
+
const columns = Object.keys(data).filter((k) => data[k] !== void 0);
|
|
562
|
+
const values = columns.map((k) => data[k]);
|
|
563
|
+
if (columns.length === 0) {
|
|
564
|
+
throw new Error("Cannot insert empty record");
|
|
565
|
+
}
|
|
566
|
+
const columnList = columns.map((c) => `"${c}"`).join(", ");
|
|
567
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
568
|
+
const sql = `INSERT INTO "${table}" (${columnList}) VALUES (${placeholders})`;
|
|
569
|
+
return {
|
|
570
|
+
sql,
|
|
571
|
+
params: values
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Build an UPDATE query.
|
|
576
|
+
*
|
|
577
|
+
* @param table - Table name
|
|
578
|
+
* @param id - Record ID
|
|
579
|
+
* @param data - Fields to update
|
|
580
|
+
* @param idColumn - Name of the ID column (defaults to "id")
|
|
581
|
+
* @returns Built query
|
|
582
|
+
*/
|
|
583
|
+
buildUpdateQuery(table, id, data, idColumn = "id") {
|
|
584
|
+
const columns = Object.keys(data).filter(
|
|
585
|
+
(k) => k !== idColumn && data[k] !== void 0
|
|
586
|
+
);
|
|
587
|
+
if (columns.length === 0) {
|
|
588
|
+
throw new Error("No fields to update");
|
|
589
|
+
}
|
|
590
|
+
const setClauses = columns.map((c) => `"${c}" = ?`).join(", ");
|
|
591
|
+
const values = columns.map((k) => data[k]);
|
|
592
|
+
const sql = `UPDATE "${table}" SET ${setClauses} WHERE "${idColumn}" = ?`;
|
|
593
|
+
return {
|
|
594
|
+
sql,
|
|
595
|
+
params: [...values, id]
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Build a DELETE query.
|
|
600
|
+
*
|
|
601
|
+
* @param table - Table name
|
|
602
|
+
* @param id - Record ID
|
|
603
|
+
* @param idColumn - Name of the ID column (defaults to "id")
|
|
604
|
+
* @returns Built query
|
|
605
|
+
*/
|
|
606
|
+
buildDeleteQuery(table, id, idColumn = "id") {
|
|
607
|
+
const sql = `DELETE FROM "${table}" WHERE "${idColumn}" = ?`;
|
|
608
|
+
return { sql, params: [id] };
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Build a COUNT query.
|
|
612
|
+
*
|
|
613
|
+
* @param table - Table name
|
|
614
|
+
* @param where - Optional where clause
|
|
615
|
+
* @returns Built query
|
|
616
|
+
*/
|
|
617
|
+
buildCountQuery(table, where) {
|
|
618
|
+
const params = [];
|
|
619
|
+
let sql = `SELECT COUNT(*) as count FROM "${table}"`;
|
|
620
|
+
if (where && Object.keys(where).length > 0) {
|
|
621
|
+
const whereClauses = [];
|
|
622
|
+
for (const [field, value] of Object.entries(where)) {
|
|
623
|
+
if (value === null) {
|
|
624
|
+
whereClauses.push(`"${field}" IS NULL`);
|
|
625
|
+
} else if (isWhereOperator(value)) {
|
|
626
|
+
const operatorClauses = this.buildOperatorClauses(field, value, params);
|
|
627
|
+
whereClauses.push(...operatorClauses);
|
|
628
|
+
} else {
|
|
629
|
+
whereClauses.push(`"${field}" = ?`);
|
|
630
|
+
params.push(value);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (whereClauses.length > 0) {
|
|
634
|
+
sql += ` WHERE ${whereClauses.join(" AND ")}`;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return { sql, params };
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
function createSQLBuilder() {
|
|
641
|
+
return new SQLBuilder();
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// src/query/result-joiner.ts
|
|
645
|
+
var ResultJoiner = class {
|
|
646
|
+
/**
|
|
647
|
+
* Join related data onto base records.
|
|
648
|
+
*
|
|
649
|
+
* @param baseRecords - The base table records
|
|
650
|
+
* @param relatedRecords - Records from the related table
|
|
651
|
+
* @param relationship - The relationship definition
|
|
652
|
+
* @param relationName - The property name to use for the relation
|
|
653
|
+
* @returns Base records with related data attached
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* // One-to-many: EquipmentUnit -> EquipmentFixture[]
|
|
657
|
+
* const joiner = new ResultJoiner();
|
|
658
|
+
* const result = joiner.join(
|
|
659
|
+
* equipmentUnits,
|
|
660
|
+
* equipmentFixtures,
|
|
661
|
+
* { type: "one-to-many", foreignKey: "equipmentUnitId", referencedColumn: "id", ... },
|
|
662
|
+
* "EquipmentFixture"
|
|
663
|
+
* );
|
|
664
|
+
* // Each equipmentUnit now has EquipmentFixture: [...]
|
|
665
|
+
*
|
|
666
|
+
* @example
|
|
667
|
+
* // Many-to-one: EquipmentUnit -> ProjectDatabase
|
|
668
|
+
* const result = joiner.join(
|
|
669
|
+
* equipmentUnits,
|
|
670
|
+
* projectDatabases,
|
|
671
|
+
* { type: "many-to-one", foreignKey: "projectDatabaseId", referencedColumn: "id", ... },
|
|
672
|
+
* "ProjectDatabase"
|
|
673
|
+
* );
|
|
674
|
+
* // Each equipmentUnit now has ProjectDatabase: {...} or null
|
|
675
|
+
*/
|
|
676
|
+
join(baseRecords, relatedRecords, relationship, relationName) {
|
|
677
|
+
if (baseRecords.length === 0) {
|
|
678
|
+
return baseRecords;
|
|
679
|
+
}
|
|
680
|
+
if (relationship.type === "one-to-many") {
|
|
681
|
+
return baseRecords.map((base) => {
|
|
682
|
+
const baseId = base[relationship.referencedColumn];
|
|
683
|
+
const related = relatedRecords.filter(
|
|
684
|
+
(r) => r[relationship.foreignKey] === baseId
|
|
685
|
+
);
|
|
686
|
+
return {
|
|
687
|
+
...base,
|
|
688
|
+
[relationName]: related
|
|
689
|
+
};
|
|
690
|
+
});
|
|
691
|
+
} else {
|
|
692
|
+
const relatedMap = /* @__PURE__ */ new Map();
|
|
693
|
+
for (const r of relatedRecords) {
|
|
694
|
+
const refValue = r[relationship.referencedColumn];
|
|
695
|
+
if (refValue !== null && refValue !== void 0) {
|
|
696
|
+
relatedMap.set(refValue, r);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return baseRecords.map((base) => {
|
|
700
|
+
const fkValue = base[relationship.foreignKey];
|
|
701
|
+
const related = fkValue != null ? relatedMap.get(fkValue) ?? null : null;
|
|
702
|
+
return {
|
|
703
|
+
...base,
|
|
704
|
+
[relationName]: related
|
|
705
|
+
};
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Recursively join nested relations onto records.
|
|
711
|
+
*
|
|
712
|
+
* @param baseRecords - The base records
|
|
713
|
+
* @param relationData - Map of relation names to join data
|
|
714
|
+
* @returns Base records with all nested relations attached
|
|
715
|
+
*
|
|
716
|
+
* @example
|
|
717
|
+
* // Join EquipmentFixture and ProjectDatabase onto EquipmentUnit
|
|
718
|
+
* // where EquipmentFixture has its own nested Organization relation
|
|
719
|
+
* const result = joiner.joinNested(equipmentUnits, new Map([
|
|
720
|
+
* ["EquipmentFixture", {
|
|
721
|
+
* records: fixtures,
|
|
722
|
+
* relationship: { type: "one-to-many", ... },
|
|
723
|
+
* nestedRelations: new Map()
|
|
724
|
+
* }],
|
|
725
|
+
* ["ProjectDatabase", {
|
|
726
|
+
* records: projects,
|
|
727
|
+
* relationship: { type: "many-to-one", ... },
|
|
728
|
+
* nestedRelations: new Map([
|
|
729
|
+
* ["Organization", { records: orgs, relationship: ..., nestedRelations: new Map() }]
|
|
730
|
+
* ])
|
|
731
|
+
* }]
|
|
732
|
+
* ]));
|
|
733
|
+
*/
|
|
734
|
+
joinNested(baseRecords, relationData) {
|
|
735
|
+
let result = [...baseRecords];
|
|
736
|
+
const entries = Array.from(relationData.entries());
|
|
737
|
+
for (const [relationName, data] of entries) {
|
|
738
|
+
let relatedRecords = data.records;
|
|
739
|
+
if (data.nestedRelations.size > 0) {
|
|
740
|
+
relatedRecords = this.joinNested(relatedRecords, data.nestedRelations);
|
|
741
|
+
}
|
|
742
|
+
result = this.join(result, relatedRecords, data.relationship, relationName);
|
|
743
|
+
}
|
|
744
|
+
return result;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Group records by a key field.
|
|
748
|
+
* Useful for preparing data before joining.
|
|
749
|
+
*
|
|
750
|
+
* @param records - Records to group
|
|
751
|
+
* @param keyField - Field to group by
|
|
752
|
+
* @returns Map of key values to arrays of records
|
|
753
|
+
*/
|
|
754
|
+
groupBy(records, keyField) {
|
|
755
|
+
const groups = /* @__PURE__ */ new Map();
|
|
756
|
+
for (const record of records) {
|
|
757
|
+
const key = record[keyField];
|
|
758
|
+
if (!groups.has(key)) {
|
|
759
|
+
groups.set(key, []);
|
|
760
|
+
}
|
|
761
|
+
groups.get(key).push(record);
|
|
762
|
+
}
|
|
763
|
+
return groups;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Index records by a unique key field.
|
|
767
|
+
* Useful for many-to-one lookups.
|
|
768
|
+
*
|
|
769
|
+
* @param records - Records to index
|
|
770
|
+
* @param keyField - Field to index by (should be unique)
|
|
771
|
+
* @returns Map of key values to single records
|
|
772
|
+
*/
|
|
773
|
+
indexBy(records, keyField) {
|
|
774
|
+
const index = /* @__PURE__ */ new Map();
|
|
775
|
+
for (const record of records) {
|
|
776
|
+
const key = record[keyField];
|
|
777
|
+
if (key !== null && key !== void 0) {
|
|
778
|
+
index.set(key, record);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return index;
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Extract unique values for a field from records.
|
|
785
|
+
* Useful for building IN clauses for related queries.
|
|
786
|
+
*
|
|
787
|
+
* @param records - Records to extract from
|
|
788
|
+
* @param field - Field to extract
|
|
789
|
+
* @returns Array of unique non-null values
|
|
790
|
+
*/
|
|
791
|
+
extractUniqueValues(records, field) {
|
|
792
|
+
const values = /* @__PURE__ */ new Set();
|
|
793
|
+
for (const record of records) {
|
|
794
|
+
const value = record[field];
|
|
795
|
+
if (value !== null && value !== void 0) {
|
|
796
|
+
values.add(value);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return Array.from(values);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Remove a relation property from records (for cleanup).
|
|
803
|
+
*
|
|
804
|
+
* @param records - Records to process
|
|
805
|
+
* @param relationName - Relation property to remove
|
|
806
|
+
* @returns Records without the specified property
|
|
807
|
+
*/
|
|
808
|
+
removeRelation(records, relationName) {
|
|
809
|
+
return records.map((record) => {
|
|
810
|
+
const { [relationName]: _removed, ...rest } = record;
|
|
811
|
+
return rest;
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Flatten a nested relation into the parent record.
|
|
816
|
+
* Useful for flattening many-to-one relations.
|
|
817
|
+
*
|
|
818
|
+
* @param records - Records with nested relation
|
|
819
|
+
* @param relationName - Name of the relation to flatten
|
|
820
|
+
* @param prefix - Prefix for flattened field names
|
|
821
|
+
* @returns Records with flattened relation fields
|
|
822
|
+
*/
|
|
823
|
+
flattenRelation(records, relationName, prefix = "") {
|
|
824
|
+
return records.map((record) => {
|
|
825
|
+
const relation = record[relationName];
|
|
826
|
+
const { [relationName]: _removed, ...rest } = record;
|
|
827
|
+
if (!relation) {
|
|
828
|
+
return rest;
|
|
829
|
+
}
|
|
830
|
+
const flattenedRelation = {};
|
|
831
|
+
for (const [key, value] of Object.entries(relation)) {
|
|
832
|
+
const fieldName = prefix ? `${prefix}_${key}` : `${relationName}_${key}`;
|
|
833
|
+
flattenedRelation[fieldName] = value;
|
|
834
|
+
}
|
|
835
|
+
return { ...rest, ...flattenedRelation };
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
function createResultJoiner() {
|
|
840
|
+
return new ResultJoiner();
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/query/executor.ts
|
|
844
|
+
var QueryExecutor = class {
|
|
845
|
+
constructor(db, schema) {
|
|
846
|
+
this.db = db;
|
|
847
|
+
this.schema = schema;
|
|
848
|
+
this.resolver = new RelationshipResolver(schema);
|
|
849
|
+
this.builder = new SQLBuilder();
|
|
850
|
+
this.joiner = new ResultJoiner();
|
|
851
|
+
}
|
|
852
|
+
resolver;
|
|
853
|
+
builder;
|
|
854
|
+
joiner;
|
|
855
|
+
/**
|
|
856
|
+
* Execute a query and return results.
|
|
857
|
+
*
|
|
858
|
+
* @param table - The table to query
|
|
859
|
+
* @param options - Query options including select, where, orderBy, limit, offset
|
|
860
|
+
* @returns Array of records with relations attached
|
|
861
|
+
*/
|
|
862
|
+
async execute(table, options = {}) {
|
|
863
|
+
const parsed = parseSelect(options.select ?? "*");
|
|
864
|
+
const baseQuery = this.builder.build(table, parsed.columns, {
|
|
865
|
+
where: options.where,
|
|
866
|
+
orderBy: options.orderBy,
|
|
867
|
+
limit: options.limit,
|
|
868
|
+
offset: options.offset
|
|
869
|
+
});
|
|
870
|
+
const baseRecords = await this.db.getAll(
|
|
871
|
+
baseQuery.sql,
|
|
872
|
+
baseQuery.params
|
|
873
|
+
);
|
|
874
|
+
if (baseRecords.length === 0 || parsed.relations.length === 0) {
|
|
875
|
+
return baseRecords;
|
|
876
|
+
}
|
|
877
|
+
const result = await this.queryAndJoinRelations(
|
|
878
|
+
table,
|
|
879
|
+
baseRecords,
|
|
880
|
+
parsed.relations
|
|
881
|
+
);
|
|
882
|
+
return result;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Execute a query for a single record by ID.
|
|
886
|
+
*
|
|
887
|
+
* @param table - The table to query
|
|
888
|
+
* @param id - The record ID
|
|
889
|
+
* @param options - Query options (only select is used)
|
|
890
|
+
* @returns Single record or null
|
|
891
|
+
*/
|
|
892
|
+
async executeById(table, id, options = {}) {
|
|
893
|
+
const parsed = parseSelect(options.select ?? "*");
|
|
894
|
+
const idColumn = this.resolver.getPrimaryKey(table);
|
|
895
|
+
const baseQuery = this.builder.buildByIdQuery(
|
|
896
|
+
table,
|
|
897
|
+
id,
|
|
898
|
+
parsed.columns,
|
|
899
|
+
idColumn
|
|
900
|
+
);
|
|
901
|
+
const records = await this.db.getAll(
|
|
902
|
+
baseQuery.sql,
|
|
903
|
+
baseQuery.params
|
|
904
|
+
);
|
|
905
|
+
if (records.length === 0) {
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
if (parsed.relations.length === 0) {
|
|
909
|
+
return records[0];
|
|
910
|
+
}
|
|
911
|
+
const result = await this.queryAndJoinRelations(
|
|
912
|
+
table,
|
|
913
|
+
records,
|
|
914
|
+
parsed.relations
|
|
915
|
+
);
|
|
916
|
+
return result[0];
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Execute a count query.
|
|
920
|
+
*
|
|
921
|
+
* @param table - The table to count
|
|
922
|
+
* @param options - Query options (only where is used)
|
|
923
|
+
* @returns Count of matching records
|
|
924
|
+
*/
|
|
925
|
+
async count(table, options = {}) {
|
|
926
|
+
const query = this.builder.buildCountQuery(table, options.where);
|
|
927
|
+
const result = await this.db.getAll(
|
|
928
|
+
query.sql,
|
|
929
|
+
query.params
|
|
930
|
+
);
|
|
931
|
+
return result[0]?.count ?? 0;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Insert a record.
|
|
935
|
+
*
|
|
936
|
+
* @param table - The table to insert into
|
|
937
|
+
* @param data - The record data
|
|
938
|
+
* @returns The inserted record (re-fetched to get defaults)
|
|
939
|
+
*/
|
|
940
|
+
async insert(table, data) {
|
|
941
|
+
const idColumn = this.resolver.getPrimaryKey(table);
|
|
942
|
+
if (!data[idColumn]) {
|
|
943
|
+
const [{ id }] = await this.db.getAll(
|
|
944
|
+
"SELECT uuid() as id"
|
|
945
|
+
);
|
|
946
|
+
data = { ...data, [idColumn]: id };
|
|
947
|
+
}
|
|
948
|
+
const query = this.builder.buildInsertQuery(table, data);
|
|
949
|
+
await this.db.execute(query.sql, query.params);
|
|
950
|
+
const result = await this.executeById(table, data[idColumn]);
|
|
951
|
+
return result ?? data;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Update a record.
|
|
955
|
+
*
|
|
956
|
+
* @param table - The table to update
|
|
957
|
+
* @param id - The record ID
|
|
958
|
+
* @param data - The fields to update
|
|
959
|
+
* @returns The updated record
|
|
960
|
+
*/
|
|
961
|
+
async update(table, id, data) {
|
|
962
|
+
const idColumn = this.resolver.getPrimaryKey(table);
|
|
963
|
+
const query = this.builder.buildUpdateQuery(table, id, data, idColumn);
|
|
964
|
+
await this.db.execute(query.sql, query.params);
|
|
965
|
+
const result = await this.executeById(table, id);
|
|
966
|
+
if (result) {
|
|
967
|
+
return result;
|
|
968
|
+
}
|
|
969
|
+
return { ...data, [idColumn]: id };
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Upsert a record (insert or update).
|
|
973
|
+
*
|
|
974
|
+
* @param table - The table to upsert into
|
|
975
|
+
* @param data - The record data (must include ID for update)
|
|
976
|
+
* @returns The upserted record
|
|
977
|
+
*/
|
|
978
|
+
async upsert(table, data) {
|
|
979
|
+
const idColumn = this.resolver.getPrimaryKey(table);
|
|
980
|
+
let id = data[idColumn];
|
|
981
|
+
if (!id) {
|
|
982
|
+
const [{ id: generatedId }] = await this.db.getAll(
|
|
983
|
+
"SELECT uuid() as id"
|
|
984
|
+
);
|
|
985
|
+
id = generatedId;
|
|
986
|
+
data = { ...data, [idColumn]: id };
|
|
987
|
+
}
|
|
988
|
+
const existing = await this.executeById(table, id);
|
|
989
|
+
if (existing) {
|
|
990
|
+
return this.update(table, id, data);
|
|
991
|
+
}
|
|
992
|
+
return this.insert(table, data);
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Delete a record.
|
|
996
|
+
*
|
|
997
|
+
* @param table - The table to delete from
|
|
998
|
+
* @param id - The record ID
|
|
999
|
+
*/
|
|
1000
|
+
async delete(table, id) {
|
|
1001
|
+
const idColumn = this.resolver.getPrimaryKey(table);
|
|
1002
|
+
const query = this.builder.buildDeleteQuery(table, id, idColumn);
|
|
1003
|
+
await this.db.execute(query.sql, query.params);
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Query and join relations onto parent records.
|
|
1007
|
+
*
|
|
1008
|
+
* @param parentTable - The parent table name
|
|
1009
|
+
* @param parentRecords - Parent records to add relations to
|
|
1010
|
+
* @param relations - Relations to query and join
|
|
1011
|
+
* @returns Parent records with relations attached
|
|
1012
|
+
*/
|
|
1013
|
+
async queryAndJoinRelations(parentTable, parentRecords, relations) {
|
|
1014
|
+
let result = [...parentRecords];
|
|
1015
|
+
for (const relation of relations) {
|
|
1016
|
+
const resolved = this.resolver.resolve(parentTable, relation.name);
|
|
1017
|
+
if (!resolved) {
|
|
1018
|
+
console.warn(
|
|
1019
|
+
`Could not resolve relationship: ${parentTable} -> ${relation.name}`
|
|
1020
|
+
);
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
const parentIds = this.getParentIdsForRelation(parentRecords, resolved);
|
|
1024
|
+
if (parentIds.length === 0) {
|
|
1025
|
+
result = result.map((r) => ({
|
|
1026
|
+
...r,
|
|
1027
|
+
[relation.alias ?? relation.name]: resolved.type === "one-to-many" ? [] : null
|
|
1028
|
+
}));
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
const relQuery = this.buildRelatedQuery(relation, resolved, parentIds);
|
|
1032
|
+
let relatedRecords = await this.db.getAll(
|
|
1033
|
+
relQuery.sql,
|
|
1034
|
+
relQuery.params
|
|
1035
|
+
);
|
|
1036
|
+
if (relation.relations.length > 0 && relatedRecords.length > 0) {
|
|
1037
|
+
relatedRecords = await this.queryAndJoinRelations(
|
|
1038
|
+
relation.name,
|
|
1039
|
+
relatedRecords,
|
|
1040
|
+
relation.relations
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
result = this.joiner.join(
|
|
1044
|
+
result,
|
|
1045
|
+
relatedRecords,
|
|
1046
|
+
resolved,
|
|
1047
|
+
relation.alias ?? relation.name
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
return result;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Get parent IDs needed for a relation query.
|
|
1054
|
+
*/
|
|
1055
|
+
getParentIdsForRelation(parentRecords, resolved) {
|
|
1056
|
+
let parentIds;
|
|
1057
|
+
if (resolved.type === "one-to-many") {
|
|
1058
|
+
parentIds = this.joiner.extractUniqueValues(
|
|
1059
|
+
parentRecords,
|
|
1060
|
+
resolved.referencedColumn
|
|
1061
|
+
);
|
|
1062
|
+
} else {
|
|
1063
|
+
parentIds = this.joiner.extractUniqueValues(
|
|
1064
|
+
parentRecords,
|
|
1065
|
+
resolved.foreignKey
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
return parentIds;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Build a query for related records.
|
|
1072
|
+
*/
|
|
1073
|
+
buildRelatedQuery(relation, resolved, parentIds) {
|
|
1074
|
+
const filterColumn = resolved.type === "one-to-many" ? resolved.foreignKey : resolved.referencedColumn;
|
|
1075
|
+
const uniqueIds = Array.from(new Set(parentIds));
|
|
1076
|
+
return this.builder.buildRelationQuery(
|
|
1077
|
+
relation.name,
|
|
1078
|
+
filterColumn,
|
|
1079
|
+
uniqueIds,
|
|
1080
|
+
relation.columns
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Get the relationship resolver (for advanced use).
|
|
1085
|
+
*/
|
|
1086
|
+
getResolver() {
|
|
1087
|
+
return this.resolver;
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Get the SQL builder (for advanced use).
|
|
1091
|
+
*/
|
|
1092
|
+
getBuilder() {
|
|
1093
|
+
return this.builder;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Get the result joiner (for advanced use).
|
|
1097
|
+
*/
|
|
1098
|
+
getJoiner() {
|
|
1099
|
+
return this.joiner;
|
|
1100
|
+
}
|
|
1101
|
+
};
|
|
1102
|
+
function createQueryExecutor(db, schema) {
|
|
1103
|
+
return new QueryExecutor(db, schema);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// src/query/useQuery.ts
|
|
1107
|
+
import { useMemo } from "react";
|
|
1108
|
+
import { useDelayedValue } from "@pol-studios/hooks/state";
|
|
1109
|
+
function useQuery2(query, config) {
|
|
1110
|
+
const queryKey = encode(query, false);
|
|
1111
|
+
const queryKeyString = queryKey.join("-");
|
|
1112
|
+
const debouncedKeyString = useDelayedValue(queryKeyString, 50);
|
|
1113
|
+
const isKeyStable = queryKeyString === debouncedKeyString;
|
|
1114
|
+
const effectiveEnabled = config?.enabled !== false && isKeyStable;
|
|
1115
|
+
const request = useQuery(
|
|
1116
|
+
query,
|
|
1117
|
+
useMemo(() => omit({ retry: 1, ...config, enabled: effectiveEnabled }, ["queryKey"]), [config, effectiveEnabled])
|
|
1118
|
+
);
|
|
1119
|
+
return request;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// src/query/usePartialQuery.ts
|
|
1123
|
+
import { useMemo as useMemo2 } from "react";
|
|
1124
|
+
import { useSessionStorageState } from "@pol-studios/hooks/storage";
|
|
1125
|
+
function usePartialQuery(query, itemCountPerPage, config) {
|
|
1126
|
+
const initialQuery = encode(query, false);
|
|
1127
|
+
const id = useMemo2(
|
|
1128
|
+
() => [
|
|
1129
|
+
initialQuery[7],
|
|
1130
|
+
initialQuery[8]
|
|
1131
|
+
].join("-"),
|
|
1132
|
+
[
|
|
1133
|
+
initialQuery[7],
|
|
1134
|
+
initialQuery[8]
|
|
1135
|
+
]
|
|
1136
|
+
);
|
|
1137
|
+
const [currentPage, setCurrentPage] = useSessionStorageState(id, 1);
|
|
1138
|
+
const rangedQuery = useMemo2(
|
|
1139
|
+
() => {
|
|
1140
|
+
const page = currentPage ?? 1;
|
|
1141
|
+
return query.range(
|
|
1142
|
+
(page - 1) * itemCountPerPage,
|
|
1143
|
+
page * itemCountPerPage - 1
|
|
1144
|
+
);
|
|
1145
|
+
},
|
|
1146
|
+
[query, currentPage, itemCountPerPage]
|
|
1147
|
+
);
|
|
1148
|
+
const baseQuery = useQuery2(rangedQuery, config);
|
|
1149
|
+
const safeFetchNextPage = () => {
|
|
1150
|
+
setCurrentPage((currentPage2) => currentPage2 + 1);
|
|
1151
|
+
};
|
|
1152
|
+
const fetchPreviousPage = () => {
|
|
1153
|
+
setCurrentPage((currentPage2) => currentPage2 - 1);
|
|
1154
|
+
};
|
|
1155
|
+
const pageCount = Math.max(
|
|
1156
|
+
Math.ceil(
|
|
1157
|
+
(baseQuery.count ?? 0) / itemCountPerPage
|
|
1158
|
+
),
|
|
1159
|
+
1
|
|
1160
|
+
);
|
|
1161
|
+
return {
|
|
1162
|
+
...baseQuery,
|
|
1163
|
+
fetchPreviousPage,
|
|
1164
|
+
fetchNextPage: safeFetchNextPage,
|
|
1165
|
+
currentPage,
|
|
1166
|
+
setCurrentPage,
|
|
1167
|
+
data: baseQuery.data ? toPagedResponse(
|
|
1168
|
+
baseQuery.data,
|
|
1169
|
+
currentPage,
|
|
1170
|
+
baseQuery.count ?? baseQuery.data.length,
|
|
1171
|
+
itemCountPerPage
|
|
1172
|
+
) : null,
|
|
1173
|
+
pageCount,
|
|
1174
|
+
hasNextPage: currentPage < pageCount,
|
|
1175
|
+
hasPreviousPage: currentPage > 1
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
function toPagedResponse(results, currentPage, totalCount, itemPerPage) {
|
|
1179
|
+
const newPage = {
|
|
1180
|
+
Items: results,
|
|
1181
|
+
CurrentPage: currentPage,
|
|
1182
|
+
ItemCount: totalCount,
|
|
1183
|
+
MaxCountPerPage: itemPerPage,
|
|
1184
|
+
PageCount: Math.max(Math.ceil(totalCount / itemPerPage), 1)
|
|
1185
|
+
};
|
|
1186
|
+
return newPage;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// src/query/useAdvancedQuery.ts
|
|
1190
|
+
import {
|
|
1191
|
+
useEffect,
|
|
1192
|
+
useMemo as useMemo3,
|
|
1193
|
+
useRef,
|
|
1194
|
+
useState
|
|
1195
|
+
} from "react";
|
|
1196
|
+
import {
|
|
1197
|
+
useQuery as useQuery3
|
|
1198
|
+
} from "@tanstack/react-query";
|
|
1199
|
+
import { useSessionStorageState as useSessionStorageState2 } from "@pol-studios/hooks/storage";
|
|
1200
|
+
var normalizeFilter = (filter) => {
|
|
1201
|
+
const groupOp = filter.op || filter.operator;
|
|
1202
|
+
if (groupOp && (groupOp === "AND" || groupOp === "OR") && filter.filters) {
|
|
1203
|
+
return {
|
|
1204
|
+
id: filter.id || generateUUID(),
|
|
1205
|
+
op: groupOp,
|
|
1206
|
+
not: filter.not || filter.inverted,
|
|
1207
|
+
// Support both 'not' and legacy 'inverted' for groups
|
|
1208
|
+
filters: filter.filters.map(normalizeFilter).filter(
|
|
1209
|
+
Boolean
|
|
1210
|
+
)
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
let operator = filter.op || filter.condition;
|
|
1214
|
+
let shouldNegate = filter.not || filter.inverted || false;
|
|
1215
|
+
if (operator === "\u220B") operator = "in";
|
|
1216
|
+
if (operator === "\u2260" || operator === "!=") {
|
|
1217
|
+
operator = "=";
|
|
1218
|
+
shouldNegate = true;
|
|
1219
|
+
}
|
|
1220
|
+
if (operator === "like") operator = "contains";
|
|
1221
|
+
if (operator === "regex" || operator === "regex_i" || operator === "similar_to") {
|
|
1222
|
+
operator = "contains";
|
|
1223
|
+
}
|
|
1224
|
+
if (!operator) {
|
|
1225
|
+
console.error(
|
|
1226
|
+
"Filter has undefined operator:",
|
|
1227
|
+
JSON.stringify(filter, null, 2)
|
|
1228
|
+
);
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
if (filter.propertyName) {
|
|
1232
|
+
return {
|
|
1233
|
+
id: filter.id || generateUUID(),
|
|
1234
|
+
field: filter.propertyName,
|
|
1235
|
+
op: operator,
|
|
1236
|
+
value: filter.value,
|
|
1237
|
+
not: shouldNegate,
|
|
1238
|
+
display: filter.display || filter.propertyName
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
id: filter.id || generateUUID(),
|
|
1243
|
+
field: filter.field || "",
|
|
1244
|
+
op: operator,
|
|
1245
|
+
value: filter.value,
|
|
1246
|
+
not: shouldNegate,
|
|
1247
|
+
similarity: filter.similarity,
|
|
1248
|
+
where: filter.where,
|
|
1249
|
+
display: filter.display || filter.field || ""
|
|
1250
|
+
};
|
|
1251
|
+
};
|
|
1252
|
+
function useAdvancedFilterQuery(query, config) {
|
|
1253
|
+
const filterKey = useMemo3(
|
|
1254
|
+
() => config?.filterKey ?? window.location?.pathname,
|
|
1255
|
+
[
|
|
1256
|
+
config?.filterKey
|
|
1257
|
+
]
|
|
1258
|
+
);
|
|
1259
|
+
const [filterLayer, setFilterLayer] = useSessionStorageState2(filterKey, {
|
|
1260
|
+
id: "root",
|
|
1261
|
+
op: "AND",
|
|
1262
|
+
filters: [],
|
|
1263
|
+
pagination: void 0,
|
|
1264
|
+
sort: [],
|
|
1265
|
+
isReady: isUsable(config?.searchByDefault) ? config.searchByDefault ? false : true : true
|
|
1266
|
+
});
|
|
1267
|
+
const parser = useMemo3(
|
|
1268
|
+
() => new PostgrestParser(query),
|
|
1269
|
+
[query, config?.key]
|
|
1270
|
+
);
|
|
1271
|
+
useEffect(() => {
|
|
1272
|
+
const searchParam = parser.searchParams.get("order");
|
|
1273
|
+
if (searchParam) {
|
|
1274
|
+
const orderColumns = searchParam.split(",");
|
|
1275
|
+
const orders = [];
|
|
1276
|
+
orderColumns.forEach((x) => {
|
|
1277
|
+
const values = x.split(".");
|
|
1278
|
+
orders.push({
|
|
1279
|
+
field: values[0],
|
|
1280
|
+
direction: values[1] === "asc" ? "asc" : "desc"
|
|
1281
|
+
});
|
|
1282
|
+
});
|
|
1283
|
+
setFilterLayer((pre) => {
|
|
1284
|
+
if (!pre) {
|
|
1285
|
+
return {
|
|
1286
|
+
id: "root",
|
|
1287
|
+
op: "AND",
|
|
1288
|
+
filters: [],
|
|
1289
|
+
isReady: true,
|
|
1290
|
+
sort: orders
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
...pre,
|
|
1295
|
+
id: pre.id || "root",
|
|
1296
|
+
op: pre.op || "AND",
|
|
1297
|
+
filters: pre.filters || [],
|
|
1298
|
+
isReady: pre.isReady ?? true,
|
|
1299
|
+
sort: [
|
|
1300
|
+
...(pre.sort || []).filter(
|
|
1301
|
+
(old) => orders.some((o) => o.field === old.field) === false
|
|
1302
|
+
),
|
|
1303
|
+
...orders
|
|
1304
|
+
]
|
|
1305
|
+
};
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
}, [JSON.stringify(parser.searchParams), config?.key]);
|
|
1309
|
+
const encodedQueryKeyRef = useRef([]);
|
|
1310
|
+
const encodedQueryKey = useMemo3(() => {
|
|
1311
|
+
const newEncoded = encode(query, false);
|
|
1312
|
+
const newEncodedString = JSON.stringify(newEncoded);
|
|
1313
|
+
const oldEncodedString = JSON.stringify(encodedQueryKeyRef.current);
|
|
1314
|
+
if (newEncodedString !== oldEncodedString) {
|
|
1315
|
+
encodedQueryKeyRef.current = newEncoded;
|
|
1316
|
+
}
|
|
1317
|
+
return encodedQueryKeyRef.current;
|
|
1318
|
+
}, [query, parser.offset, parser.limit]);
|
|
1319
|
+
const filterLayerStringRef = useRef("");
|
|
1320
|
+
const currentFilterString = JSON.stringify(filterLayer, (key, value) => key === "id" ? "redacted" : value);
|
|
1321
|
+
if (filterLayerStringRef.current !== currentFilterString) {
|
|
1322
|
+
filterLayerStringRef.current = currentFilterString;
|
|
1323
|
+
}
|
|
1324
|
+
const queryKeyRef = useRef([]);
|
|
1325
|
+
const newKey = [
|
|
1326
|
+
encodedQueryKey[0],
|
|
1327
|
+
encodedQueryKey[1],
|
|
1328
|
+
encodedQueryKey[2],
|
|
1329
|
+
encodedQueryKey[3],
|
|
1330
|
+
encodedQueryKey[4],
|
|
1331
|
+
encodedQueryKey[5],
|
|
1332
|
+
"count=" + (config?.count ?? ""),
|
|
1333
|
+
encodedQueryKey[7],
|
|
1334
|
+
encodedQueryKey[8],
|
|
1335
|
+
filterLayerStringRef.current
|
|
1336
|
+
];
|
|
1337
|
+
const newKeyString = JSON.stringify(newKey);
|
|
1338
|
+
const oldKeyString = JSON.stringify(queryKeyRef.current);
|
|
1339
|
+
if (oldKeyString !== newKeyString) {
|
|
1340
|
+
queryKeyRef.current = newKey;
|
|
1341
|
+
}
|
|
1342
|
+
const queryKey = queryKeyRef.current;
|
|
1343
|
+
const [previousKey, setPreviousKey] = useState(queryKey);
|
|
1344
|
+
useEffect(() => {
|
|
1345
|
+
if (filterLayer?.isReady) {
|
|
1346
|
+
setPreviousKey(queryKey);
|
|
1347
|
+
}
|
|
1348
|
+
}, [...queryKey]);
|
|
1349
|
+
const loadingKey = filterLayer?.isReady ? queryKey : previousKey ?? queryKey;
|
|
1350
|
+
const supabase = useSupabase();
|
|
1351
|
+
const isEnabled = config?.enabled == null || config?.enabled === void 0 ? true : config.enabled;
|
|
1352
|
+
const [extraData, setExtraData] = useState({});
|
|
1353
|
+
const queryResponse = useQuery3({
|
|
1354
|
+
...omit(
|
|
1355
|
+
{
|
|
1356
|
+
retry: 0,
|
|
1357
|
+
// Changed from 1 to 0 to prevent retries
|
|
1358
|
+
...config ?? {},
|
|
1359
|
+
// Override any config settings to prevent multiple executions
|
|
1360
|
+
refetchOnMount: false,
|
|
1361
|
+
refetchOnWindowFocus: false,
|
|
1362
|
+
refetchOnReconnect: false,
|
|
1363
|
+
structuralSharing: false,
|
|
1364
|
+
enabled: filterLayer?.isReady && isEnabled
|
|
1365
|
+
},
|
|
1366
|
+
["queryKey", "persister", "initialData"]
|
|
1367
|
+
),
|
|
1368
|
+
queryKey: loadingKey,
|
|
1369
|
+
queryFn: async (props) => {
|
|
1370
|
+
if (!filterLayer) {
|
|
1371
|
+
throw new Error("Filter layer is not initialized");
|
|
1372
|
+
}
|
|
1373
|
+
try {
|
|
1374
|
+
const searchParams = Array.from(parser.searchParams.entries());
|
|
1375
|
+
const body = {
|
|
1376
|
+
...filterLayer,
|
|
1377
|
+
filters: [...filterLayer.filters],
|
|
1378
|
+
pagination: { ...filterLayer.pagination },
|
|
1379
|
+
sort: [...filterLayer.sort || []]
|
|
1380
|
+
};
|
|
1381
|
+
const currentKey = `${parser.schema}${parser.table}${parser.select}${JSON.stringify(omit(body, "pagination"))}`;
|
|
1382
|
+
const requiresEdgeForOrdering = false;
|
|
1383
|
+
const hasNaturalLanguageQuery = !!filterLayer.naturalLanguageQuery;
|
|
1384
|
+
if (filterLayer.filters.length == 0 && requiresEdgeForOrdering === false && !hasNaturalLanguageQuery) {
|
|
1385
|
+
const result2 = await executeSupabaseQuery(
|
|
1386
|
+
supabase,
|
|
1387
|
+
body,
|
|
1388
|
+
parser,
|
|
1389
|
+
extraData,
|
|
1390
|
+
props.signal,
|
|
1391
|
+
config?.count
|
|
1392
|
+
);
|
|
1393
|
+
setExtraData((pre) => ({
|
|
1394
|
+
...omit(result2, "data"),
|
|
1395
|
+
count: result2.count ? result2.count : pre.count,
|
|
1396
|
+
key: currentKey
|
|
1397
|
+
}));
|
|
1398
|
+
return result2;
|
|
1399
|
+
}
|
|
1400
|
+
searchParams.forEach(([k, v]) => {
|
|
1401
|
+
if (k.includes("offset")) {
|
|
1402
|
+
body.pagination.offset = Number(v);
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
if (k.includes("limit")) {
|
|
1406
|
+
body.pagination.limit = Number(v);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (k.includes("order")) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
if (k === "or") {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (v.includes(".") === false) return;
|
|
1416
|
+
const values = v.split(".");
|
|
1417
|
+
const column = k;
|
|
1418
|
+
let rawCondition = values[0];
|
|
1419
|
+
let condition = "";
|
|
1420
|
+
let value = values[1];
|
|
1421
|
+
const inverted = values[0] === "not";
|
|
1422
|
+
if (column == "select") return;
|
|
1423
|
+
if (rawCondition === "not") {
|
|
1424
|
+
rawCondition = values[1];
|
|
1425
|
+
value = values[2];
|
|
1426
|
+
}
|
|
1427
|
+
switch (rawCondition) {
|
|
1428
|
+
case "eq":
|
|
1429
|
+
condition = "=";
|
|
1430
|
+
break;
|
|
1431
|
+
case "in":
|
|
1432
|
+
condition = "in";
|
|
1433
|
+
value = value.slice(1, value.length - 1).split(",").filter((x) => isNullOrWhitespace(x) === false);
|
|
1434
|
+
break;
|
|
1435
|
+
case "lt":
|
|
1436
|
+
condition = "<";
|
|
1437
|
+
break;
|
|
1438
|
+
case "gt":
|
|
1439
|
+
condition = ">";
|
|
1440
|
+
break;
|
|
1441
|
+
case "lte":
|
|
1442
|
+
condition = "<=";
|
|
1443
|
+
break;
|
|
1444
|
+
case "gte":
|
|
1445
|
+
condition = ">=";
|
|
1446
|
+
break;
|
|
1447
|
+
case "is":
|
|
1448
|
+
condition = "is";
|
|
1449
|
+
if (value == "null") {
|
|
1450
|
+
value = null;
|
|
1451
|
+
}
|
|
1452
|
+
break;
|
|
1453
|
+
}
|
|
1454
|
+
body.filters = [{
|
|
1455
|
+
id: `filter_${column}_${Date.now()}`,
|
|
1456
|
+
field: column,
|
|
1457
|
+
op: condition,
|
|
1458
|
+
value,
|
|
1459
|
+
not: inverted,
|
|
1460
|
+
display: column
|
|
1461
|
+
}, { filters: [...body.filters], op: body.op ?? "AND", id: "filterstate" }];
|
|
1462
|
+
body.op = "AND";
|
|
1463
|
+
const bodyCopy = JSON.parse(JSON.stringify(body));
|
|
1464
|
+
bodyCopy.pagination = { page: 0, pageSize: 50 };
|
|
1465
|
+
bodyCopy.isReady = true;
|
|
1466
|
+
});
|
|
1467
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
1468
|
+
if (!session?.access_token) {
|
|
1469
|
+
throw new Error("No active session");
|
|
1470
|
+
}
|
|
1471
|
+
const controller = new AbortController();
|
|
1472
|
+
props.signal.addEventListener("abort", () => {
|
|
1473
|
+
controller.abort();
|
|
1474
|
+
});
|
|
1475
|
+
const timeout = setTimeout(
|
|
1476
|
+
() => controller.abort(),
|
|
1477
|
+
config?.timeout ?? 15e3
|
|
1478
|
+
);
|
|
1479
|
+
let result = null;
|
|
1480
|
+
let response2 = { error: null, data: null };
|
|
1481
|
+
try {
|
|
1482
|
+
const UI_ONLY_KEYS = /* @__PURE__ */ new Set(["info", "options", "display"]);
|
|
1483
|
+
const filteredBody = JSON.parse(JSON.stringify(body, (key, value) => {
|
|
1484
|
+
if (UI_ONLY_KEYS.has(key)) {
|
|
1485
|
+
return void 0;
|
|
1486
|
+
}
|
|
1487
|
+
return value;
|
|
1488
|
+
}));
|
|
1489
|
+
filteredBody.filters = filteredBody.filters.map(normalizeFilter).filter(Boolean);
|
|
1490
|
+
const orParam = parser.searchParams.get("or");
|
|
1491
|
+
if (orParam) {
|
|
1492
|
+
const cleanedOrParam = orParam.replace(/^\(|\)$/g, "");
|
|
1493
|
+
const orConditions = cleanedOrParam.split(",");
|
|
1494
|
+
const orFilters = orConditions.map((condition, idx) => {
|
|
1495
|
+
const match = condition.match(/^(.+?)\.([^.]+)\.(.+)$/);
|
|
1496
|
+
if (match) {
|
|
1497
|
+
const [_, field, rawOp, value] = match;
|
|
1498
|
+
let op = rawOp.trim();
|
|
1499
|
+
switch (op) {
|
|
1500
|
+
case "eq":
|
|
1501
|
+
op = "=";
|
|
1502
|
+
break;
|
|
1503
|
+
case "neq":
|
|
1504
|
+
op = "!=";
|
|
1505
|
+
break;
|
|
1506
|
+
case "gt":
|
|
1507
|
+
op = ">";
|
|
1508
|
+
break;
|
|
1509
|
+
case "gte":
|
|
1510
|
+
op = ">=";
|
|
1511
|
+
break;
|
|
1512
|
+
case "lt":
|
|
1513
|
+
op = "<";
|
|
1514
|
+
break;
|
|
1515
|
+
case "lte":
|
|
1516
|
+
op = "<=";
|
|
1517
|
+
break;
|
|
1518
|
+
case "like":
|
|
1519
|
+
op = "contains";
|
|
1520
|
+
break;
|
|
1521
|
+
case "ilike":
|
|
1522
|
+
op = "contains";
|
|
1523
|
+
break;
|
|
1524
|
+
case "in":
|
|
1525
|
+
op = "in";
|
|
1526
|
+
break;
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
id: `or-${idx}`,
|
|
1530
|
+
field: field.trim(),
|
|
1531
|
+
op,
|
|
1532
|
+
value: value.trim()
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
return null;
|
|
1536
|
+
}).filter(Boolean);
|
|
1537
|
+
if (orFilters.length > 0) {
|
|
1538
|
+
filteredBody.filters = [
|
|
1539
|
+
...filteredBody.filters,
|
|
1540
|
+
{
|
|
1541
|
+
id: "base-or-group",
|
|
1542
|
+
op: "OR",
|
|
1543
|
+
filters: orFilters,
|
|
1544
|
+
pagination: void 0,
|
|
1545
|
+
sort: void 0,
|
|
1546
|
+
isReady: true
|
|
1547
|
+
}
|
|
1548
|
+
];
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
const res = await fetch(
|
|
1552
|
+
`${getSupabaseUrl()}/functions/v1/query?forceDenoVersion=2`,
|
|
1553
|
+
{
|
|
1554
|
+
method: "POST",
|
|
1555
|
+
headers: {
|
|
1556
|
+
"Content-Type": "application/json",
|
|
1557
|
+
"Authorization": `Bearer ${session.access_token}`
|
|
1558
|
+
},
|
|
1559
|
+
body: JSON.stringify({
|
|
1560
|
+
table: parser.table,
|
|
1561
|
+
schema: parser.schema,
|
|
1562
|
+
select: parser.select,
|
|
1563
|
+
filters: {
|
|
1564
|
+
id: filteredBody.id || "root",
|
|
1565
|
+
op: filteredBody.op || filteredBody.operator || "AND",
|
|
1566
|
+
not: filteredBody.not || filteredBody.inverted,
|
|
1567
|
+
// Support both 'not' and legacy 'inverted'
|
|
1568
|
+
filters: filteredBody.filters || []
|
|
1569
|
+
},
|
|
1570
|
+
pagination: filteredBody.pagination,
|
|
1571
|
+
sort: filteredBody.sort,
|
|
1572
|
+
distinctOn: filteredBody.distinctOn,
|
|
1573
|
+
naturalLanguageQuery: filteredBody.naturalLanguageQuery,
|
|
1574
|
+
count: currentKey === extraData.key ? "" : config?.count ?? "",
|
|
1575
|
+
debug: true
|
|
1576
|
+
}),
|
|
1577
|
+
signal: controller.signal
|
|
1578
|
+
}
|
|
1579
|
+
);
|
|
1580
|
+
if (!res.ok) {
|
|
1581
|
+
const errorData = await res.json();
|
|
1582
|
+
const errorMessage = typeof errorData?.error === "string" ? errorData.error : errorData?.error?.message || errorData?.message || "An error occurred while processing your request";
|
|
1583
|
+
throw new Error(errorMessage);
|
|
1584
|
+
}
|
|
1585
|
+
const data = await res.json();
|
|
1586
|
+
if (data.clarification) {
|
|
1587
|
+
return {
|
|
1588
|
+
data: [],
|
|
1589
|
+
// Empty data array
|
|
1590
|
+
count: 0,
|
|
1591
|
+
clarification: data.clarification,
|
|
1592
|
+
error: void 0
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
result = data;
|
|
1596
|
+
response2 = { error: null, data };
|
|
1597
|
+
} catch (err) {
|
|
1598
|
+
if (err.name === "AbortError") {
|
|
1599
|
+
console.error("Fetch aborted/time-out");
|
|
1600
|
+
response2 = { error: new Error("This query timed out"), data: null };
|
|
1601
|
+
} else if (err instanceof Error) {
|
|
1602
|
+
response2 = { error: err, data: null };
|
|
1603
|
+
} else {
|
|
1604
|
+
const errorMessage = err?.error || err?.message || String(err);
|
|
1605
|
+
response2 = { error: new Error(errorMessage), data: null };
|
|
1606
|
+
}
|
|
1607
|
+
} finally {
|
|
1608
|
+
clearTimeout(timeout);
|
|
1609
|
+
}
|
|
1610
|
+
if (response2.error) {
|
|
1611
|
+
throw response2.error;
|
|
1612
|
+
} else if (response2.data?.error) {
|
|
1613
|
+
throw new Error(response2.data.error);
|
|
1614
|
+
}
|
|
1615
|
+
if (result.clarification) {
|
|
1616
|
+
return {
|
|
1617
|
+
data: [],
|
|
1618
|
+
count: 0,
|
|
1619
|
+
clarification: result.clarification,
|
|
1620
|
+
error: void 0
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
setExtraData((pre) => ({
|
|
1624
|
+
...omit(result, "data"),
|
|
1625
|
+
count: pre.key === currentKey ? pre.count : result.count,
|
|
1626
|
+
key: currentKey
|
|
1627
|
+
}));
|
|
1628
|
+
return {
|
|
1629
|
+
...result,
|
|
1630
|
+
statusText: "",
|
|
1631
|
+
status: result.data?.length > result.count ? 206 : 200,
|
|
1632
|
+
error: response2.data?.error ?? null,
|
|
1633
|
+
hasMore: result.data?.length < result.count ? true : false
|
|
1634
|
+
};
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1637
|
+
console.log("Fetch aborted");
|
|
1638
|
+
} else {
|
|
1639
|
+
console.error("Error fetching data:", error);
|
|
1640
|
+
}
|
|
1641
|
+
if (error instanceof Error && error.message === "Failed to send a request to the Edge Function") {
|
|
1642
|
+
throw new Error("Could not contact query server");
|
|
1643
|
+
}
|
|
1644
|
+
throw error;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
const response = {
|
|
1649
|
+
...queryResponse,
|
|
1650
|
+
data: queryResponse.data?.data,
|
|
1651
|
+
count: extraData.count,
|
|
1652
|
+
clarification: queryResponse.data?.clarification
|
|
1653
|
+
};
|
|
1654
|
+
useEffect(() => {
|
|
1655
|
+
if (queryResponse.isFetched && response.count == null) {
|
|
1656
|
+
queryResponse.refetch();
|
|
1657
|
+
}
|
|
1658
|
+
}, [response.count]);
|
|
1659
|
+
useEffect(() => {
|
|
1660
|
+
if (queryResponse.data?.error == null) return;
|
|
1661
|
+
if (queryResponse.data?.error?.message?.includes(" does not exist")) {
|
|
1662
|
+
setFilterLayer({
|
|
1663
|
+
id: "root",
|
|
1664
|
+
op: "AND",
|
|
1665
|
+
filters: [],
|
|
1666
|
+
pagination: void 0,
|
|
1667
|
+
sort: [],
|
|
1668
|
+
isReady: isUsable(config?.searchByDefault) ? config.searchByDefault ? false : true : true
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
}, [queryResponse.data?.error]);
|
|
1672
|
+
return [response, filterLayer, setFilterLayer];
|
|
1673
|
+
}
|
|
1674
|
+
function isSearchableColumn(columnName) {
|
|
1675
|
+
return true;
|
|
1676
|
+
}
|
|
1677
|
+
async function executeSupabaseQuery(supabase, body, parser, extraData, signal, count) {
|
|
1678
|
+
const searchParams = Array.from(parser.searchParams.entries());
|
|
1679
|
+
const currentKey = `${parser.schema}${parser.table}${parser.select}${JSON.stringify(omit(body, "pagination"))}`;
|
|
1680
|
+
const query = supabase.schema(parser.schema).from(parser.table).select(parser.select, {
|
|
1681
|
+
count: currentKey === extraData.key ? void 0 : count || void 0
|
|
1682
|
+
});
|
|
1683
|
+
if (body.sort && Array.isArray(body.sort)) {
|
|
1684
|
+
body.sort.forEach((s) => {
|
|
1685
|
+
query.order(s.field, { ascending: s.direction === "asc" });
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
const from = searchParams.find((x) => x[0].includes("offset"))?.[1];
|
|
1689
|
+
const to = searchParams.find((x) => x[0].includes("limit"))?.[1];
|
|
1690
|
+
searchParams.forEach(([k, v]) => {
|
|
1691
|
+
if (k.includes("offset") && isUsable(v)) {
|
|
1692
|
+
body.pagination.from = Number(v);
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
if (k.includes("limit") && isUsable(v)) {
|
|
1696
|
+
body.pagination.to = Number(from) + Number(v) - 1;
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
if (k === "or") {
|
|
1700
|
+
query.or(v.slice(1, v.length - 2));
|
|
1701
|
+
return;
|
|
1702
|
+
}
|
|
1703
|
+
const values = v.split(".");
|
|
1704
|
+
const column = k;
|
|
1705
|
+
let rawCondition = values[0];
|
|
1706
|
+
let value = values[1];
|
|
1707
|
+
if (column == "select") return;
|
|
1708
|
+
if (v.includes(".") === false) return;
|
|
1709
|
+
if (rawCondition === "ilike" && !isSearchableColumn(column)) {
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
if (rawCondition === "not") {
|
|
1713
|
+
rawCondition = values[1];
|
|
1714
|
+
value = values[2];
|
|
1715
|
+
query.not(column, rawCondition, value);
|
|
1716
|
+
} else {
|
|
1717
|
+
query.filter(column, rawCondition, value);
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
if (body.pagination.from !== null && body.pagination.from !== void 0 && body.pagination.to !== null && body.pagination.to !== void 0 && isNaN(body.pagination.from) === false && isNaN(body.pagination.to) === false) {
|
|
1721
|
+
query.range(body.pagination.from, body.pagination.to);
|
|
1722
|
+
}
|
|
1723
|
+
const result = await query.abortSignal(signal);
|
|
1724
|
+
const dataLength = result.data?.length ?? 0;
|
|
1725
|
+
const totalCount = result.count ?? 0;
|
|
1726
|
+
return {
|
|
1727
|
+
...result,
|
|
1728
|
+
statusText: "",
|
|
1729
|
+
status: dataLength > totalCount ? 206 : 200,
|
|
1730
|
+
error: result.error,
|
|
1731
|
+
hasMore: dataLength < totalCount
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
var useAdvancedQuery = useAdvancedFilterQuery;
|
|
1735
|
+
|
|
1736
|
+
// src/query/useInfiniteQuery.ts
|
|
1737
|
+
import {
|
|
1738
|
+
useInfiniteQuery as useTanstackInfiniteQuery
|
|
1739
|
+
} from "@tanstack/react-query";
|
|
1740
|
+
import { useMemo as useMemo4, useRef as useRef2 } from "react";
|
|
1741
|
+
function useInfiniteQuery(query, countPerLoad, config) {
|
|
1742
|
+
const initialQueryKey = encode(query, false).join("-");
|
|
1743
|
+
const lastKnownQuery = useRef2(initialQueryKey);
|
|
1744
|
+
const currentPageNumber = useRef2(1);
|
|
1745
|
+
if (lastKnownQuery.current != initialQueryKey) {
|
|
1746
|
+
lastKnownQuery.current = initialQueryKey;
|
|
1747
|
+
currentPageNumber.current = 1;
|
|
1748
|
+
}
|
|
1749
|
+
const isFetching = useRef2(false);
|
|
1750
|
+
const queryKey = useMemo4(
|
|
1751
|
+
() => encode(query, false),
|
|
1752
|
+
[initialQueryKey, config?.crossOrganization]
|
|
1753
|
+
);
|
|
1754
|
+
const getQuery = useTanstackInfiniteQuery({
|
|
1755
|
+
...config,
|
|
1756
|
+
queryKey,
|
|
1757
|
+
queryFn: async ({ pageParam, signal }) => {
|
|
1758
|
+
let adjustableQuery = query;
|
|
1759
|
+
const pageNumber = pageParam;
|
|
1760
|
+
if (config?.onQuery && config?.enableOnQuery) {
|
|
1761
|
+
config?.onQuery({ query: adjustableQuery, pageParam: pageNumber });
|
|
1762
|
+
} else {
|
|
1763
|
+
adjustableQuery = adjustableQuery.range(
|
|
1764
|
+
(pageNumber - 1) * countPerLoad,
|
|
1765
|
+
pageNumber * countPerLoad - 1
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
adjustableQuery = adjustableQuery.abortSignal(signal);
|
|
1769
|
+
updatedCache.current = false;
|
|
1770
|
+
const response = await adjustableQuery;
|
|
1771
|
+
currentPageNumber.current = pageNumber;
|
|
1772
|
+
if (response.error) {
|
|
1773
|
+
throw response.error;
|
|
1774
|
+
} else {
|
|
1775
|
+
return response;
|
|
1776
|
+
}
|
|
1777
|
+
},
|
|
1778
|
+
initialPageParam: 1,
|
|
1779
|
+
getNextPageParam: (response, allResponses, lastParam) => {
|
|
1780
|
+
const pageParam = lastParam;
|
|
1781
|
+
const resp = response;
|
|
1782
|
+
if (allResponses.length * countPerLoad >= (resp?.count ?? 0)) {
|
|
1783
|
+
return void 0;
|
|
1784
|
+
}
|
|
1785
|
+
return pageParam + 1;
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
const updatedCache = useRef2(true);
|
|
1789
|
+
return useMemo4(
|
|
1790
|
+
() => {
|
|
1791
|
+
const pages = getQuery.data?.pages;
|
|
1792
|
+
return {
|
|
1793
|
+
...getQuery,
|
|
1794
|
+
count: pages?.[pages.length - 1]?.count,
|
|
1795
|
+
data: pages?.flatMap((x) => x.data ?? [])
|
|
1796
|
+
};
|
|
1797
|
+
},
|
|
1798
|
+
[getQuery.data, currentPageNumber.current]
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// src/query/usePartialAdvancedQuery.ts
|
|
1803
|
+
import { useLayoutEffect, useMemo as useMemo5, useState as useState2 } from "react";
|
|
1804
|
+
import { useSessionStorageState as useSessionStorageState3 } from "@pol-studios/hooks/storage";
|
|
1805
|
+
function usePartialAdvancedQuery(query, itemCountPerPage, config) {
|
|
1806
|
+
const initialQuery = encode(query, false);
|
|
1807
|
+
const [id, setId] = useState2(window.location.pathname);
|
|
1808
|
+
const [currentPage, setCurrentPage] = useSessionStorageState3(
|
|
1809
|
+
`${id}-currentPage`,
|
|
1810
|
+
1
|
|
1811
|
+
);
|
|
1812
|
+
const rangedQuery = useMemo5(
|
|
1813
|
+
() => {
|
|
1814
|
+
const page = currentPage ?? 1;
|
|
1815
|
+
return query.range(
|
|
1816
|
+
(page - 1) * itemCountPerPage,
|
|
1817
|
+
page * itemCountPerPage - 1
|
|
1818
|
+
);
|
|
1819
|
+
},
|
|
1820
|
+
[query, currentPage, itemCountPerPage]
|
|
1821
|
+
);
|
|
1822
|
+
const [baseQuery, filter, setFilters] = useAdvancedFilterQuery(
|
|
1823
|
+
rangedQuery,
|
|
1824
|
+
{
|
|
1825
|
+
...config,
|
|
1826
|
+
filterKey: config?.filterKey,
|
|
1827
|
+
count: "exact",
|
|
1828
|
+
key: (currentPage ?? 1).toString()
|
|
1829
|
+
}
|
|
1830
|
+
);
|
|
1831
|
+
const filterKey = JSON.stringify(omit(filter, ["pagination"]));
|
|
1832
|
+
const select = initialQuery[4].split("&").find(
|
|
1833
|
+
(x) => x.startsWith("select=")
|
|
1834
|
+
);
|
|
1835
|
+
useLayoutEffect(() => {
|
|
1836
|
+
const newId = [
|
|
1837
|
+
initialQuery[3],
|
|
1838
|
+
select,
|
|
1839
|
+
initialQuery[5],
|
|
1840
|
+
initialQuery[6],
|
|
1841
|
+
initialQuery[7],
|
|
1842
|
+
initialQuery[8],
|
|
1843
|
+
filterKey
|
|
1844
|
+
].join("-");
|
|
1845
|
+
console.log({ newId, id });
|
|
1846
|
+
setId(newId);
|
|
1847
|
+
}, [
|
|
1848
|
+
initialQuery[3],
|
|
1849
|
+
select,
|
|
1850
|
+
initialQuery[5],
|
|
1851
|
+
initialQuery[6],
|
|
1852
|
+
initialQuery[7],
|
|
1853
|
+
initialQuery[8],
|
|
1854
|
+
filterKey
|
|
1855
|
+
]);
|
|
1856
|
+
const safeFetchNextPage = () => {
|
|
1857
|
+
setCurrentPage((currentPage2) => (currentPage2 ?? 1) + 1);
|
|
1858
|
+
};
|
|
1859
|
+
const fetchPreviousPage = () => {
|
|
1860
|
+
setCurrentPage((currentPage2) => (currentPage2 ?? 1) - 1);
|
|
1861
|
+
};
|
|
1862
|
+
const pageCount = Math.max(
|
|
1863
|
+
Math.ceil(
|
|
1864
|
+
(baseQuery.count ?? 0) / itemCountPerPage
|
|
1865
|
+
),
|
|
1866
|
+
1
|
|
1867
|
+
);
|
|
1868
|
+
const request = {
|
|
1869
|
+
...baseQuery,
|
|
1870
|
+
clarification: baseQuery.clarification,
|
|
1871
|
+
// Explicitly pass through clarification
|
|
1872
|
+
fetchPreviousPage,
|
|
1873
|
+
fetchNextPage: safeFetchNextPage,
|
|
1874
|
+
currentPage,
|
|
1875
|
+
setCurrentPage,
|
|
1876
|
+
data: baseQuery.data ? toPagedResponse2(
|
|
1877
|
+
baseQuery.data,
|
|
1878
|
+
currentPage ?? 1,
|
|
1879
|
+
baseQuery.count ?? baseQuery.data.length,
|
|
1880
|
+
itemCountPerPage
|
|
1881
|
+
) : null,
|
|
1882
|
+
pageCount,
|
|
1883
|
+
hasNextPage: (currentPage ?? 1) < pageCount,
|
|
1884
|
+
hasPreviousPage: (currentPage ?? 1) > 1,
|
|
1885
|
+
count: baseQuery.count ?? baseQuery.data?.length
|
|
1886
|
+
};
|
|
1887
|
+
return [
|
|
1888
|
+
request,
|
|
1889
|
+
filter,
|
|
1890
|
+
setFilters
|
|
1891
|
+
];
|
|
1892
|
+
}
|
|
1893
|
+
function toPagedResponse2(results, currentPage, totalCount, itemPerPage) {
|
|
1894
|
+
const newPage = {
|
|
1895
|
+
Items: results,
|
|
1896
|
+
CurrentPage: currentPage,
|
|
1897
|
+
ItemCount: totalCount,
|
|
1898
|
+
MaxCountPerPage: itemPerPage,
|
|
1899
|
+
PageCount: Math.max(Math.ceil(totalCount / itemPerPage), 1)
|
|
1900
|
+
};
|
|
1901
|
+
return newPage;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
export {
|
|
1905
|
+
tokenizeTopLevel,
|
|
1906
|
+
parseSelect,
|
|
1907
|
+
stringifySelect,
|
|
1908
|
+
extractColumnNames,
|
|
1909
|
+
extractRelationNames,
|
|
1910
|
+
hasRelation,
|
|
1911
|
+
getRelationSelect,
|
|
1912
|
+
RelationshipResolver,
|
|
1913
|
+
createRelationshipResolver,
|
|
1914
|
+
SQLBuilder,
|
|
1915
|
+
createSQLBuilder,
|
|
1916
|
+
ResultJoiner,
|
|
1917
|
+
createResultJoiner,
|
|
1918
|
+
QueryExecutor,
|
|
1919
|
+
createQueryExecutor,
|
|
1920
|
+
useQuery2 as useQuery,
|
|
1921
|
+
usePartialQuery,
|
|
1922
|
+
useAdvancedFilterQuery,
|
|
1923
|
+
useAdvancedQuery,
|
|
1924
|
+
useInfiniteQuery,
|
|
1925
|
+
usePartialAdvancedQuery
|
|
1926
|
+
};
|
|
1927
|
+
//# sourceMappingURL=chunk-ZTSBF536.js.map
|