@adonis-agora/filter 0.0.0-beta-20261110131309
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/CHANGELOG.md +417 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/commands/main.d.ts +19 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +19 -0
- package/dist/commands/main.js.map +1 -0
- package/dist/commands/make_filter.d.ts +18 -0
- package/dist/commands/make_filter.d.ts.map +1 -0
- package/dist/commands/make_filter.js +32 -0
- package/dist/commands/make_filter.js.map +1 -0
- package/dist/commands/make_filter_client.d.ts +35 -0
- package/dist/commands/make_filter_client.d.ts.map +1 -0
- package/dist/commands/make_filter_client.js +85 -0
- package/dist/commands/make_filter_client.js.map +1 -0
- package/dist/configure.d.ts +15 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +20 -0
- package/dist/configure.js.map +1 -0
- package/dist/providers/filter_provider.d.ts +28 -0
- package/dist/providers/filter_provider.d.ts.map +1 -0
- package/dist/providers/filter_provider.js +33 -0
- package/dist/providers/filter_provider.js.map +1 -0
- package/dist/src/aggregate.d.ts +70 -0
- package/dist/src/aggregate.d.ts.map +1 -0
- package/dist/src/aggregate.js +195 -0
- package/dist/src/aggregate.js.map +1 -0
- package/dist/src/aggregate_path.d.ts +25 -0
- package/dist/src/aggregate_path.d.ts.map +1 -0
- package/dist/src/aggregate_path.js +35 -0
- package/dist/src/aggregate_path.js.map +1 -0
- package/dist/src/apply_from_request.d.ts +85 -0
- package/dist/src/apply_from_request.d.ts.map +1 -0
- package/dist/src/apply_from_request.js +164 -0
- package/dist/src/apply_from_request.js.map +1 -0
- package/dist/src/base_filter.d.ts +49 -0
- package/dist/src/base_filter.d.ts.map +1 -0
- package/dist/src/base_filter.js +33 -0
- package/dist/src/base_filter.js.map +1 -0
- package/dist/src/base_model_filter.d.ts +90 -0
- package/dist/src/base_model_filter.d.ts.map +1 -0
- package/dist/src/base_model_filter.js +85 -0
- package/dist/src/base_model_filter.js.map +1 -0
- package/dist/src/coerce_value.d.ts +26 -0
- package/dist/src/coerce_value.d.ts.map +1 -0
- package/dist/src/coerce_value.js +82 -0
- package/dist/src/coerce_value.js.map +1 -0
- package/dist/src/cursor.d.ts +92 -0
- package/dist/src/cursor.d.ts.map +1 -0
- package/dist/src/cursor.js +107 -0
- package/dist/src/cursor.js.map +1 -0
- package/dist/src/custom_filter.d.ts +36 -0
- package/dist/src/custom_filter.d.ts.map +1 -0
- package/dist/src/custom_filter.js +127 -0
- package/dist/src/custom_filter.js.map +1 -0
- package/dist/src/decorator_metadata.d.ts +41 -0
- package/dist/src/decorator_metadata.d.ts.map +1 -0
- package/dist/src/decorator_metadata.js +83 -0
- package/dist/src/decorator_metadata.js.map +1 -0
- package/dist/src/decorators.d.ts +73 -0
- package/dist/src/decorators.d.ts.map +1 -0
- package/dist/src/decorators.js +118 -0
- package/dist/src/decorators.js.map +1 -0
- package/dist/src/escape-like.d.ts +10 -0
- package/dist/src/escape-like.d.ts.map +1 -0
- package/dist/src/escape-like.js +15 -0
- package/dist/src/escape-like.js.map +1 -0
- package/dist/src/field_aliases.d.ts +30 -0
- package/dist/src/field_aliases.d.ts.map +1 -0
- package/dist/src/field_aliases.js +65 -0
- package/dist/src/field_aliases.js.map +1 -0
- package/dist/src/filter_class.d.ts +46 -0
- package/dist/src/filter_class.d.ts.map +1 -0
- package/dist/src/filter_class.js +244 -0
- package/dist/src/filter_class.js.map +1 -0
- package/dist/src/filter_spec.d.ts +231 -0
- package/dist/src/filter_spec.d.ts.map +1 -0
- package/dist/src/filter_spec.js +182 -0
- package/dist/src/filter_spec.js.map +1 -0
- package/dist/src/filterable_mixin.d.ts +49 -0
- package/dist/src/filterable_mixin.d.ts.map +1 -0
- package/dist/src/filterable_mixin.js +47 -0
- package/dist/src/filterable_mixin.js.map +1 -0
- package/dist/src/generate_client.d.ts +107 -0
- package/dist/src/generate_client.d.ts.map +1 -0
- package/dist/src/generate_client.js +164 -0
- package/dist/src/generate_client.js.map +1 -0
- package/dist/src/group_by_count.d.ts +67 -0
- package/dist/src/group_by_count.d.ts.map +1 -0
- package/dist/src/group_by_count.js +71 -0
- package/dist/src/group_by_count.js.map +1 -0
- package/dist/src/in_memory.d.ts +279 -0
- package/dist/src/in_memory.d.ts.map +1 -0
- package/dist/src/in_memory.js +810 -0
- package/dist/src/in_memory.js.map +1 -0
- package/dist/src/index.d.ts +51 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +33 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lucid_adapter.d.ts +311 -0
- package/dist/src/lucid_adapter.d.ts.map +1 -0
- package/dist/src/lucid_adapter.js +469 -0
- package/dist/src/lucid_adapter.js.map +1 -0
- package/dist/src/lucid_macros.d.ts +87 -0
- package/dist/src/lucid_macros.d.ts.map +1 -0
- package/dist/src/lucid_macros.js +67 -0
- package/dist/src/lucid_macros.js.map +1 -0
- package/dist/src/normalizer.d.ts +21 -0
- package/dist/src/normalizer.d.ts.map +1 -0
- package/dist/src/normalizer.js +61 -0
- package/dist/src/normalizer.js.map +1 -0
- package/dist/src/operators.d.ts +37 -0
- package/dist/src/operators.d.ts.map +1 -0
- package/dist/src/operators.js +43 -0
- package/dist/src/operators.js.map +1 -0
- package/dist/src/paginate.d.ts +12 -0
- package/dist/src/paginate.d.ts.map +1 -0
- package/dist/src/paginate.js +88 -0
- package/dist/src/paginate.js.map +1 -0
- package/dist/src/pagination_stash.d.ts +4 -0
- package/dist/src/pagination_stash.d.ts.map +1 -0
- package/dist/src/pagination_stash.js +19 -0
- package/dist/src/pagination_stash.js.map +1 -0
- package/dist/src/parse_request.d.ts +50 -0
- package/dist/src/parse_request.d.ts.map +1 -0
- package/dist/src/parse_request.js +220 -0
- package/dist/src/parse_request.js.map +1 -0
- package/dist/src/runner.d.ts +65 -0
- package/dist/src/runner.d.ts.map +1 -0
- package/dist/src/runner.js +371 -0
- package/dist/src/runner.js.map +1 -0
- package/dist/src/source_resolver.d.ts +18 -0
- package/dist/src/source_resolver.d.ts.map +1 -0
- package/dist/src/source_resolver.js +64 -0
- package/dist/src/source_resolver.js.map +1 -0
- package/dist/src/spatie_parser.d.ts +39 -0
- package/dist/src/spatie_parser.d.ts.map +1 -0
- package/dist/src/spatie_parser.js +134 -0
- package/dist/src/spatie_parser.js.map +1 -0
- package/dist/src/testing.d.ts +73 -0
- package/dist/src/testing.d.ts.map +1 -0
- package/dist/src/testing.js +136 -0
- package/dist/src/testing.js.map +1 -0
- package/dist/src/types.d.ts +226 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/validate-column-filter.d.ts +30 -0
- package/dist/src/validate-column-filter.d.ts.map +1 -0
- package/dist/src/validate-column-filter.js +148 -0
- package/dist/src/validate-column-filter.js.map +1 -0
- package/dist/src/value-shape.d.ts +18 -0
- package/dist/src/value-shape.d.ts.map +1 -0
- package/dist/src/value-shape.js +36 -0
- package/dist/src/value-shape.js.map +1 -0
- package/dist/stubs/main.d.ts +3 -0
- package/dist/stubs/main.d.ts.map +1 -0
- package/dist/stubs/main.js +4 -0
- package/dist/stubs/main.js.map +1 -0
- package/dist/stubs/make/filter/main.stub +45 -0
- package/package.json +102 -0
- package/skills/filter-basics/SKILL.md +285 -0
- package/skills/filter-codegen/SKILL.md +246 -0
- package/skills/filter-definitions/SKILL.md +340 -0
- package/skills/filter-querying/SKILL.md +319 -0
- package/skills/filter-safety/SKILL.md +304 -0
- package/stubs/main.ts +4 -0
- package/stubs/make/filter/main.stub +45 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { escapeLike } from './escape-like.js';
|
|
2
|
+
import { normalizeOperator } from './validate-column-filter.js';
|
|
3
|
+
/** pgvector distance operator for each {@link VectorDistanceMetric}. */
|
|
4
|
+
const VECTOR_OPERATORS = {
|
|
5
|
+
cosine: '<=>',
|
|
6
|
+
l2: '<->',
|
|
7
|
+
innerProduct: '<#>',
|
|
8
|
+
};
|
|
9
|
+
/** A safe SQL identifier: an unquoted column, optionally `table.column` qualified. */
|
|
10
|
+
const SAFE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
11
|
+
/** Quote a (possibly dotted) identifier for Postgres, one segment at a time. */
|
|
12
|
+
function quoteColumn(column) {
|
|
13
|
+
return column
|
|
14
|
+
.split('.')
|
|
15
|
+
.map((seg) => `"${seg}"`)
|
|
16
|
+
.join('.');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Serialize a numeric embedding to a pgvector literal (`[1,2,3]`). Returns `null`
|
|
20
|
+
* for an empty vector (a no-op signal). Throws on a non-finite component so a bad
|
|
21
|
+
* embedding fails loudly rather than emitting `NaN`/`Infinity` into SQL.
|
|
22
|
+
*/
|
|
23
|
+
function serializeVector(vector) {
|
|
24
|
+
if (vector.length === 0)
|
|
25
|
+
return null;
|
|
26
|
+
for (const n of vector) {
|
|
27
|
+
if (typeof n !== 'number' || !Number.isFinite(n)) {
|
|
28
|
+
throw new TypeError('Vector search embedding must contain only finite numbers.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return `[${vector.join(',')}]`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Apply an **embedding similarity** ordering (pgvector) to a Lucid query builder.
|
|
35
|
+
*
|
|
36
|
+
* This is *not* full-text search — it ranks rows by distance between a stored
|
|
37
|
+
* embedding column and a query *embedding vector*. For matching a text query
|
|
38
|
+
* string, use {@link applyFullTextSearch} instead.
|
|
39
|
+
*
|
|
40
|
+
* pgvector expresses similarity through raw distance operators (`<=>`, `<->`,
|
|
41
|
+
* `<#>`) that no structured query-builder method covers, so this drives the
|
|
42
|
+
* adapter's raw seam ({@link QueryBuilderLike.whereRaw}/`orderByRaw`) with the
|
|
43
|
+
* query embedding passed as a positional binding — the column name is the only
|
|
44
|
+
* interpolated fragment, and it is validated against a strict identifier charset
|
|
45
|
+
* so it can never carry injection. The distance expression is
|
|
46
|
+
* `<column> <op> ?::vector`, cast to `vector` so a text binding compares against
|
|
47
|
+
* the column.
|
|
48
|
+
*
|
|
49
|
+
* A no-op when the embedding is empty. When `order` is not `false`, rows are
|
|
50
|
+
* ordered nearest-first (ascending distance); `threshold` adds a max-distance
|
|
51
|
+
* filter; `topK` truncates to the K nearest.
|
|
52
|
+
*/
|
|
53
|
+
export function applyVectorSimilarity(qb, opts) {
|
|
54
|
+
const literal = serializeVector(opts.vector);
|
|
55
|
+
if (literal === null)
|
|
56
|
+
return;
|
|
57
|
+
if (!SAFE_IDENTIFIER.test(opts.column)) {
|
|
58
|
+
throw new TypeError(`Invalid vector column name: ${JSON.stringify(opts.column)}`);
|
|
59
|
+
}
|
|
60
|
+
const operator = VECTOR_OPERATORS[opts.metric ?? 'cosine'];
|
|
61
|
+
const distance = `${quoteColumn(opts.column)} ${operator} ?::vector`;
|
|
62
|
+
if (opts.threshold !== undefined) {
|
|
63
|
+
qb.whereRaw(`${distance} < ?`, [literal, opts.threshold]);
|
|
64
|
+
}
|
|
65
|
+
if (opts.order !== false) {
|
|
66
|
+
qb.orderByRaw(`${distance} asc`, [literal]);
|
|
67
|
+
}
|
|
68
|
+
if (opts.topK !== undefined) {
|
|
69
|
+
qb.limit(opts.topK);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Apply a Postgres **tsvector full-text search** to a Lucid query builder — the
|
|
74
|
+
* parity port of the NestJS reference's `applyVectorSearch` (tsvector), NOT the
|
|
75
|
+
* embedding similarity {@link applyVectorSimilarity}.
|
|
76
|
+
*
|
|
77
|
+
* Injection-safety: the user `query` string ALWAYS travels as a positional
|
|
78
|
+
* binding (`websearch_to_tsquery('<lang>', ?)`); it is never interpolated.
|
|
79
|
+
* `websearch_to_tsquery` (not the raw `to_tsquery`) parses arbitrary user input
|
|
80
|
+
* — multi-word text, `"quoted phrases"`, `-exclude`, `or` — without throwing a
|
|
81
|
+
* syntax error. The only spliced fragments are the column name(s) and the
|
|
82
|
+
* language config, each validated against the strict identifier charset
|
|
83
|
+
* ({@link SAFE_IDENTIFIER}) before use, so neither can carry injection.
|
|
84
|
+
*
|
|
85
|
+
* The match predicate is `<document> @@ websearch_to_tsquery('<lang>', ?)` where
|
|
86
|
+
* `<document>` is either the tsvector column directly, or
|
|
87
|
+
* `to_tsvector('<lang>', coalesce(col1,'') || ' ' || coalesce(col2,''))` for
|
|
88
|
+
* text columns. When `rank` is set, rows are additionally ordered by descending
|
|
89
|
+
* `ts_rank(<document>, websearch_to_tsquery('<lang>', ?))`. A no-op for a blank
|
|
90
|
+
* query.
|
|
91
|
+
*/
|
|
92
|
+
export function applyFullTextSearch(qb, opts) {
|
|
93
|
+
const query = opts.query.trim();
|
|
94
|
+
if (query.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
const columns = Array.isArray(opts.column) ? opts.column : [opts.column];
|
|
97
|
+
if (columns.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
for (const col of columns) {
|
|
100
|
+
if (!SAFE_IDENTIFIER.test(col)) {
|
|
101
|
+
throw new TypeError(`Invalid full-text search column name: ${JSON.stringify(col)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const language = opts.language ?? 'english';
|
|
105
|
+
if (!SAFE_IDENTIFIER.test(language)) {
|
|
106
|
+
throw new TypeError(`Invalid full-text search language: ${JSON.stringify(language)}`);
|
|
107
|
+
}
|
|
108
|
+
// The user query is a positional binding; only the validated language splices in.
|
|
109
|
+
const tsquery = `websearch_to_tsquery('${language}', ?)`;
|
|
110
|
+
// A precomputed tsvector column is matched directly; text columns are tokenized
|
|
111
|
+
// at query time via to_tsvector, coalescing NULLs so a null column can't null
|
|
112
|
+
// the whole document.
|
|
113
|
+
const kind = opts.columnKind ?? (columns.length > 1 ? 'text' : 'tsvector');
|
|
114
|
+
const document = kind === 'text'
|
|
115
|
+
? `to_tsvector('${language}', ${columns
|
|
116
|
+
.map((c) => `coalesce(${quoteColumn(c)}, '')`)
|
|
117
|
+
.join(" || ' ' || ")})`
|
|
118
|
+
: quoteColumn(columns[0]);
|
|
119
|
+
qb.whereRaw(`${document} @@ ${tsquery}`, [query]);
|
|
120
|
+
if (opts.rank) {
|
|
121
|
+
qb.orderByRaw(`ts_rank(${document}, ${tsquery}) desc`, [query]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/** Wrap a value as a LIKE pattern with escaped metacharacters. */
|
|
125
|
+
function like(value, kind) {
|
|
126
|
+
const v = escapeLike(String(value));
|
|
127
|
+
if (kind === 'startsWith')
|
|
128
|
+
return `${v}%`;
|
|
129
|
+
if (kind === 'endsWith')
|
|
130
|
+
return `%${v}`;
|
|
131
|
+
return `%${v}%`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Apply a single (leaf) column filter to the builder.
|
|
135
|
+
*
|
|
136
|
+
* A dotted `field` is a **relation path** (`posts.title`, `posts.comments.body`):
|
|
137
|
+
* every segment but the last is a relation hop, translated into a nested Lucid
|
|
138
|
+
* `whereHas` subquery, and the final segment is the bare column the operator
|
|
139
|
+
* lands on inside the innermost relation query — so `posts.title = x` becomes
|
|
140
|
+
* `whereHas('posts', (q) => q.where('title', x))`. Depth is already bounded by
|
|
141
|
+
* the spec's `maxDepth` allow-list before a filter reaches the adapter, so this
|
|
142
|
+
* only translates paths that were explicitly whitelisted. A non-dotted `field`
|
|
143
|
+
* is a plain base column and takes the operator switch directly.
|
|
144
|
+
*/
|
|
145
|
+
function applyLeaf(qb, field, operator, value) {
|
|
146
|
+
const dot = field.indexOf('.');
|
|
147
|
+
if (dot !== -1) {
|
|
148
|
+
const relation = field.slice(0, dot);
|
|
149
|
+
const rest = field.slice(dot + 1);
|
|
150
|
+
qb.whereHas(relation, (sub) => applyLeaf(sub, rest, operator, value));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
switch (operator) {
|
|
154
|
+
case 'equals':
|
|
155
|
+
qb.where(field, value);
|
|
156
|
+
break;
|
|
157
|
+
case 'notEquals':
|
|
158
|
+
qb.whereNot(field, value);
|
|
159
|
+
break;
|
|
160
|
+
case 'contains':
|
|
161
|
+
case 'iContains':
|
|
162
|
+
qb.whereILike(field, like(value, 'contains'));
|
|
163
|
+
break;
|
|
164
|
+
case 'startsWith':
|
|
165
|
+
qb.whereILike(field, like(value, 'startsWith'));
|
|
166
|
+
break;
|
|
167
|
+
case 'endsWith':
|
|
168
|
+
qb.whereILike(field, like(value, 'endsWith'));
|
|
169
|
+
break;
|
|
170
|
+
case 'notContains':
|
|
171
|
+
qb.where((sub) => sub
|
|
172
|
+
.whereNotNull(field)
|
|
173
|
+
.whereNot((inner) => inner.whereILike(field, like(value, 'contains'))));
|
|
174
|
+
break;
|
|
175
|
+
case 'gt':
|
|
176
|
+
qb.where(field, '>', value);
|
|
177
|
+
break;
|
|
178
|
+
case 'gte':
|
|
179
|
+
qb.where(field, '>=', value);
|
|
180
|
+
break;
|
|
181
|
+
case 'lt':
|
|
182
|
+
qb.where(field, '<', value);
|
|
183
|
+
break;
|
|
184
|
+
case 'lte':
|
|
185
|
+
qb.where(field, '<=', value);
|
|
186
|
+
break;
|
|
187
|
+
case 'between':
|
|
188
|
+
qb.whereBetween(field, value);
|
|
189
|
+
break;
|
|
190
|
+
case 'notBetween':
|
|
191
|
+
qb.whereNotBetween(field, value);
|
|
192
|
+
break;
|
|
193
|
+
case 'in':
|
|
194
|
+
case 'isAnyOf':
|
|
195
|
+
qb.whereIn(field, value);
|
|
196
|
+
break;
|
|
197
|
+
case 'notIn':
|
|
198
|
+
qb.whereNotIn(field, value);
|
|
199
|
+
break;
|
|
200
|
+
case 'isNull':
|
|
201
|
+
case 'notExists':
|
|
202
|
+
qb.whereNull(field);
|
|
203
|
+
break;
|
|
204
|
+
case 'isNotNull':
|
|
205
|
+
case 'exists':
|
|
206
|
+
qb.whereNotNull(field);
|
|
207
|
+
break;
|
|
208
|
+
case 'isEmpty':
|
|
209
|
+
qb.where(field, '');
|
|
210
|
+
break;
|
|
211
|
+
case 'isNotEmpty':
|
|
212
|
+
qb.whereNot(field, '');
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Apply one {@link ColumnFilter} (possibly an AND/OR group) to a builder. A leaf
|
|
218
|
+
* is a single condition; `AND`/`OR` recurse into a grouped sub-builder so the
|
|
219
|
+
* boolean structure maps to Lucid's nested `where`/`orWhere` closures.
|
|
220
|
+
*/
|
|
221
|
+
function applyOne(qb, filter) {
|
|
222
|
+
const op = normalizeOperator(filter.operator);
|
|
223
|
+
const hasField = typeof filter.field === 'string' && filter.field.length > 0;
|
|
224
|
+
qb.where((group) => {
|
|
225
|
+
if (hasField) {
|
|
226
|
+
applyLeaf(group, filter.field, op, filter.value);
|
|
227
|
+
}
|
|
228
|
+
if (filter.AND) {
|
|
229
|
+
for (const sub of filter.AND) {
|
|
230
|
+
group.where((g) => applyOne(g, sub));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (filter.OR) {
|
|
234
|
+
for (const sub of filter.OR) {
|
|
235
|
+
group.orWhere((g) => applyOne(g, sub));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
/** Apply an array of column filters (combined with AND) to a Lucid query builder. */
|
|
241
|
+
export function applyColumnFilters(qb, filters) {
|
|
242
|
+
for (const filter of filters) {
|
|
243
|
+
applyOne(qb, filter);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/** Apply sort directives to a Lucid query builder, in order. */
|
|
247
|
+
export function applySort(qb, sorts) {
|
|
248
|
+
for (const sort of sorts) {
|
|
249
|
+
qb.orderBy(sort.field, sort.direction);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Apply a DISTINCT projection over `columns` to a Lucid query builder — the
|
|
254
|
+
* executable half of the client's `.distinct(...)` (which until now the server
|
|
255
|
+
* silently ignored). A no-op for an empty list. The columns are the
|
|
256
|
+
* already-validated, alias-resolved field names (the runner resolves aliases and
|
|
257
|
+
* enforces the allow-list before calling this), so nothing client-controlled is
|
|
258
|
+
* interpolated: Lucid quotes each identifier itself.
|
|
259
|
+
*/
|
|
260
|
+
export function applyDistinct(qb, columns) {
|
|
261
|
+
if (columns.length === 0)
|
|
262
|
+
return;
|
|
263
|
+
qb.distinct(...columns);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Terminal group-by-count aggregation over one column:
|
|
267
|
+
* `SELECT <col> AS value, COUNT(*) AS count … GROUP BY <col>`, most groups first —
|
|
268
|
+
* what populates a filter dropdown. `column` must already be validated (allow-listed): it is
|
|
269
|
+
* interpolated as an identifier, while every client VALUE rides a positional binding.
|
|
270
|
+
*
|
|
271
|
+
* Fixed ordering (count desc, value asc) is load-bearing, not cosmetic: the answer is pageable,
|
|
272
|
+
* and paging an unordered listing repeats and skips rows.
|
|
273
|
+
*
|
|
274
|
+
* Requires a builder carrying the optional aggregation seam (`select`/`count`/`groupBy`/`offset`);
|
|
275
|
+
* throws a plain `Error` naming the missing method otherwise, so a minimal custom implementation
|
|
276
|
+
* fails loudly instead of silently returning entity rows.
|
|
277
|
+
*/
|
|
278
|
+
export function applyGroupByCount(qb, column, opts = {}) {
|
|
279
|
+
if (!qb.select || !qb.count || !qb.groupBy || !qb.offset) {
|
|
280
|
+
throw new Error('groupByCount needs a builder with select/count/groupBy/offset — the active one does not implement the aggregation seam.');
|
|
281
|
+
}
|
|
282
|
+
qb.select(`${column} AS value`);
|
|
283
|
+
qb.count('* AS count');
|
|
284
|
+
const needle = opts.search?.trim();
|
|
285
|
+
if (needle) {
|
|
286
|
+
qb.whereRaw(`LOWER(??) LIKE ?`, [column, `%${escapeLike(needle.toLowerCase())}%`]);
|
|
287
|
+
}
|
|
288
|
+
qb.groupBy(column);
|
|
289
|
+
qb.orderBy('count', 'desc');
|
|
290
|
+
qb.orderBy(column, 'asc');
|
|
291
|
+
if (opts.limit !== undefined)
|
|
292
|
+
qb.limit(Math.max(0, opts.limit));
|
|
293
|
+
if (opts.offset !== undefined)
|
|
294
|
+
qb.offset(Math.max(0, opts.offset));
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Resolve a {@link ComputedSource} to its final SQL expression string. The
|
|
298
|
+
* string form is verbatim; the function form is invoked with the root table
|
|
299
|
+
* {@link ComputedContext} so it can splice the outer alias into a correlated
|
|
300
|
+
* subquery. Either way the result is dev-authored — never client text.
|
|
301
|
+
*/
|
|
302
|
+
export function resolveComputedExpression(source, alias) {
|
|
303
|
+
return typeof source === 'function' ? source({ alias }) : source;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Apply a filter on a dev-declared **computed field** to a Lucid query builder.
|
|
307
|
+
*
|
|
308
|
+
* The already-resolved `expression` is inlined as the raw left-hand side (it is
|
|
309
|
+
* dev-authored — a verbatim string or a function's output — never client text);
|
|
310
|
+
* the client's filter VALUE always rides through as a positional `?` binding,
|
|
311
|
+
* exactly the injection-safety contract real-column filters have. The whole
|
|
312
|
+
* expression is parenthesized so a compound source
|
|
313
|
+
* (`first || ' ' || last`, `(SELECT …)`) composes under the operator without a
|
|
314
|
+
* precedence surprise.
|
|
315
|
+
*/
|
|
316
|
+
export function applyComputedField(qb, expression, filter) {
|
|
317
|
+
const op = normalizeOperator(filter.operator);
|
|
318
|
+
const value = filter.value;
|
|
319
|
+
const lhs = `(${expression})`;
|
|
320
|
+
switch (op) {
|
|
321
|
+
case 'equals':
|
|
322
|
+
qb.whereRaw(`${lhs} = ?`, [value]);
|
|
323
|
+
break;
|
|
324
|
+
case 'notEquals':
|
|
325
|
+
qb.whereRaw(`${lhs} <> ?`, [value]);
|
|
326
|
+
break;
|
|
327
|
+
case 'gt':
|
|
328
|
+
qb.whereRaw(`${lhs} > ?`, [value]);
|
|
329
|
+
break;
|
|
330
|
+
case 'gte':
|
|
331
|
+
qb.whereRaw(`${lhs} >= ?`, [value]);
|
|
332
|
+
break;
|
|
333
|
+
case 'lt':
|
|
334
|
+
qb.whereRaw(`${lhs} < ?`, [value]);
|
|
335
|
+
break;
|
|
336
|
+
case 'lte':
|
|
337
|
+
qb.whereRaw(`${lhs} <= ?`, [value]);
|
|
338
|
+
break;
|
|
339
|
+
case 'in':
|
|
340
|
+
case 'isAnyOf': {
|
|
341
|
+
const values = Array.isArray(value) ? value : [];
|
|
342
|
+
// An empty IN list matches nothing — emit an always-false predicate rather
|
|
343
|
+
// than invalid `IN ()` SQL.
|
|
344
|
+
if (values.length === 0)
|
|
345
|
+
qb.whereRaw('1 = 0');
|
|
346
|
+
else
|
|
347
|
+
qb.whereRaw(`${lhs} in (${values.map(() => '?').join(', ')})`, values);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case 'notIn': {
|
|
351
|
+
const values = Array.isArray(value) ? value : [];
|
|
352
|
+
if (values.length === 0)
|
|
353
|
+
qb.whereRaw('1 = 1');
|
|
354
|
+
else
|
|
355
|
+
qb.whereRaw(`${lhs} not in (${values.map(() => '?').join(', ')})`, values);
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case 'between': {
|
|
359
|
+
const [low, high] = value;
|
|
360
|
+
qb.whereRaw(`${lhs} between ? and ?`, [low, high]);
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
case 'notBetween': {
|
|
364
|
+
const [low, high] = value;
|
|
365
|
+
qb.whereRaw(`${lhs} not between ? and ?`, [low, high]);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case 'contains':
|
|
369
|
+
case 'iContains':
|
|
370
|
+
qb.whereRaw(`${lhs} ilike ?`, [like(value, 'contains')]);
|
|
371
|
+
break;
|
|
372
|
+
case 'notContains':
|
|
373
|
+
qb.whereRaw(`(${lhs} not ilike ? or ${lhs} is null)`, [like(value, 'contains')]);
|
|
374
|
+
break;
|
|
375
|
+
case 'startsWith':
|
|
376
|
+
qb.whereRaw(`${lhs} ilike ?`, [like(value, 'startsWith')]);
|
|
377
|
+
break;
|
|
378
|
+
case 'endsWith':
|
|
379
|
+
qb.whereRaw(`${lhs} ilike ?`, [like(value, 'endsWith')]);
|
|
380
|
+
break;
|
|
381
|
+
case 'isNull':
|
|
382
|
+
case 'notExists':
|
|
383
|
+
qb.whereRaw(`${lhs} is null`);
|
|
384
|
+
break;
|
|
385
|
+
case 'isNotNull':
|
|
386
|
+
case 'exists':
|
|
387
|
+
qb.whereRaw(`${lhs} is not null`);
|
|
388
|
+
break;
|
|
389
|
+
case 'isEmpty':
|
|
390
|
+
qb.whereRaw(`${lhs} = ?`, ['']);
|
|
391
|
+
break;
|
|
392
|
+
case 'isNotEmpty':
|
|
393
|
+
qb.whereRaw(`${lhs} <> ?`, ['']);
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Append an ORDER BY on a dev-declared **computed field** to a Lucid query
|
|
399
|
+
* builder via `orderByRaw`. Uses append semantics (like `orderBy`) so a computed
|
|
400
|
+
* sort composes with real-column sorts in request order. The expression is
|
|
401
|
+
* dev-authored and parenthesized; the direction is a validated literal.
|
|
402
|
+
*/
|
|
403
|
+
export function applyComputedSort(qb, expression, direction) {
|
|
404
|
+
qb.orderByRaw(`(${expression}) ${direction === 'desc' ? 'desc' : 'asc'}`);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Apply a keyset (cursor) seek predicate to a Lucid query builder for row-value
|
|
408
|
+
* comparison across the keyset columns.
|
|
409
|
+
*
|
|
410
|
+
* Given a keyset (the active sort columns plus a primary-key tiebreaker, each
|
|
411
|
+
* with a direction) and a boundary row's values, this constrains the query to
|
|
412
|
+
* rows strictly *after* the boundary in keyset order. The row-value comparison
|
|
413
|
+
* is expanded into the portable "OR of AND tiers" form that works across all
|
|
414
|
+
* SQL dialects:
|
|
415
|
+
*
|
|
416
|
+
* ```text
|
|
417
|
+
* (c0 OP0 v0)
|
|
418
|
+
* OR (c0 = v0 AND c1 OP1 v1)
|
|
419
|
+
* OR (c0 = v0 AND c1 = v1 AND c2 OP2 v2) ...
|
|
420
|
+
* ```
|
|
421
|
+
*
|
|
422
|
+
* where `OPi` is `>` for an `asc` column and `<` for a `desc` column. The whole
|
|
423
|
+
* predicate is wrapped in one AND-group so it composes with any existing
|
|
424
|
+
* `where` conditions. A no-op when the keyset is empty or `values` does not line
|
|
425
|
+
* up positionally with it.
|
|
426
|
+
*
|
|
427
|
+
* With more than one column, the group is preceded by the bound every tier
|
|
428
|
+
* already implies, `c0 >= v0` (`<=` for `desc`): Postgres cannot derive an index
|
|
429
|
+
* range from the OR form alone and would scan from the start of the index. The
|
|
430
|
+
* bound is skipped when `v0` is null, where the OR form matches no `c0 > NULL`
|
|
431
|
+
* tier and a `>= NULL` bound would wrongly match nothing.
|
|
432
|
+
*/
|
|
433
|
+
export function applyKeyset(qb, keyset, values) {
|
|
434
|
+
if (keyset.length === 0 || values.length !== keyset.length)
|
|
435
|
+
return;
|
|
436
|
+
const lead = keyset[0];
|
|
437
|
+
if (keyset.length > 1 && values[0] !== null && values[0] !== undefined) {
|
|
438
|
+
qb.where(lead.field, lead.direction === 'asc' ? '>=' : '<=', values[0]);
|
|
439
|
+
}
|
|
440
|
+
qb.where((outer) => {
|
|
441
|
+
for (let tier = 0; tier < keyset.length; tier++) {
|
|
442
|
+
const buildTier = (inner) => {
|
|
443
|
+
// Equality on every column before this tier.
|
|
444
|
+
for (let i = 0; i < tier; i++) {
|
|
445
|
+
inner.where(keyset[i].field, values[i]);
|
|
446
|
+
}
|
|
447
|
+
const cmp = keyset[tier].direction === 'asc' ? '>' : '<';
|
|
448
|
+
inner.where(keyset[tier].field, cmp, values[tier]);
|
|
449
|
+
};
|
|
450
|
+
// First tier seeds the group with AND; the rest OR onto it.
|
|
451
|
+
if (tier === 0)
|
|
452
|
+
outer.where(buildTier);
|
|
453
|
+
else
|
|
454
|
+
outer.orWhere(buildTier);
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/** Apply a free-text ILIKE search across `columns` (OR-combined) to a Lucid query builder. */
|
|
459
|
+
export function applySearch(qb, term, columns) {
|
|
460
|
+
if (columns.length === 0 || term.length === 0)
|
|
461
|
+
return;
|
|
462
|
+
const pattern = like(term, 'contains');
|
|
463
|
+
qb.where((group) => {
|
|
464
|
+
for (const column of columns) {
|
|
465
|
+
group.orWhereILike(column, pattern);
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
//# sourceMappingURL=lucid_adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lucid_adapter.js","sourceRoot":"","sources":["../../src/lucid_adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type ApplyFromRequestOptions, type FilterRequestContext } from './apply_from_request.js';
|
|
2
|
+
import { type FilterClass } from './filter_class.js';
|
|
3
|
+
import type { FilterSpec } from './filter_spec.js';
|
|
4
|
+
import type { ResolvedPagination } from './runner.js';
|
|
5
|
+
/**
|
|
6
|
+
* The static slice of a Lucid query-builder class we register onto: Adonis'
|
|
7
|
+
* `Macroable.macro(name, fn)` adds `fn` to the builder prototype so every query
|
|
8
|
+
* instance gains the method. Declared structurally so this module never
|
|
9
|
+
* hard-imports `@adonisjs/lucid` at runtime (the provider passes the real
|
|
10
|
+
* `ModelQueryBuilder` in) — matching how the rest of the package stays
|
|
11
|
+
* framework-free.
|
|
12
|
+
*/
|
|
13
|
+
export interface MacroableQueryBuilder {
|
|
14
|
+
macro(name: string, fn: (this: unknown, ...args: never[]) => unknown): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register the chainable filter macros onto a Lucid query-builder class (the
|
|
18
|
+
* method-call form of {@link applyFilterFromRequest}). Call this from a provider's
|
|
19
|
+
* `boot()` with `ModelQueryBuilder` (from `@adonisjs/lucid/orm`); the
|
|
20
|
+
* `@adonis-agora/filter` provider does exactly that.
|
|
21
|
+
*
|
|
22
|
+
* Two macros are added:
|
|
23
|
+
*
|
|
24
|
+
* - `query.applyFilterFromRequest(spec, ctx, options?)` — applies the spec's
|
|
25
|
+
* server scope + allow-listed filter/sort/search and returns the query so it
|
|
26
|
+
* chains (`User.query().applyFilterFromRequest(spec, ctx).orderBy(...)`). The
|
|
27
|
+
* resolved pagination is dropped; use `filterPaginate` (or the free function)
|
|
28
|
+
* when you need it.
|
|
29
|
+
* - `query.filterPaginate(spec, ctx, options?)` — applies the same and then
|
|
30
|
+
* `paginate(page, size)` with the resolved pagination, returning Lucid's
|
|
31
|
+
* paginator (`await User.query().filterPaginate(spec, ctx)`).
|
|
32
|
+
*
|
|
33
|
+
* Idempotent enough to call once at boot; calling twice re-defines the macros to
|
|
34
|
+
* the same implementations.
|
|
35
|
+
*/
|
|
36
|
+
export declare function registerFilterMacros(ModelQueryBuilder: MacroableQueryBuilder): void;
|
|
37
|
+
declare module '@adonisjs/lucid/types/model' {
|
|
38
|
+
interface ModelQueryBuilderContract<Model extends LucidModel, Result = InstanceType<Model>> {
|
|
39
|
+
/**
|
|
40
|
+
* Apply a {@link FilterSpec} from the request context (server scope +
|
|
41
|
+
* allow-listed filter/sort/search) and return the query for chaining — the
|
|
42
|
+
* method form of the free `applyFilterFromRequest`. Pagination is resolved
|
|
43
|
+
* but not returned here; use {@link filterPaginate} when you need it.
|
|
44
|
+
*
|
|
45
|
+
* `ctx` is optional: when omitted, the active `HttpContext` is read from
|
|
46
|
+
* AsyncLocalStorage (`HttpContext.getOrFail()`). Pass it explicitly outside a
|
|
47
|
+
* request scope (e.g. a job/command), where there is no ambient context.
|
|
48
|
+
*/
|
|
49
|
+
applyFilterFromRequest(spec: FilterSpec, ctx?: FilterRequestContext, options?: ApplyFromRequestOptions): this;
|
|
50
|
+
/**
|
|
51
|
+
* The same for a filter **class**: resolved through the IoC container (so `@inject()` works),
|
|
52
|
+
* `setup()` first, then every request key the class owns a method for.
|
|
53
|
+
*
|
|
54
|
+
* Resolution is async, so this returns a promise — of the resolved `{ page, size }`, not of
|
|
55
|
+
* the builder (a Lucid builder is thenable, so awaiting a promise of one would run the query).
|
|
56
|
+
* The builder is the one you called it on, still yours to compose on:
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const query = User.query().whereNull('deletedAt')
|
|
60
|
+
* const { page, size } = await query.applyFilterFromRequest(UserFilter)
|
|
61
|
+
* return query.preload('team').paginate(page, size)
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
applyFilterFromRequest(filter: FilterClass, ctx?: FilterRequestContext, options?: ApplyFromRequestOptions): Promise<ResolvedPagination>;
|
|
65
|
+
/**
|
|
66
|
+
* Apply a {@link FilterSpec} from the request context and paginate with the
|
|
67
|
+
* resolved `{ page, size }`, returning Lucid's paginator — filter + paginate
|
|
68
|
+
* in one terminal call. `ctx` is optional (see {@link applyFilterFromRequest}).
|
|
69
|
+
*/
|
|
70
|
+
filterPaginate(spec: FilterSpec, ctx?: FilterRequestContext, options?: ApplyFromRequestOptions): ReturnType<this['paginate']>;
|
|
71
|
+
/** The filter-class form of {@link filterPaginate} — async, for the same reason. */
|
|
72
|
+
filterPaginate(filter: FilterClass, ctx?: FilterRequestContext, options?: ApplyFromRequestOptions): Promise<Awaited<ReturnType<this['paginate']>>>;
|
|
73
|
+
/**
|
|
74
|
+
* Page a query that has **already** been filtered (by the model's `filter()` or by the
|
|
75
|
+
* `applyFilterFromRequest` macro) with the `{ page, size }` that call resolved — so a
|
|
76
|
+
* controller can keep composing on the builder and still page it the way the request asked.
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* const query = await User.filter(ctx)
|
|
80
|
+
* query.whereNotNull('confirmedAt')
|
|
81
|
+
* return query.filterPaginate()
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
filterPaginate(): ReturnType<this['paginate']>;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=lucid_macros.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lucid_macros.d.ts","sourceRoot":"","sources":["../../src/lucid_macros.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,uBAAuB,EAE5B,KAAK,oBAAoB,EAC1B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAYtD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,GAAG,IAAI,CAAC;CAC7E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAAC,iBAAiB,EAAE,qBAAqB,GAAG,IAAI,CA+CnF;AAED,OAAO,QAAQ,6BAA6B,CAAC;IAC3C,UAAU,yBAAyB,CAAC,KAAK,SAAS,UAAU,EAAE,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC;QACxF;;;;;;;;;WASG;QACH,sBAAsB,CACpB,IAAI,EAAE,UAAU,EAChB,GAAG,CAAC,EAAE,oBAAoB,EAC1B,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAAC;QACR;;;;;;;;;;;;;WAaG;QACH,sBAAsB,CACpB,MAAM,EAAE,WAAW,EACnB,GAAG,CAAC,EAAE,oBAAoB,EAC1B,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAAC;QAC/B;;;;WAIG;QACH,cAAc,CACZ,IAAI,EAAE,UAAU,EAChB,GAAG,CAAC,EAAE,oBAAoB,EAC1B,OAAO,CAAC,EAAE,uBAAuB,GAChC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAChC,oFAAoF;QACpF,cAAc,CACZ,MAAM,EAAE,WAAW,EACnB,GAAG,CAAC,EAAE,oBAAoB,EAC1B,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD;;;;;;;;;;WAUG;QACH,cAAc,IAAI,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;KAChD;CACF"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import { applyFilterFromRequest, } from './apply_from_request.js';
|
|
3
|
+
import { isFilterClass } from './filter_class.js';
|
|
4
|
+
import { paginate } from './paginate.js';
|
|
5
|
+
import { readPagination, stashPagination } from './pagination_stash.js';
|
|
6
|
+
/**
|
|
7
|
+
* O ctx da request para o macro: explícito, ou o `HttpContext` ativo lido do AsyncLocalStorage do
|
|
8
|
+
* Adonis quando omitido. `getOrFail()` lança quando não há request em escopo (ex.: chamada dentro de
|
|
9
|
+
* um job/command) — nesse caso passe o ctx explicitamente. Fica no macro (camada Adonis); o
|
|
10
|
+
* `applyFilterFromRequest` livre continua framework-agnostic, exigindo o ctx.
|
|
11
|
+
*/
|
|
12
|
+
function resolveCtx(ctx) {
|
|
13
|
+
return ctx ?? HttpContext.getOrFail();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Register the chainable filter macros onto a Lucid query-builder class (the
|
|
17
|
+
* method-call form of {@link applyFilterFromRequest}). Call this from a provider's
|
|
18
|
+
* `boot()` with `ModelQueryBuilder` (from `@adonisjs/lucid/orm`); the
|
|
19
|
+
* `@adonis-agora/filter` provider does exactly that.
|
|
20
|
+
*
|
|
21
|
+
* Two macros are added:
|
|
22
|
+
*
|
|
23
|
+
* - `query.applyFilterFromRequest(spec, ctx, options?)` — applies the spec's
|
|
24
|
+
* server scope + allow-listed filter/sort/search and returns the query so it
|
|
25
|
+
* chains (`User.query().applyFilterFromRequest(spec, ctx).orderBy(...)`). The
|
|
26
|
+
* resolved pagination is dropped; use `filterPaginate` (or the free function)
|
|
27
|
+
* when you need it.
|
|
28
|
+
* - `query.filterPaginate(spec, ctx, options?)` — applies the same and then
|
|
29
|
+
* `paginate(page, size)` with the resolved pagination, returning Lucid's
|
|
30
|
+
* paginator (`await User.query().filterPaginate(spec, ctx)`).
|
|
31
|
+
*
|
|
32
|
+
* Idempotent enough to call once at boot; calling twice re-defines the macros to
|
|
33
|
+
* the same implementations.
|
|
34
|
+
*/
|
|
35
|
+
export function registerFilterMacros(ModelQueryBuilder) {
|
|
36
|
+
ModelQueryBuilder.macro('applyFilterFromRequest', function (filter, ctx, options) {
|
|
37
|
+
// A class is resolved through the container, so this leg is async — and it resolves to the
|
|
38
|
+
// pagination rather than to the builder: a Lucid query builder is thenable, so a promise that
|
|
39
|
+
// resolved to one would execute the query instead of handing it back. You already hold the
|
|
40
|
+
// builder; `await query.applyFilterFromRequest(UserFilter)` gives you its `{ page, size }`.
|
|
41
|
+
if (isFilterClass(filter)) {
|
|
42
|
+
return applyFilterFromRequest(this, filter, resolveCtx(ctx), options).then((pagination) => {
|
|
43
|
+
stashPagination(this, pagination);
|
|
44
|
+
return pagination;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
stashPagination(this, applyFilterFromRequest(this, filter, resolveCtx(ctx), options));
|
|
48
|
+
return this;
|
|
49
|
+
});
|
|
50
|
+
ModelQueryBuilder.macro('filterPaginate', function (filter, ctx, options) {
|
|
51
|
+
// No filter argument: this query has already been through one (the model's `filter()`, or the
|
|
52
|
+
// `applyFilterFromRequest` macro), so page it with the pagination that call resolved.
|
|
53
|
+
if (filter === undefined) {
|
|
54
|
+
const stashed = readPagination(this);
|
|
55
|
+
if (!stashed) {
|
|
56
|
+
throw new Error('filterPaginate() was called with no filter on a query that has not been filtered. Pass the filter — filterPaginate(UserFilter) — or apply one first.');
|
|
57
|
+
}
|
|
58
|
+
return paginate(this, stashed.page, stashed.size);
|
|
59
|
+
}
|
|
60
|
+
if (isFilterClass(filter)) {
|
|
61
|
+
return applyFilterFromRequest(this, filter, resolveCtx(ctx), options).then(({ page, size }) => paginate(this, page, size));
|
|
62
|
+
}
|
|
63
|
+
const { page, size } = applyFilterFromRequest(this, filter, resolveCtx(ctx), options);
|
|
64
|
+
return paginate(this, page, size);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=lucid_macros.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lucid_macros.js","sourceRoot":"","sources":["../../src/lucid_macros.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAEL,sBAAsB,GAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAoB,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGpE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxE;;;;;GAKG;AACH,SAAS,UAAU,CAAC,GAAqC;IACvD,OAAO,GAAG,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC;AACxC,CAAC;AAcD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,oBAAoB,CAAC,iBAAwC;IAC3E,iBAAiB,CAAC,KAAK,CAAC,wBAAwB,EAAE,UAEhD,MAAgC,EAChC,GAA0B,EAC1B,OAAiC;QAEjC,2FAA2F;QAC3F,8FAA8F;QAC9F,2FAA2F;QAC3F,4FAA4F;QAC5F,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE;gBACxF,eAAe,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBAClC,OAAO,UAAU,CAAC;YACpB,CAAC,CAAC,CAAC;QACL,CAAC;QACD,eAAe,CAAC,IAAI,EAAE,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QACtF,OAAO,IAAI,CAAC;IACd,CAAiD,CAAC,CAAC;IAEnD,iBAAiB,CAAC,KAAK,CAAC,gBAAgB,EAAE,UAExC,MAAiC,EACjC,GAA0B,EAC1B,OAAiC;QAEjC,8FAA8F;QAC9F,sFAAsF;QACtF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACb,sJAAsJ,CACvJ,CAAC;YACJ,CAAC;YACD,OAAO,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CACxE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAsB,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CACnE,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;QACtF,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACpC,CAAiD,CAAC,CAAC;AACrD,CAAC"}
|