@dudousxd/nestjs-filter 1.7.1 → 1.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @dudousxd/nestjs-filter
2
2
 
3
+ ## 1.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#27](https://github.com/DavideCarvalho/nestjs-filter/pull/27) [`ab90005`](https://github.com/DavideCarvalho/nestjs-filter/commit/ab90005b46894bc560eee5047e9a0dca1e6495e2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Ecosystem improvements across the filter core and both ORM adapters.
8
+
9
+ - **Full-text vector search fix**: switched Postgres full-text matching to `websearch_to_tsquery` for correct, user-friendly query parsing, with optional `ts_rank`-based relevance ordering.
10
+ - **`throwOnInvalid` policy**: opt-in strict mode that rejects unknown fields/operators/invalid input instead of silently dropping them.
11
+ - **`defaultSort`**: configure a fallback sort applied when no explicit sort is requested.
12
+ - **Deterministic query param names**: stable, predictable parameter naming in generated queries for easier debugging and caching.
13
+ - **Cursor / keyset pagination**: stable, non-overlapping cursor-based pagination for both the TypeORM and MikroORM adapters.
14
+ - **Per-field operator allowlist**: restrict which operators are permitted on a per-field basis.
15
+ - **Computed / virtual field filtering**: filter on computed/virtual fields that are not plain columns.
16
+ - **Opt-in spatie / JSON:API query syntax**: support for `filter[field][op]` bracket syntax and sparse fieldsets, enabled opt-in.
17
+ - **Cross-adapter contract suite + testcontainers**: a shared contract test suite run against both adapters, backed by Postgres and MySQL testcontainers for real-DB integration coverage.
18
+
3
19
  ## 1.7.1
4
20
 
5
21
  ### Patch Changes
@@ -27,6 +27,17 @@ export interface EntityRelationInfo {
27
27
  /** Relation cardinality type. */
28
28
  type: 'one-to-one' | 'many-to-one' | 'one-to-many' | 'many-to-many';
29
29
  }
30
+ /**
31
+ * Behavior flags for full-text vector search ({@link FilterAdapter.applyVectorSearch}).
32
+ */
33
+ export interface VectorSearchOptions {
34
+ /**
35
+ * When true, add relevance ordering for matched rows (e.g. Postgres
36
+ * `ORDER BY ts_rank(...) DESC`). Off by default because it changes the
37
+ * query's default ordering.
38
+ */
39
+ rank?: boolean;
40
+ }
30
41
  export interface FilterAdapter {
31
42
  createQueryBuilder<E>(entity: Type<E>): unknown;
32
43
  /**
@@ -51,8 +62,11 @@ export interface FilterAdapter {
51
62
  *
52
63
  * @param qb - The query builder instance.
53
64
  * @param filters - Array of ColumnFilter conditions to apply.
65
+ * @param entity - The entity being queried. When provided, lets the adapter
66
+ * resolve column types (e.g. so `isEmpty`/`isNotEmpty` only compare against
67
+ * `''` on string columns — a DATE/number `col = ''` errors on MySQL).
54
68
  */
55
- applyColumnFilters?(qb: unknown, filters: ColumnFilter[]): void;
69
+ applyColumnFilters?(qb: unknown, filters: ColumnFilter[], entity?: Type<unknown>): void;
56
70
  /**
57
71
  * Applies an auto-field value to the query builder.
58
72
  *
@@ -188,13 +202,21 @@ export interface FilterAdapter {
188
202
  /**
189
203
  * Applies a full-text vector search using a tsvector column.
190
204
  *
205
+ * Implementations must parse arbitrary user input safely (e.g. Postgres
206
+ * `websearch_to_tsquery`, not the raw `to_tsquery`, which throws a syntax
207
+ * error on ordinary multi-word input like `"foo bar"`).
208
+ *
209
+ * When `opts.rank` is true, the adapter may add relevance ordering
210
+ * (e.g. `ts_rank`) for the matched rows.
211
+ *
191
212
  * Optional — adapters that don't support vector search should not implement this.
192
213
  *
193
214
  * @param qb - The query builder instance.
194
- * @param term - The search term.
215
+ * @param term - The search term (arbitrary user text).
195
216
  * @param vectorColumn - The tsvector column name.
217
+ * @param opts - Optional behavior flags (e.g. `{ rank: true }`).
196
218
  */
197
- applyVectorSearch?(qb: unknown, term: string, vectorColumn: string): void;
219
+ applyVectorSearch?(qb: unknown, term: string, vectorColumn: string, opts?: VectorSearchOptions): void;
198
220
  /**
199
221
  * Restricts the query to DISTINCT values of the given field(s).
200
222
  *
@@ -211,6 +233,21 @@ export interface FilterAdapter {
211
233
  * @param entity - The root entity class.
212
234
  */
213
235
  applyDistinct?(qb: unknown, fields: string[], entity: Type<unknown>): void;
236
+ /**
237
+ * Restricts the query's projection to the given field(s) — JSON:API "sparse
238
+ * fieldsets". Unlike {@link applyDistinct}, this adds **no** `DISTINCT`
239
+ * modifier; it only narrows the SELECT list (the primary key should remain
240
+ * selected so rows stay addressable). Active WHERE / search / sort /
241
+ * pagination are unaffected.
242
+ *
243
+ * Optional — adapters that don't support projection narrowing should not
244
+ * implement this.
245
+ *
246
+ * @param qb - The query builder instance.
247
+ * @param fields - Field/column names to select.
248
+ * @param entity - The root entity class.
249
+ */
250
+ applySelect?(qb: unknown, fields: string[], entity: Type<unknown>): void;
214
251
  /**
215
252
  * Applies sort ordering to the query builder.
216
253
  *
@@ -218,6 +255,33 @@ export interface FilterAdapter {
218
255
  * @param sorts - Array of SortItem directives (field + direction).
219
256
  */
220
257
  applySort?(qb: unknown, sorts: SortItem[]): void;
258
+ /**
259
+ * Applies a WHERE condition on a **computed/virtual field** — a developer-
260
+ * provided SQL expression declared via `@Filterable.computed`. Behaves like
261
+ * {@link applyAutoField} (scalar → equals, array → IN, operator object →
262
+ * those operators) but evaluates the raw `expression` instead of a column
263
+ * reference. The expression is dev-provided (never client input); values are
264
+ * always parameterized.
265
+ *
266
+ * Optional — adapters that don't support computed fields should not
267
+ * implement this.
268
+ *
269
+ * @param qb - The query builder instance.
270
+ * @param expression - The raw SQL expression for the computed field.
271
+ * @param value - The filter value (scalar, array, or operator object).
272
+ */
273
+ applyComputedField?(qb: unknown, expression: string, value: unknown): void;
274
+ /**
275
+ * Applies an ORDER BY on a computed/virtual field's SQL expression.
276
+ *
277
+ * Optional — adapters that don't support computed fields should not
278
+ * implement this.
279
+ *
280
+ * @param qb - The query builder instance.
281
+ * @param expression - The raw SQL expression for the computed field.
282
+ * @param direction - Sort direction.
283
+ */
284
+ applyComputedSort?(qb: unknown, expression: string, direction: 'asc' | 'desc'): void;
221
285
  /**
222
286
  * Applies offset-based pagination to the query builder.
223
287
  *
@@ -239,6 +303,58 @@ export interface FilterAdapter {
239
303
  rows: unknown[];
240
304
  total: number;
241
305
  }>;
306
+ /**
307
+ * Executes the query builder and returns the matching rows (no count). Used
308
+ * by `FilterRunner.findPage` for cursor/keyset pagination, where a total
309
+ * count is intentionally not computed.
310
+ *
311
+ * Optional — required only if you call `findPage`.
312
+ *
313
+ * @param qb - The query builder instance.
314
+ * @returns The fetched rows.
315
+ */
316
+ getResult?(qb: unknown): Promise<unknown[]>;
317
+ /**
318
+ * Returns the primary-key property name of the entity, used as the stable
319
+ * tiebreaker for keyset/cursor pagination. Returns `null` when no single
320
+ * primary key can be determined.
321
+ *
322
+ * Optional — required only for `findPage` cursor pagination.
323
+ *
324
+ * @param entity - The entity class.
325
+ */
326
+ getPrimaryKey?(entity: Type<unknown>): string | null;
327
+ /**
328
+ * Applies a keyset (cursor) WHERE predicate to the query builder for
329
+ * row-value comparison across the keyset columns.
330
+ *
331
+ * Given a keyset (the active sort columns plus a primary-key tiebreaker, each
332
+ * with a direction) and the boundary row's column values, the adapter adds a
333
+ * predicate equivalent to a lexicographic tuple comparison that selects rows
334
+ * strictly *after* the boundary in keyset order. Columns sorted `asc` use
335
+ * `>` and `desc` use `<` at each tier; ties on a column fall through to the
336
+ * next. Implementations must parameterize all values.
337
+ *
338
+ * Optional — required only for `findPage` cursor pagination.
339
+ *
340
+ * @param qb - The query builder instance.
341
+ * @param keyset - The keyset columns with directions (sort cols + pk).
342
+ * @param values - The boundary row's values, positionally aligned to keyset.
343
+ */
344
+ applyKeysetPagination?(qb: unknown, keyset: SortItem[], values: unknown[]): void;
345
+ /**
346
+ * Applies an ORDER BY for the keyset columns and a row LIMIT — the ordering
347
+ * and slice for a cursor page. Separate from {@link applySort} so `findPage`
348
+ * controls the keyset ordering (including direction reversal for backward
349
+ * paging) independently of the request `sort`.
350
+ *
351
+ * Optional — required only for `findPage` cursor pagination.
352
+ *
353
+ * @param qb - The query builder instance.
354
+ * @param keyset - The keyset columns with directions.
355
+ * @param limit - Max rows to fetch.
356
+ */
357
+ applyKeysetOrderAndLimit?(qb: unknown, keyset: SortItem[], limit: number): void;
242
358
  /**
243
359
  * Loads the given relations onto already-fetched rows in a **separate query**
244
360
  * (not a join), so that pagination of the parent is unaffected. Used by
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../src/adapter/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACrE;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,aAAa,GAAG,cAAc,CAAC;CACrE;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,kBAAkB,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;IAEhD;;;;;;;;;OASG;IACH,uBAAuB,CAAC,CACtB,EAAE,EAAE,OAAO,EACX,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,CAAC,UAAU,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/C,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAExF;;;;;;;;;;;;;;;;OAgBG;IACH,cAAc,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAElE;;;;;;;;;;;;OAYG;IACH,eAAe,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,eAAe,EAAE,GAAG,IAAI,CAAC;IAElE;;;;;;;;;;;;OAYG;IACH,kBAAkB,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,kBAAkB,EAAE,GAAG,IAAI,CAAC;IAExE;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,MAAM,GAAG,eAAe,EAAE,GAAG,IAAI,CAAC;IAEzF;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,gBAAgB,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;IAEpF;;;;;;;;;;;;;;;;;;OAkBG;IACH,sBAAsB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAEhG;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAE7E;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAExF;;;;;;;;;;;;;;;;OAgBG;IACH,iBAAiB,CAAC,CAChB,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,IAAI,CAAC,EAAE,mBAAmB,GACzB,IAAI,CAAC;IAER;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAE3E;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAEzE;;;;;OAKG;IACH,SAAS,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAEjD;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAE3E;;;;;;;;;OASG;IACH,iBAAiB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;IAErF;;;;;;OAMG;IACH,qBAAqB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAEtE;;;;;;;;OAQG;IACH,iBAAiB,CAAC,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE7E;;;;;;;;;OASG;IACH,SAAS,CAAC,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE5C;;;;;;;;OAQG;IACH,aAAa,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IAErD;;;;;;;;;;;;;;;;OAgBG;IACH,qBAAqB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAEjF;;;;;;;;;;;OAWG;IACH,wBAAwB,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhF;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvF"}
@@ -0,0 +1,30 @@
1
+ import type { FilterOperator } from '../operators/types.js';
2
+ import type { AllowedFieldEntry } from '../types.js';
3
+ /**
4
+ * A normalized view of a `@Filterable` `allowed` list:
5
+ *
6
+ * - `fields` — the set of allowed field-name keys (regardless of operator
7
+ * restrictions), suitable as the allowlist for sort/distinct/auto-field
8
+ * membership checks.
9
+ * - `operatorsByField` — for entries that restricted operators, the set of
10
+ * permitted operators per field. Fields declared as a plain string (allow
11
+ * all operators) are **absent** from this map.
12
+ */
13
+ export interface NormalizedAllowed {
14
+ fields: string[];
15
+ operatorsByField: Map<string, Set<FilterOperator>>;
16
+ }
17
+ /**
18
+ * Normalizes a `@Filterable` `allowed` list (which mixes plain field-name
19
+ * strings with `{ field, operators }` objects) into a {@link NormalizedAllowed}.
20
+ *
21
+ * Returns `undefined` when no allowlist was declared.
22
+ */
23
+ export declare function normalizeAllowed(allowed: readonly AllowedFieldEntry[] | undefined): NormalizedAllowed | undefined;
24
+ /**
25
+ * Returns the allowed field-name keys from a `@Filterable` `allowed` list,
26
+ * discarding any per-field operator restrictions. Used wherever the prior
27
+ * code expected a plain `string[]` allowlist (sort, distinct, auto-fields).
28
+ */
29
+ export declare function allowedFieldNames(allowed: readonly AllowedFieldEntry[] | undefined): string[] | undefined;
30
+ //# sourceMappingURL=allowed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowed.d.ts","sourceRoot":"","sources":["../../src/decorator/allowed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;;;;;;GASG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,SAAS,iBAAiB,EAAE,GAAG,SAAS,GAChD,iBAAiB,GAAG,SAAS,CAa/B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,iBAAiB,EAAE,GAAG,SAAS,GAChD,MAAM,EAAE,GAAG,SAAS,CAGtB"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Normalizes a `@Filterable` `allowed` list (which mixes plain field-name
3
+ * strings with `{ field, operators }` objects) into a {@link NormalizedAllowed}.
4
+ *
5
+ * Returns `undefined` when no allowlist was declared.
6
+ */
7
+ export function normalizeAllowed(allowed) {
8
+ if (!allowed)
9
+ return undefined;
10
+ const fields = [];
11
+ const operatorsByField = new Map();
12
+ for (const entry of allowed) {
13
+ if (typeof entry === 'string') {
14
+ fields.push(entry);
15
+ continue;
16
+ }
17
+ fields.push(entry.field);
18
+ operatorsByField.set(entry.field, new Set(entry.operators));
19
+ }
20
+ return { fields, operatorsByField };
21
+ }
22
+ /**
23
+ * Returns the allowed field-name keys from a `@Filterable` `allowed` list,
24
+ * discarding any per-field operator restrictions. Used wherever the prior
25
+ * code expected a plain `string[]` allowlist (sort, distinct, auto-fields).
26
+ */
27
+ export function allowedFieldNames(allowed) {
28
+ if (!allowed)
29
+ return undefined;
30
+ return allowed.map((entry) => (typeof entry === 'string' ? entry : entry.field));
31
+ }
32
+ //# sourceMappingURL=allowed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowed.js","sourceRoot":"","sources":["../../src/decorator/allowed.ts"],"names":[],"mappings":"AAkBA;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAiD;IAEjD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAA+B,CAAC;IAChE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzB,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAiD;IAEjD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AACnF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"filterable.decorator.d.ts","sourceRoot":"","sources":["../../src/decorator/filterable.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErE,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAgBrE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEhF"}
1
+ {"version":3,"file":"filterable.decorator.d.ts","sourceRoot":"","sources":["../../src/decorator/filterable.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErE,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAmBrE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEhF"}
@@ -14,6 +14,9 @@ export function Filterable(options) {
14
14
  ...(options.allowed !== undefined && { allowed: options.allowed }),
15
15
  ...(options.blocked !== undefined && { blocked: options.blocked }),
16
16
  autoFields: options.autoFields ?? true,
17
+ ...(options.throwOnInvalid !== undefined && { throwOnInvalid: options.throwOnInvalid }),
18
+ ...(options.defaultSort !== undefined && { defaultSort: options.defaultSort }),
19
+ ...(options.computed !== undefined && { computed: options.computed }),
17
20
  };
18
21
  Reflect.defineMetadata(FILTERABLE_METADATA, meta, target);
19
22
  };
@@ -1 +1 @@
1
- {"version":3,"file":"filterable.decorator.js","sourceRoot":"","sources":["../../src/decorator/filterable.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGnD,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAChC,MAAM,IAAI,4BAA4B,CAAC,MAAM,CAAC,IAAI,IAAI,iBAAiB,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,kBAAkB,MAAM,CAAC,IAAI,6CAA6C,CAAC,CAAC;QAC9F,CAAC;QACD,MAAM,IAAI,GAAmB;YAC3B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClE,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;SACvC,CAAC;QACF,OAAO,CAAC,cAAc,CAAC,mBAAmB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAClD,OAAO,OAAO,CAAC,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAA+B,CAAC;AACxF,CAAC"}
1
+ {"version":3,"file":"filterable.decorator.js","sourceRoot":"","sources":["../../src/decorator/filterable.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,4BAA4B,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGnD,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAChC,MAAM,IAAI,4BAA4B,CAAC,MAAM,CAAC,IAAI,IAAI,iBAAiB,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,kBAAkB,MAAM,CAAC,IAAI,6CAA6C,CAAC,CAAC;QAC9F,CAAC;QACD,MAAM,IAAI,GAAmB;YAC3B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClE,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI;YACtC,GAAG,CAAC,OAAO,CAAC,cAAc,KAAK,SAAS,IAAI,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;YACvF,GAAG,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;YAC9E,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC;SACtE,CAAC;QACF,OAAO,CAAC,cAAc,CAAC,mBAAmB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAClD,OAAO,OAAO,CAAC,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAA+B,CAAC;AACxF,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export declare const VERSION = "0.0.0";
2
2
  export { BaseFilter } from './base-filter.js';
3
- export type { FilterAdapter, EntityFieldInfo, EntityRelationInfo } from './adapter/adapter.js';
3
+ export type { FilterAdapter, EntityFieldInfo, EntityRelationInfo, VectorSearchOptions, } from './adapter/adapter.js';
4
4
  export { Filterable, getFilterableMetadata } from './decorator/filterable.decorator.js';
5
+ export { normalizeAllowed, allowedFieldNames } from './decorator/allowed.js';
6
+ export type { NormalizedAllowed } from './decorator/allowed.js';
5
7
  export { FilterFor, getFilterForMap, getFilterForOptsMap, } from './decorator/filter-for.decorator.js';
6
8
  export type { FilterFieldTypeHint, FilterForOptions, } from './decorator/filter-for.decorator.js';
7
9
  export { ApplyFilter, getApplyFilterMetadata } from './decorator/apply-filter.decorator.js';
@@ -15,10 +17,13 @@ export { FilterRunner } from './runner.js';
15
17
  export { FilterModule } from './module.js';
16
18
  export { FilterException, FilterNotRegisteredException, FilterMissingEntityException, FilterStateUnavailableException, UnknownFilterKeyException, FilterValidationException, FilterMissingAdapterException, FilterMethodException, } from './errors/exceptions.js';
17
19
  export { FILTER_MODULE_OPTIONS, FILTER_ADAPTER, FILTERABLE_METADATA, FILTER_FOR_METADATA, FILTER_FOR_OPTS_METADATA, FILTER_RELATIONS_METADATA, APPLY_FILTER_METADATA, APPLY_FILTER_REQ_KEY, TENANT_SCOPED_METADATA, CONTEXT_ACCESSOR, } from './tokens.js';
18
- export type { ApplyFilterOptions, CursorPagination, EntityDescription, FieldMeta, FilterableOptions, FilterContext, FilterInput, FilterInputStrict, FilterInputLoose, FilterMetadata, FilterModuleOptions, FilterModuleOptionsFactory, FilterModuleAsyncOptions, InputNormalizer, InputSource, OffsetPagination, OnUnknownKey, RelationMeta, SortItem, StructuredInput, ValidationMode, } from './types.js';
20
+ export type { AllowedFieldEntry, ApplyFilterOptions, CursorPage, CursorPagination, EntityDescription, FieldMeta, FilterableOptions, FilterContext, FilterInput, FilterInputStrict, FilterInputLoose, FilterMetadata, FilterModuleOptions, FilterModuleOptionsFactory, FilterModuleAsyncOptions, InputFormat, InputNormalizer, InputSource, OffsetPagination, OnUnknownKey, RelationMeta, SortItem, StructuredInput, ValidationMode, } from './types.js';
19
21
  export { resolveInputFromRequest } from './input/source-resolver.js';
20
22
  export { normalizeInput } from './input/normalizer.js';
23
+ export { parseSpatieInput } from './input/spatie-parser.js';
21
24
  export { escapeLike } from './utils/escape-like.js';
25
+ export { encodeCursor, decodeCursor, buildKeyset, extractCursorValues, } from './pagination/cursor.js';
26
+ export type { CursorValues } from './pagination/cursor.js';
22
27
  export type { ColumnFilter, FilterOperator, FilterOperatorAlias, FilterOperatorInput, } from './operators/types.js';
23
28
  export { FILTER_OPERATORS, OPERATOR_ALIASES } from './operators/types.js';
24
29
  export { ColumnFilterDto } from './operators/column-filter.dto.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EACL,SAAS,EACT,eAAe,EACf,mBAAmB,GACpB,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACjG,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvF,OAAO,EACL,YAAY,EACZ,oBAAoB,GACrB,MAAM,wCAAwC,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,4BAA4B,EAC5B,4BAA4B,EAC5B,+BAA+B,EAC/B,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,YAAY,EACV,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,uCAAuC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,UAAU,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EACL,SAAS,EACT,eAAe,EACf,mBAAmB,GACpB,MAAM,qCAAqC,CAAC;AAC7C,YAAY,EACV,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACjG,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvF,OAAO,EACL,YAAY,EACZ,oBAAoB,GACrB,MAAM,wCAAwC,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACpF,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,4BAA4B,EAC5B,4BAA4B,EAC5B,+BAA+B,EAC/B,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,WAAW,EACX,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EACV,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,uCAAuC,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export const VERSION = '0.0.0';
2
2
  export { BaseFilter } from './base-filter.js';
3
3
  export { Filterable, getFilterableMetadata } from './decorator/filterable.decorator.js';
4
+ export { normalizeAllowed, allowedFieldNames } from './decorator/allowed.js';
4
5
  export { FilterFor, getFilterForMap, getFilterForOptsMap, } from './decorator/filter-for.decorator.js';
5
6
  export { ApplyFilter, getApplyFilterMetadata } from './decorator/apply-filter.decorator.js';
6
7
  export { Relations, getRelationsMap, resolveRelation } from './decorator/relations.decorator.js';
@@ -13,7 +14,9 @@ export { FilterException, FilterNotRegisteredException, FilterMissingEntityExcep
13
14
  export { FILTER_MODULE_OPTIONS, FILTER_ADAPTER, FILTERABLE_METADATA, FILTER_FOR_METADATA, FILTER_FOR_OPTS_METADATA, FILTER_RELATIONS_METADATA, APPLY_FILTER_METADATA, APPLY_FILTER_REQ_KEY, TENANT_SCOPED_METADATA, CONTEXT_ACCESSOR, } from './tokens.js';
14
15
  export { resolveInputFromRequest } from './input/source-resolver.js';
15
16
  export { normalizeInput } from './input/normalizer.js';
17
+ export { parseSpatieInput } from './input/spatie-parser.js';
16
18
  export { escapeLike } from './utils/escape-like.js';
19
+ export { encodeCursor, decodeCursor, buildKeyset, extractCursorValues, } from './pagination/cursor.js';
17
20
  export { FILTER_OPERATORS, OPERATOR_ALIASES } from './operators/types.js';
18
21
  export { ColumnFilterDto } from './operators/column-filter.dto.js';
19
22
  export { validateColumnFilter, validateColumnFilters, normalizeOperator, InvalidColumnFilterError, MAX_FILTER_DEPTH, } from './operators/validate-column-filter.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EACL,SAAS,EACT,eAAe,EACf,mBAAmB,GACpB,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAEjG,OAAO,EACL,YAAY,EACZ,oBAAoB,GACrB,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,4BAA4B,EAC5B,4BAA4B,EAC5B,+BAA+B,EAC/B,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAwBrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAOpD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,uCAAuC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAO9C,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE7E,OAAO,EACL,SAAS,EACT,eAAe,EACf,mBAAmB,GACpB,MAAM,qCAAqC,CAAC;AAK7C,OAAO,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC5F,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAEjG,OAAO,EACL,YAAY,EACZ,oBAAoB,GACrB,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,4BAA4B,EAC5B,4BAA4B,EAC5B,+BAA+B,EAC/B,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,oBAAoB,EACpB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AA2BrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAQhC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,uCAAuC,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { StructuredInput } from '../types.js';
2
+ /**
3
+ * Parses a spatie-laravel-query-builder / JSON:API-style query input into the
4
+ * library's internal {@link StructuredInput} model.
5
+ *
6
+ * This is an **opt-in** input format (enabled via the `inputFormat: 'spatie'`
7
+ * module option). The default `'native'` format is unchanged. The parser is a
8
+ * pure reshape — it does **not** enforce safety itself; its output flows through
9
+ * the same runner pipeline as native input, so the allowlist, per-field operator
10
+ * allowlist, and `throwOnInvalid` policy still apply.
11
+ *
12
+ * The input is the object produced after a query string has been decoded by
13
+ * Express + `qs` (bracket notation expanded into nested objects):
14
+ *
15
+ * | Query string | Decoded input | Mapped to |
16
+ * | ------------------------------------ | ----------------------------------------------- | ------------------------------------------ |
17
+ * | `filter[name]=Al` | `{ filter: { name: 'Al' } }` | `{ filter: { name: 'Al' } }` (equals) |
18
+ * | `filter[id]=1,2,3` | `{ filter: { id: '1,2,3' } }` | `{ filter: { id: ['1','2','3'] } }` (in) |
19
+ * | `filter[name][contains]=Al` | `{ filter: { name: { contains: 'Al' } } }` | operator object → existing operator path |
20
+ * | `sort=-createdAt,name` | `{ sort: '-createdAt,name' }` | `{ sort: '-createdAt,name' }` |
21
+ * | `include=posts,comments` | `{ include: 'posts,comments' }` | `{ include: 'posts,comments' }` |
22
+ * | `fields[users]=id,name` | `{ fields: { users: 'id,name' } }` | `{ select: ['id','name'] }` (sparse) |
23
+ * | `page[number]=2&page[size]=10` | `{ page: { number: '2', size: '10' } }` | `{ paginate: { page: 2, size: 10 } }` |
24
+ * | `page[after]=cur&page[size]=10` | `{ page: { after: 'cur', size: '10' } }` | `{ paginate: { after, first } }` (cursor) |
25
+ *
26
+ * Design notes:
27
+ * - **Filter values**: a bare scalar is interpreted by the adapter as `equals`;
28
+ * a comma-separated scalar (spatie's convention for multi-value filters) is
29
+ * split into an array (interpreted as `in`); an object whose keys are operators
30
+ * passes straight through to the existing operator-object mechanism.
31
+ * - **Sparse fieldsets** (`fields[resource]`): JSON:API keys these by resource
32
+ * type, but the runner applies a single SELECT to the primary entity, so the
33
+ * per-resource lists are flattened (deduped, order-preserving) into one
34
+ * `select` list and validated against the entity / allowlist downstream.
35
+ * - **Pagination**: JSON:API page-based (`page[number]`/`page[size]`) maps to
36
+ * offset pagination; cursor-style (`page[after]`/`page[before]`) maps to the
37
+ * library's keyset pagination input.
38
+ */
39
+ export declare function parseSpatieInput(input: unknown): StructuredInput;
40
+ //# sourceMappingURL=spatie-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatie-parser.d.ts","sourceRoot":"","sources":["../../src/input/spatie-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAmChE"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Parses a spatie-laravel-query-builder / JSON:API-style query input into the
3
+ * library's internal {@link StructuredInput} model.
4
+ *
5
+ * This is an **opt-in** input format (enabled via the `inputFormat: 'spatie'`
6
+ * module option). The default `'native'` format is unchanged. The parser is a
7
+ * pure reshape — it does **not** enforce safety itself; its output flows through
8
+ * the same runner pipeline as native input, so the allowlist, per-field operator
9
+ * allowlist, and `throwOnInvalid` policy still apply.
10
+ *
11
+ * The input is the object produced after a query string has been decoded by
12
+ * Express + `qs` (bracket notation expanded into nested objects):
13
+ *
14
+ * | Query string | Decoded input | Mapped to |
15
+ * | ------------------------------------ | ----------------------------------------------- | ------------------------------------------ |
16
+ * | `filter[name]=Al` | `{ filter: { name: 'Al' } }` | `{ filter: { name: 'Al' } }` (equals) |
17
+ * | `filter[id]=1,2,3` | `{ filter: { id: '1,2,3' } }` | `{ filter: { id: ['1','2','3'] } }` (in) |
18
+ * | `filter[name][contains]=Al` | `{ filter: { name: { contains: 'Al' } } }` | operator object → existing operator path |
19
+ * | `sort=-createdAt,name` | `{ sort: '-createdAt,name' }` | `{ sort: '-createdAt,name' }` |
20
+ * | `include=posts,comments` | `{ include: 'posts,comments' }` | `{ include: 'posts,comments' }` |
21
+ * | `fields[users]=id,name` | `{ fields: { users: 'id,name' } }` | `{ select: ['id','name'] }` (sparse) |
22
+ * | `page[number]=2&page[size]=10` | `{ page: { number: '2', size: '10' } }` | `{ paginate: { page: 2, size: 10 } }` |
23
+ * | `page[after]=cur&page[size]=10` | `{ page: { after: 'cur', size: '10' } }` | `{ paginate: { after, first } }` (cursor) |
24
+ *
25
+ * Design notes:
26
+ * - **Filter values**: a bare scalar is interpreted by the adapter as `equals`;
27
+ * a comma-separated scalar (spatie's convention for multi-value filters) is
28
+ * split into an array (interpreted as `in`); an object whose keys are operators
29
+ * passes straight through to the existing operator-object mechanism.
30
+ * - **Sparse fieldsets** (`fields[resource]`): JSON:API keys these by resource
31
+ * type, but the runner applies a single SELECT to the primary entity, so the
32
+ * per-resource lists are flattened (deduped, order-preserving) into one
33
+ * `select` list and validated against the entity / allowlist downstream.
34
+ * - **Pagination**: JSON:API page-based (`page[number]`/`page[size]`) maps to
35
+ * offset pagination; cursor-style (`page[after]`/`page[before]`) maps to the
36
+ * library's keyset pagination input.
37
+ */
38
+ export function parseSpatieInput(input) {
39
+ if (input == null || typeof input !== 'object')
40
+ return {};
41
+ const src = input;
42
+ const out = {};
43
+ if (src.filter != null && typeof src.filter === 'object' && !Array.isArray(src.filter)) {
44
+ out.filter = parseFilters(src.filter);
45
+ }
46
+ if (typeof src.sort === 'string') {
47
+ out.sort = src.sort;
48
+ }
49
+ else if (Array.isArray(src.sort)) {
50
+ // `sort[]=a&sort[]=-b` decodes to an array — join to the JSON:API string form.
51
+ out.sort = src.sort.filter((s) => typeof s === 'string').join(',');
52
+ }
53
+ if (typeof src.include === 'string') {
54
+ out.include = src.include;
55
+ }
56
+ else if (Array.isArray(src.include)) {
57
+ out.include = src.include.filter((s) => typeof s === 'string');
58
+ }
59
+ if (typeof src.search === 'string') {
60
+ out.search = src.search;
61
+ }
62
+ const select = parseSparseFieldsets(src.fields);
63
+ if (select.length > 0) {
64
+ out.select = select;
65
+ }
66
+ const paginate = parsePage(src.page);
67
+ if (paginate)
68
+ out.paginate = paginate;
69
+ return out;
70
+ }
71
+ /**
72
+ * Reshapes the `filter` map: scalar values become equals (with comma → array),
73
+ * operator objects pass through unchanged, and arrays are kept as-is.
74
+ */
75
+ function parseFilters(filter) {
76
+ const out = {};
77
+ for (const [field, value] of Object.entries(filter)) {
78
+ if (Array.isArray(value)) {
79
+ out[field] = value;
80
+ }
81
+ else if (value != null && typeof value === 'object') {
82
+ // Operator object (`{ contains: 'x' }`) — passed straight through; the
83
+ // runner / adapter interprets and validates the operator keys.
84
+ out[field] = value;
85
+ }
86
+ else if (typeof value === 'string' && value.includes(',')) {
87
+ // spatie multi-value filter convention: comma-separated → array (IN).
88
+ out[field] = value
89
+ .split(',')
90
+ .map((s) => s.trim())
91
+ .filter((s) => s.length > 0);
92
+ }
93
+ else {
94
+ out[field] = value;
95
+ }
96
+ }
97
+ return out;
98
+ }
99
+ /**
100
+ * Flattens JSON:API sparse fieldsets (`fields[resource]=a,b`) into a single
101
+ * order-preserving, de-duplicated list of column names. Per-resource keys are
102
+ * collapsed because the runner applies one SELECT to the primary entity; unknown
103
+ * columns are dropped downstream when validated against entity metadata.
104
+ */
105
+ function parseSparseFieldsets(fields) {
106
+ if (fields == null || typeof fields !== 'object' || Array.isArray(fields))
107
+ return [];
108
+ const seen = new Set();
109
+ const out = [];
110
+ for (const value of Object.values(fields)) {
111
+ const cols = typeof value === 'string'
112
+ ? value.split(',')
113
+ : Array.isArray(value)
114
+ ? value.filter((s) => typeof s === 'string')
115
+ : [];
116
+ for (const raw of cols) {
117
+ const col = raw.trim();
118
+ if (col.length > 0 && !seen.has(col)) {
119
+ seen.add(col);
120
+ out.push(col);
121
+ }
122
+ }
123
+ }
124
+ return out;
125
+ }
126
+ /**
127
+ * Maps a JSON:API `page` object to the library's pagination input.
128
+ * `page[number]`/`page[size]` → offset; `page[after]`/`page[before]` → cursor.
129
+ */
130
+ function parsePage(page) {
131
+ if (page == null || typeof page !== 'object' || Array.isArray(page))
132
+ return undefined;
133
+ const p = page;
134
+ const after = typeof p.after === 'string' ? p.after : undefined;
135
+ const before = typeof p.before === 'string' ? p.before : undefined;
136
+ const size = toInt(p.size);
137
+ if (after !== undefined || before !== undefined) {
138
+ if (after !== undefined) {
139
+ return { after, ...(size !== undefined && { first: size }) };
140
+ }
141
+ return { before: before, ...(size !== undefined && { last: size }) };
142
+ }
143
+ const number = toInt(p.number);
144
+ if (number !== undefined || size !== undefined) {
145
+ return { page: number ?? 0, size: size ?? 25 };
146
+ }
147
+ return undefined;
148
+ }
149
+ function toInt(value) {
150
+ if (typeof value === 'number' && Number.isFinite(value))
151
+ return value;
152
+ if (typeof value === 'string' && value.trim() !== '') {
153
+ const n = Number(value);
154
+ if (Number.isFinite(n))
155
+ return n;
156
+ }
157
+ return undefined;
158
+ }
159
+ //# sourceMappingURL=spatie-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatie-parser.js","sourceRoot":"","sources":["../../src/input/spatie-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { SortItem } from '../types.js';
2
+ /**
3
+ * A decoded keyset cursor: the ordered values of the keyset columns (the active
4
+ * sort columns plus a stable primary-key tiebreaker) captured from a boundary
5
+ * row. The values line up positionally with the keyset's `SortItem[]`.
6
+ */
7
+ export type CursorValues = unknown[];
8
+ /**
9
+ * Encodes keyset cursor values into a compact, URL-safe opaque string
10
+ * (base64url of a JSON array). The shape is intentionally opaque to clients —
11
+ * only this module reads it back.
12
+ *
13
+ * `Date` values are encoded as `{ $d: <iso> }` so they round-trip to `Date`
14
+ * instances on decode (plain JSON would yield a string and break date keyset
15
+ * comparisons).
16
+ */
17
+ export declare function encodeCursor(values: CursorValues): string;
18
+ /**
19
+ * Decodes an opaque cursor string back into its keyset values. Returns `null`
20
+ * when the cursor is malformed (bad base64, bad JSON, or not an array) so the
21
+ * caller can ignore an invalid cursor instead of crashing.
22
+ */
23
+ export declare function decodeCursor(cursor: string): CursorValues | null;
24
+ /**
25
+ * Builds the keyset `SortItem[]` for cursor pagination: the caller's effective
26
+ * sorts, with a stable primary-key tiebreaker appended if it is not already
27
+ * present. The tiebreaker inherits the direction of the last sort column so the
28
+ * overall ordering stays monotonic (important for a correct `(cols, pk) > (...)`
29
+ * comparison).
30
+ */
31
+ export declare function buildKeyset(sorts: SortItem[], primaryKey: string): SortItem[];
32
+ /**
33
+ * Extracts the keyset values from a fetched row, in keyset column order.
34
+ * Supports dotted relation paths (e.g. `author.name`) by walking the object.
35
+ */
36
+ export declare function extractCursorValues(row: Record<string, unknown>, keyset: SortItem[]): CursorValues;
37
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/pagination/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,EAAE,CAAC;AAErC;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAOzD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAahE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,QAAQ,EAAE,CAK7E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,MAAM,EAAE,QAAQ,EAAE,GACjB,YAAY,CAUd"}