@rapiq/sql 2.0.0-beta.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,876 @@
1
+ import { AdapterError, FieldOperator, Filter, FilterFieldOperator, FilterRegexFlag, Filters, ITSELF, createFilterRegex } from "@rapiq/core";
2
+ //#region src/helpers/name-split.ts
3
+ function splitFirst(input) {
4
+ const separatorIndex = input.indexOf(".");
5
+ if (separatorIndex === -1) return [input, ""];
6
+ return [input.substring(0, separatorIndex), input.substring(separatorIndex + 1)];
7
+ }
8
+ function splitLast(input) {
9
+ const separatorIndex = input.lastIndexOf(".");
10
+ if (separatorIndex === -1) return [input, ""];
11
+ return [input.substring(0, separatorIndex), input.substring(separatorIndex + 1)];
12
+ }
13
+ //#endregion
14
+ //#region src/helpers/relation-alias.ts
15
+ /**
16
+ * The tightest identifier limit among the shipped dialect presets:
17
+ * PostgreSQL truncates identifiers to 63 bytes (silently, with a
18
+ * NOTICE), so an alias longer than that would lose its uniqueness
19
+ * guarantee on the way into the database.
20
+ */
21
+ const MAX_ALIAS_LENGTH = 63;
22
+ /**
23
+ * FNV-1a (32 bit) — deterministic, dependency-free disambiguator for
24
+ * aliases that exceed the identifier limit.
25
+ */
26
+ function hashPath(input) {
27
+ let hash = 2166136261;
28
+ for (let i = 0; i < input.length; i++) {
29
+ hash ^= input.charCodeAt(i);
30
+ hash = Math.imul(hash, 16777619);
31
+ }
32
+ return (hash >>> 0).toString(36);
33
+ }
34
+ /**
35
+ * Default join-alias derivation: every path segment is length-prefixed
36
+ * (e.g. `role.realm` -> `r4_role_5_realm`). Unlike replacing dots with
37
+ * underscores, this is injective even when relation names contain `_`.
38
+ * Aliases that would exceed the database identifier limit are bounded:
39
+ * a readable prefix plus a hash of the full path — otherwise engines
40
+ * such as PostgreSQL would truncate them (silently collapsing long,
41
+ * distinct paths onto one alias).
42
+ */
43
+ function buildRelationAlias(path) {
44
+ const alias = `r${path.split(".").map((segment) => `${segment.length}_${segment}`).join("_")}`;
45
+ if (alias.length <= MAX_ALIAS_LENGTH) return alias;
46
+ const suffix = `_h${hashPath(path)}`;
47
+ return `${alias.substring(0, MAX_ALIAS_LENGTH - suffix.length)}${suffix}`;
48
+ }
49
+ //#endregion
50
+ //#region src/helpers/field.ts
51
+ function parseField(input, rootAlias, relationAlias = buildRelationAlias) {
52
+ const [relation, name] = splitLast(input);
53
+ if (!name) {
54
+ if (rootAlias) return {
55
+ prefix: rootAlias,
56
+ name: relation
57
+ };
58
+ return { name: relation };
59
+ }
60
+ return {
61
+ relation,
62
+ prefix: relationAlias(relation),
63
+ name
64
+ };
65
+ }
66
+ //#endregion
67
+ //#region src/helpers/like.ts
68
+ /**
69
+ * Escape LIKE pattern wildcards (incl. the MSSQL bracket range)
70
+ * so user input matches literally under `ESCAPE '\'`.
71
+ */
72
+ function escapeLikePattern(input) {
73
+ return input.replace(/[\\%_[]/g, (character) => `\\${character}`);
74
+ }
75
+ //#endregion
76
+ //#region src/helpers/param-placeholder-indexer.ts
77
+ var ParamPlaceholderIndexer = class {
78
+ index;
79
+ constructor(index) {
80
+ this.index = index ?? 0;
81
+ }
82
+ next() {
83
+ this.index++;
84
+ return this.index;
85
+ }
86
+ reset() {
87
+ this.index = 0;
88
+ }
89
+ };
90
+ //#endregion
91
+ //#region src/helpers/root-alias.ts
92
+ function toRootAliasFn(input) {
93
+ if (typeof input === "function") return input;
94
+ return () => input;
95
+ }
96
+ function isRootAliasFn(input) {
97
+ return typeof input === "function";
98
+ }
99
+ //#endregion
100
+ //#region src/adapter/fields/base.ts
101
+ var FieldsBaseAdapter = class {
102
+ relations;
103
+ /**
104
+ * selection fields.
105
+ *
106
+ * e.g. ['name', 'project.id']
107
+ */
108
+ value;
109
+ constructor(relations) {
110
+ this.relations = relations;
111
+ this.value = [];
112
+ }
113
+ clear() {
114
+ this.value = [];
115
+ }
116
+ add(input, operator) {
117
+ const name = this.buildField(input);
118
+ this.value.push({
119
+ name,
120
+ operator
121
+ });
122
+ }
123
+ /**
124
+ * Escaped selection columns (excluded fields are dropped).
125
+ */
126
+ getColumns() {
127
+ const output = [];
128
+ for (let i = 0; i < this.value.length; i++) {
129
+ const element = this.value[i];
130
+ if (element.operator === FieldOperator.EXCLUDE) continue;
131
+ output.push(element.name);
132
+ }
133
+ return output;
134
+ }
135
+ buildField(input) {
136
+ const output = parseField(input, this.rootAlias(), (path) => this.relations.buildAlias(path));
137
+ if (output.relation) this.relations.add(output.relation);
138
+ if (output.prefix) return `${this.escapeField(output.prefix)}.${this.escapeField(output.name)}`;
139
+ return this.escapeField(output.name);
140
+ }
141
+ };
142
+ //#endregion
143
+ //#region src/adapter/fields/module.ts
144
+ var FieldsAdapter = class extends FieldsBaseAdapter {
145
+ options;
146
+ constructor(relations, options) {
147
+ super(relations);
148
+ this.options = options;
149
+ }
150
+ escapeField(field) {
151
+ if (this.options.escapeField) return this.options.escapeField(field);
152
+ return field;
153
+ }
154
+ rootAlias() {
155
+ if (this.options.rootAlias) return this.options.rootAlias;
156
+ }
157
+ execute() {}
158
+ };
159
+ //#endregion
160
+ //#region src/adapter/filters/base.ts
161
+ var FiltersBaseAdapter = class {
162
+ /**
163
+ * where conditions
164
+ *
165
+ * e.g. ['"foo.bar" = "1"' ]
166
+ */
167
+ conditions;
168
+ params;
169
+ relations;
170
+ paramPlaceholderIndexer;
171
+ fieldPrefix;
172
+ constructor(relations) {
173
+ this.conditions = [];
174
+ this.params = [];
175
+ this.relations = relations;
176
+ this.paramPlaceholderIndexer = new ParamPlaceholderIndexer();
177
+ this.fieldPrefix = "";
178
+ }
179
+ clear() {
180
+ this.conditions = [];
181
+ this.params = [];
182
+ this.paramPlaceholderIndexer.reset();
183
+ this.fieldPrefix = "";
184
+ }
185
+ /**
186
+ * Whether the dialect can build regular-expression conditions.
187
+ * Anchored operators fall back to LIKE otherwise.
188
+ */
189
+ isRegexpSupported() {
190
+ return true;
191
+ }
192
+ /**
193
+ * Fold an expression for a case-insensitive equality comparison
194
+ * (eq/ne/in/nin on strings). Dialects whose plain `=` already
195
+ * compares case-insensitively return the input unchanged.
196
+ */
197
+ caseFold(input) {
198
+ return `lower(${input})`;
199
+ }
200
+ /**
201
+ * Whether equality comparisons on this field may case-fold at all.
202
+ * Backends with column metadata override this to exempt non-string
203
+ * columns — folding them is wasted work at best and a type error at
204
+ * worst (e.g. `lower(integer)` on postgres).
205
+ */
206
+ isCaseFoldable(_field) {
207
+ return true;
208
+ }
209
+ where(field, operator, value) {
210
+ return this.whereRaw(`${this.buildField(field)} ${operator} ${this.buildParamPlaceholder()}`, value);
211
+ }
212
+ whereRaw(sql, ...values) {
213
+ this.conditions.push(sql);
214
+ if (values) this.params.push(...values);
215
+ return this;
216
+ }
217
+ buildParamPlaceholder() {
218
+ return this.paramPlaceholder(this.paramPlaceholderIndexer.next());
219
+ }
220
+ buildParamsPlaceholders(input) {
221
+ return input.map((_) => this.paramPlaceholder(this.paramPlaceholderIndexer.next()));
222
+ }
223
+ buildField(input) {
224
+ let inputNormalized;
225
+ if (this.fieldPrefix) inputNormalized = this.fieldPrefix + input;
226
+ else inputNormalized = input;
227
+ if (inputNormalized.split(".").includes(ITSELF)) throw AdapterError.featureUnsupported("filters:itself");
228
+ const output = parseField(inputNormalized, this.rootAlias(), (path) => this.relations.buildAlias(path));
229
+ if (output.relation) this.relations.add(output.relation);
230
+ if (output.prefix) return `${this.escapeField(output.prefix)}.${this.escapeField(output.name)}`;
231
+ return this.escapeField(output.name);
232
+ }
233
+ merge(query, operator = "and", isInverted = false) {
234
+ if (query.conditions.length > 0) {
235
+ let sql = query.conditions.join(` ${operator} `);
236
+ if (query.conditions.length > 1) sql = `(${sql})`;
237
+ this.conditions.push(`${isInverted ? "not " : ""}${sql}`);
238
+ this.params.push(...query.params);
239
+ }
240
+ return this;
241
+ }
242
+ setChildAttributes(child) {
243
+ child.setFieldPrefix(this.fieldPrefix);
244
+ child.setLastPlaceholderIndexer(this.paramPlaceholderIndexer);
245
+ return child;
246
+ }
247
+ setLastPlaceholderIndexer(input) {
248
+ this.paramPlaceholderIndexer = input;
249
+ }
250
+ getFieldPrefix() {
251
+ return this.fieldPrefix;
252
+ }
253
+ setFieldPrefix(prefix) {
254
+ this.fieldPrefix = prefix;
255
+ }
256
+ getQuery() {
257
+ return this.conditions.join(" and ");
258
+ }
259
+ getQueryAndParameters() {
260
+ return [this.getQuery(), this.params];
261
+ }
262
+ };
263
+ //#endregion
264
+ //#region src/adapter/filters/module.ts
265
+ var FiltersAdapter = class FiltersAdapter extends FiltersBaseAdapter {
266
+ options;
267
+ constructor(relations, options) {
268
+ super(relations);
269
+ this.options = options;
270
+ }
271
+ rootAlias() {
272
+ return this.options.rootAlias;
273
+ }
274
+ paramPlaceholder(index) {
275
+ return this.options.paramPlaceholder(index);
276
+ }
277
+ escapeField(field) {
278
+ return this.options.escapeField(field);
279
+ }
280
+ isRegexpSupported() {
281
+ return typeof this.options.regexp !== "undefined";
282
+ }
283
+ regexp(field, placeholder, ignoreCase) {
284
+ if (!this.options.regexp) throw AdapterError.featureUnsupported("regexp");
285
+ return this.options.regexp(field, placeholder, ignoreCase);
286
+ }
287
+ caseFold(input) {
288
+ if (this.options.caseFold) return this.options.caseFold(input);
289
+ return super.caseFold(input);
290
+ }
291
+ child() {
292
+ const child = new FiltersAdapter(this.relations, this.options);
293
+ this.setChildAttributes(child);
294
+ return child;
295
+ }
296
+ execute() {}
297
+ };
298
+ //#endregion
299
+ //#region src/visitor/fields.ts
300
+ var FieldsVisitor = class {
301
+ adapter;
302
+ options = {};
303
+ constructor(adapter, options = {}) {
304
+ this.adapter = adapter;
305
+ this.options = options;
306
+ }
307
+ visitField(expr) {
308
+ this.adapter.add(expr.name, expr.operator);
309
+ return this.adapter;
310
+ }
311
+ visitFields(expr) {
312
+ for (const item of expr.value) item.accept(this);
313
+ return this.adapter;
314
+ }
315
+ };
316
+ //#endregion
317
+ //#region src/visitor/filters.ts
318
+ var FiltersVisitor = class FiltersVisitor {
319
+ adapter;
320
+ options;
321
+ caseSensitiveFields;
322
+ constructor(adapter, options = {}) {
323
+ this.adapter = adapter;
324
+ this.options = options;
325
+ this.caseSensitiveFields = new Set(options.caseSensitive || []);
326
+ }
327
+ visitFilterEqual(expr) {
328
+ if (expr.value === null) return this.adapter.whereRaw(`${this.adapter.buildField(expr.field)} is null`);
329
+ if (this.isCaseInsensitive(expr.field, expr.value)) {
330
+ const field = this.adapter.buildField(expr.field);
331
+ return this.adapter.whereRaw(`${this.adapter.caseFold(field)} = ${this.adapter.caseFold(this.adapter.buildParamPlaceholder())}`, expr.value);
332
+ }
333
+ return this.adapter.where(expr.field, "=", expr.value);
334
+ }
335
+ visitFilterNotEqual(expr) {
336
+ const field = this.adapter.buildField(expr.field);
337
+ if (expr.value === null) return this.adapter.whereRaw(`${field} is not null`);
338
+ if (this.isCaseInsensitive(expr.field, expr.value)) return this.whereComplement(field, `${this.adapter.caseFold(field)} <> ${this.adapter.caseFold(this.adapter.buildParamPlaceholder())}`, expr.value);
339
+ return this.whereComplement(field, `${field} <> ${this.adapter.buildParamPlaceholder()}`, expr.value);
340
+ }
341
+ visitFilterLessThan(expr) {
342
+ return this.adapter.where(expr.field, "<", expr.value);
343
+ }
344
+ visitFilterLessThanEqual(expr) {
345
+ return this.adapter.where(expr.field, "<=", expr.value);
346
+ }
347
+ visitFilterGreaterThan(expr) {
348
+ return this.adapter.where(expr.field, ">", expr.value);
349
+ }
350
+ visitFilterGreaterThanEqual(expr) {
351
+ return this.adapter.where(expr.field, ">=", expr.value);
352
+ }
353
+ visitFilterExists(expr) {
354
+ return this.adapter.whereRaw(`${this.adapter.buildField(expr.field)} is ${expr.value ? "not " : ""}null`);
355
+ }
356
+ visitFilterIn(expr) {
357
+ return this.whereIn(expr.field, expr.value, false);
358
+ }
359
+ visitFilterNotIn(expr) {
360
+ return this.whereIn(expr.field, expr.value, true);
361
+ }
362
+ visitFilterMod(expr) {
363
+ const params = this.adapter.buildParamsPlaceholders(expr.value);
364
+ const sql = `mod(${this.adapter.buildField(expr.field)}, ${params[0]}) = ${params[1]}`;
365
+ return this.adapter.whereRaw(sql, ...expr.value);
366
+ }
367
+ visitFilterSize() {
368
+ throw AdapterError.featureUnsupported("filters:size");
369
+ }
370
+ visitFilterElemMatch(expr) {
371
+ const oldPrefix = this.adapter.getFieldPrefix();
372
+ this.adapter.setFieldPrefix(`${oldPrefix}${expr.field}.`);
373
+ expr.value.accept(this);
374
+ this.adapter.setFieldPrefix(oldPrefix);
375
+ return this.adapter;
376
+ }
377
+ visitFilterStartsWith(expr) {
378
+ return this.whereAnchored(expr.field, expr.value, FilterRegexFlag.STARTS_WITH);
379
+ }
380
+ visitFilterNotStartsWith(expr) {
381
+ return this.whereAnchored(expr.field, expr.value, FilterRegexFlag.STARTS_WITH | FilterRegexFlag.NEGATION);
382
+ }
383
+ visitFilterEndsWith(expr) {
384
+ return this.whereAnchored(expr.field, expr.value, FilterRegexFlag.ENDS_WITH);
385
+ }
386
+ visitFilterNotEndsWith(expr) {
387
+ return this.whereAnchored(expr.field, expr.value, FilterRegexFlag.ENDS_WITH | FilterRegexFlag.NEGATION);
388
+ }
389
+ visitFilterContains(expr) {
390
+ return this.whereAnchored(expr.field, expr.value, FilterRegexFlag.CONTAINS);
391
+ }
392
+ visitFilterNotContains(expr) {
393
+ return this.whereAnchored(expr.field, expr.value, FilterRegexFlag.CONTAINS | FilterRegexFlag.NEGATION);
394
+ }
395
+ visitFilterRegex(expr) {
396
+ const isRegExp = expr.value instanceof RegExp;
397
+ if (!isRegExp && typeof expr.value !== "string") throw AdapterError.featureUnsupported("filters:regex:value");
398
+ const source = isRegExp ? expr.value.source : expr.value;
399
+ const ignoreCase = isRegExp ? expr.value.ignoreCase : false;
400
+ const sql = this.adapter.regexp(this.adapter.buildField(expr.field), this.adapter.buildParamPlaceholder(), ignoreCase);
401
+ return this.adapter.whereRaw(sql, source);
402
+ }
403
+ visitFilters(expr) {
404
+ const adapter = this.adapter.child();
405
+ const visitor = new FiltersVisitor(adapter, this.options);
406
+ for (let i = 0; i < expr.value.length; i++) {
407
+ const child = expr.value[i];
408
+ if (child instanceof Filter) child.accept(visitor);
409
+ if (child instanceof Filters) child.accept(visitor);
410
+ }
411
+ switch (expr.operator) {
412
+ case "or":
413
+ this.adapter.merge(adapter, "or");
414
+ break;
415
+ case "nor":
416
+ this.adapter.merge(adapter, "or", true);
417
+ break;
418
+ case "not":
419
+ this.adapter.merge(adapter, "and", true);
420
+ break;
421
+ default: this.adapter.merge(adapter, "and");
422
+ }
423
+ return adapter;
424
+ }
425
+ visitFilter(expr) {
426
+ throw AdapterError.operatorUnsupported(expr.operator);
427
+ }
428
+ /**
429
+ * Render an IN/NOT IN condition.
430
+ * A null element also matches the absence of a value (IS NULL);
431
+ * an empty list matches no row (every row when negated).
432
+ */
433
+ whereIn(fieldName, input, negated) {
434
+ if (input.length === 0) return this.adapter.whereRaw(negated ? "1 = 1" : "1 = 0");
435
+ const values = input.filter((value) => value !== null);
436
+ const field = this.adapter.buildField(fieldName);
437
+ const nullCondition = `${field} is ${negated ? "not " : ""}null`;
438
+ if (values.length === 0) return this.adapter.whereRaw(nullCondition);
439
+ let inCondition;
440
+ if (this.isCaseFoldableField(fieldName) && values.some((value) => typeof value === "string")) {
441
+ const placeholders = values.map((value) => {
442
+ const placeholder = this.adapter.buildParamPlaceholder();
443
+ return typeof value === "string" ? this.adapter.caseFold(placeholder) : placeholder;
444
+ });
445
+ inCondition = `${this.adapter.caseFold(field)} ${negated ? "not " : ""}in(${placeholders.join(", ")})`;
446
+ } else inCondition = `${field} ${negated ? "not " : ""}in(${this.adapter.buildParamsPlaceholders(values).join(", ")})`;
447
+ if (values.length === input.length) {
448
+ if (negated) return this.whereComplement(field, inCondition, ...values);
449
+ return this.adapter.whereRaw(inCondition, ...values);
450
+ }
451
+ return this.adapter.whereRaw(`(${inCondition} ${negated ? "and" : "or"} ${nullCondition})`, ...values);
452
+ }
453
+ /**
454
+ * Render a startsWith/endsWith/contains condition (or its negation)
455
+ * as a regexp condition, falling back to LIKE on regexp-less dialects.
456
+ */
457
+ whereAnchored(field, value, flag) {
458
+ if (!this.adapter.isRegexpSupported()) {
459
+ const escaped = escapeLikePattern(`${value}`);
460
+ let pattern;
461
+ if (flag & FilterRegexFlag.STARTS_WITH) pattern = `${escaped}%`;
462
+ else if (flag & FilterRegexFlag.ENDS_WITH) pattern = `%${escaped}`;
463
+ else pattern = `%${escaped}%`;
464
+ return this.whereLike(field, pattern, !!(flag & FilterRegexFlag.NEGATION));
465
+ }
466
+ const regex = createFilterRegex(`${value}`, flag);
467
+ if (!(flag & FilterRegexFlag.NEGATION)) return this.visitFilterRegex(new Filter(FilterFieldOperator.REGEX, field, regex));
468
+ const fieldBuilt = this.adapter.buildField(field);
469
+ const sql = this.adapter.regexp(fieldBuilt, this.adapter.buildParamPlaceholder(), regex.ignoreCase);
470
+ return this.whereComplement(fieldBuilt, sql, regex.source);
471
+ }
472
+ whereLike(field, pattern, negated = false) {
473
+ const fieldBuilt = this.adapter.buildField(field);
474
+ const condition = `${fieldBuilt} ${negated ? "not " : ""}like ${this.adapter.buildParamPlaceholder()} escape '\\'`;
475
+ if (negated) return this.whereComplement(fieldBuilt, condition, pattern);
476
+ return this.adapter.whereRaw(condition, pattern);
477
+ }
478
+ /**
479
+ * Render a negated condition null-inclusively. Negated operators are
480
+ * exact complements of their positive twins (complement law), but the
481
+ * bare SQL negation follows three-valued logic and drops NULL rows.
482
+ */
483
+ whereComplement(field, sql, ...values) {
484
+ return this.adapter.whereRaw(`(${sql} or ${field} is null)`, ...values);
485
+ }
486
+ /**
487
+ * Equality-family comparisons (eq/ne/in/nin) match string values
488
+ * case-insensitively unless the field is opted out via the
489
+ * `caseSensitive` visitor option or the adapter exempts it
490
+ * (non-string columns never fold).
491
+ */
492
+ isCaseInsensitive(field, value) {
493
+ return typeof value === "string" && this.isCaseFoldableField(field);
494
+ }
495
+ isCaseFoldableField(field) {
496
+ const path = `${this.adapter.getFieldPrefix()}${field}`;
497
+ return !this.caseSensitiveFields.has(path) && this.adapter.isCaseFoldable(path);
498
+ }
499
+ };
500
+ //#endregion
501
+ //#region src/visitor/pagination.ts
502
+ var PaginationVisitor = class {
503
+ adapter;
504
+ options = {};
505
+ constructor(adapter, options = {}) {
506
+ this.adapter = adapter;
507
+ this.options = options;
508
+ }
509
+ visitPagination(expr) {
510
+ this.adapter.setLimit(expr.limit);
511
+ this.adapter.setOffset(expr.offset);
512
+ return this.adapter;
513
+ }
514
+ };
515
+ //#endregion
516
+ //#region src/visitor/relations.ts
517
+ var RelationsVisitor = class {
518
+ adapter;
519
+ options = {};
520
+ constructor(adapter, options = {}) {
521
+ this.adapter = adapter;
522
+ this.options = options;
523
+ }
524
+ visitRelation(expr) {
525
+ this.adapter.add(expr.name);
526
+ return this.adapter;
527
+ }
528
+ visitRelations(expr) {
529
+ for (const item of expr.value) item.accept(this);
530
+ return this.adapter;
531
+ }
532
+ };
533
+ //#endregion
534
+ //#region src/visitor/sort.ts
535
+ var SortsVisitor = class {
536
+ adapter;
537
+ options;
538
+ constructor(adapter, options = {}) {
539
+ this.adapter = adapter;
540
+ this.options = options;
541
+ }
542
+ visitSort(expr) {
543
+ this.adapter.add(expr.name, expr.operator);
544
+ return this.adapter;
545
+ }
546
+ visitSorts(expr) {
547
+ for (const item of expr.value) item.accept(this);
548
+ return this.adapter;
549
+ }
550
+ };
551
+ //#endregion
552
+ //#region src/visitor/module.ts
553
+ var QueryVisitor = class {
554
+ container;
555
+ options;
556
+ fields;
557
+ filters;
558
+ pagination;
559
+ relations;
560
+ sorts;
561
+ constructor(adapter, options = {}) {
562
+ this.container = adapter;
563
+ this.options = options;
564
+ this.fields = new FieldsVisitor(adapter.fields, options);
565
+ this.filters = new FiltersVisitor(adapter.filters, options);
566
+ this.pagination = new PaginationVisitor(adapter.pagination, options);
567
+ this.relations = new RelationsVisitor(adapter.relations, options);
568
+ this.sorts = new SortsVisitor(adapter.sort, options);
569
+ }
570
+ visitQuery(input) {
571
+ input.relations.accept(this.relations);
572
+ input.fields.accept(this.fields);
573
+ input.filters.accept(this.filters);
574
+ input.pagination.accept(this.pagination);
575
+ input.sorts.accept(this.sorts);
576
+ return this.container;
577
+ }
578
+ visitFields(expr) {
579
+ return expr.accept(this.fields);
580
+ }
581
+ visitField(expr) {
582
+ return expr.accept(this.fields);
583
+ }
584
+ visitFilters(expr) {
585
+ return expr.accept(this.filters);
586
+ }
587
+ visitFilter(expr) {
588
+ return expr.accept(this.filters);
589
+ }
590
+ visitPagination(expr) {
591
+ return expr.accept(this.pagination);
592
+ }
593
+ visitRelations(input) {
594
+ return input.accept(this.relations);
595
+ }
596
+ visitRelation(expr) {
597
+ return expr.accept(this.relations);
598
+ }
599
+ visitSorts(expr) {
600
+ return expr.accept(this.sorts);
601
+ }
602
+ visitSort(expr) {
603
+ return expr.accept(this.sorts);
604
+ }
605
+ };
606
+ //#endregion
607
+ //#region src/adapter/pagination/base.ts
608
+ var PaginationBaseAdapter = class {
609
+ limit;
610
+ offset;
611
+ clear() {
612
+ this.limit = void 0;
613
+ this.offset = void 0;
614
+ }
615
+ setLimit(limit) {
616
+ this.limit = limit;
617
+ }
618
+ setOffset(offset) {
619
+ this.offset = offset;
620
+ }
621
+ };
622
+ //#endregion
623
+ //#region src/adapter/pagination/module.ts
624
+ var PaginationAdapter = class extends PaginationBaseAdapter {
625
+ execute() {}
626
+ };
627
+ //#endregion
628
+ //#region src/adapter/relations/base.ts
629
+ var RelationsBaseAdapter = class {
630
+ /**
631
+ * joins
632
+ *
633
+ * [alias].project.user.name
634
+ *
635
+ * JOIN xxx.yyy as yyy
636
+ *
637
+ * { path: "xxx.zzz", name: "zzz" }[]
638
+ * @protected
639
+ */
640
+ value;
641
+ relationAlias;
642
+ constructor(options = {}) {
643
+ this.value = [];
644
+ this.relationAlias = options.relationAlias ?? buildRelationAlias;
645
+ }
646
+ /**
647
+ * Join alias for a relation path (e.g. `role.realm` ->
648
+ * `r4_role_5_realm`).
649
+ * The single derivation point shared by join application and the
650
+ * field references built by the fields/filters/sort adapters.
651
+ */
652
+ buildAlias(path) {
653
+ return this.relationAlias(path);
654
+ }
655
+ clear() {
656
+ this.value = [];
657
+ }
658
+ add(relation) {
659
+ if (this.has(relation)) return true;
660
+ let path;
661
+ let last = relation;
662
+ while (last) {
663
+ let relationName;
664
+ [relationName, last] = splitFirst(last);
665
+ path = path ? `${path}.${relationName}` : relationName;
666
+ if (this.has(path)) continue;
667
+ this.value.push({
668
+ path,
669
+ name: relationName
670
+ });
671
+ }
672
+ return true;
673
+ }
674
+ has(name) {
675
+ return this.value.some((join) => join.path === name);
676
+ }
677
+ /**
678
+ * Canonical relation paths (parents included), e.g. ['items', 'items.realm'].
679
+ */
680
+ getPaths() {
681
+ return this.value.map((join) => join.path);
682
+ }
683
+ };
684
+ //#endregion
685
+ //#region src/adapter/relations/module.ts
686
+ var RelationsAdapter = class extends RelationsBaseAdapter {
687
+ options;
688
+ constructor(options = {}) {
689
+ super(options);
690
+ this.options = options;
691
+ }
692
+ execute() {}
693
+ };
694
+ //#endregion
695
+ //#region src/adapter/sort/base.ts
696
+ var SortBaseAdapter = class {
697
+ relations;
698
+ value;
699
+ constructor(relations) {
700
+ this.relations = relations;
701
+ this.value = {};
702
+ }
703
+ clear() {
704
+ this.value = {};
705
+ }
706
+ add(input, value) {
707
+ const name = this.normalizeField(input);
708
+ this.value[name] = value;
709
+ }
710
+ /**
711
+ * Escaped ORDER BY entries, e.g. ['"user"."age" DESC'].
712
+ */
713
+ getOrderBy() {
714
+ const keys = Object.keys(this.value);
715
+ const output = [];
716
+ for (const key_ of keys) {
717
+ const key = key_;
718
+ output.push(`${key} ${this.value[key]}`);
719
+ }
720
+ return output;
721
+ }
722
+ normalizeField(input) {
723
+ const output = parseField(input, this.rootAlias(), (path) => this.relations.buildAlias(path));
724
+ if (output.relation) this.relations.add(output.relation);
725
+ if (output.prefix) return `${this.escapeField(output.prefix)}.${this.escapeField(output.name)}`;
726
+ return this.escapeField(output.name);
727
+ }
728
+ };
729
+ //#endregion
730
+ //#region src/adapter/sort/module.ts
731
+ var SortAdapter = class extends SortBaseAdapter {
732
+ options;
733
+ constructor(relations, options) {
734
+ super(relations);
735
+ this.options = options;
736
+ }
737
+ escapeField(field) {
738
+ if (this.options.escapeField) return this.options.escapeField(field);
739
+ return field;
740
+ }
741
+ rootAlias() {
742
+ if (this.options.rootAlias) return this.options.rootAlias;
743
+ }
744
+ execute() {}
745
+ };
746
+ //#endregion
747
+ //#region src/adapter/module.ts
748
+ var Adapter = class {
749
+ relations;
750
+ fields;
751
+ filters;
752
+ pagination;
753
+ sort;
754
+ constructor(options) {
755
+ this.relations = new RelationsAdapter({
756
+ join: () => true,
757
+ relationAlias: options.relationAlias
758
+ });
759
+ this.fields = new FieldsAdapter(this.relations, {
760
+ escapeField: options.escapeField,
761
+ rootAlias: options.rootAlias
762
+ });
763
+ this.filters = new FiltersAdapter(this.relations, {
764
+ paramPlaceholder: options.paramPlaceholder,
765
+ regexp: options.regexp,
766
+ caseFold: options.caseFold,
767
+ escapeField: options.escapeField,
768
+ rootAlias: options.rootAlias
769
+ });
770
+ this.pagination = new PaginationAdapter();
771
+ this.sort = new SortAdapter(this.relations, {
772
+ escapeField: options.escapeField,
773
+ rootAlias: options.rootAlias
774
+ });
775
+ }
776
+ clear() {
777
+ this.fields.clear();
778
+ this.filters.clear();
779
+ this.pagination.clear();
780
+ this.sort.clear();
781
+ this.relations.clear();
782
+ }
783
+ /**
784
+ * Walk `query` into the sub-adapters and collect the accumulated
785
+ * clause fragments. Plain SQL has no backend target — it returns the
786
+ * fragments for the caller to assemble.
787
+ */
788
+ execute(query, options = {}) {
789
+ if (options.clear ?? true) this.clear();
790
+ query.accept(new QueryVisitor(this, options.visitor));
791
+ const [where, params] = this.filters.getQueryAndParameters();
792
+ return {
793
+ columns: this.fields.getColumns(),
794
+ where,
795
+ params,
796
+ orderBy: this.sort.getOrderBy(),
797
+ limit: this.pagination.limit,
798
+ offset: this.pagination.offset,
799
+ relations: this.relations.getPaths()
800
+ };
801
+ }
802
+ };
803
+ //#endregion
804
+ //#region src/dialect/mssql.ts
805
+ const mssql = {
806
+ paramPlaceholder: () => "?",
807
+ escapeField: (field) => `[${field}]`,
808
+ caseFold: (input) => input
809
+ };
810
+ //#endregion
811
+ //#region src/dialect/mysql.ts
812
+ const mysql = {
813
+ regexp: (field, placeholder) => `${field} regexp ${placeholder} = 1`,
814
+ paramPlaceholder: () => "?",
815
+ escapeField: (field) => `\`${field}\``,
816
+ caseFold: (input) => input
817
+ };
818
+ //#endregion
819
+ //#region src/dialect/oracle.ts
820
+ const oracle = {
821
+ regexp: (field, placeholder, ignoreCase) => ignoreCase ? `regexp_like(${field}, ${placeholder}, 'i')` : `regexp_like(${field}, ${placeholder})`,
822
+ escapeField: (field) => `"${field}"`,
823
+ paramPlaceholder: (index) => `:${index}`
824
+ };
825
+ //#endregion
826
+ //#region src/dialect/pg.ts
827
+ const pg = {
828
+ regexp: (field, placeholder, ignoreCase) => {
829
+ return `${field} ${ignoreCase ? "~*" : "~"} ${placeholder}`;
830
+ },
831
+ escapeField: (field) => `"${field}"`,
832
+ paramPlaceholder: (index) => `$${index}`
833
+ };
834
+ //#endregion
835
+ //#region src/dialect/sqlite.ts
836
+ const sqlite = {
837
+ paramPlaceholder: mysql.paramPlaceholder,
838
+ escapeField: mysql.escapeField
839
+ };
840
+ //#endregion
841
+ //#region src/dialect/resolve.ts
842
+ const presets = {
843
+ pg,
844
+ postgres: pg,
845
+ postgresql: pg,
846
+ "aurora-postgres": pg,
847
+ cockroachdb: pg,
848
+ mysql,
849
+ mysql2: mysql,
850
+ mariadb: mysql,
851
+ "aurora-mysql": mysql,
852
+ sqlite,
853
+ sqlite3: sqlite,
854
+ "better-sqlite3": sqlite,
855
+ sqljs: sqlite,
856
+ capacitor: sqlite,
857
+ cordova: sqlite,
858
+ "react-native": sqlite,
859
+ nativescript: sqlite,
860
+ expo: sqlite,
861
+ mssql,
862
+ oracle,
863
+ oracledb: oracle
864
+ };
865
+ /**
866
+ * Resolve a driver/connection type name (e.g. TypeORM's
867
+ * `connection.options.type` or a knex client name) to the
868
+ * matching {@link DialectOptions} preset.
869
+ */
870
+ function resolveDialect(name) {
871
+ return presets[name.toLowerCase()];
872
+ }
873
+ //#endregion
874
+ export { Adapter, FieldsAdapter, FieldsBaseAdapter, FieldsVisitor, FiltersAdapter, FiltersBaseAdapter, FiltersVisitor, PaginationAdapter, PaginationBaseAdapter, PaginationVisitor, ParamPlaceholderIndexer, QueryVisitor, RelationsAdapter, RelationsBaseAdapter, RelationsVisitor, SortAdapter, SortBaseAdapter, SortsVisitor, buildRelationAlias, escapeLikePattern, isRootAliasFn, mssql, mysql, oracle, parseField, pg, resolveDialect, splitFirst, splitLast, sqlite, toRootAliasFn };
875
+
876
+ //# sourceMappingURL=index.mjs.map