@adonis-agora/filter 0.1.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/LICENSE +21 -0
- package/README.md +30 -0
- package/dist/commands/main.d.ts +17 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +17 -0
- package/dist/commands/main.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/src/apply_from_request.d.ts +69 -0
- package/dist/src/apply_from_request.d.ts.map +1 -0
- package/dist/src/apply_from_request.js +115 -0
- package/dist/src/apply_from_request.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/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 +28 -0
- package/dist/src/field_aliases.d.ts.map +1 -0
- package/dist/src/field_aliases.js +61 -0
- package/dist/src/field_aliases.js.map +1 -0
- package/dist/src/filter_spec.d.ts +146 -0
- package/dist/src/filter_spec.d.ts.map +1 -0
- package/dist/src/filter_spec.js +140 -0
- package/dist/src/filter_spec.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 +154 -0
- package/dist/src/generate_client.js.map +1 -0
- package/dist/src/index.d.ts +28 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +18 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lucid_adapter.d.ts +213 -0
- package/dist/src/lucid_adapter.d.ts.map +1 -0
- package/dist/src/lucid_adapter.js +303 -0
- package/dist/src/lucid_adapter.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/parse_request.d.ts +22 -0
- package/dist/src/parse_request.d.ts.map +1 -0
- package/dist/src/parse_request.js +107 -0
- package/dist/src/parse_request.js.map +1 -0
- package/dist/src/runner.d.ts +46 -0
- package/dist/src/runner.d.ts.map +1 -0
- package/dist/src/runner.js +190 -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 +38 -0
- package/dist/src/spatie_parser.d.ts.map +1 -0
- package/dist/src/spatie_parser.js +130 -0
- package/dist/src/spatie_parser.js.map +1 -0
- package/dist/src/testing.d.ts +62 -0
- package/dist/src/testing.d.ts.map +1 -0
- package/dist/src/testing.js +113 -0
- package/dist/src/testing.js.map +1 -0
- package/dist/src/types.d.ts +130 -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/package.json +71 -0
|
@@ -0,0 +1,303 @@
|
|
|
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.whereNot(field, value).whereNotNull(field));
|
|
172
|
+
break;
|
|
173
|
+
case 'gt':
|
|
174
|
+
qb.where(field, '>', value);
|
|
175
|
+
break;
|
|
176
|
+
case 'gte':
|
|
177
|
+
qb.where(field, '>=', value);
|
|
178
|
+
break;
|
|
179
|
+
case 'lt':
|
|
180
|
+
qb.where(field, '<', value);
|
|
181
|
+
break;
|
|
182
|
+
case 'lte':
|
|
183
|
+
qb.where(field, '<=', value);
|
|
184
|
+
break;
|
|
185
|
+
case 'between':
|
|
186
|
+
qb.whereBetween(field, value);
|
|
187
|
+
break;
|
|
188
|
+
case 'notBetween':
|
|
189
|
+
qb.whereNotBetween(field, value);
|
|
190
|
+
break;
|
|
191
|
+
case 'in':
|
|
192
|
+
case 'isAnyOf':
|
|
193
|
+
qb.whereIn(field, value);
|
|
194
|
+
break;
|
|
195
|
+
case 'notIn':
|
|
196
|
+
qb.whereNotIn(field, value);
|
|
197
|
+
break;
|
|
198
|
+
case 'isNull':
|
|
199
|
+
case 'notExists':
|
|
200
|
+
qb.whereNull(field);
|
|
201
|
+
break;
|
|
202
|
+
case 'isNotNull':
|
|
203
|
+
case 'exists':
|
|
204
|
+
qb.whereNotNull(field);
|
|
205
|
+
break;
|
|
206
|
+
case 'isEmpty':
|
|
207
|
+
qb.where(field, '');
|
|
208
|
+
break;
|
|
209
|
+
case 'isNotEmpty':
|
|
210
|
+
qb.whereNot(field, '');
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Apply one {@link ColumnFilter} (possibly an AND/OR group) to a builder. A leaf
|
|
216
|
+
* is a single condition; `AND`/`OR` recurse into a grouped sub-builder so the
|
|
217
|
+
* boolean structure maps to Lucid's nested `where`/`orWhere` closures.
|
|
218
|
+
*/
|
|
219
|
+
function applyOne(qb, filter) {
|
|
220
|
+
const op = normalizeOperator(filter.operator);
|
|
221
|
+
const hasField = typeof filter.field === 'string' && filter.field.length > 0;
|
|
222
|
+
qb.where((group) => {
|
|
223
|
+
if (hasField) {
|
|
224
|
+
applyLeaf(group, filter.field, op, filter.value);
|
|
225
|
+
}
|
|
226
|
+
if (filter.AND) {
|
|
227
|
+
for (const sub of filter.AND) {
|
|
228
|
+
group.where((g) => applyOne(g, sub));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (filter.OR) {
|
|
232
|
+
for (const sub of filter.OR) {
|
|
233
|
+
group.orWhere((g) => applyOne(g, sub));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/** Apply an array of column filters (combined with AND) to a Lucid query builder. */
|
|
239
|
+
export function applyColumnFilters(qb, filters) {
|
|
240
|
+
for (const filter of filters) {
|
|
241
|
+
applyOne(qb, filter);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** Apply sort directives to a Lucid query builder, in order. */
|
|
245
|
+
export function applySort(qb, sorts) {
|
|
246
|
+
for (const sort of sorts) {
|
|
247
|
+
qb.orderBy(sort.field, sort.direction);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Apply a keyset (cursor) seek predicate to a Lucid query builder for row-value
|
|
252
|
+
* comparison across the keyset columns.
|
|
253
|
+
*
|
|
254
|
+
* Given a keyset (the active sort columns plus a primary-key tiebreaker, each
|
|
255
|
+
* with a direction) and a boundary row's values, this constrains the query to
|
|
256
|
+
* rows strictly *after* the boundary in keyset order. The row-value comparison
|
|
257
|
+
* is expanded into the portable "OR of AND tiers" form that works across all
|
|
258
|
+
* SQL dialects:
|
|
259
|
+
*
|
|
260
|
+
* ```text
|
|
261
|
+
* (c0 OP0 v0)
|
|
262
|
+
* OR (c0 = v0 AND c1 OP1 v1)
|
|
263
|
+
* OR (c0 = v0 AND c1 = v1 AND c2 OP2 v2) ...
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* where `OPi` is `>` for an `asc` column and `<` for a `desc` column. The whole
|
|
267
|
+
* predicate is wrapped in one AND-group so it composes with any existing
|
|
268
|
+
* `where` conditions. A no-op when the keyset is empty or `values` does not line
|
|
269
|
+
* up positionally with it.
|
|
270
|
+
*/
|
|
271
|
+
export function applyKeyset(qb, keyset, values) {
|
|
272
|
+
if (keyset.length === 0 || values.length !== keyset.length)
|
|
273
|
+
return;
|
|
274
|
+
qb.where((outer) => {
|
|
275
|
+
for (let tier = 0; tier < keyset.length; tier++) {
|
|
276
|
+
const buildTier = (inner) => {
|
|
277
|
+
// Equality on every column before this tier.
|
|
278
|
+
for (let i = 0; i < tier; i++) {
|
|
279
|
+
inner.where(keyset[i].field, values[i]);
|
|
280
|
+
}
|
|
281
|
+
const cmp = keyset[tier].direction === 'asc' ? '>' : '<';
|
|
282
|
+
inner.where(keyset[tier].field, cmp, values[tier]);
|
|
283
|
+
};
|
|
284
|
+
// First tier seeds the group with AND; the rest OR onto it.
|
|
285
|
+
if (tier === 0)
|
|
286
|
+
outer.where(buildTier);
|
|
287
|
+
else
|
|
288
|
+
outer.orWhere(buildTier);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/** Apply a free-text ILIKE search across `columns` (OR-combined) to a Lucid query builder. */
|
|
293
|
+
export function applySearch(qb, term, columns) {
|
|
294
|
+
if (columns.length === 0 || term.length === 0)
|
|
295
|
+
return;
|
|
296
|
+
const pattern = like(term, 'contains');
|
|
297
|
+
qb.where((group) => {
|
|
298
|
+
for (const column of columns) {
|
|
299
|
+
group.orWhereILike(column, pattern);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=lucid_adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lucid_adapter.js","sourceRoot":"","sources":["../../src/lucid_adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAkEhE,wEAAwE;AACxE,MAAM,gBAAgB,GAAyC;IAC7D,MAAM,EAAE,KAAK;IACb,EAAE,EAAE,KAAK;IACT,YAAY,EAAE,KAAK;CACpB,CAAC;AAkCF,sFAAsF;AACtF,MAAM,eAAe,GAAG,qDAAqD,CAAC;AAE9E,gFAAgF;AAChF,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO,MAAM;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC;SACxB,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,MAAyB;IAChD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,SAAS,CAAC,2DAA2D,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,qBAAqB,CAAC,EAAoB,EAAE,IAA6B;IACvF,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO;IAC7B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,SAAS,CAAC,+BAA+B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,QAAQ,YAAY,CAAC;IAErE,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACjC,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,MAAM,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QACzB,EAAE,CAAC,UAAU,CAAC,GAAG,QAAQ,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAkDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAoB,EAAE,IAA2B;IACnF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAE/B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAgB,CAAC,CAAC;IACnF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACjC,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,SAAS,CAAC,yCAAyC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,SAAS,CAAC;IAC5C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CAAC,sCAAsC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,kFAAkF;IAClF,MAAM,OAAO,GAAG,yBAAyB,QAAQ,OAAO,CAAC;IAEzD,gFAAgF;IAChF,8EAA8E;IAC9E,sBAAsB;IACtB,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAC3E,MAAM,QAAQ,GACZ,IAAI,KAAK,MAAM;QACb,CAAC,CAAC,gBAAgB,QAAQ,MAAM,OAAO;aAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC;aAC7C,IAAI,CAAC,aAAa,CAAC,GAAG;QAC3B,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC;IAE/B,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,OAAO,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAElD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,EAAE,CAAC,UAAU,CAAC,WAAW,QAAQ,KAAK,OAAO,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAClE,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,SAAS,IAAI,CAAC,KAAc,EAAE,IAA4C;IACxE,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC;IAC1C,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC,EAAE,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,SAAS,CAAC,EAAoB,EAAE,KAAa,EAAE,QAAwB,EAAE,KAAc;IAC9F,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;QACf,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QAClC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;QACtE,OAAO;IACT,CAAC;IACD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,QAAQ;YACX,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACvB,MAAM;QACR,KAAK,WAAW;YACd,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAC1B,MAAM;QACR,KAAK,UAAU,CAAC;QAChB,KAAK,WAAW;YACd,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;YAC9C,MAAM;QACR,KAAK,YAAY;YACf,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;YAChD,MAAM;QACR,KAAK,UAAU;YACb,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;YAC9C,MAAM;QACR,KAAK,aAAa;YAChB,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;YAClE,MAAM;QACR,KAAK,IAAI;YACP,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC5B,MAAM;QACR,KAAK,KAAK;YACR,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC7B,MAAM;QACR,KAAK,IAAI;YACP,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC5B,MAAM;QACR,KAAK,KAAK;YACR,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;YAC7B,MAAM;QACR,KAAK,SAAS;YACZ,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,KAA2B,CAAC,CAAC;YACpD,MAAM;QACR,KAAK,YAAY;YACf,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,KAA2B,CAAC,CAAC;YACvD,MAAM;QACR,KAAK,IAAI,CAAC;QACV,KAAK,SAAS;YACZ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,KAAkB,CAAC,CAAC;YACtC,MAAM;QACR,KAAK,OAAO;YACV,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,KAAkB,CAAC,CAAC;YACzC,MAAM;QACR,KAAK,QAAQ,CAAC;QACd,KAAK,WAAW;YACd,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM;QACR,KAAK,WAAW,CAAC;QACjB,KAAK,QAAQ;YACX,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;YACvB,MAAM;QACR,KAAK,SAAS;YACZ,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACpB,MAAM;QACR,KAAK,YAAY;YACf,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACvB,MAAM;IACV,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,QAAQ,CAAC,EAAoB,EAAE,MAAoB;IAC1D,MAAM,EAAE,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAE7E,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACjB,IAAI,QAAQ,EAAE,CAAC;YACb,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YACf,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;gBAC7B,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QACD,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;gBAC5B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YACzC,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,kBAAkB,CAAC,EAAoB,EAAE,OAAuB;IAC9E,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,SAAS,CAAC,EAAoB,EAAE,KAAiB;IAC/D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,WAAW,CAAC,EAAoB,EAAE,MAAkB,EAAE,MAAiB;IACrF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;QAAE,OAAO;IAEnE,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACjB,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;YAChD,MAAM,SAAS,GAAG,CAAC,KAAuB,EAAE,EAAE;gBAC5C,6CAA6C;gBAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC9B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC3C,CAAC;gBACD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAE,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBAC1D,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACtD,CAAC,CAAC;YACF,4DAA4D;YAC5D,IAAI,IAAI,KAAK,CAAC;gBAAE,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;;gBAClC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,WAAW,CAAC,EAAoB,EAAE,IAAY,EAAE,OAAiB;IAC/E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACvC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACjB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { InputNormalizer } from './types.js';
|
|
2
|
+
export interface NormalizeOptions {
|
|
3
|
+
/** Key transform — a built-in case mode or a custom mapping function. */
|
|
4
|
+
normalizer: InputNormalizer;
|
|
5
|
+
/** Strip a trailing `Id`/`_id` (and drop a bare `id`) from each key. Default false. */
|
|
6
|
+
dropId?: boolean;
|
|
7
|
+
/** Strip `null`/`undefined`/empty-string values. Default true. */
|
|
8
|
+
stripEmpty?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Normalize a decoded input object's top-level keys — case-transforming them via
|
|
12
|
+
* the chosen {@link InputNormalizer}, optionally stripping `Id` suffixes and
|
|
13
|
+
* empty values — into a null-prototype object.
|
|
14
|
+
*
|
|
15
|
+
* Prototype-pollution keys (`__proto__`, `constructor`, `prototype`, `toString`,
|
|
16
|
+
* `valueOf`) are dropped both before and after normalization, and the result has
|
|
17
|
+
* a null prototype, so a hostile key can never reach `Object.prototype`. Nested
|
|
18
|
+
* values are left untouched — only top-level keys are normalized.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeInput(input: unknown, options: NormalizeOptions): Record<string, unknown>;
|
|
21
|
+
//# sourceMappingURL=normalizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizer.d.ts","sourceRoot":"","sources":["../../src/normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,UAAU,EAAE,eAAe,CAAC;IAC5B,uFAAuF;IACvF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kEAAkE;IAClE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBjG"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype', 'toString', 'valueOf']);
|
|
2
|
+
/**
|
|
3
|
+
* Normalize a decoded input object's top-level keys — case-transforming them via
|
|
4
|
+
* the chosen {@link InputNormalizer}, optionally stripping `Id` suffixes and
|
|
5
|
+
* empty values — into a null-prototype object.
|
|
6
|
+
*
|
|
7
|
+
* Prototype-pollution keys (`__proto__`, `constructor`, `prototype`, `toString`,
|
|
8
|
+
* `valueOf`) are dropped both before and after normalization, and the result has
|
|
9
|
+
* a null prototype, so a hostile key can never reach `Object.prototype`. Nested
|
|
10
|
+
* values are left untouched — only top-level keys are normalized.
|
|
11
|
+
*/
|
|
12
|
+
export function normalizeInput(input, options) {
|
|
13
|
+
if (input == null || typeof input !== 'object')
|
|
14
|
+
return {};
|
|
15
|
+
const norm = pickNormalizer(options.normalizer);
|
|
16
|
+
const drop = options.dropId === true;
|
|
17
|
+
const strip = options.stripEmpty !== false;
|
|
18
|
+
const out = Object.create(null);
|
|
19
|
+
for (const [rawKey, value] of Object.entries(input)) {
|
|
20
|
+
if (BLOCKED_KEYS.has(rawKey))
|
|
21
|
+
continue;
|
|
22
|
+
if (strip && (value === null || value === undefined || value === ''))
|
|
23
|
+
continue;
|
|
24
|
+
let key = norm(rawKey);
|
|
25
|
+
if (BLOCKED_KEYS.has(key))
|
|
26
|
+
continue;
|
|
27
|
+
if (drop)
|
|
28
|
+
key = stripId(key);
|
|
29
|
+
if (key.length === 0)
|
|
30
|
+
continue;
|
|
31
|
+
out[key] = value;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
function pickNormalizer(spec) {
|
|
36
|
+
if (typeof spec === 'function')
|
|
37
|
+
return spec;
|
|
38
|
+
if (spec === 'camelCase')
|
|
39
|
+
return toCamelCase;
|
|
40
|
+
return toSnakeCase;
|
|
41
|
+
}
|
|
42
|
+
function toCamelCase(key) {
|
|
43
|
+
const result = key.replace(/[_-](\w)/g, (_, c) => c.toUpperCase());
|
|
44
|
+
return result.charAt(0).toLowerCase() + result.slice(1);
|
|
45
|
+
}
|
|
46
|
+
function toSnakeCase(key) {
|
|
47
|
+
return key
|
|
48
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
|
|
49
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
50
|
+
.toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
function stripId(key) {
|
|
53
|
+
if (key.endsWith('Id'))
|
|
54
|
+
return key.slice(0, -2);
|
|
55
|
+
if (key.endsWith('_id'))
|
|
56
|
+
return key.slice(0, -3);
|
|
57
|
+
if (key === 'id')
|
|
58
|
+
return '';
|
|
59
|
+
return key;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=normalizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizer.js","sourceRoot":"","sources":["../../src/normalizer.ts"],"names":[],"mappings":"AAWA,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;AAE/F;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc,EAAE,OAAyB;IACtE,IAAI,KAAK,IAAI,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAC1D,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,KAAK,GAAG,OAAO,CAAC,UAAU,KAAK,KAAK,CAAC;IAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAA4B,CAAC;IAC3D,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QAC/E,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,SAAS;QACvC,IAAI,KAAK,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC;YAAE,SAAS;QAC/E,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QACpC,IAAI,IAAI;YAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAC/B,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,cAAc,CAAC,IAAqB;IAC3C,IAAI,OAAO,IAAI,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAC5C,IAAI,IAAI,KAAK,WAAW;QAAE,OAAO,WAAW,CAAC;IAC7C,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAE,CAAY,CAAC,WAAW,EAAE,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,GAAG;SACP,OAAO,CAAC,uBAAuB,EAAE,OAAO,CAAC;SACzC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC;SACtC,WAAW,EAAE,CAAC;AACnB,CAAC;AAED,SAAS,OAAO,CAAC,GAAW;IAC1B,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAChD,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACjD,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC5B,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported filter operators for generic column-based filtering.
|
|
3
|
+
*
|
|
4
|
+
* Compatible with flip-nestjs SqlQueryBuilderService ColumnFilter[].
|
|
5
|
+
*/
|
|
6
|
+
export type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'iContains' | 'startsWith' | 'endsWith' | 'gt' | 'gte' | 'lt' | 'lte' | 'between' | 'notBetween' | 'in' | 'notIn' | 'isAnyOf' | 'isEmpty' | 'isNotEmpty' | 'isNull' | 'isNotNull' | 'exists' | 'notExists';
|
|
7
|
+
/**
|
|
8
|
+
* All valid operator strings, used for runtime validation.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FILTER_OPERATORS: readonly FilterOperator[];
|
|
11
|
+
/**
|
|
12
|
+
* SQL-symbol aliases accepted on input and normalized to a canonical
|
|
13
|
+
* {@link FilterOperator} before validation and query building. Lets callers
|
|
14
|
+
* write the familiar `=`/`!=`/`<`/`>` shorthands instead of the named form.
|
|
15
|
+
*/
|
|
16
|
+
export declare const OPERATOR_ALIASES: Readonly<Record<string, FilterOperator>>;
|
|
17
|
+
/**
|
|
18
|
+
* An operator alias string (e.g. `'='`). Always normalized to a canonical
|
|
19
|
+
* {@link FilterOperator} via `normalizeOperator`.
|
|
20
|
+
*/
|
|
21
|
+
export type FilterOperatorAlias = keyof typeof OPERATOR_ALIASES;
|
|
22
|
+
/**
|
|
23
|
+
* Operator value accepted on input: either a canonical {@link FilterOperator}
|
|
24
|
+
* or one of its SQL-symbol {@link FilterOperatorAlias aliases}.
|
|
25
|
+
*/
|
|
26
|
+
export type FilterOperatorInput = FilterOperator | FilterOperatorAlias;
|
|
27
|
+
/**
|
|
28
|
+
* A single column filter condition with optional AND/OR composition.
|
|
29
|
+
*/
|
|
30
|
+
export interface ColumnFilter {
|
|
31
|
+
field: string;
|
|
32
|
+
operator: FilterOperatorInput;
|
|
33
|
+
value?: unknown;
|
|
34
|
+
AND?: ColumnFilter[];
|
|
35
|
+
OR?: ColumnFilter[];
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=operators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operators.d.ts","sourceRoot":"","sources":["../../src/operators.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACtB,QAAQ,GACR,WAAW,GACX,UAAU,GACV,aAAa,GACb,WAAW,GACX,YAAY,GACZ,UAAU,GACV,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,SAAS,GACT,YAAY,GACZ,IAAI,GACJ,OAAO,GACP,SAAS,GACT,SAAS,GACT,YAAY,GACZ,QAAQ,GACR,WAAW,GACX,QAAQ,GACR,WAAW,CAAC;AAEhB;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAuB5C,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAS5D,CAAC;AAEX;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,gBAAgB,CAAC;AAEhE;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,mBAAmB,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,YAAY,EAAE,CAAC;IACrB,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC;CACrB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All valid operator strings, used for runtime validation.
|
|
3
|
+
*/
|
|
4
|
+
export const FILTER_OPERATORS = [
|
|
5
|
+
'equals',
|
|
6
|
+
'notEquals',
|
|
7
|
+
'contains',
|
|
8
|
+
'notContains',
|
|
9
|
+
'iContains',
|
|
10
|
+
'startsWith',
|
|
11
|
+
'endsWith',
|
|
12
|
+
'gt',
|
|
13
|
+
'gte',
|
|
14
|
+
'lt',
|
|
15
|
+
'lte',
|
|
16
|
+
'between',
|
|
17
|
+
'notBetween',
|
|
18
|
+
'in',
|
|
19
|
+
'notIn',
|
|
20
|
+
'isAnyOf',
|
|
21
|
+
'isEmpty',
|
|
22
|
+
'isNotEmpty',
|
|
23
|
+
'isNull',
|
|
24
|
+
'isNotNull',
|
|
25
|
+
'exists',
|
|
26
|
+
'notExists',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* SQL-symbol aliases accepted on input and normalized to a canonical
|
|
30
|
+
* {@link FilterOperator} before validation and query building. Lets callers
|
|
31
|
+
* write the familiar `=`/`!=`/`<`/`>` shorthands instead of the named form.
|
|
32
|
+
*/
|
|
33
|
+
export const OPERATOR_ALIASES = {
|
|
34
|
+
'=': 'equals',
|
|
35
|
+
'==': 'equals',
|
|
36
|
+
'!=': 'notEquals',
|
|
37
|
+
'<>': 'notEquals',
|
|
38
|
+
'>': 'gt',
|
|
39
|
+
'>=': 'gte',
|
|
40
|
+
'<': 'lt',
|
|
41
|
+
'<=': 'lte',
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=operators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operators.js","sourceRoot":"","sources":["../../src/operators.ts"],"names":[],"mappings":"AA6BA;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA8B;IACzD,QAAQ;IACR,WAAW;IACX,UAAU;IACV,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU;IACV,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,KAAK;IACL,SAAS;IACT,YAAY;IACZ,IAAI;IACJ,OAAO;IACP,SAAS;IACT,SAAS;IACT,YAAY;IACZ,QAAQ;IACR,WAAW;IACX,QAAQ;IACR,WAAW;CACH,CAAC;AAEX;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA6C;IACxE,GAAG,EAAE,QAAQ;IACb,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,WAAW;IACjB,GAAG,EAAE,IAAI;IACT,IAAI,EAAE,KAAK;IACX,GAAG,EAAE,IAAI;IACT,IAAI,EAAE,KAAK;CACH,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ColumnFilter } from './operators.js';
|
|
2
|
+
import type { FilterInput, SortItem } from './types.js';
|
|
3
|
+
/** Map one `filter[field]=…` entry to `ColumnFilter[]` (Spatie/JSON:API shapes). */
|
|
4
|
+
export declare function toColumnFilters(field: string, value: unknown): ColumnFilter[];
|
|
5
|
+
/** Parse the `sort` param (`-createdAt,name` or `sort[]=…`) into ordered {@link SortItem}s. */
|
|
6
|
+
export declare function parseSort(sort: unknown): SortItem[];
|
|
7
|
+
/**
|
|
8
|
+
* Parse a decoded request query object — e.g. AdonisJS `ctx.request.qs()` — into
|
|
9
|
+
* a structured {@link FilterInput}. Understands the Spatie / JSON:API shapes the
|
|
10
|
+
* `@adonis-agora/filter-client` builder emits:
|
|
11
|
+
*
|
|
12
|
+
* - `filter[status]=active` → equals
|
|
13
|
+
* - `filter[id]=1,2,3` / `filter[id][]=1&filter[id][]=2` → IN
|
|
14
|
+
* - `filter[age][gte]=18` → operator filter
|
|
15
|
+
* - `sort=-createdAt,name` → sort items
|
|
16
|
+
* - `search=term`, `page`/`size` (or `page[number]`/`page[size]`)
|
|
17
|
+
*
|
|
18
|
+
* Pure reshape — no validation or allow-listing here; that happens in
|
|
19
|
+
* {@link applyFilter} against the {@link FilterConfig}.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseFilterRequest(qs: Record<string, unknown>): FilterInput;
|
|
22
|
+
//# sourceMappingURL=parse_request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse_request.d.ts","sourceRoot":"","sources":["../../src/parse_request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,gBAAgB,CAAC;AACxE,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAWxD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,CA4B7E;AAED,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,EAAE,CAanD;AAcD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAqB3E"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
function toInt(value) {
|
|
2
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
3
|
+
return value;
|
|
4
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
5
|
+
const n = Number(value);
|
|
6
|
+
if (Number.isFinite(n))
|
|
7
|
+
return n;
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
/** Map one `filter[field]=…` entry to `ColumnFilter[]` (Spatie/JSON:API shapes). */
|
|
12
|
+
export function toColumnFilters(field, value) {
|
|
13
|
+
// Array (`filter[id][]=1&filter[id][]=2`) → IN.
|
|
14
|
+
if (Array.isArray(value)) {
|
|
15
|
+
return [{ field, operator: 'in', value }];
|
|
16
|
+
}
|
|
17
|
+
// Operator object (`filter[age][gte]=18`) → one filter per operator key.
|
|
18
|
+
if (value != null && typeof value === 'object') {
|
|
19
|
+
return Object.entries(value).map(([operator, opValue]) => ({
|
|
20
|
+
field,
|
|
21
|
+
operator: operator,
|
|
22
|
+
value: opValue,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
// Comma-separated scalar (Spatie multi-value convention) → IN.
|
|
26
|
+
if (typeof value === 'string' && value.includes(',')) {
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
field,
|
|
30
|
+
operator: 'in',
|
|
31
|
+
value: value
|
|
32
|
+
.split(',')
|
|
33
|
+
.map((s) => s.trim())
|
|
34
|
+
.filter((s) => s.length > 0),
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
// Bare scalar → equals.
|
|
39
|
+
return [{ field, operator: 'equals', value }];
|
|
40
|
+
}
|
|
41
|
+
/** Parse the `sort` param (`-createdAt,name` or `sort[]=…`) into ordered {@link SortItem}s. */
|
|
42
|
+
export function parseSort(sort) {
|
|
43
|
+
let raw = [];
|
|
44
|
+
if (typeof sort === 'string')
|
|
45
|
+
raw = sort.split(',');
|
|
46
|
+
else if (Array.isArray(sort))
|
|
47
|
+
raw = sort.filter((s) => typeof s === 'string');
|
|
48
|
+
const items = [];
|
|
49
|
+
for (const entry of raw) {
|
|
50
|
+
const field = entry.trim();
|
|
51
|
+
if (field.length === 0)
|
|
52
|
+
continue;
|
|
53
|
+
if (field.startsWith('-'))
|
|
54
|
+
items.push({ field: field.slice(1), direction: 'desc' });
|
|
55
|
+
else
|
|
56
|
+
items.push({ field, direction: 'asc' });
|
|
57
|
+
}
|
|
58
|
+
return items;
|
|
59
|
+
}
|
|
60
|
+
function parsePagination(qs) {
|
|
61
|
+
let page = toInt(qs.page);
|
|
62
|
+
let size = toInt(qs.size);
|
|
63
|
+
// JSON:API nested form: page[number] / page[size].
|
|
64
|
+
if (qs.page != null && typeof qs.page === 'object' && !Array.isArray(qs.page)) {
|
|
65
|
+
const p = qs.page;
|
|
66
|
+
page = toInt(p.number) ?? page;
|
|
67
|
+
size = toInt(p.size) ?? size;
|
|
68
|
+
}
|
|
69
|
+
return { ...(page !== undefined && { page }), ...(size !== undefined && { size }) };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse a decoded request query object — e.g. AdonisJS `ctx.request.qs()` — into
|
|
73
|
+
* a structured {@link FilterInput}. Understands the Spatie / JSON:API shapes the
|
|
74
|
+
* `@adonis-agora/filter-client` builder emits:
|
|
75
|
+
*
|
|
76
|
+
* - `filter[status]=active` → equals
|
|
77
|
+
* - `filter[id]=1,2,3` / `filter[id][]=1&filter[id][]=2` → IN
|
|
78
|
+
* - `filter[age][gte]=18` → operator filter
|
|
79
|
+
* - `sort=-createdAt,name` → sort items
|
|
80
|
+
* - `search=term`, `page`/`size` (or `page[number]`/`page[size]`)
|
|
81
|
+
*
|
|
82
|
+
* Pure reshape — no validation or allow-listing here; that happens in
|
|
83
|
+
* {@link applyFilter} against the {@link FilterConfig}.
|
|
84
|
+
*/
|
|
85
|
+
export function parseFilterRequest(qs) {
|
|
86
|
+
const out = {};
|
|
87
|
+
if (qs.filter != null && typeof qs.filter === 'object' && !Array.isArray(qs.filter)) {
|
|
88
|
+
const filters = [];
|
|
89
|
+
for (const [field, value] of Object.entries(qs.filter)) {
|
|
90
|
+
filters.push(...toColumnFilters(field, value));
|
|
91
|
+
}
|
|
92
|
+
if (filters.length > 0)
|
|
93
|
+
out.filters = filters;
|
|
94
|
+
}
|
|
95
|
+
const sort = parseSort(qs.sort);
|
|
96
|
+
if (sort.length > 0)
|
|
97
|
+
out.sort = sort;
|
|
98
|
+
if (typeof qs.search === 'string' && qs.search.length > 0)
|
|
99
|
+
out.search = qs.search;
|
|
100
|
+
const { page, size } = parsePagination(qs);
|
|
101
|
+
if (page !== undefined)
|
|
102
|
+
out.page = page;
|
|
103
|
+
if (size !== undefined)
|
|
104
|
+
out.size = size;
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=parse_request.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse_request.js","sourceRoot":"","sources":["../../src/parse_request.ts"],"names":[],"mappings":"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"}
|