@danceroutine/tango-orm 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -0
  3. package/dist/{PostgresAdapter-_X36-mLL.js → PostgresAdapter-C9a1XJRx.js} +31 -7
  4. package/dist/PostgresAdapter-C9a1XJRx.js.map +1 -0
  5. package/dist/PostgresAdapter-CBc1u8eT.js +3 -0
  6. package/dist/SqliteAdapter-BJKNxCvS.js +3 -0
  7. package/dist/SqliteAdapter-Dp6VRXmz.js +118 -0
  8. package/dist/SqliteAdapter-Dp6VRXmz.js.map +1 -0
  9. package/dist/connection/adapters/Adapter.d.ts +9 -0
  10. package/dist/connection/adapters/AdapterRegistry.d.ts +28 -1
  11. package/dist/connection/adapters/dialects/PostgresAdapter.d.ts +10 -1
  12. package/dist/connection/adapters/dialects/SqliteAdapter.d.ts +11 -1
  13. package/dist/connection/clients/DBClient.d.ts +8 -0
  14. package/dist/connection/clients/dialects/PostgresClient.d.ts +22 -1
  15. package/dist/connection/clients/dialects/SqliteClient.d.ts +26 -1
  16. package/dist/connection/index.js +3 -3
  17. package/dist/{connection-DytAsjC9.js → connection-CVvycXus.js} +21 -6
  18. package/dist/connection-CVvycXus.js.map +1 -0
  19. package/dist/index.d.ts +7 -4
  20. package/dist/index.js +9 -8
  21. package/dist/manager/ManagerLike.d.ts +15 -0
  22. package/dist/manager/ModelManager.d.ts +48 -0
  23. package/dist/manager/index.d.ts +6 -0
  24. package/dist/manager/index.js +8 -0
  25. package/dist/manager/internal/MutationCompiler.d.ts +15 -0
  26. package/dist/manager/internal/RuntimeBoundClient.d.ts +16 -0
  27. package/dist/manager/registerModelObjects.d.ts +5 -0
  28. package/dist/manager-D6tU8xTO.js +13 -0
  29. package/dist/manager-D6tU8xTO.js.map +1 -0
  30. package/dist/query/QBuilder.d.ts +18 -0
  31. package/dist/query/QuerySet.d.ts +60 -9
  32. package/dist/query/compiler/QueryCompiler.d.ts +19 -2
  33. package/dist/query/domain/{RepositoryMeta.d.ts → TableMeta.d.ts} +1 -1
  34. package/dist/query/domain/index.d.ts +1 -1
  35. package/dist/query/index.d.ts +2 -2
  36. package/dist/query/index.js +1 -2
  37. package/dist/query-wnl4h2o7.js +671 -0
  38. package/dist/query-wnl4h2o7.js.map +1 -0
  39. package/dist/registerModelObjects-emX7Hja9.js +354 -0
  40. package/dist/registerModelObjects-emX7Hja9.js.map +1 -0
  41. package/dist/runtime/TangoRuntime.d.ts +34 -0
  42. package/dist/runtime/defaultRuntime.d.ts +13 -0
  43. package/dist/runtime/index.d.ts +13 -0
  44. package/dist/runtime/index.js +8 -0
  45. package/dist/runtime-7U5_XDad.js +17 -0
  46. package/dist/runtime-7U5_XDad.js.map +1 -0
  47. package/dist/transaction/UnitOfWork.d.ts +21 -3
  48. package/dist/transaction/index.js +1 -1
  49. package/dist/{transaction-DIGJnp19.js → transaction-DooTMuAl.js} +29 -11
  50. package/dist/transaction-DooTMuAl.js.map +1 -0
  51. package/dist/validation/OrmSqlSafetyAdapter.d.ts +22 -0
  52. package/dist/validation/SQLValidationEngine.d.ts +51 -0
  53. package/dist/validation/SqlValidationPlan.d.ts +42 -0
  54. package/dist/validation/index.d.ts +3 -0
  55. package/package.json +81 -74
  56. package/dist/PostgresAdapter-DCF8T4vh.js +0 -3
  57. package/dist/PostgresAdapter-_X36-mLL.js.map +0 -1
  58. package/dist/QuerySet-BzR5QzGi.js +0 -411
  59. package/dist/QuerySet-BzR5QzGi.js.map +0 -1
  60. package/dist/SqliteAdapter-CBnxCznk.js +0 -3
  61. package/dist/SqliteAdapter-J03fEjmr.js +0 -70
  62. package/dist/SqliteAdapter-J03fEjmr.js.map +0 -1
  63. package/dist/connection/clients/DBClient.js +0 -1
  64. package/dist/connection/clients/dialects/PostgresClient.js +0 -32
  65. package/dist/connection/clients/dialects/SqliteClient.js +0 -44
  66. package/dist/connection-DytAsjC9.js.map +0 -1
  67. package/dist/query/QuerySet.js +0 -108
  68. package/dist/query/compiler/QueryCompiler.js +0 -183
  69. package/dist/query/domain/CompiledQuery.js +0 -1
  70. package/dist/query/domain/WhereClause.js +0 -1
  71. package/dist/query-CQbvLeuh.js +0 -21
  72. package/dist/query-CQbvLeuh.js.map +0 -1
  73. package/dist/repository/Repository.d.ts +0 -40
  74. package/dist/repository/Repository.js +0 -100
  75. package/dist/repository/index.d.ts +0 -4
  76. package/dist/repository/index.js +0 -4
  77. package/dist/repository-DaRvsfjs.js +0 -78
  78. package/dist/repository-DaRvsfjs.js.map +0 -1
  79. package/dist/transaction-DIGJnp19.js.map +0 -1
@@ -0,0 +1,671 @@
1
+ import { __export } from "./chunk-DLY2FNSh.js";
2
+ import { SqlSafetyEngine } from "@danceroutine/tango-core";
3
+
4
+ //#region src/query/domain/internal/InternalDialect.ts
5
+ const InternalDialect = {
6
+ POSTGRES: "postgres",
7
+ SQLITE: "sqlite"
8
+ };
9
+
10
+ //#endregion
11
+ //#region src/query/domain/internal/InternalQNodeType.ts
12
+ const InternalQNodeType = {
13
+ ATOM: "atom",
14
+ AND: "and",
15
+ OR: "or",
16
+ NOT: "not"
17
+ };
18
+
19
+ //#endregion
20
+ //#region src/query/domain/internal/InternalLookupType.ts
21
+ const InternalLookupType = {
22
+ EXACT: "exact",
23
+ LT: "lt",
24
+ LTE: "lte",
25
+ GT: "gt",
26
+ GTE: "gte",
27
+ IN: "in",
28
+ ISNULL: "isnull",
29
+ CONTAINS: "contains",
30
+ ICONTAINS: "icontains",
31
+ STARTSWITH: "startswith",
32
+ ISTARTSWITH: "istartswith",
33
+ ENDSWITH: "endswith",
34
+ IENDSWITH: "iendswith"
35
+ };
36
+
37
+ //#endregion
38
+ //#region src/validation/OrmSqlSafetyAdapter.ts
39
+ const ALLOWED_LOOKUPS = Object.values(InternalLookupType);
40
+ var OrmSqlSafetyAdapter = class OrmSqlSafetyAdapter {
41
+ static BRAND = "tango.orm.orm_sql_safety_adapter";
42
+ __tangoBrand = OrmSqlSafetyAdapter.BRAND;
43
+ constructor(engine = new SqlSafetyEngine()) {
44
+ this.engine = engine;
45
+ }
46
+ validate(plan) {
47
+ switch (plan.kind) {
48
+ case "select": {
49
+ const meta = this.validateTableMeta(plan.meta, plan.relationNames ?? []);
50
+ return {
51
+ kind: "select",
52
+ meta,
53
+ selectFields: Object.fromEntries((plan.selectFields ?? []).map((field) => [field, `${meta.table}.${this.resolveColumn(meta, field)}`])),
54
+ filterKeys: Object.fromEntries((plan.filterKeys ?? []).map((rawKey) => [rawKey, this.validateFilterKey(meta, rawKey)])),
55
+ orderFields: Object.fromEntries((plan.orderFields ?? []).map((field) => [field, `${meta.table}.${this.resolveColumn(meta, field)}`])),
56
+ relations: Object.fromEntries((plan.relationNames ?? []).map((relationName) => [relationName, this.resolveRelation(meta, relationName)]))
57
+ };
58
+ }
59
+ case "insert": {
60
+ const meta = this.validateTableMeta(plan.meta);
61
+ return {
62
+ kind: "insert",
63
+ meta,
64
+ writeKeys: plan.writeKeys.map((key) => this.resolveColumn(meta, key))
65
+ };
66
+ }
67
+ case "update": {
68
+ const meta = this.validateTableMeta(plan.meta);
69
+ return {
70
+ kind: "update",
71
+ meta,
72
+ writeKeys: plan.writeKeys.map((key) => this.resolveColumn(meta, key))
73
+ };
74
+ }
75
+ case "delete": return {
76
+ kind: "delete",
77
+ meta: this.validateTableMeta(plan.meta)
78
+ };
79
+ }
80
+ }
81
+ validateTableMeta(meta, relationNames = []) {
82
+ const columnNames = Object.keys(meta.columns);
83
+ const validated = this.engine.validate({ identifiers: [
84
+ {
85
+ key: "table",
86
+ role: "table",
87
+ value: meta.table
88
+ },
89
+ {
90
+ key: "pk",
91
+ role: "primaryKey",
92
+ value: meta.pk,
93
+ allowlist: columnNames
94
+ },
95
+ ...columnNames.map((column) => ({
96
+ key: `column:${column}`,
97
+ role: "column",
98
+ value: column
99
+ }))
100
+ ] });
101
+ const validatedMeta = {
102
+ table: validated.identifiers.table.value,
103
+ pk: validated.identifiers.pk.value,
104
+ columns: Object.fromEntries(columnNames.map((column) => [validated.identifiers[`column:${column}`].value, meta.columns[column]]))
105
+ };
106
+ if (!(validatedMeta.pk in validatedMeta.columns)) throw new Error(`Unknown column '${validatedMeta.pk}' for table '${validatedMeta.table}'.`);
107
+ if (relationNames.length > 0) validatedMeta.relations = Object.fromEntries(relationNames.map((relationName) => [relationName, this.validateRelationMeta(validatedMeta, relationName, meta.relations)]));
108
+ return validatedMeta;
109
+ }
110
+ validateRelationMeta(meta, relationName, relations) {
111
+ const relation = relations?.[relationName];
112
+ if (!relation) throw new Error(`Unknown relation '${relationName}' for table '${meta.table}'.`);
113
+ const validated = this.engine.validate({ identifiers: [
114
+ {
115
+ key: "table",
116
+ role: "relationTable",
117
+ value: relation.table
118
+ },
119
+ {
120
+ key: "alias",
121
+ role: "alias",
122
+ value: relation.alias
123
+ },
124
+ {
125
+ key: "targetPk",
126
+ role: "relationTargetPrimaryKey",
127
+ value: relation.targetPk
128
+ },
129
+ ...relation.foreignKey ? [{
130
+ key: "foreignKey",
131
+ role: "relationForeignKey",
132
+ value: relation.foreignKey
133
+ }] : []
134
+ ] });
135
+ return {
136
+ ...relation,
137
+ table: validated.identifiers.table.value,
138
+ alias: validated.identifiers.alias.value,
139
+ targetPk: validated.identifiers.targetPk.value,
140
+ localKey: relation.localKey ? this.resolveColumn(meta, relation.localKey) : undefined,
141
+ foreignKey: relation.foreignKey ? validated.identifiers.foreignKey.value : undefined
142
+ };
143
+ }
144
+ validateFilterKey(meta, rawKey) {
145
+ const segments = rawKey.split("__");
146
+ if (segments.length > 2) throw new Error(`Invalid SQL lookup key: '${rawKey}'.`);
147
+ const field = segments[0];
148
+ const lookup = segments[1] ?? InternalLookupType.EXACT;
149
+ const validated = this.engine.validate({ lookupTokens: [{
150
+ key: rawKey,
151
+ lookup,
152
+ allowed: ALLOWED_LOOKUPS
153
+ }] });
154
+ return {
155
+ rawKey,
156
+ field,
157
+ lookup: validated.lookupTokens[rawKey].lookup,
158
+ qualifiedColumn: `${meta.table}.${this.resolveColumn(meta, field)}`
159
+ };
160
+ }
161
+ resolveColumn(meta, field) {
162
+ if (!(field in meta.columns)) throw new Error(`Unknown column '${field}' for table '${meta.table}'.`);
163
+ return field;
164
+ }
165
+ resolveRelation(meta, relationName) {
166
+ const relation = meta.relations?.[relationName];
167
+ if (!relation) throw new Error(`Unknown relation '${relationName}' for table '${meta.table}'.`);
168
+ if (relation.kind === "belongsTo" && !relation.localKey) throw new Error(`Relation '${relationName}' for table '${meta.table}' requires a local key.`);
169
+ return relation;
170
+ }
171
+ };
172
+
173
+ //#endregion
174
+ //#region src/query/compiler/QueryCompiler.ts
175
+ const sqlSafetyAdapter = new OrmSqlSafetyAdapter();
176
+ var QueryCompiler = class QueryCompiler {
177
+ static BRAND = "tango.orm.query_compiler";
178
+ __tangoBrand = QueryCompiler.BRAND;
179
+ /**
180
+ * Build a compiler for the given repository metadata and SQL dialect.
181
+ */
182
+ constructor(meta, dialect = InternalDialect.POSTGRES) {
183
+ this.meta = meta;
184
+ this.dialect = dialect;
185
+ }
186
+ /**
187
+ * Narrow an unknown value to `QueryCompiler`.
188
+ */
189
+ static isQueryCompiler(value) {
190
+ return typeof value === "object" && value !== null && value.__tangoBrand === QueryCompiler.BRAND;
191
+ }
192
+ /**
193
+ * Compile a query state tree into a SQL statement and bound parameters.
194
+ */
195
+ compile(state) {
196
+ const knownRelationNames = (state.selectRelated ?? []).filter((relationName) => this.meta.relations?.[relationName] !== undefined);
197
+ const validatedPlan = sqlSafetyAdapter.validate({
198
+ kind: "select",
199
+ meta: this.meta,
200
+ selectFields: state.select?.map(String),
201
+ filterKeys: this.collectStateFilterKeys(state),
202
+ orderFields: state.order?.map((order) => String(order.by)),
203
+ relationNames: knownRelationNames
204
+ });
205
+ const table = validatedPlan.meta.table;
206
+ const whereParts = [];
207
+ const params = [];
208
+ if (state.q) {
209
+ const result = this.compileQNode(state.q, params.length + 1, validatedPlan.filterKeys);
210
+ if (result.sql) {
211
+ whereParts.push(result.sql);
212
+ params.push(...result.params);
213
+ }
214
+ }
215
+ state.excludes?.forEach((exclude) => {
216
+ const result = this.compileQNode({
217
+ kind: InternalQNodeType.NOT,
218
+ node: exclude
219
+ }, params.length + 1, validatedPlan.filterKeys);
220
+ if (result.sql) {
221
+ whereParts.push(result.sql);
222
+ params.push(...result.params);
223
+ }
224
+ });
225
+ const select = state.select?.length ? state.select.map((field) => validatedPlan.selectFields[String(field)]).join(", ") : `${table}.*`;
226
+ const joins = knownRelationNames.map((rel) => {
227
+ const relation = validatedPlan.relations[rel];
228
+ if (!relation || relation.kind !== "belongsTo") return "";
229
+ return `LEFT JOIN ${relation.table} ${relation.alias} ON ${relation.alias}.${relation.targetPk} = ${table}.${relation.localKey}`;
230
+ }).filter(Boolean).join(" ");
231
+ const whereSQL = whereParts.length ? ` WHERE ${whereParts.join(" AND ")}` : "";
232
+ const orderSQL = ` ORDER BY ${state.order?.length ? state.order.map((order) => `${validatedPlan.orderFields[String(order.by)]} ${order.dir.toUpperCase()}`).join(", ") : `${table}.${validatedPlan.meta.pk} ASC`}`;
233
+ const limitSQL = state.limit ? ` LIMIT ${state.limit}` : "";
234
+ const offsetSQL = state.offset ? ` OFFSET ${state.offset}` : "";
235
+ const sql = `SELECT ${select} FROM ${table}${joins ? ` ${joins}` : ""}${whereSQL}${orderSQL}${limitSQL}${offsetSQL}`;
236
+ return {
237
+ sql,
238
+ params
239
+ };
240
+ }
241
+ compileQNode(node, paramIndex, filterKeys) {
242
+ switch (node.kind) {
243
+ case InternalQNodeType.ATOM: return this.compileAtom(node.where || {}, paramIndex, filterKeys);
244
+ case InternalQNodeType.AND: return this.compileAnd(node.nodes || [], paramIndex, filterKeys);
245
+ case InternalQNodeType.OR: return this.compileOr(node.nodes || [], paramIndex, filterKeys);
246
+ case InternalQNodeType.NOT: return this.compileNot(node.node, paramIndex, filterKeys);
247
+ default: return {
248
+ sql: "",
249
+ params: []
250
+ };
251
+ }
252
+ }
253
+ compileAtom(where, paramIndex, filterKeys) {
254
+ const entries = Object.entries(where).filter(([, value]) => value !== undefined);
255
+ const { parts, params } = entries.reduce((accumulator, [key, value]) => {
256
+ const descriptor = filterKeys[String(key)];
257
+ const idx = paramIndex + accumulator.params.length;
258
+ const clause = this.lookupToSQL(descriptor.qualifiedColumn, descriptor.lookup, value, idx);
259
+ accumulator.parts.push(clause.sql);
260
+ accumulator.params.push(...clause.params);
261
+ return accumulator;
262
+ }, {
263
+ parts: [],
264
+ params: []
265
+ });
266
+ return {
267
+ sql: parts.length ? `(${parts.join(" AND ")})` : "",
268
+ params
269
+ };
270
+ }
271
+ compileAnd(nodes, paramIndex, filterKeys) {
272
+ const { parts, params } = nodes.reduce((accumulator, node) => {
273
+ const result = this.compileQNode(node, paramIndex + accumulator.params.length, filterKeys);
274
+ if (result.sql) {
275
+ accumulator.parts.push(result.sql);
276
+ accumulator.params.push(...result.params);
277
+ }
278
+ return accumulator;
279
+ }, {
280
+ parts: [],
281
+ params: []
282
+ });
283
+ return {
284
+ sql: parts.length ? `(${parts.join(" AND ")})` : "",
285
+ params
286
+ };
287
+ }
288
+ compileOr(nodes, paramIndex, filterKeys) {
289
+ const { parts, params } = nodes.reduce((accumulator, node) => {
290
+ const result = this.compileQNode(node, paramIndex + accumulator.params.length, filterKeys);
291
+ if (result.sql) {
292
+ accumulator.parts.push(result.sql);
293
+ accumulator.params.push(...result.params);
294
+ }
295
+ return accumulator;
296
+ }, {
297
+ parts: [],
298
+ params: []
299
+ });
300
+ return {
301
+ sql: parts.length ? `(${parts.join(" OR ")})` : "",
302
+ params
303
+ };
304
+ }
305
+ compileNot(node, paramIndex, filterKeys) {
306
+ const result = this.compileQNode(node, paramIndex, filterKeys);
307
+ if (!result.sql) return {
308
+ sql: "",
309
+ params: []
310
+ };
311
+ return {
312
+ sql: `(NOT ${result.sql})`,
313
+ params: result.params
314
+ };
315
+ }
316
+ lookupToSQL(col, lookup, value, paramIndex) {
317
+ const placeholder = this.dialect === InternalDialect.POSTGRES ? `$${paramIndex}` : "?";
318
+ const normalized = this.normalizeParam(value);
319
+ switch (lookup) {
320
+ case InternalLookupType.EXACT:
321
+ if (value === null) return {
322
+ sql: `${col} IS NULL`,
323
+ params: []
324
+ };
325
+ return {
326
+ sql: `${col} = ${placeholder}`,
327
+ params: [normalized]
328
+ };
329
+ case InternalLookupType.LT: return {
330
+ sql: `${col} < ${placeholder}`,
331
+ params: [normalized]
332
+ };
333
+ case InternalLookupType.LTE: return {
334
+ sql: `${col} <= ${placeholder}`,
335
+ params: [normalized]
336
+ };
337
+ case InternalLookupType.GT: return {
338
+ sql: `${col} > ${placeholder}`,
339
+ params: [normalized]
340
+ };
341
+ case InternalLookupType.GTE: return {
342
+ sql: `${col} >= ${placeholder}`,
343
+ params: [normalized]
344
+ };
345
+ case InternalLookupType.IN: {
346
+ const entries = (Array.isArray(value) ? value : [value]).map((entry) => this.normalizeParam(entry));
347
+ if (entries.length === 0) return {
348
+ sql: "1=0",
349
+ params: []
350
+ };
351
+ const placeholders = this.dialect === InternalDialect.POSTGRES ? entries.map((_, index) => `$${paramIndex + index}`).join(", ") : entries.map(() => "?").join(", ");
352
+ return {
353
+ sql: `${col} IN (${placeholders})`,
354
+ params: entries
355
+ };
356
+ }
357
+ case InternalLookupType.ISNULL: return {
358
+ sql: value ? `${col} IS NULL` : `${col} IS NOT NULL`,
359
+ params: []
360
+ };
361
+ case InternalLookupType.CONTAINS: return {
362
+ sql: `${col} LIKE ${placeholder}`,
363
+ params: [`%${value}%`]
364
+ };
365
+ case InternalLookupType.ICONTAINS: {
366
+ const lowerCol = this.dialect === InternalDialect.POSTGRES ? `LOWER(${col})` : `${col}`;
367
+ return {
368
+ sql: `${lowerCol} LIKE ${placeholder}`,
369
+ params: [`%${String(value).toLowerCase()}%`]
370
+ };
371
+ }
372
+ case InternalLookupType.STARTSWITH: return {
373
+ sql: `${col} LIKE ${placeholder}`,
374
+ params: [`${value}%`]
375
+ };
376
+ case InternalLookupType.ISTARTSWITH: {
377
+ const lowerCol = this.dialect === InternalDialect.POSTGRES ? `LOWER(${col})` : `${col}`;
378
+ return {
379
+ sql: `${lowerCol} LIKE ${placeholder}`,
380
+ params: [`${String(value).toLowerCase()}%`]
381
+ };
382
+ }
383
+ case InternalLookupType.ENDSWITH: return {
384
+ sql: `${col} LIKE ${placeholder}`,
385
+ params: [`%${value}`]
386
+ };
387
+ case InternalLookupType.IENDSWITH: {
388
+ const lowerCol = this.dialect === InternalDialect.POSTGRES ? `LOWER(${col})` : `${col}`;
389
+ return {
390
+ sql: `${lowerCol} LIKE ${placeholder}`,
391
+ params: [`%${String(value).toLowerCase()}`]
392
+ };
393
+ }
394
+ default: throw new Error(`Unknown lookup: ${lookup}`);
395
+ }
396
+ }
397
+ normalizeParam(value) {
398
+ if (this.dialect === InternalDialect.SQLITE && typeof value === "boolean") return value ? 1 : 0;
399
+ return value;
400
+ }
401
+ collectStateFilterKeys(state) {
402
+ const filterKeys = new Set();
403
+ if (state.q) this.collectNodeFilterKeys(state.q, filterKeys);
404
+ state.excludes?.forEach((exclude) => this.collectNodeFilterKeys(exclude, filterKeys));
405
+ return [...filterKeys];
406
+ }
407
+ collectNodeFilterKeys(node, filterKeys) {
408
+ Object.keys(node.where ?? {}).forEach((key) => filterKeys.add(key));
409
+ node.nodes?.forEach((child) => this.collectNodeFilterKeys(child, filterKeys));
410
+ if (node.node) this.collectNodeFilterKeys(node.node, filterKeys);
411
+ }
412
+ };
413
+
414
+ //#endregion
415
+ //#region src/query/compiler/index.ts
416
+ var compiler_exports = {};
417
+ __export(compiler_exports, { QueryCompiler: () => QueryCompiler });
418
+
419
+ //#endregion
420
+ //#region src/query/domain/index.ts
421
+ var domain_exports = {};
422
+
423
+ //#endregion
424
+ //#region src/query/domain/internal/InternalDirection.ts
425
+ const InternalDirection = {
426
+ ASC: "asc",
427
+ DESC: "desc"
428
+ };
429
+
430
+ //#endregion
431
+ //#region src/query/QBuilder.ts
432
+ var QBuilder = class QBuilder {
433
+ static BRAND = "tango.orm.q_builder";
434
+ __tangoBrand = QBuilder.BRAND;
435
+ /**
436
+ * Narrow an unknown value to `QBuilder`.
437
+ */
438
+ static isQBuilder(value) {
439
+ return typeof value === "object" && value !== null && value.__tangoBrand === QBuilder.BRAND;
440
+ }
441
+ /**
442
+ * Combine multiple filter fragments using logical `AND`.
443
+ */
444
+ static and(...nodes) {
445
+ return {
446
+ kind: InternalQNodeType.AND,
447
+ nodes: nodes.map(QBuilder.wrapNode)
448
+ };
449
+ }
450
+ /**
451
+ * Combine multiple filter fragments using logical `OR`.
452
+ */
453
+ static or(...nodes) {
454
+ return {
455
+ kind: InternalQNodeType.OR,
456
+ nodes: nodes.map(QBuilder.wrapNode)
457
+ };
458
+ }
459
+ /**
460
+ * Negate a filter fragment using logical `NOT`.
461
+ */
462
+ static not(node) {
463
+ return {
464
+ kind: InternalQNodeType.NOT,
465
+ node: QBuilder.wrapNode(node)
466
+ };
467
+ }
468
+ static wrapNode(input) {
469
+ if (input.kind) return input;
470
+ return {
471
+ kind: InternalQNodeType.ATOM,
472
+ where: input
473
+ };
474
+ }
475
+ };
476
+
477
+ //#endregion
478
+ //#region src/query/QuerySet.ts
479
+ var QuerySet = class QuerySet {
480
+ static BRAND = "tango.orm.query_set";
481
+ __tangoBrand = QuerySet.BRAND;
482
+ constructor(executor, state = {}) {
483
+ this.executor = executor;
484
+ this.state = state;
485
+ }
486
+ /**
487
+ * Narrow an unknown value to `QuerySet`.
488
+ */
489
+ static isQuerySet(value) {
490
+ return typeof value === "object" && value !== null && value.__tangoBrand === QuerySet.BRAND;
491
+ }
492
+ /**
493
+ * Add a filter expression to the query.
494
+ *
495
+ * Multiple `filter()` calls are composed with `AND`.
496
+ */
497
+ filter(q) {
498
+ const wrapped = q.kind ? q : {
499
+ kind: InternalQNodeType.ATOM,
500
+ where: q
501
+ };
502
+ const merged = this.state.q ? QBuilder.and(this.state.q, wrapped) : wrapped;
503
+ return new QuerySet(this.executor, {
504
+ ...this.state,
505
+ q: merged
506
+ });
507
+ }
508
+ /**
509
+ * Add an exclusion expression to the query.
510
+ *
511
+ * Exclusions are translated to `NOT (...)` predicates.
512
+ */
513
+ exclude(q) {
514
+ const wrapped = q.kind ? q : {
515
+ kind: InternalQNodeType.ATOM,
516
+ where: q
517
+ };
518
+ const excludes = [...this.state.excludes ?? [], wrapped];
519
+ return new QuerySet(this.executor, {
520
+ ...this.state,
521
+ excludes
522
+ });
523
+ }
524
+ /**
525
+ * Apply ordering tokens such as `'name'` or `'-createdAt'`.
526
+ */
527
+ orderBy(...tokens) {
528
+ const order = tokens.map((t) => {
529
+ const str = String(t);
530
+ if (str.startsWith("-")) return {
531
+ by: str.slice(1),
532
+ dir: InternalDirection.DESC
533
+ };
534
+ return {
535
+ by: t,
536
+ dir: InternalDirection.ASC
537
+ };
538
+ });
539
+ return new QuerySet(this.executor, {
540
+ ...this.state,
541
+ order
542
+ });
543
+ }
544
+ /**
545
+ * Limit the maximum number of rows returned.
546
+ */
547
+ limit(n) {
548
+ return new QuerySet(this.executor, {
549
+ ...this.state,
550
+ limit: n
551
+ });
552
+ }
553
+ /**
554
+ * Skip the first `n` rows.
555
+ */
556
+ offset(n) {
557
+ return new QuerySet(this.executor, {
558
+ ...this.state,
559
+ offset: n
560
+ });
561
+ }
562
+ /**
563
+ * Restrict selected columns.
564
+ */
565
+ select(cols) {
566
+ return new QuerySet(this.executor, {
567
+ ...this.state,
568
+ select: cols
569
+ });
570
+ }
571
+ /**
572
+ * Request SQL joins for related data when supported by relation metadata.
573
+ */
574
+ selectRelated(...rels) {
575
+ return new QuerySet(this.executor, {
576
+ ...this.state,
577
+ selectRelated: rels
578
+ });
579
+ }
580
+ /**
581
+ * Register relation names for prefetch behavior.
582
+ *
583
+ * Prefetch orchestration is adapter-specific.
584
+ */
585
+ prefetchRelated(...rels) {
586
+ return new QuerySet(this.executor, {
587
+ ...this.state,
588
+ prefetchRelated: rels
589
+ });
590
+ }
591
+ /**
592
+ * Execute the query and optionally shape each row.
593
+ */
594
+ async fetch(shape) {
595
+ const compiler = new QueryCompiler(this.executor.meta, this.executor.dialect);
596
+ const compiled = compiler.compile(this.state);
597
+ const rows = await this.executor.run(compiled);
598
+ const normalizedRows = this.normalizeRowsForSchemaParsing(rows, shape);
599
+ const results = !shape ? normalizedRows : typeof shape === "function" ? normalizedRows.map(shape) : normalizedRows.map((r) => shape.parse(r));
600
+ return {
601
+ results,
602
+ nextCursor: null
603
+ };
604
+ }
605
+ /**
606
+ * Execute the query and return the first row, or `null`.
607
+ */
608
+ async fetchOne(shape) {
609
+ const limited = this.limit(1);
610
+ const result = await limited.fetch(shape);
611
+ return result.results[0] ?? null;
612
+ }
613
+ /**
614
+ * Execute a `COUNT(*)` query for the current filtered state.
615
+ */
616
+ async count() {
617
+ const compiler = new QueryCompiler(this.executor.meta, this.executor.dialect);
618
+ const compiled = compiler.compile(this.state);
619
+ const countQuery = `SELECT COUNT(*) as count FROM (${compiled.sql}) AS tango_count_subquery`;
620
+ const rows = await this.executor.client.query(countQuery, compiled.params);
621
+ return Number(rows.rows[0]?.count ?? 0);
622
+ }
623
+ /**
624
+ * Return whether at least one row matches the current query state.
625
+ */
626
+ async exists() {
627
+ const count = await this.count();
628
+ return count > 0;
629
+ }
630
+ normalizeRowsForSchemaParsing(rows, shape) {
631
+ if (!shape || typeof shape === "function" || this.executor.dialect !== InternalDialect.SQLITE) return rows;
632
+ const booleanColumns = Object.entries(this.executor.meta.columns).filter(([, value]) => this.isBooleanColumnType(value)).map(([column]) => column);
633
+ if (booleanColumns.length === 0) return rows;
634
+ return rows.map((row) => this.normalizeBooleanColumns(row, booleanColumns));
635
+ }
636
+ isBooleanColumnType(value) {
637
+ return typeof value === "string" && ["bool", "boolean"].includes(value.trim().toLowerCase());
638
+ }
639
+ normalizeSqliteBoolean(value) {
640
+ if (value === 0 || value === "0") return false;
641
+ if (value === 1 || value === "1") return true;
642
+ return value;
643
+ }
644
+ normalizeBooleanColumns(row, columns) {
645
+ let normalized = null;
646
+ for (const column of columns) {
647
+ const current = row[column];
648
+ const next = this.normalizeSqliteBoolean(current);
649
+ if (next === current) continue;
650
+ if (!normalized) normalized = { ...row };
651
+ normalized[column] = next;
652
+ }
653
+ return normalized ?? row;
654
+ }
655
+ };
656
+
657
+ //#endregion
658
+ //#region src/query/index.ts
659
+ var query_exports = {};
660
+ __export(query_exports, {
661
+ Q: () => QBuilder,
662
+ QBuilder: () => QBuilder,
663
+ QueryCompiler: () => QueryCompiler,
664
+ QuerySet: () => QuerySet,
665
+ compiler: () => compiler_exports,
666
+ domain: () => domain_exports
667
+ });
668
+
669
+ //#endregion
670
+ export { OrmSqlSafetyAdapter, QBuilder, QueryCompiler, QuerySet, compiler_exports, domain_exports, query_exports };
671
+ //# sourceMappingURL=query-wnl4h2o7.js.map