@spooky-sync/query-builder 0.0.0-canary.1

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/index.js ADDED
@@ -0,0 +1,459 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let surrealdb = require("surrealdb");
3
+
4
+ //#region src/query-builder.ts
5
+ /**
6
+ * Parse a string ID to RecordId
7
+ * - If it's in the format "table:id", use it as-is
8
+ * - If it's just an ID without ":", prepend the table name
9
+ * @param value - The value to parse (could be a string ID)
10
+ * @param tableName - The table name to use if the ID doesn't contain ":"
11
+ * @param fieldName - The field name to determine if this is an ID field
12
+ */
13
+ function parseStringToRecordId(value, tableName, fieldName) {
14
+ if (typeof value !== "string") return value;
15
+ if (value.includes(":")) {
16
+ const [table, ...idParts] = value.split(":");
17
+ return new surrealdb.RecordId(table, idParts.join(":"));
18
+ }
19
+ if (fieldName === "id" && tableName) return new surrealdb.RecordId(tableName, value);
20
+ return value;
21
+ }
22
+ /**
23
+ * Recursively parse string IDs to RecordId in an object
24
+ * @param obj - The object to parse
25
+ * @param tableName - The table name to use for ID fields without ":"
26
+ */
27
+ function parseObjectIdsToRecordId(obj, tableName) {
28
+ if (obj === null || obj === void 0) return obj;
29
+ if (typeof obj === "string") return parseStringToRecordId(obj, tableName);
30
+ if (Array.isArray(obj)) return obj.map((item) => parseObjectIdsToRecordId(item, tableName));
31
+ if (typeof obj === "object" && obj.constructor === Object) {
32
+ const result = {};
33
+ for (const [key, value] of Object.entries(obj)) result[key] = typeof value === "string" ? parseStringToRecordId(value, tableName, key) : parseObjectIdsToRecordId(value, tableName);
34
+ return result;
35
+ }
36
+ return obj;
37
+ }
38
+ var InnerQuery = class {
39
+ constructor(_tableName, options, schema, executor) {
40
+ this._tableName = _tableName;
41
+ this.options = options;
42
+ this.schema = schema;
43
+ this.executor = executor;
44
+ this._selectQuery = buildQueryFromOptions("SELECT", this._tableName, this.options, this.schema);
45
+ this._mainQuery = buildQueryFromOptions("SELECT", this._tableName, {
46
+ ...this.options,
47
+ related: []
48
+ }, this.schema);
49
+ this._hash = this._selectQuery.hash;
50
+ this._selectLiveQuery = buildQueryFromOptions("LIVE SELECT", this._tableName, this.options, this.schema);
51
+ this._subqueries = extractSubqueryQueryInfos(schema, this._tableName, this.options, this.executor);
52
+ }
53
+ get mainQuery() {
54
+ return this._mainQuery;
55
+ }
56
+ get subqueries() {
57
+ return this._subqueries;
58
+ }
59
+ get selectQuery() {
60
+ return this._selectQuery;
61
+ }
62
+ get selectLiveQuery() {
63
+ return this._selectLiveQuery;
64
+ }
65
+ get tableName() {
66
+ return this._tableName;
67
+ }
68
+ get hash() {
69
+ return this._hash;
70
+ }
71
+ get isOne() {
72
+ return this.options.isOne ?? false;
73
+ }
74
+ run() {
75
+ return this.executor(this);
76
+ }
77
+ buildUpdateQuery(patches) {
78
+ return buildQueryFromOptions("UPDATE", this._tableName, this.options, this.schema, patches);
79
+ }
80
+ buildDeleteQuery() {
81
+ return buildQueryFromOptions("DELETE", this._tableName, this.options, this.schema);
82
+ }
83
+ getOptions() {
84
+ return this.options;
85
+ }
86
+ };
87
+ var FinalQuery = class {
88
+ constructor(tableName, options, schema, executor) {
89
+ this.tableName = tableName;
90
+ this.options = options;
91
+ this.schema = schema;
92
+ this.executor = executor;
93
+ this._innerQuery = new InnerQuery(this.tableName, this.options, this.schema, this.executor);
94
+ }
95
+ run() {
96
+ return this.executor(this._innerQuery);
97
+ }
98
+ buildUpdateQuery(patches) {
99
+ return this._innerQuery.buildUpdateQuery(patches);
100
+ }
101
+ buildDeleteQuery() {
102
+ return this._innerQuery.buildDeleteQuery();
103
+ }
104
+ selectLive() {
105
+ return this._innerQuery.selectLiveQuery;
106
+ }
107
+ get innerQuery() {
108
+ return this._innerQuery;
109
+ }
110
+ get isOne() {
111
+ return this.options.isOne ?? false;
112
+ }
113
+ get hash() {
114
+ return this._innerQuery.hash;
115
+ }
116
+ };
117
+ /**
118
+ * Schema-aware query modifier builder implementation
119
+ * This version provides full type safety for nested relationships
120
+ */
121
+ var SchemaAwareQueryModifierBuilderImpl = class {
122
+ constructor(tableName, schema) {
123
+ this.tableName = tableName;
124
+ this.schema = schema;
125
+ this.options = {};
126
+ }
127
+ where(conditions) {
128
+ this.options.where = {
129
+ ...this.options.where,
130
+ ...conditions
131
+ };
132
+ return this;
133
+ }
134
+ select(...fields) {
135
+ if (this.options.select) throw new Error("Select can only be called once per query");
136
+ this.options.select = fields;
137
+ return this;
138
+ }
139
+ limit(count) {
140
+ this.options.limit = count;
141
+ return this;
142
+ }
143
+ offset(count) {
144
+ this.options.offset = count;
145
+ return this;
146
+ }
147
+ orderBy(field, direction = "asc") {
148
+ this.options.orderBy = {
149
+ ...this.options.orderBy,
150
+ [field]: direction
151
+ };
152
+ return this;
153
+ }
154
+ related(relatedField, modifier) {
155
+ if (!this.options.related) this.options.related = [];
156
+ if (!this.options.related.some((r) => (r.alias || r.relatedTable) === relatedField)) {
157
+ const relationship = this.schema.relationships.find((r) => r.from === this.tableName && r.field === relatedField);
158
+ if (!relationship) throw new Error(`Relationship '${String(relatedField)}' not found for table '${this.tableName}'`);
159
+ const relatedTable = relationship.to;
160
+ const cardinality = relationship.cardinality;
161
+ const foreignKeyField = cardinality === "many" ? this.tableName : relatedField;
162
+ this.options.related.push({
163
+ relatedTable,
164
+ alias: relatedField,
165
+ modifier,
166
+ cardinality,
167
+ foreignKeyField
168
+ });
169
+ }
170
+ return this;
171
+ }
172
+ _getOptions() {
173
+ return this.options;
174
+ }
175
+ };
176
+ /**
177
+ * Fluent query builder for constructing queries with chainable methods
178
+ * Now with full type inference from schema constant AND related field accumulation!
179
+ */
180
+ var QueryBuilder = class QueryBuilder {
181
+ constructor(schema, tableName, executer = () => void 0, options = {}) {
182
+ this.schema = schema;
183
+ this.tableName = tableName;
184
+ this.executer = executer;
185
+ this.options = options;
186
+ }
187
+ /**
188
+ * Add additional where conditions
189
+ */
190
+ where(conditions) {
191
+ this.options.where = {
192
+ ...this.options.where,
193
+ ...conditions
194
+ };
195
+ return this;
196
+ }
197
+ /**
198
+ * Specify fields to select
199
+ */
200
+ select(...fields) {
201
+ if (this.options.select) throw new Error("Select can only be called once per query");
202
+ this.options.select = fields;
203
+ return this;
204
+ }
205
+ /**
206
+ * Add ordering to the query (only for non-live queries)
207
+ */
208
+ orderBy(field, direction = "asc") {
209
+ this.options.orderBy = {
210
+ ...this.options.orderBy,
211
+ [field]: direction
212
+ };
213
+ return this;
214
+ }
215
+ /**
216
+ * Add limit to the query (only for non-live queries)
217
+ */
218
+ limit(count) {
219
+ this.options.limit = count;
220
+ return this;
221
+ }
222
+ /**
223
+ * Add offset to the query (only for non-live queries)
224
+ */
225
+ offset(count) {
226
+ this.options.offset = count;
227
+ return this;
228
+ }
229
+ one() {
230
+ return new QueryBuilder(this.schema, this.tableName, this.executer, {
231
+ ...this.options,
232
+ isOne: true
233
+ });
234
+ }
235
+ /**
236
+ * Include related data via subqueries
237
+ * Field and cardinality are validated against schema relationships
238
+ * Now accumulates the related field in the type!
239
+ */
240
+ related(field, modifierOrCardinality, modifier) {
241
+ if (!this.options.related) this.options.related = [];
242
+ if (this.options.related.some((r) => (r.alias || r.relatedTable) === field)) return this;
243
+ const relationship = this.schema.relationships.find((r) => r.from === this.tableName && r.field === field);
244
+ if (!relationship) throw new Error(`Relationship '${String(field)}' not found for table '${this.tableName}'`);
245
+ let actualCardinality;
246
+ let actualModifier;
247
+ if (typeof modifierOrCardinality === "function") {
248
+ actualCardinality = relationship.cardinality;
249
+ actualModifier = modifierOrCardinality;
250
+ } else if (modifierOrCardinality === "one" || modifierOrCardinality === "many") {
251
+ actualCardinality = modifierOrCardinality;
252
+ actualModifier = modifier;
253
+ } else {
254
+ actualCardinality = relationship.cardinality;
255
+ actualModifier = void 0;
256
+ }
257
+ let foreignKeyField = actualCardinality === "many" ? this.tableName : field;
258
+ if (actualCardinality === "many") {
259
+ const reverseRelationships = this.schema.relationships.filter((r) => r.from === relationship.to && r.to === this.tableName && r.cardinality === "one");
260
+ if (reverseRelationships.length > 0) {
261
+ const exactMatch = reverseRelationships.find((r) => r.field === this.tableName);
262
+ if (exactMatch) foreignKeyField = exactMatch.field;
263
+ else foreignKeyField = reverseRelationships[0].field;
264
+ } else if (this.tableName.startsWith(`${relationship.to}_`)) foreignKeyField = this.tableName.slice(relationship.to.length + 1);
265
+ }
266
+ const wrappedModifier = actualModifier;
267
+ this.options.related.push({
268
+ relatedTable: relationship.to,
269
+ alias: field,
270
+ modifier: wrappedModifier,
271
+ cardinality: actualCardinality,
272
+ foreignKeyField
273
+ });
274
+ return this;
275
+ }
276
+ /**
277
+ * Get the current query options
278
+ */
279
+ getOptions() {
280
+ return this.options;
281
+ }
282
+ /**
283
+ * Build query methods for SELECT and LIVE SELECT (custom implementation)
284
+ * @returns FinalQuery object with select() method for custom usage
285
+ */
286
+ build() {
287
+ return new FinalQuery(this.tableName, this.options, this.schema, this.executer);
288
+ }
289
+ };
290
+ function cyrb53(str, seed = 0) {
291
+ let h1 = 3735928559 ^ seed, h2 = 1103547991 ^ seed;
292
+ for (let i = 0, ch; i < str.length; i++) {
293
+ ch = str.charCodeAt(i);
294
+ h1 = Math.imul(h1 ^ ch, 2654435761);
295
+ h2 = Math.imul(h2 ^ ch, 1597334677);
296
+ }
297
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
298
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
299
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
300
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
301
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
302
+ }
303
+ function extractSubqueryQueryInfos(schema, parentTableName, options, executer) {
304
+ if (!options.related) return [];
305
+ return options.related.map((rel) => {
306
+ const subOptions = rel.modifier?.(new SchemaAwareQueryModifierBuilderImpl(rel.relatedTable, schema))._getOptions() ?? {};
307
+ const relationship = schema.relationships.find((r) => r.from === parentTableName && r.field === rel.alias);
308
+ if (relationship) {
309
+ let foreignKeyField = rel.alias;
310
+ if (relationship.cardinality === "many") {
311
+ const reverseRelationships = schema.relationships.filter((r) => r.from === rel.relatedTable && r.to === parentTableName && r.cardinality === "one");
312
+ if (reverseRelationships.length > 0) {
313
+ const exactMatch = reverseRelationships.find((r) => r.field === parentTableName);
314
+ if (exactMatch) foreignKeyField = exactMatch.field;
315
+ else foreignKeyField = reverseRelationships[0].field;
316
+ } else if (parentTableName.startsWith(`${rel.relatedTable}_`)) foreignKeyField = parentTableName.slice(rel.relatedTable.length + 1);
317
+ else foreignKeyField = parentTableName;
318
+ }
319
+ subOptions.where = subOptions.where || {};
320
+ if (relationship.cardinality === "many") subOptions.where[foreignKeyField] = {
321
+ _op: "∋",
322
+ _val: "$parentIds",
323
+ _swap: true
324
+ };
325
+ else subOptions.where.id = {
326
+ _op: "∋",
327
+ _val: `$parent_${foreignKeyField}`,
328
+ _swap: true
329
+ };
330
+ }
331
+ return new InnerQuery(rel.relatedTable, subOptions, schema, executer);
332
+ });
333
+ }
334
+ /**
335
+ * Build a query string from query options
336
+ * @param method - The query method (SELECT or LIVE SELECT)
337
+ * @param tableName - The table name to query
338
+ * @param options - The query options (where, select, orderBy, etc.)
339
+ * @param schema - Optional schema for resolving nested relationships
340
+ * @returns QueryInfo with the generated SQL and variables
341
+ */
342
+ function buildQueryFromOptions(method, tableName, options, schema, patches) {
343
+ if (options.isOne) options.limit = 1;
344
+ const isLiveQuery = method === "LIVE SELECT" || method === "LIVE SELECT DIFF";
345
+ const parsedWhere = options.where ? parseObjectIdsToRecordId(options.where, tableName) : void 0;
346
+ let selectClause = "*";
347
+ if (method === "LIVE SELECT DIFF") selectClause = "";
348
+ else if (options.select && options.select.length > 0) selectClause = options.select.join(", ");
349
+ let fetchClauses = "";
350
+ if (!isLiveQuery && options.related && options.related.length > 0) fetchClauses = ", " + options.related.map((rel) => buildSubquery(rel, schema)).join(", ");
351
+ let query = "";
352
+ if (method === "UPDATE") query = `UPDATE ${tableName}`;
353
+ else if (method === "DELETE") query = `DELETE FROM ${tableName}`;
354
+ else query = `${method}${selectClause ? ` ${selectClause}` : ""}${fetchClauses} FROM ${tableName}`;
355
+ const vars = {};
356
+ if (parsedWhere && Object.keys(parsedWhere).length > 0) {
357
+ const conditions = [];
358
+ for (const [key, value] of Object.entries(parsedWhere)) {
359
+ const varName = key;
360
+ if (value && typeof value === "object" && "_op" in value && "_val" in value) {
361
+ const { _op, _val, _swap } = value;
362
+ let rightSide = "";
363
+ if (typeof _val === "string" && _val.startsWith("$")) rightSide = _val;
364
+ else {
365
+ vars[varName] = _val;
366
+ rightSide = `$${varName}`;
367
+ }
368
+ if (_swap) conditions.push(`${rightSide} ${_op} ${key}`);
369
+ else conditions.push(`${key} ${_op} ${rightSide}`);
370
+ } else {
371
+ vars[varName] = value;
372
+ conditions.push(`${key} = $${varName}`);
373
+ }
374
+ }
375
+ query += ` WHERE ${conditions.join(" AND ")}`;
376
+ }
377
+ if (method === "UPDATE" && patches) query += ` PATCH ${JSON.stringify(patches)}`;
378
+ if (!isLiveQuery) {
379
+ if (options.orderBy && Object.keys(options.orderBy).length > 0) {
380
+ const orderClauses = Object.entries(options.orderBy).map(([field, direction]) => `${field} ${direction}`);
381
+ query += ` ORDER BY ${orderClauses.join(", ")}`;
382
+ }
383
+ if (options.limit !== void 0) query += ` LIMIT ${options.limit}`;
384
+ if (options.offset !== void 0) query += ` START ${options.offset}`;
385
+ }
386
+ query += ";";
387
+ return {
388
+ query,
389
+ hash: cyrb53(`${query}::${Object.entries(vars).map(([key, value]) => `${key}=${value}`).join("&")}`, 0),
390
+ vars: Object.keys(vars).length > 0 ? vars : void 0
391
+ };
392
+ }
393
+ /**
394
+ * Build a subquery for a related field
395
+ */
396
+ function buildSubquery(rel, schema) {
397
+ const { relatedTable, alias, modifier, cardinality } = rel;
398
+ const foreignKeyField = rel.foreignKeyField || alias;
399
+ let subquerySelect = "*";
400
+ let subqueryWhere = "";
401
+ let subqueryOrderBy = "";
402
+ let subqueryLimit = "";
403
+ if (modifier) {
404
+ const modifierBuilder = new SchemaAwareQueryModifierBuilderImpl(relatedTable, schema);
405
+ modifier(modifierBuilder);
406
+ const subOptions = modifierBuilder._getOptions();
407
+ if (subOptions.select && subOptions.select.length > 0) subquerySelect = subOptions.select.join(", ");
408
+ if (subOptions.where && Object.keys(subOptions.where).length > 0) {
409
+ const parsedSubWhere = parseObjectIdsToRecordId(subOptions.where, relatedTable);
410
+ subqueryWhere = ` AND ${Object.entries(parsedSubWhere).map(([key, value]) => {
411
+ if (value instanceof surrealdb.RecordId) return `${key} = ${value.toString()}`;
412
+ return `${key} = ${JSON.stringify(value)}`;
413
+ }).join(" AND ")}`;
414
+ }
415
+ if (subOptions.orderBy && Object.keys(subOptions.orderBy).length > 0) subqueryOrderBy = ` ORDER BY ${Object.entries(subOptions.orderBy).map(([field, direction]) => `${field} ${direction}`).join(", ")}`;
416
+ if (subOptions.limit !== void 0) subqueryLimit = ` LIMIT ${subOptions.limit}`;
417
+ if (subOptions.related && subOptions.related.length > 0) {
418
+ const nestedSubqueries = subOptions.related.map((nestedRel) => {
419
+ if (schema) {
420
+ const relationship = schema.relationships.find((r) => r.from === relatedTable && r.field === nestedRel.alias);
421
+ if (relationship) {
422
+ const nestedForeignKeyField = relationship.cardinality === "many" ? relatedTable : nestedRel.alias;
423
+ return {
424
+ ...nestedRel,
425
+ relatedTable: relationship.to,
426
+ cardinality: relationship.cardinality,
427
+ foreignKeyField: nestedForeignKeyField
428
+ };
429
+ }
430
+ }
431
+ return nestedRel;
432
+ }).map((nestedRel) => buildSubquery(nestedRel, schema));
433
+ subquerySelect += ", " + nestedSubqueries.join(", ");
434
+ }
435
+ }
436
+ let whereCondition;
437
+ if (cardinality === "one") {
438
+ whereCondition = `WHERE id=$parent.${foreignKeyField}`;
439
+ if (!subqueryLimit) subqueryLimit = " LIMIT 1";
440
+ } else whereCondition = `WHERE ${foreignKeyField}=$parent.id`;
441
+ let subquery = `(SELECT ${subquerySelect} FROM ${relatedTable} ${whereCondition}${subqueryWhere}${subqueryOrderBy}${subqueryLimit})`;
442
+ if (cardinality === "one") subquery += "[0]";
443
+ subquery += ` AS ${alias}`;
444
+ return subquery;
445
+ }
446
+
447
+ //#endregion
448
+ exports.FinalQuery = FinalQuery;
449
+ exports.InnerQuery = InnerQuery;
450
+ exports.QueryBuilder = QueryBuilder;
451
+ Object.defineProperty(exports, 'RecordId', {
452
+ enumerable: true,
453
+ get: function () {
454
+ return surrealdb.RecordId;
455
+ }
456
+ });
457
+ exports.buildQueryFromOptions = buildQueryFromOptions;
458
+ exports.cyrb53 = cyrb53;
459
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["RecordId"],"sources":["../src/query-builder.ts"],"sourcesContent":["import { RecordId } from 'surrealdb';\nimport type {\n GenericModel,\n QueryInfo,\n QueryOptions,\n QueryModifier,\n RelatedQuery,\n SchemaAwareQueryModifier,\n SchemaAwareQueryModifierBuilder,\n} from './types';\nimport type {\n TableNames,\n GetTable,\n TableModel,\n TableRelationships,\n GetRelationship,\n SchemaStructure,\n TableFieldNames,\n ColumnSchema,\n} from './table-schema';\n\n/**\n * Parse a string ID to RecordId\n * - If it's in the format \"table:id\", use it as-is\n * - If it's just an ID without \":\", prepend the table name\n * @param value - The value to parse (could be a string ID)\n * @param tableName - The table name to use if the ID doesn't contain \":\"\n * @param fieldName - The field name to determine if this is an ID field\n */\nfunction parseStringToRecordId(value: unknown, tableName?: string, fieldName?: string): unknown {\n if (typeof value !== 'string') return value;\n\n // If it already contains \":\", parse it as a full record ID\n if (value.includes(':')) {\n const [table, ...idParts] = value.split(':');\n const id = idParts.join(':'); // Handle IDs that contain colons\n return new RecordId(table, id);\n }\n\n // If this is an \"id\" field and we have a table name, prepend it\n if (fieldName === 'id' && tableName) {\n return new RecordId(tableName, value);\n }\n\n // Otherwise, return as-is (it might not be an ID at all)\n return value;\n}\n\n/**\n * Recursively parse string IDs to RecordId in an object\n * @param obj - The object to parse\n * @param tableName - The table name to use for ID fields without \":\"\n */\nfunction parseObjectIdsToRecordId(obj: unknown, tableName?: string): unknown {\n if (obj === null || obj === undefined) return obj;\n\n if (typeof obj === 'string') {\n return parseStringToRecordId(obj, tableName);\n }\n\n if (Array.isArray(obj)) {\n return obj.map((item) => parseObjectIdsToRecordId(item, tableName));\n }\n\n if (typeof obj === 'object' && obj.constructor === Object) {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n // Parse recursively, passing the field name to identify ID fields\n result[key] =\n typeof value === 'string'\n ? parseStringToRecordId(value, tableName, key)\n : parseObjectIdsToRecordId(value, tableName);\n }\n return result;\n }\n\n return obj;\n}\n\nexport type Executor<T extends { columns: Record<string, ColumnSchema> }, R = void> = (\n query: InnerQuery<T, boolean>\n) => R;\n\nexport class InnerQuery<\n T extends { columns: Record<string, ColumnSchema> },\n IsOne extends boolean,\n R = void,\n> {\n private _hash: number;\n private _mainQuery: QueryInfo;\n private _selectQuery: QueryInfo;\n private _selectLiveQuery: QueryInfo;\n private _subqueries: InnerQuery<{ columns: Record<string, ColumnSchema> }, boolean>[];\n\n constructor(\n private readonly _tableName: string,\n private readonly options: QueryOptions<TableModel<T>, IsOne>,\n private readonly schema: SchemaStructure,\n private readonly executor: Executor<any, R>\n ) {\n this._selectQuery = buildQueryFromOptions('SELECT', this._tableName, this.options, this.schema);\n\n this._mainQuery = buildQueryFromOptions(\n 'SELECT',\n this._tableName,\n { ...this.options, related: [] },\n this.schema\n );\n\n this._hash = this._selectQuery.hash;\n\n this._selectLiveQuery = buildQueryFromOptions(\n 'LIVE SELECT',\n this._tableName,\n this.options,\n this.schema\n );\n\n this._subqueries = extractSubqueryQueryInfos(\n schema,\n this._tableName,\n this.options,\n this.executor\n );\n }\n\n get mainQuery(): QueryInfo {\n return this._mainQuery;\n }\n\n get subqueries(): InnerQuery<{ columns: Record<string, ColumnSchema> }, boolean>[] {\n return this._subqueries;\n }\n\n get selectQuery(): QueryInfo {\n return this._selectQuery;\n }\n\n get selectLiveQuery(): QueryInfo {\n return this._selectLiveQuery;\n }\n\n get tableName(): string {\n return this._tableName;\n }\n\n get hash(): number {\n return this._hash;\n }\n\n get isOne(): boolean {\n return this.options.isOne ?? false;\n }\n\n public run(): R {\n return this.executor(this);\n }\n\n public buildUpdateQuery(patches: any[]): QueryInfo {\n return buildQueryFromOptions('UPDATE', this._tableName, this.options, this.schema, patches);\n }\n\n public buildDeleteQuery(): QueryInfo {\n return buildQueryFromOptions('DELETE', this._tableName, this.options, this.schema);\n }\n\n public getOptions(): QueryOptions<TableModel<T>, IsOne> {\n return this.options;\n }\n}\n\n/**\n * Helper type to get the model type for a related table\n */\ntype GetRelatedModel<S extends SchemaStructure, RelatedTableName extends string> =\n RelatedTableName extends TableNames<S> ? TableModel<GetTable<S, RelatedTableName>> : never;\n\n/**\n * Helper type to extract field names from RelatedFields\n */\nexport type ExtractFieldNames<RelatedFields extends RelatedFieldsMap> = keyof RelatedFields;\n\nexport type RelatedFieldMapEntry = {\n to: string;\n cardinality: 'one' | 'many';\n relatedFields: RelatedFieldsMap;\n};\n\nexport type RelatedFieldsMap = Record<string, RelatedFieldMapEntry>;\n\n/**\n * Helper type to build the related fields object based on accumulated relationships\n */\nexport type BuildRelatedFields<\n S extends SchemaStructure,\n RelatedFields extends RelatedFieldsMap,\n> = {\n [K in keyof RelatedFields]: QueryResult<\n S,\n RelatedFields[K]['to'],\n RelatedFields[K]['relatedFields'],\n RelatedFields[K]['cardinality'] extends 'one' ? true : false\n >;\n};\n\nexport type BuildResultModelOne<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n RelatedFields extends RelatedFieldsMap,\n> = Omit<TableModel<GetTable<S, TableName>>, ExtractFieldNames<RelatedFields>> &\n BuildRelatedFields<S, RelatedFields>;\n\nexport type BuildResultModelMany<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n RelatedFields extends RelatedFieldsMap,\n> = (Omit<TableModel<GetTable<S, TableName>>, ExtractFieldNames<RelatedFields>> &\n BuildRelatedFields<S, RelatedFields>)[];\n\n/**\n * The final result type combining base model with related fields\n * Excludes related field keys from the base model to avoid type conflicts\n */\nexport type QueryResult<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n RelatedFields extends RelatedFieldsMap,\n IsOne extends boolean,\n> = IsOne extends true\n ? BuildResultModelOne<S, TableName, RelatedFields>\n : BuildResultModelMany<S, TableName, RelatedFields>;\n\nexport class FinalQuery<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n T extends { columns: Record<string, ColumnSchema> },\n RelatedFields extends RelatedFieldsMap,\n IsOne extends boolean,\n R = void,\n> {\n private _innerQuery: InnerQuery<T, IsOne, R>;\n\n constructor(\n private readonly tableName: TableName,\n private readonly options: QueryOptions<TableModel<T>, IsOne>,\n private readonly schema: S,\n private readonly executor: Executor<T, R>\n ) {\n this._innerQuery = new InnerQuery<T, IsOne, R>(\n this.tableName,\n this.options,\n this.schema,\n this.executor\n );\n }\n\n run(): R {\n return this.executor(this._innerQuery);\n }\n\n buildUpdateQuery(patches: any[]): QueryInfo {\n return this._innerQuery.buildUpdateQuery(patches);\n }\n\n buildDeleteQuery(): QueryInfo {\n return this._innerQuery.buildDeleteQuery();\n }\n\n selectLive(): QueryInfo {\n return this._innerQuery.selectLiveQuery;\n }\n\n get innerQuery(): InnerQuery<T, IsOne, R> {\n return this._innerQuery;\n }\n\n get isOne(): boolean {\n return this.options.isOne ?? false;\n }\n\n get hash(): number {\n return this._innerQuery.hash;\n }\n}\n\n/**\n * Schema-aware query modifier builder implementation\n * This version provides full type safety for nested relationships\n */\nclass SchemaAwareQueryModifierBuilderImpl<\n S extends SchemaStructure,\n TableName extends TableNames<S>,\n RelatedFields extends RelatedFieldsMap = {},\n> implements SchemaAwareQueryModifierBuilder<S, TableName, RelatedFields> {\n private options: QueryOptions<TableModel<GetTable<S, TableName>>, boolean> = {};\n\n constructor(\n private readonly tableName: TableName,\n private readonly schema: S\n ) {}\n\n where(conditions: Partial<TableModel<GetTable<S, TableName>>>): this {\n this.options.where = { ...this.options.where, ...conditions };\n return this;\n }\n\n select(...fields: ((keyof TableModel<GetTable<S, TableName>> & string) | '*')[]): this {\n if (this.options.select) {\n throw new Error('Select can only be called once per query');\n }\n this.options.select = fields;\n return this;\n }\n\n limit(count: number): this {\n this.options.limit = count;\n return this;\n }\n\n offset(count: number): this {\n this.options.offset = count;\n return this;\n }\n\n orderBy(\n field: keyof TableModel<GetTable<S, TableName>> & string,\n direction: 'asc' | 'desc' = 'asc'\n ): this {\n this.options.orderBy = {\n ...this.options.orderBy,\n [field]: direction,\n } as Partial<Record<keyof TableModel<GetTable<S, TableName>>, 'asc' | 'desc'>>;\n return this;\n }\n\n // Schema-aware implementation for nested relationships with full type inference\n related<\n Field extends TableRelationships<S, TableName>['field'],\n Rel extends GetRelationship<S, TableName, Field>,\n RelatedFields2 extends RelatedFieldsMap = {},\n >(\n relatedField: Field,\n modifier?: SchemaAwareQueryModifier<S, Rel['to'], RelatedFields2>\n ): SchemaAwareQueryModifierBuilderImpl<\n S,\n TableName,\n RelatedFields & {\n [K in Field]: {\n to: Rel['to'];\n cardinality: Rel['cardinality'];\n relatedFields: RelatedFields2;\n };\n }\n > {\n if (!this.options.related) {\n this.options.related = [];\n }\n\n const exists = this.options.related.some((r) => (r.alias || r.relatedTable) === relatedField);\n\n if (!exists) {\n // Look up the relationship from schema\n const relationship = this.schema.relationships.find(\n (r) => r.from === this.tableName && r.field === relatedField\n );\n\n if (!relationship) {\n throw new Error(\n `Relationship '${String(relatedField)}' not found for table '${this.tableName}'`\n );\n }\n\n const relatedTable = relationship.to;\n const cardinality = relationship.cardinality;\n const foreignKeyField = cardinality === 'many' ? this.tableName : relatedField;\n\n this.options.related.push({\n relatedTable,\n alias: relatedField as string,\n modifier: modifier as QueryModifier<GenericModel>,\n cardinality,\n foreignKeyField: foreignKeyField as string,\n } as RelatedQuery & { foreignKeyField: string });\n }\n return this as any;\n }\n\n _getOptions(): QueryOptions<TableModel<GetTable<S, TableName>>, boolean> {\n return this.options;\n }\n}\n\n/**\n * Fluent query builder for constructing queries with chainable methods\n * Now with full type inference from schema constant AND related field accumulation!\n */\nexport class QueryBuilder<\n const S extends SchemaStructure,\n const TableName extends TableNames<S>,\n const R = void,\n const RelatedFields extends RelatedFieldsMap = {},\n const IsOne extends boolean = false,\n> {\n constructor(\n private readonly schema: S,\n private readonly tableName: TableName,\n private readonly executer: Executor<GetTable<S, TableName>, R> = () => undefined as R,\n private options: QueryOptions<TableModel<GetTable<S, TableName>>, IsOne> = {}\n ) {}\n\n /**\n * Add additional where conditions\n */\n where(\n conditions: Partial<TableModel<GetTable<S, TableName>>>\n ): QueryBuilder<S, TableName, R, RelatedFields, IsOne> {\n this.options.where = { ...this.options.where, ...conditions };\n return this;\n }\n\n /**\n * Specify fields to select\n */\n select(\n ...fields: ((keyof TableModel<GetTable<S, TableName>> & string) | '*')[]\n ): QueryBuilder<S, TableName, R, RelatedFields, IsOne> {\n if (this.options.select) {\n throw new Error('Select can only be called once per query');\n }\n this.options.select = fields;\n return this;\n }\n\n /**\n * Add ordering to the query (only for non-live queries)\n */\n orderBy(\n field: TableFieldNames<GetTable<S, TableName>>,\n direction: 'asc' | 'desc' = 'asc'\n ): QueryBuilder<S, TableName, R, RelatedFields, IsOne> {\n this.options.orderBy = {\n ...this.options.orderBy,\n [field]: direction,\n } as Partial<Record<keyof TableModel<GetTable<S, TableName>>, 'asc' | 'desc'>>;\n return this;\n }\n\n /**\n * Add limit to the query (only for non-live queries)\n */\n limit(count: number): QueryBuilder<S, TableName, R, RelatedFields, IsOne> {\n this.options.limit = count;\n return this;\n }\n\n /**\n * Add offset to the query (only for non-live queries)\n */\n offset(count: number): QueryBuilder<S, TableName, R, RelatedFields, IsOne> {\n this.options.offset = count;\n return this;\n }\n\n one(): QueryBuilder<S, TableName, R, RelatedFields, true> {\n return new QueryBuilder<S, TableName, R, RelatedFields, true>(\n this.schema,\n this.tableName,\n this.executer,\n { ...this.options, isOne: true }\n );\n }\n\n /**\n * Include related data via subqueries\n * Field and cardinality are validated against schema relationships\n * Now accumulates the related field in the type!\n */\n related<\n Field extends TableRelationships<S, TableName>['field'],\n Rel extends GetRelationship<S, TableName, Field>,\n RelatedFields2 extends RelatedFieldsMap = {},\n >(\n field: Field,\n modifierOrCardinality?:\n | SchemaAwareQueryModifier<S, Rel['to'], RelatedFields2>\n | Rel['cardinality'],\n modifier?: SchemaAwareQueryModifier<S, Rel['to'], RelatedFields2>\n ): QueryBuilder<\n S,\n TableName,\n R,\n RelatedFields & {\n [K in Field]: {\n to: Rel['to'];\n cardinality: Rel['cardinality'];\n relatedFields: RelatedFields2;\n };\n },\n IsOne\n > {\n if (!this.options.related) {\n this.options.related = [];\n }\n\n // Check if field already exists\n const exists = this.options.related.some((r) => (r.alias || r.relatedTable) === field);\n\n if (exists) {\n return this as any;\n }\n\n // Look up relationship metadata from schema\n const relationship = this.schema.relationships.find(\n (r) => r.from === this.tableName && r.field === field\n );\n\n if (!relationship) {\n throw new Error(`Relationship '${String(field)}' not found for table '${this.tableName}'`);\n }\n\n // Determine cardinality and modifier based on arguments\n let actualCardinality: 'one' | 'many';\n let actualModifier: SchemaAwareQueryModifier<S, Rel['to']> | undefined;\n\n if (typeof modifierOrCardinality === 'function') {\n // Signature: related(field, modifier)\n actualCardinality = relationship.cardinality;\n actualModifier = modifierOrCardinality;\n } else if (modifierOrCardinality === 'one' || modifierOrCardinality === 'many') {\n // Signature: related(field, cardinality, modifier)\n actualCardinality = modifierOrCardinality;\n actualModifier = modifier;\n } else {\n // Signature: related(field)\n actualCardinality = relationship.cardinality;\n actualModifier = undefined;\n }\n\n // Determine foreign key field based on cardinality\n let foreignKeyField: string =\n actualCardinality === 'many' ? (this.tableName as string) : (field as string);\n\n if (actualCardinality === 'many') {\n // For one-to-many, we need to find the field on the child table that points back to the parent\n // We look for a relationship from Child -> Parent\n const reverseRelationships = this.schema.relationships.filter(\n (r) => r.from === relationship.to && r.to === this.tableName && r.cardinality === 'one'\n );\n\n if (reverseRelationships.length > 0) {\n // Prioritize field that matches parent table name\n const exactMatch = reverseRelationships.find((r) => r.field === this.tableName);\n if (exactMatch) {\n foreignKeyField = exactMatch.field;\n } else {\n foreignKeyField = reverseRelationships[0].field;\n }\n } else {\n // Fallback heuristics\n if (this.tableName.startsWith(`${relationship.to}_`)) {\n // If parent table is \"game_database\" and child is \"game\", try \"database\"\n foreignKeyField = this.tableName.slice(relationship.to.length + 1);\n }\n }\n }\n\n // Cast the schema-aware modifier to the runtime type\n // At runtime, QueryModifierBuilderImpl will work correctly with the schema\n const wrappedModifier = actualModifier as QueryModifier<GenericModel> | undefined;\n\n this.options.related.push({\n relatedTable: relationship.to,\n alias: field as string,\n modifier: wrappedModifier,\n cardinality: actualCardinality,\n foreignKeyField: foreignKeyField as any,\n } as RelatedQuery & { foreignKeyField: string });\n\n return this as any;\n }\n\n /**\n * Get the current query options\n */\n getOptions(): QueryOptions<TableModel<GetTable<S, TableName>>, IsOne> {\n return this.options;\n }\n\n /**\n * Build query methods for SELECT and LIVE SELECT (custom implementation)\n * @returns FinalQuery object with select() method for custom usage\n */\n build(): FinalQuery<S, TableName, GetTable<S, TableName>, RelatedFields, IsOne, R> {\n return new FinalQuery<S, TableName, GetTable<S, TableName>, RelatedFields, IsOne, R>(\n this.tableName,\n this.options,\n this.schema,\n this.executer\n );\n }\n}\n\nexport function cyrb53(str: string, seed: number = 0): number {\n let h1 = 0xdeadbeef ^ seed,\n h2 = 0x41c6ce57 ^ seed;\n for (let i = 0, ch; i < str.length; i++) {\n ch = str.charCodeAt(i);\n h1 = Math.imul(h1 ^ ch, 2654435761);\n h2 = Math.imul(h2 ^ ch, 1597334677);\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n\n return 4294967296 * (2097151 & h2) + (h1 >>> 0);\n}\n\nexport function extractSubqueryQueryInfos<S extends SchemaStructure>(\n schema: S,\n parentTableName: string,\n options: QueryOptions<GenericModel, boolean>,\n executer: Executor<{ columns: Record<string, ColumnSchema> }>\n): InnerQuery<{ columns: Record<string, ColumnSchema> }, boolean>[] {\n if (!options.related) {\n return [];\n }\n\n return options.related.map((rel) => {\n // Get base options from modifier\n const subOptions =\n rel\n .modifier?.(new SchemaAwareQueryModifierBuilderImpl(rel.relatedTable, schema))\n ._getOptions() ?? {};\n\n // Find relationship to determine how to filter\n const relationship = schema.relationships.find(\n (r) => r.from === parentTableName && r.field === rel.alias\n );\n\n if (relationship) {\n // Determine foreign key field\n // rel.alias is guaranteed to be defined if relationship is found (matched r.field)\n let foreignKeyField = rel.alias!;\n\n if (relationship.cardinality === 'many') {\n // For one-to-many, we need to find the field on the child table that points back to the parent\n // We look for a relationship from Child -> Parent\n const reverseRelationships = schema.relationships.filter(\n (r) => r.from === rel.relatedTable && r.to === parentTableName && r.cardinality === 'one'\n );\n\n if (reverseRelationships.length > 0) {\n // Prioritize field that matches parent table name\n const exactMatch = reverseRelationships.find((r) => r.field === parentTableName);\n if (exactMatch) {\n foreignKeyField = exactMatch.field;\n } else {\n foreignKeyField = reverseRelationships[0].field;\n }\n } else {\n // Fallback heuristics\n if (parentTableName.startsWith(`${rel.relatedTable}_`)) {\n // If parent table is \"game_database\" and child is \"game\", try \"database\"\n foreignKeyField = parentTableName.slice(rel.relatedTable.length + 1);\n } else {\n // Default to parent table name\n foreignKeyField = parentTableName;\n }\n }\n }\n\n // Add parent filter to where clause\n subOptions.where = subOptions.where || {};\n\n if (relationship.cardinality === 'many') {\n // One-to-Many: Child has foreign key to parent\n // WHERE $parentIds ∋ child.parent_id\n (subOptions.where as any)[foreignKeyField] = { _op: '∋', _val: '$parentIds', _swap: true };\n } else {\n // One-to-One: Parent has foreign key to child\n // WHERE $parent_<foreignKeyField> ∋ child.id\n // We use a dynamic variable name derived from the foreign key field on the parent\n (subOptions.where as any).id = {\n _op: '∋',\n _val: `$parent_${foreignKeyField}`,\n _swap: true,\n };\n }\n }\n\n return new InnerQuery(rel.relatedTable, subOptions, schema, executer);\n });\n}\n\n/**\n * Build a query string from query options\n * @param method - The query method (SELECT or LIVE SELECT)\n * @param tableName - The table name to query\n * @param options - The query options (where, select, orderBy, etc.)\n * @param schema - Optional schema for resolving nested relationships\n * @returns QueryInfo with the generated SQL and variables\n */\nexport function buildQueryFromOptions<TModel extends GenericModel, IsOne extends boolean>(\n method: 'SELECT' | 'LIVE SELECT' | 'LIVE SELECT DIFF' | 'UPDATE' | 'DELETE',\n tableName: string,\n options: QueryOptions<TModel, IsOne>,\n schema: SchemaStructure,\n patches?: any[]\n): QueryInfo {\n if (options.isOne) {\n options.limit = 1;\n }\n const isLiveQuery = method === 'LIVE SELECT' || method === 'LIVE SELECT DIFF';\n\n // Parse where conditions to convert string IDs to RecordId\n const parsedWhere = options.where\n ? parseObjectIdsToRecordId(options.where, tableName)\n : undefined;\n\n // Build SELECT clause\n let selectClause = '*';\n\n if (method === 'LIVE SELECT DIFF') {\n selectClause = '';\n } else {\n if (options.select && options.select.length > 0) {\n selectClause = options.select.join(', ');\n }\n }\n\n // Build related subqueries (fetch clauses)\n let fetchClauses = '';\n if (!isLiveQuery && options.related && options.related.length > 0) {\n const subqueries = options.related.map((rel) => buildSubquery(rel, schema));\n fetchClauses = ', ' + subqueries.join(', ');\n }\n\n // Start building the query\n let query = '';\n\n if (method === 'UPDATE') {\n query = `UPDATE ${tableName}`;\n } else if (method === 'DELETE') {\n query = `DELETE FROM ${tableName}`;\n } else {\n query = `${method}${selectClause ? ` ${selectClause}` : ''}${fetchClauses} FROM ${tableName}`;\n }\n\n // Build WHERE clause\n const vars: Record<string, unknown> = {};\n if (parsedWhere && Object.keys(parsedWhere).length > 0) {\n const conditions: string[] = [];\n for (const [key, value] of Object.entries(parsedWhere)) {\n const varName = key;\n\n // Handle operator objects { _op, _val }\n if (value && typeof value === 'object' && '_op' in value && '_val' in value) {\n const { _op, _val, _swap } = value as { _op: string; _val: unknown; _swap?: boolean };\n\n let rightSide = '';\n if (typeof _val === 'string' && _val.startsWith('$')) {\n rightSide = _val;\n } else {\n vars[varName] = _val;\n rightSide = `$${varName}`;\n }\n\n if (_swap) {\n conditions.push(`${rightSide} ${_op} ${key}`);\n } else {\n conditions.push(`${key} ${_op} ${rightSide}`);\n }\n } else {\n vars[varName] = value;\n conditions.push(`${key} = $${varName}`);\n }\n }\n query += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n // Add PATCH for UPDATE\n if (method === 'UPDATE' && patches) {\n query += ` PATCH ${JSON.stringify(patches)}`;\n }\n\n // Add ORDER BY, LIMIT, START only for non-live queries and non-update/delete queries (unless supported)\n // SurrealDB UPDATE/DELETE supports WHERE, but LIMIT/START/ORDER BY might be restricted or behave differently.\n // For now, let's allow them if they are set, as SurrealDB supports them for DELETE/UPDATE.\n if (!isLiveQuery) {\n if (options.orderBy && Object.keys(options.orderBy).length > 0) {\n const orderClauses = Object.entries(options.orderBy).map(\n ([field, direction]) => `${field} ${direction}`\n );\n query += ` ORDER BY ${orderClauses.join(', ')}`;\n }\n\n if (options.limit !== undefined) {\n query += ` LIMIT ${options.limit}`;\n }\n\n if (options.offset !== undefined) {\n query += ` START ${options.offset}`;\n }\n }\n\n query += ';';\n\n return {\n query,\n hash: cyrb53(\n `${query}::${Object.entries(vars)\n .map(([key, value]) => `${key}=${value}`)\n .join('&')}`,\n 0\n ),\n vars: Object.keys(vars).length > 0 ? vars : undefined,\n };\n}\n\n/**\n * Build a subquery for a related field\n */\nfunction buildSubquery(\n rel: RelatedQuery & { foreignKeyField?: string },\n schema: SchemaStructure\n): string {\n const { relatedTable, alias, modifier, cardinality } = rel;\n const foreignKeyField = rel.foreignKeyField || alias;\n\n let subquerySelect = '*';\n let subqueryWhere = '';\n let subqueryOrderBy = '';\n let subqueryLimit = '';\n\n // If there's a modifier, apply it to get the sub-options\n if (modifier) {\n const modifierBuilder = new SchemaAwareQueryModifierBuilderImpl(relatedTable, schema);\n modifier(modifierBuilder);\n const subOptions = modifierBuilder._getOptions();\n\n // Build sub-select\n if (subOptions.select && subOptions.select.length > 0) {\n subquerySelect = subOptions.select.join(', ');\n }\n\n // Build sub-where\n if (subOptions.where && Object.keys(subOptions.where).length > 0) {\n const parsedSubWhere = parseObjectIdsToRecordId(subOptions.where, relatedTable) as Record<\n string,\n unknown\n >;\n const conditions = Object.entries(parsedSubWhere).map(([key, value]) => {\n if (value instanceof RecordId) {\n return `${key} = ${value.toString()}`;\n }\n return `${key} = ${JSON.stringify(value)}`;\n });\n subqueryWhere = ` AND ${conditions.join(' AND ')}`;\n }\n\n // Build sub-orderBy\n if (subOptions.orderBy && Object.keys(subOptions.orderBy).length > 0) {\n const orderClauses = Object.entries(subOptions.orderBy).map(\n ([field, direction]) => `${field} ${direction}`\n );\n subqueryOrderBy = ` ORDER BY ${orderClauses.join(', ')}`;\n }\n\n // Build sub-limit\n if (subOptions.limit !== undefined) {\n subqueryLimit = ` LIMIT ${subOptions.limit}`;\n }\n\n // Handle nested relationships\n if (subOptions.related && subOptions.related.length > 0) {\n // Resolve nested relationship metadata if schema is available\n const resolvedNestedRels = subOptions.related.map((nestedRel) => {\n if (schema) {\n // Look up the actual relationship metadata from schema\n const relationship = schema.relationships.find(\n (r) => r.from === relatedTable && r.field === nestedRel.alias\n );\n\n if (relationship) {\n // Use the resolved table name and add foreign key field\n const nestedForeignKeyField =\n relationship.cardinality === 'many' ? relatedTable : nestedRel.alias;\n\n return {\n ...nestedRel,\n relatedTable: relationship.to,\n cardinality: relationship.cardinality,\n foreignKeyField: nestedForeignKeyField,\n } as RelatedQuery & { foreignKeyField: string };\n }\n }\n return nestedRel;\n });\n\n const nestedSubqueries = resolvedNestedRels.map((nestedRel) =>\n buildSubquery(nestedRel, schema)\n );\n subquerySelect += ', ' + nestedSubqueries.join(', ');\n }\n }\n\n // Determine the WHERE condition based on cardinality\n let whereCondition: string;\n if (cardinality === 'one') {\n // For one-to-one, the related table's id matches parent's foreign key field\n whereCondition = `WHERE id=$parent.${foreignKeyField}`;\n // Add LIMIT 1 for one-to-one relationships if not already set\n if (!subqueryLimit) {\n subqueryLimit = ' LIMIT 1';\n }\n } else {\n // For one-to-many, the related table has a foreign key field pointing to parent's id\n whereCondition = `WHERE ${foreignKeyField}=$parent.id`;\n }\n\n // Build the complete subquery\n let subquery = `(SELECT ${subquerySelect} FROM ${relatedTable} ${whereCondition}${subqueryWhere}${subqueryOrderBy}${subqueryLimit})`;\n\n // For one-to-one relationships, select the first element\n if (cardinality === 'one') {\n subquery += '[0]';\n }\n\n subquery += ` AS ${alias}`;\n\n return subquery;\n}\n"],"mappings":"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"}