@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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/commands/main.d.ts +17 -0
  4. package/dist/commands/main.d.ts.map +1 -0
  5. package/dist/commands/main.js +17 -0
  6. package/dist/commands/main.js.map +1 -0
  7. package/dist/commands/make_filter_client.d.ts +35 -0
  8. package/dist/commands/make_filter_client.d.ts.map +1 -0
  9. package/dist/commands/make_filter_client.js +85 -0
  10. package/dist/commands/make_filter_client.js.map +1 -0
  11. package/dist/src/apply_from_request.d.ts +69 -0
  12. package/dist/src/apply_from_request.d.ts.map +1 -0
  13. package/dist/src/apply_from_request.js +115 -0
  14. package/dist/src/apply_from_request.js.map +1 -0
  15. package/dist/src/cursor.d.ts +92 -0
  16. package/dist/src/cursor.d.ts.map +1 -0
  17. package/dist/src/cursor.js +107 -0
  18. package/dist/src/cursor.js.map +1 -0
  19. package/dist/src/escape-like.d.ts +10 -0
  20. package/dist/src/escape-like.d.ts.map +1 -0
  21. package/dist/src/escape-like.js +15 -0
  22. package/dist/src/escape-like.js.map +1 -0
  23. package/dist/src/field_aliases.d.ts +28 -0
  24. package/dist/src/field_aliases.d.ts.map +1 -0
  25. package/dist/src/field_aliases.js +61 -0
  26. package/dist/src/field_aliases.js.map +1 -0
  27. package/dist/src/filter_spec.d.ts +146 -0
  28. package/dist/src/filter_spec.d.ts.map +1 -0
  29. package/dist/src/filter_spec.js +140 -0
  30. package/dist/src/filter_spec.js.map +1 -0
  31. package/dist/src/generate_client.d.ts +107 -0
  32. package/dist/src/generate_client.d.ts.map +1 -0
  33. package/dist/src/generate_client.js +154 -0
  34. package/dist/src/generate_client.js.map +1 -0
  35. package/dist/src/index.d.ts +28 -0
  36. package/dist/src/index.d.ts.map +1 -0
  37. package/dist/src/index.js +18 -0
  38. package/dist/src/index.js.map +1 -0
  39. package/dist/src/lucid_adapter.d.ts +213 -0
  40. package/dist/src/lucid_adapter.d.ts.map +1 -0
  41. package/dist/src/lucid_adapter.js +303 -0
  42. package/dist/src/lucid_adapter.js.map +1 -0
  43. package/dist/src/normalizer.d.ts +21 -0
  44. package/dist/src/normalizer.d.ts.map +1 -0
  45. package/dist/src/normalizer.js +61 -0
  46. package/dist/src/normalizer.js.map +1 -0
  47. package/dist/src/operators.d.ts +37 -0
  48. package/dist/src/operators.d.ts.map +1 -0
  49. package/dist/src/operators.js +43 -0
  50. package/dist/src/operators.js.map +1 -0
  51. package/dist/src/parse_request.d.ts +22 -0
  52. package/dist/src/parse_request.d.ts.map +1 -0
  53. package/dist/src/parse_request.js +107 -0
  54. package/dist/src/parse_request.js.map +1 -0
  55. package/dist/src/runner.d.ts +46 -0
  56. package/dist/src/runner.d.ts.map +1 -0
  57. package/dist/src/runner.js +190 -0
  58. package/dist/src/runner.js.map +1 -0
  59. package/dist/src/source_resolver.d.ts +18 -0
  60. package/dist/src/source_resolver.d.ts.map +1 -0
  61. package/dist/src/source_resolver.js +64 -0
  62. package/dist/src/source_resolver.js.map +1 -0
  63. package/dist/src/spatie_parser.d.ts +38 -0
  64. package/dist/src/spatie_parser.d.ts.map +1 -0
  65. package/dist/src/spatie_parser.js +130 -0
  66. package/dist/src/spatie_parser.js.map +1 -0
  67. package/dist/src/testing.d.ts +62 -0
  68. package/dist/src/testing.d.ts.map +1 -0
  69. package/dist/src/testing.js +113 -0
  70. package/dist/src/testing.js.map +1 -0
  71. package/dist/src/types.d.ts +130 -0
  72. package/dist/src/types.d.ts.map +1 -0
  73. package/dist/src/types.js +2 -0
  74. package/dist/src/types.js.map +1 -0
  75. package/dist/src/validate-column-filter.d.ts +30 -0
  76. package/dist/src/validate-column-filter.d.ts.map +1 -0
  77. package/dist/src/validate-column-filter.js +148 -0
  78. package/dist/src/validate-column-filter.js.map +1 -0
  79. package/dist/src/value-shape.d.ts +18 -0
  80. package/dist/src/value-shape.d.ts.map +1 -0
  81. package/dist/src/value-shape.js +36 -0
  82. package/dist/src/value-shape.js.map +1 -0
  83. 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":"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"}
@@ -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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"}