@adonis-agora/filter 0.0.0-beta-20261110131309
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +417 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/commands/main.d.ts +19 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +19 -0
- package/dist/commands/main.js.map +1 -0
- package/dist/commands/make_filter.d.ts +18 -0
- package/dist/commands/make_filter.d.ts.map +1 -0
- package/dist/commands/make_filter.js +32 -0
- package/dist/commands/make_filter.js.map +1 -0
- package/dist/commands/make_filter_client.d.ts +35 -0
- package/dist/commands/make_filter_client.d.ts.map +1 -0
- package/dist/commands/make_filter_client.js +85 -0
- package/dist/commands/make_filter_client.js.map +1 -0
- package/dist/configure.d.ts +15 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +20 -0
- package/dist/configure.js.map +1 -0
- package/dist/providers/filter_provider.d.ts +28 -0
- package/dist/providers/filter_provider.d.ts.map +1 -0
- package/dist/providers/filter_provider.js +33 -0
- package/dist/providers/filter_provider.js.map +1 -0
- package/dist/src/aggregate.d.ts +70 -0
- package/dist/src/aggregate.d.ts.map +1 -0
- package/dist/src/aggregate.js +195 -0
- package/dist/src/aggregate.js.map +1 -0
- package/dist/src/aggregate_path.d.ts +25 -0
- package/dist/src/aggregate_path.d.ts.map +1 -0
- package/dist/src/aggregate_path.js +35 -0
- package/dist/src/aggregate_path.js.map +1 -0
- package/dist/src/apply_from_request.d.ts +85 -0
- package/dist/src/apply_from_request.d.ts.map +1 -0
- package/dist/src/apply_from_request.js +164 -0
- package/dist/src/apply_from_request.js.map +1 -0
- package/dist/src/base_filter.d.ts +49 -0
- package/dist/src/base_filter.d.ts.map +1 -0
- package/dist/src/base_filter.js +33 -0
- package/dist/src/base_filter.js.map +1 -0
- package/dist/src/base_model_filter.d.ts +90 -0
- package/dist/src/base_model_filter.d.ts.map +1 -0
- package/dist/src/base_model_filter.js +85 -0
- package/dist/src/base_model_filter.js.map +1 -0
- package/dist/src/coerce_value.d.ts +26 -0
- package/dist/src/coerce_value.d.ts.map +1 -0
- package/dist/src/coerce_value.js +82 -0
- package/dist/src/coerce_value.js.map +1 -0
- package/dist/src/cursor.d.ts +92 -0
- package/dist/src/cursor.d.ts.map +1 -0
- package/dist/src/cursor.js +107 -0
- package/dist/src/cursor.js.map +1 -0
- package/dist/src/custom_filter.d.ts +36 -0
- package/dist/src/custom_filter.d.ts.map +1 -0
- package/dist/src/custom_filter.js +127 -0
- package/dist/src/custom_filter.js.map +1 -0
- package/dist/src/decorator_metadata.d.ts +41 -0
- package/dist/src/decorator_metadata.d.ts.map +1 -0
- package/dist/src/decorator_metadata.js +83 -0
- package/dist/src/decorator_metadata.js.map +1 -0
- package/dist/src/decorators.d.ts +73 -0
- package/dist/src/decorators.d.ts.map +1 -0
- package/dist/src/decorators.js +118 -0
- package/dist/src/decorators.js.map +1 -0
- package/dist/src/escape-like.d.ts +10 -0
- package/dist/src/escape-like.d.ts.map +1 -0
- package/dist/src/escape-like.js +15 -0
- package/dist/src/escape-like.js.map +1 -0
- package/dist/src/field_aliases.d.ts +30 -0
- package/dist/src/field_aliases.d.ts.map +1 -0
- package/dist/src/field_aliases.js +65 -0
- package/dist/src/field_aliases.js.map +1 -0
- package/dist/src/filter_class.d.ts +46 -0
- package/dist/src/filter_class.d.ts.map +1 -0
- package/dist/src/filter_class.js +244 -0
- package/dist/src/filter_class.js.map +1 -0
- package/dist/src/filter_spec.d.ts +231 -0
- package/dist/src/filter_spec.d.ts.map +1 -0
- package/dist/src/filter_spec.js +182 -0
- package/dist/src/filter_spec.js.map +1 -0
- package/dist/src/filterable_mixin.d.ts +49 -0
- package/dist/src/filterable_mixin.d.ts.map +1 -0
- package/dist/src/filterable_mixin.js +47 -0
- package/dist/src/filterable_mixin.js.map +1 -0
- package/dist/src/generate_client.d.ts +107 -0
- package/dist/src/generate_client.d.ts.map +1 -0
- package/dist/src/generate_client.js +164 -0
- package/dist/src/generate_client.js.map +1 -0
- package/dist/src/group_by_count.d.ts +67 -0
- package/dist/src/group_by_count.d.ts.map +1 -0
- package/dist/src/group_by_count.js +71 -0
- package/dist/src/group_by_count.js.map +1 -0
- package/dist/src/in_memory.d.ts +279 -0
- package/dist/src/in_memory.d.ts.map +1 -0
- package/dist/src/in_memory.js +810 -0
- package/dist/src/in_memory.js.map +1 -0
- package/dist/src/index.d.ts +51 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +33 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lucid_adapter.d.ts +311 -0
- package/dist/src/lucid_adapter.d.ts.map +1 -0
- package/dist/src/lucid_adapter.js +469 -0
- package/dist/src/lucid_adapter.js.map +1 -0
- package/dist/src/lucid_macros.d.ts +87 -0
- package/dist/src/lucid_macros.d.ts.map +1 -0
- package/dist/src/lucid_macros.js +67 -0
- package/dist/src/lucid_macros.js.map +1 -0
- package/dist/src/normalizer.d.ts +21 -0
- package/dist/src/normalizer.d.ts.map +1 -0
- package/dist/src/normalizer.js +61 -0
- package/dist/src/normalizer.js.map +1 -0
- package/dist/src/operators.d.ts +37 -0
- package/dist/src/operators.d.ts.map +1 -0
- package/dist/src/operators.js +43 -0
- package/dist/src/operators.js.map +1 -0
- package/dist/src/paginate.d.ts +12 -0
- package/dist/src/paginate.d.ts.map +1 -0
- package/dist/src/paginate.js +88 -0
- package/dist/src/paginate.js.map +1 -0
- package/dist/src/pagination_stash.d.ts +4 -0
- package/dist/src/pagination_stash.d.ts.map +1 -0
- package/dist/src/pagination_stash.js +19 -0
- package/dist/src/pagination_stash.js.map +1 -0
- package/dist/src/parse_request.d.ts +50 -0
- package/dist/src/parse_request.d.ts.map +1 -0
- package/dist/src/parse_request.js +220 -0
- package/dist/src/parse_request.js.map +1 -0
- package/dist/src/runner.d.ts +65 -0
- package/dist/src/runner.d.ts.map +1 -0
- package/dist/src/runner.js +371 -0
- package/dist/src/runner.js.map +1 -0
- package/dist/src/source_resolver.d.ts +18 -0
- package/dist/src/source_resolver.d.ts.map +1 -0
- package/dist/src/source_resolver.js +64 -0
- package/dist/src/source_resolver.js.map +1 -0
- package/dist/src/spatie_parser.d.ts +39 -0
- package/dist/src/spatie_parser.d.ts.map +1 -0
- package/dist/src/spatie_parser.js +134 -0
- package/dist/src/spatie_parser.js.map +1 -0
- package/dist/src/testing.d.ts +73 -0
- package/dist/src/testing.d.ts.map +1 -0
- package/dist/src/testing.js +136 -0
- package/dist/src/testing.js.map +1 -0
- package/dist/src/types.d.ts +226 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/validate-column-filter.d.ts +30 -0
- package/dist/src/validate-column-filter.d.ts.map +1 -0
- package/dist/src/validate-column-filter.js +148 -0
- package/dist/src/validate-column-filter.js.map +1 -0
- package/dist/src/value-shape.d.ts +18 -0
- package/dist/src/value-shape.d.ts.map +1 -0
- package/dist/src/value-shape.js +36 -0
- package/dist/src/value-shape.js.map +1 -0
- package/dist/stubs/main.d.ts +3 -0
- package/dist/stubs/main.d.ts.map +1 -0
- package/dist/stubs/main.js +4 -0
- package/dist/stubs/main.js.map +1 -0
- package/dist/stubs/make/filter/main.stub +45 -0
- package/package.json +102 -0
- package/skills/filter-basics/SKILL.md +285 -0
- package/skills/filter-codegen/SKILL.md +246 -0
- package/skills/filter-definitions/SKILL.md +340 -0
- package/skills/filter-querying/SKILL.md +319 -0
- package/skills/filter-safety/SKILL.md +304 -0
- package/stubs/main.ts +4 -0
- package/stubs/make/filter/main.stub +45 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type CursorParams, type ResolvedCursor } from './cursor.js';
|
|
2
|
+
import { type QueryBuilderLike } from './lucid_adapter.js';
|
|
3
|
+
import type { FilterConfig, FilterInput } from './types.js';
|
|
4
|
+
/** The resolved offset pagination to hand to Lucid's `query.paginate(page, size)`. */
|
|
5
|
+
export interface ResolvedPagination {
|
|
6
|
+
page: number;
|
|
7
|
+
size: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve aliases, structurally validate, and prune `input.filters`/`input.search`
|
|
11
|
+
* against the allow-lists, applying the survivors to the builder. Shared by both
|
|
12
|
+
* {@link applyFilter} (offset) and {@link applyCursor} (keyset) so the security
|
|
13
|
+
* boundary is identical for either pagination style.
|
|
14
|
+
*/
|
|
15
|
+
export declare function applyFilterConditions(qb: QueryBuilderLike, input: FilterInput, config: FilterConfig): void;
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the alias-mapped, allow-listed distinct fields for the request. A
|
|
18
|
+
* distinct field is a projected column, so it is gated by the SAME `allowed`
|
|
19
|
+
* boundary a `where` field is — aliases resolve first, then the allow-list;
|
|
20
|
+
* unknown fields are dropped (or rejected under `throwOnInvalid`).
|
|
21
|
+
*
|
|
22
|
+
* A field that clears the allow-list but is not projectable
|
|
23
|
+
* ({@link isProjectable}) is dropped/rejected too, with its OWN message: it is
|
|
24
|
+
* not an allow-list problem, so pointing the reader at `allowed` would send them
|
|
25
|
+
* to edit the wrong thing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveSafeDistinct(fields: string[], config: FilterConfig): string[];
|
|
28
|
+
/**
|
|
29
|
+
* Apply a parsed {@link FilterInput} to a Lucid query builder under a
|
|
30
|
+
* {@link FilterConfig} policy, and return the resolved offset pagination.
|
|
31
|
+
*
|
|
32
|
+
* The allow-lists are the security boundary — fields not in `allowed`/`sortable`/
|
|
33
|
+
* `searchable` are dropped (or rejected with `throwOnInvalid`). Pagination is
|
|
34
|
+
* returned (not applied) so the caller drives Lucid's `query.paginate()`:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const { page, size } = applyFilter(Users.query(), input, { allowed: ['name', 'age'] })
|
|
38
|
+
* const result = await Users.query()... // or: await query.paginate(page, size)
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function applyFilter(qb: QueryBuilderLike, input: FilterInput, config: FilterConfig): ResolvedPagination;
|
|
42
|
+
/** Per-call policy for {@link applyCursor} — a {@link FilterConfig} plus the keyset tiebreaker. */
|
|
43
|
+
export interface CursorConfig extends FilterConfig {
|
|
44
|
+
/** Primary-key column appended to the keyset as a stable tiebreaker. Default `'id'`. */
|
|
45
|
+
primaryKey?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Apply a parsed {@link FilterInput} plus {@link CursorParams} to a Lucid query
|
|
49
|
+
* builder as a **keyset (cursor) page** under a {@link CursorConfig} policy.
|
|
50
|
+
*
|
|
51
|
+
* Filters/search go through the same allow-list boundary as {@link applyFilter}.
|
|
52
|
+
* The effective (allow-listed) sort, plus the primary-key tiebreaker, forms the
|
|
53
|
+
* keyset; a supplied `after`/`before` cursor becomes a row-value seek predicate,
|
|
54
|
+
* and the builder is ordered + limited to `size + 1` (one extra row to detect a
|
|
55
|
+
* further page). Feed the fetched rows and the returned {@link ResolvedCursor}
|
|
56
|
+
* to {@link buildCursorPage} to assemble the page and its boundary cursors:
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* const resolved = applyCursor(Users.query(), input, { allowed: ['name'], primaryKey: 'id' })
|
|
60
|
+
* const rows = await Users.query()...exec()
|
|
61
|
+
* const page = buildCursorPage(rows, resolved)
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function applyCursor(qb: QueryBuilderLike, input: FilterInput & CursorParams, config: CursorConfig): ResolvedCursor;
|
|
65
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,cAAc,EAEpB,MAAM,aAAa,CAAC;AAOrB,OAAO,EAUL,KAAK,gBAAgB,EAEtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAA6B,YAAY,EAAE,WAAW,EAAY,MAAM,YAAY,CAAC;AAGjG,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAyID;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,gBAAgB,EACpB,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,YAAY,GACnB,IAAI,CA4DN;AAyBD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,EAAE,CA0BpF;AAwDD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,gBAAgB,EACpB,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,YAAY,GACnB,kBAAkB,CAmCpB;AAED,mGAAmG;AACnG,MAAM,WAAW,YAAa,SAAQ,YAAY;IAChD,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,gBAAgB,EACpB,KAAK,EAAE,WAAW,GAAG,YAAY,EACjC,MAAM,EAAE,YAAY,GACnB,cAAc,CA0BhB"}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { countExistencePredicate } from './aggregate.js';
|
|
2
|
+
import { coerceFilterValue } from './coerce_value.js';
|
|
3
|
+
import { buildKeyset, decodeCursor, reverseKeyset, } from './cursor.js';
|
|
4
|
+
import { remapDistinctAliases, remapFilterAliases, remapSortAliases, resolveFieldAlias, } from './field_aliases.js';
|
|
5
|
+
import { applyColumnFilters, applyComputedField, applyComputedSort, applyDistinct, applyFullTextSearch, applyKeyset, applySearch, applySort, applyVectorSimilarity, resolveComputedExpression, } from './lucid_adapter.js';
|
|
6
|
+
import { InvalidColumnFilterError, validateColumnFilters } from './validate-column-filter.js';
|
|
7
|
+
function isAllowed(field, allow) {
|
|
8
|
+
if (allow === '*')
|
|
9
|
+
return true;
|
|
10
|
+
if (typeof allow === 'function')
|
|
11
|
+
return allow(field);
|
|
12
|
+
return allow.includes(field);
|
|
13
|
+
}
|
|
14
|
+
function clamp(n, min, max) {
|
|
15
|
+
return Math.min(max, Math.max(min, n));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Own-property lookup of a declared computed field. A client-supplied field name
|
|
19
|
+
* is never trusted as a bare map key (prototype-pollution guard) — only an
|
|
20
|
+
* OWN, non-inherited key resolves to a computed source.
|
|
21
|
+
*/
|
|
22
|
+
function computedKey(computed, field) {
|
|
23
|
+
return (computed !== undefined &&
|
|
24
|
+
typeof field === 'string' &&
|
|
25
|
+
field.length > 0 &&
|
|
26
|
+
Object.hasOwn(computed, field));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A top-level filter that targets a declared computed field: a leaf (has a
|
|
30
|
+
* field, no AND/OR children) whose name is an own key of the computed map.
|
|
31
|
+
* Computed fields are only recognised at the top level of `input.filters` — the
|
|
32
|
+
* shape the client emits — never nested inside a boolean group.
|
|
33
|
+
*/
|
|
34
|
+
function isComputedLeaf(filter, computed) {
|
|
35
|
+
return computedKey(computed, filter.field) && filter.AND === undefined && filter.OR === undefined;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Operators whose value is a LIKE pattern rather than a column-typed value. Their argument stays a
|
|
39
|
+
* string no matter what kind the column is declared as — coercing `contains: '3'` on a numeric
|
|
40
|
+
* column to the number `3` would destroy the pattern the caller asked for.
|
|
41
|
+
*/
|
|
42
|
+
const PATTERN_OPERATORS = new Set([
|
|
43
|
+
'contains',
|
|
44
|
+
'notContains',
|
|
45
|
+
'iContains',
|
|
46
|
+
'startsWith',
|
|
47
|
+
'endsWith',
|
|
48
|
+
]);
|
|
49
|
+
/** Operators whose value is a list/tuple — each element is coerced independently. */
|
|
50
|
+
const ARRAY_OPERATORS = new Set(['in', 'notIn', 'isAnyOf', 'between', 'notBetween']);
|
|
51
|
+
/**
|
|
52
|
+
* Coerce one leaf's value against its declared column kind. Returns `ok` unchanged when the field
|
|
53
|
+
* has no declared type (backwards compatible), when the operator carries a LIKE pattern, or when
|
|
54
|
+
* the kind has no contract to enforce. An array-valued operator fails as a whole if ANY element
|
|
55
|
+
* fails — a partially-coerced list would silently filter on something the client never asked for.
|
|
56
|
+
*/
|
|
57
|
+
function coerceFilterForField(filter, fieldTypes) {
|
|
58
|
+
const kind = fieldTypes?.[filter.field]?.kind;
|
|
59
|
+
if (!kind || PATTERN_OPERATORS.has(String(filter.operator))) {
|
|
60
|
+
return { ok: true, value: filter.value };
|
|
61
|
+
}
|
|
62
|
+
if (ARRAY_OPERATORS.has(String(filter.operator)) && Array.isArray(filter.value)) {
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const element of filter.value) {
|
|
65
|
+
const r = coerceFilterValue(element, kind);
|
|
66
|
+
if (!r.ok)
|
|
67
|
+
return r;
|
|
68
|
+
out.push(r.value);
|
|
69
|
+
}
|
|
70
|
+
return { ok: true, value: out };
|
|
71
|
+
}
|
|
72
|
+
return coerceFilterValue(filter.value, kind);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Recursively prune a filter against the allow-list. A leaf on a disallowed field
|
|
76
|
+
* is dropped (or throws when `throwOnInvalid`). Group nodes keep only their
|
|
77
|
+
* surviving children; a group left empty is itself dropped (returns `null`).
|
|
78
|
+
*/
|
|
79
|
+
function prune(filter, allowed, throwOnInvalid, fieldTypes) {
|
|
80
|
+
const hasField = typeof filter.field === 'string' && filter.field.length > 0;
|
|
81
|
+
if (hasField && !isAllowed(filter.field, allowed)) {
|
|
82
|
+
if (throwOnInvalid) {
|
|
83
|
+
throw new InvalidColumnFilterError(`Field "${filter.field}" is not filterable.`);
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const next = { field: filter.field, operator: filter.operator };
|
|
88
|
+
if (filter.value !== undefined) {
|
|
89
|
+
// The field passed the allow-list; now guard the VALUE. An uncoercible value is treated
|
|
90
|
+
// exactly like a disallowed field (drop, or throw under `throwOnInvalid`) rather than being
|
|
91
|
+
// handed to the driver, where Postgres would reject it as a 500 from user input.
|
|
92
|
+
const coerced = coerceFilterForField(filter, fieldTypes);
|
|
93
|
+
if (!coerced.ok) {
|
|
94
|
+
if (throwOnInvalid) {
|
|
95
|
+
throw new InvalidColumnFilterError(`Value for field "${filter.field}" is invalid: ${coerced.reason}.`);
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
next.value = coerced.value;
|
|
100
|
+
}
|
|
101
|
+
if (filter.AND) {
|
|
102
|
+
const kept = filter.AND.map((f) => prune(f, allowed, throwOnInvalid, fieldTypes)).filter((f) => f !== null);
|
|
103
|
+
if (kept.length > 0)
|
|
104
|
+
next.AND = kept;
|
|
105
|
+
}
|
|
106
|
+
if (filter.OR) {
|
|
107
|
+
const kept = filter.OR.map((f) => prune(f, allowed, throwOnInvalid, fieldTypes)).filter((f) => f !== null);
|
|
108
|
+
if (kept.length > 0)
|
|
109
|
+
next.OR = kept;
|
|
110
|
+
}
|
|
111
|
+
// A pure group node (no field of its own) with no surviving children is empty.
|
|
112
|
+
if (!hasField && next.AND === undefined && next.OR === undefined) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return next;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolve aliases, structurally validate, and prune `input.filters`/`input.search`
|
|
119
|
+
* against the allow-lists, applying the survivors to the builder. Shared by both
|
|
120
|
+
* {@link applyFilter} (offset) and {@link applyCursor} (keyset) so the security
|
|
121
|
+
* boundary is identical for either pagination style.
|
|
122
|
+
*/
|
|
123
|
+
export function applyFilterConditions(qb, input, config) {
|
|
124
|
+
const throwOnInvalid = config.throwOnInvalid ?? false;
|
|
125
|
+
if (input.filters && input.filters.length > 0) {
|
|
126
|
+
// Computed fields are a separate namespace from real columns: a top-level
|
|
127
|
+
// leaf on a declared computed key is routed to the computed hook (its
|
|
128
|
+
// declaration IS its allow-list) and NEVER run through the column
|
|
129
|
+
// alias/validate/allow-list path below. Everything else is a normal column
|
|
130
|
+
// filter. Checked before alias resolution so a computed key is not remapped.
|
|
131
|
+
const columnFilters = [];
|
|
132
|
+
for (const filter of input.filters) {
|
|
133
|
+
if (isComputedLeaf(filter, config.computed)) {
|
|
134
|
+
const source = config.computed?.[filter.field];
|
|
135
|
+
const existence = countExistencePredicate(source, config.table ?? '', filter);
|
|
136
|
+
if (existence)
|
|
137
|
+
qb.whereRaw(existence);
|
|
138
|
+
else
|
|
139
|
+
applyComputedField(qb, resolveComputedExpression(source, config.table ?? ''), filter);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
columnFilters.push(filter);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (columnFilters.length > 0) {
|
|
146
|
+
// Alias resolution runs FIRST — the allow-list, validation and query builder
|
|
147
|
+
// all see the resolved target column, never the client-facing alias key.
|
|
148
|
+
const aliased = config.aliases
|
|
149
|
+
? columnFilters.map((f) => remapFilterAliases(f, config.aliases))
|
|
150
|
+
: columnFilters;
|
|
151
|
+
// Structural validation next (operator/value shape, depth, field charset).
|
|
152
|
+
validateColumnFilters(aliased);
|
|
153
|
+
const safe = aliased
|
|
154
|
+
.map((f) => prune(f, config.allowed, throwOnInvalid, config.fieldTypes))
|
|
155
|
+
.filter((f) => f !== null);
|
|
156
|
+
if (safe.length > 0) {
|
|
157
|
+
applyColumnFilters(qb, safe);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (input.search) {
|
|
162
|
+
const term = input.search.trim();
|
|
163
|
+
if (term.length > 0) {
|
|
164
|
+
// tsvector full-text search is the primary path when the policy declares
|
|
165
|
+
// it; otherwise fall back to the portable ILIKE scan across `searchable`.
|
|
166
|
+
if (config.fullText) {
|
|
167
|
+
applyFullTextSearch(qb, {
|
|
168
|
+
query: term,
|
|
169
|
+
column: config.fullText.column,
|
|
170
|
+
...(config.fullText.language !== undefined && { language: config.fullText.language }),
|
|
171
|
+
...(config.fullText.rank !== undefined && { rank: config.fullText.rank }),
|
|
172
|
+
...(config.fullText.columnKind !== undefined && {
|
|
173
|
+
columnKind: config.fullText.columnKind,
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else if (config.searchable && config.searchable.length > 0) {
|
|
178
|
+
applySearch(qb, term, config.searchable);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Is this (alias-resolved) field projectable in the outer SELECT?
|
|
185
|
+
*
|
|
186
|
+
* A `where` on a relation path is translated by `applyLeaf` into a nested Lucid
|
|
187
|
+
* `whereHas`, which compiles to a **correlated EXISTS subquery** — the relation
|
|
188
|
+
* is never added to the outer `FROM`. A projection has no such escape hatch: the
|
|
189
|
+
* outer SELECT can only name a column of something the outer FROM holds. So
|
|
190
|
+
* `distinct=posts.title` on a spec that (correctly) whitelists `posts.title` for
|
|
191
|
+
* filtering would emit `select distinct "posts"."title" from "users"` and
|
|
192
|
+
* Postgres answers `missing FROM-clause entry for table "posts"` — a 500 driven
|
|
193
|
+
* by user input.
|
|
194
|
+
*
|
|
195
|
+
* The one qualifier that IS in the outer FROM is the root table itself (Lucid
|
|
196
|
+
* gives the main table no generated alias, so the table name is the alias — the
|
|
197
|
+
* same fact {@link FilterConfig.table} already encodes for computed fields).
|
|
198
|
+
* A single-segment name is a plain root column and always projectable.
|
|
199
|
+
*/
|
|
200
|
+
function isProjectable(field, config) {
|
|
201
|
+
const dot = field.indexOf('.');
|
|
202
|
+
if (dot === -1)
|
|
203
|
+
return true;
|
|
204
|
+
return config.table !== undefined && field.slice(0, dot) === config.table;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Resolve the alias-mapped, allow-listed distinct fields for the request. A
|
|
208
|
+
* distinct field is a projected column, so it is gated by the SAME `allowed`
|
|
209
|
+
* boundary a `where` field is — aliases resolve first, then the allow-list;
|
|
210
|
+
* unknown fields are dropped (or rejected under `throwOnInvalid`).
|
|
211
|
+
*
|
|
212
|
+
* A field that clears the allow-list but is not projectable
|
|
213
|
+
* ({@link isProjectable}) is dropped/rejected too, with its OWN message: it is
|
|
214
|
+
* not an allow-list problem, so pointing the reader at `allowed` would send them
|
|
215
|
+
* to edit the wrong thing.
|
|
216
|
+
*/
|
|
217
|
+
export function resolveSafeDistinct(fields, config) {
|
|
218
|
+
const throwOnInvalid = config.throwOnInvalid ?? false;
|
|
219
|
+
const aliased = config.aliases
|
|
220
|
+
? remapDistinctAliases(fields, config.aliases)
|
|
221
|
+
: fields;
|
|
222
|
+
const safe = [];
|
|
223
|
+
for (const field of aliased) {
|
|
224
|
+
if (!isAllowed(field, config.allowed)) {
|
|
225
|
+
if (throwOnInvalid) {
|
|
226
|
+
throw new InvalidColumnFilterError(`Field "${field}" is not a distinct-able column.`);
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (!isProjectable(field, config)) {
|
|
231
|
+
if (throwOnInvalid) {
|
|
232
|
+
const relation = field.slice(0, field.indexOf('.'));
|
|
233
|
+
throw new InvalidColumnFilterError(`Field "${field}" cannot be used for distinct: it is a relation path, and a relation is filtered through a correlated EXISTS subquery, never joined into the query's FROM — so there is no "${relation}" table whose column the SELECT could project. This is NOT an allow-list problem: filtering on "${field}" works, projecting it does not, and adding it to \`allowed\` will not change that. Use a column of the root table for distinct.`);
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (!safe.includes(field))
|
|
238
|
+
safe.push(field);
|
|
239
|
+
}
|
|
240
|
+
return safe;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Apply the request's sort directives in order, routing each to the right hook:
|
|
244
|
+
* a declared computed field goes to `applyComputedSort` (an appended
|
|
245
|
+
* `orderByRaw`), a real column goes through alias resolution + the `sortable`
|
|
246
|
+
* allow-list to `applySort` (an appended `orderBy`). Both hooks append, so a
|
|
247
|
+
* computed sort and a real-column sort compose in request order — a client can
|
|
248
|
+
* sort by `fullName` then `id` and get exactly that ORDER BY sequence.
|
|
249
|
+
*/
|
|
250
|
+
function applySortsWithComputed(qb, sort, config) {
|
|
251
|
+
const throwOnInvalid = config.throwOnInvalid ?? false;
|
|
252
|
+
const sortable = config.sortable ?? config.allowed;
|
|
253
|
+
for (const item of sort) {
|
|
254
|
+
if (computedKey(config.computed, item.field)) {
|
|
255
|
+
const expression = resolveComputedExpression(config.computed?.[item.field], config.table ?? '');
|
|
256
|
+
applyComputedSort(qb, expression, item.direction);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const field = config.aliases
|
|
260
|
+
? resolveFieldAlias(config.aliases, item.field)
|
|
261
|
+
: item.field;
|
|
262
|
+
if (isAllowed(field, sortable)) {
|
|
263
|
+
applySort(qb, [{ field, direction: item.direction }]);
|
|
264
|
+
}
|
|
265
|
+
else if (throwOnInvalid) {
|
|
266
|
+
throw new InvalidColumnFilterError(`Field "${item.field}" is not sortable.`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Resolve the alias-mapped, allow-listed sort directives for the request. */
|
|
271
|
+
function resolveSafeSort(sort, config) {
|
|
272
|
+
const throwOnInvalid = config.throwOnInvalid ?? false;
|
|
273
|
+
const sortable = config.sortable ?? config.allowed;
|
|
274
|
+
const aliased = config.aliases
|
|
275
|
+
? remapSortAliases(sort, config.aliases)
|
|
276
|
+
: sort;
|
|
277
|
+
const safe = [];
|
|
278
|
+
for (const item of aliased) {
|
|
279
|
+
if (isAllowed(item.field, sortable)) {
|
|
280
|
+
safe.push(item);
|
|
281
|
+
}
|
|
282
|
+
else if (throwOnInvalid) {
|
|
283
|
+
throw new InvalidColumnFilterError(`Field "${item.field}" is not sortable.`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return safe;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Apply a parsed {@link FilterInput} to a Lucid query builder under a
|
|
290
|
+
* {@link FilterConfig} policy, and return the resolved offset pagination.
|
|
291
|
+
*
|
|
292
|
+
* The allow-lists are the security boundary — fields not in `allowed`/`sortable`/
|
|
293
|
+
* `searchable` are dropped (or rejected with `throwOnInvalid`). Pagination is
|
|
294
|
+
* returned (not applied) so the caller drives Lucid's `query.paginate()`:
|
|
295
|
+
*
|
|
296
|
+
* ```ts
|
|
297
|
+
* const { page, size } = applyFilter(Users.query(), input, { allowed: ['name', 'age'] })
|
|
298
|
+
* const result = await Users.query()... // or: await query.paginate(page, size)
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
export function applyFilter(qb, input, config) {
|
|
302
|
+
applyFilterConditions(qb, input, config);
|
|
303
|
+
// Embedding-similarity ranking (opt-in, additive): only when the policy declares
|
|
304
|
+
// a similarity column AND the request carries a query embedding. Applied before
|
|
305
|
+
// the user sort so nearest-first distance is the primary ordering; any allowed
|
|
306
|
+
// sort then acts as a tiebreaker.
|
|
307
|
+
if (config.vectorSimilarity && input.vectorSimilarity && input.vectorSimilarity.length > 0) {
|
|
308
|
+
applyVectorSimilarity(qb, {
|
|
309
|
+
column: config.vectorSimilarity.column,
|
|
310
|
+
vector: input.vectorSimilarity,
|
|
311
|
+
...(config.vectorSimilarity.metric !== undefined && {
|
|
312
|
+
metric: config.vectorSimilarity.metric,
|
|
313
|
+
}),
|
|
314
|
+
...(config.vectorSimilarity.threshold !== undefined && {
|
|
315
|
+
threshold: config.vectorSimilarity.threshold,
|
|
316
|
+
}),
|
|
317
|
+
...(config.vectorSimilarity.topK !== undefined && { topK: config.vectorSimilarity.topK }),
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
if (input.sort && input.sort.length > 0) {
|
|
321
|
+
applySortsWithComputed(qb, input.sort, config);
|
|
322
|
+
}
|
|
323
|
+
if (input.distinct && input.distinct.length > 0) {
|
|
324
|
+
const safeDistinct = resolveSafeDistinct(input.distinct, config);
|
|
325
|
+
if (safeDistinct.length > 0) {
|
|
326
|
+
applyDistinct(qb, safeDistinct);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const size = clamp(input.size ?? config.defaultSize ?? 25, 1, config.maxSize ?? 100);
|
|
330
|
+
const page = Math.max(1, input.page ?? 1);
|
|
331
|
+
return { page, size };
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Apply a parsed {@link FilterInput} plus {@link CursorParams} to a Lucid query
|
|
335
|
+
* builder as a **keyset (cursor) page** under a {@link CursorConfig} policy.
|
|
336
|
+
*
|
|
337
|
+
* Filters/search go through the same allow-list boundary as {@link applyFilter}.
|
|
338
|
+
* The effective (allow-listed) sort, plus the primary-key tiebreaker, forms the
|
|
339
|
+
* keyset; a supplied `after`/`before` cursor becomes a row-value seek predicate,
|
|
340
|
+
* and the builder is ordered + limited to `size + 1` (one extra row to detect a
|
|
341
|
+
* further page). Feed the fetched rows and the returned {@link ResolvedCursor}
|
|
342
|
+
* to {@link buildCursorPage} to assemble the page and its boundary cursors:
|
|
343
|
+
*
|
|
344
|
+
* ```ts
|
|
345
|
+
* const resolved = applyCursor(Users.query(), input, { allowed: ['name'], primaryKey: 'id' })
|
|
346
|
+
* const rows = await Users.query()...exec()
|
|
347
|
+
* const page = buildCursorPage(rows, resolved)
|
|
348
|
+
* ```
|
|
349
|
+
*/
|
|
350
|
+
export function applyCursor(qb, input, config) {
|
|
351
|
+
applyFilterConditions(qb, input, config);
|
|
352
|
+
const safeSort = input.sort && input.sort.length > 0 ? resolveSafeSort(input.sort, config) : [];
|
|
353
|
+
const baseKeyset = buildKeyset(safeSort, config.primaryKey ?? 'id');
|
|
354
|
+
const backward = input.before !== undefined && input.after === undefined;
|
|
355
|
+
const cursorStr = input.after ?? input.before;
|
|
356
|
+
const requested = backward ? input.last : input.first;
|
|
357
|
+
const size = clamp(requested ?? config.defaultSize ?? 25, 1, config.maxSize ?? 100);
|
|
358
|
+
// For backward paging, reverse keyset directions so the boundary seek and
|
|
359
|
+
// ordering walk the other way; buildCursorPage re-reverses the rows.
|
|
360
|
+
const queryKeyset = backward ? reverseKeyset(baseKeyset) : baseKeyset;
|
|
361
|
+
if (cursorStr !== undefined) {
|
|
362
|
+
const values = decodeCursor(cursorStr);
|
|
363
|
+
if (values && values.length === queryKeyset.length) {
|
|
364
|
+
applyKeyset(qb, queryKeyset, values);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
applySort(qb, queryKeyset);
|
|
368
|
+
qb.limit(size + 1);
|
|
369
|
+
return { keyset: baseKeyset, size, backward, hasCursor: cursorStr !== undefined };
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InputSource } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the raw structured input from a request according to an
|
|
4
|
+
* {@link InputSource}:
|
|
5
|
+
*
|
|
6
|
+
* - a function — a custom extractor, called with the request.
|
|
7
|
+
* - `'query'` / `'body'` — always that container.
|
|
8
|
+
* - `'auto'` (or any other bare token) — query on reads (GET/HEAD), query+body
|
|
9
|
+
* merged (body wins) on writes (POST/PUT/PATCH/DELETE).
|
|
10
|
+
* - a dot-path (e.g. `'body.filters'`) — the nested object at that path, or `{}`
|
|
11
|
+
* when the path is missing or does not resolve to an object.
|
|
12
|
+
*
|
|
13
|
+
* The `req` is treated structurally (`{ method, query, body }`), so an AdonisJS
|
|
14
|
+
* `HttpContext.request` — whose `qs()`/`body()` you pass in — or a plain object
|
|
15
|
+
* both work. The result is always a fresh shallow copy.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveInputFromRequest(req: unknown, source: InputSource): Record<string, unknown>;
|
|
18
|
+
//# sourceMappingURL=source_resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source_resolver.d.ts","sourceRoot":"","sources":["../../src/source_resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAW9C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,WAAW,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA+BzB"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const WRITE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
2
|
+
const READ_METHODS = new Set(['GET', 'HEAD']);
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the raw structured input from a request according to an
|
|
5
|
+
* {@link InputSource}:
|
|
6
|
+
*
|
|
7
|
+
* - a function — a custom extractor, called with the request.
|
|
8
|
+
* - `'query'` / `'body'` — always that container.
|
|
9
|
+
* - `'auto'` (or any other bare token) — query on reads (GET/HEAD), query+body
|
|
10
|
+
* merged (body wins) on writes (POST/PUT/PATCH/DELETE).
|
|
11
|
+
* - a dot-path (e.g. `'body.filters'`) — the nested object at that path, or `{}`
|
|
12
|
+
* when the path is missing or does not resolve to an object.
|
|
13
|
+
*
|
|
14
|
+
* The `req` is treated structurally (`{ method, query, body }`), so an AdonisJS
|
|
15
|
+
* `HttpContext.request` — whose `qs()`/`body()` you pass in — or a plain object
|
|
16
|
+
* both work. The result is always a fresh shallow copy.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveInputFromRequest(req, source) {
|
|
19
|
+
if (typeof source === 'function') {
|
|
20
|
+
const r = source(req);
|
|
21
|
+
return { ...(r ?? {}) };
|
|
22
|
+
}
|
|
23
|
+
const r = (req ?? {});
|
|
24
|
+
const query = (r.query ?? {});
|
|
25
|
+
const body = (r.body ?? {});
|
|
26
|
+
switch (source) {
|
|
27
|
+
case 'query':
|
|
28
|
+
return { ...query };
|
|
29
|
+
case 'body':
|
|
30
|
+
return { ...body };
|
|
31
|
+
case 'auto': {
|
|
32
|
+
const method = (r.method ?? 'GET').toUpperCase();
|
|
33
|
+
if (READ_METHODS.has(method))
|
|
34
|
+
return { ...query };
|
|
35
|
+
if (WRITE_METHODS.has(method))
|
|
36
|
+
return { ...query, ...body };
|
|
37
|
+
return { ...query };
|
|
38
|
+
}
|
|
39
|
+
default: {
|
|
40
|
+
if (source.includes('.')) {
|
|
41
|
+
const resolved = resolveDotPath(r, source);
|
|
42
|
+
return { ...(resolved ?? {}) };
|
|
43
|
+
}
|
|
44
|
+
const method = (r.method ?? 'GET').toUpperCase();
|
|
45
|
+
if (READ_METHODS.has(method))
|
|
46
|
+
return { ...query };
|
|
47
|
+
if (WRITE_METHODS.has(method))
|
|
48
|
+
return { ...query, ...body };
|
|
49
|
+
return { ...query };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function resolveDotPath(obj, path) {
|
|
54
|
+
let current = obj;
|
|
55
|
+
for (const segment of path.split('.')) {
|
|
56
|
+
if (current == null || typeof current !== 'object')
|
|
57
|
+
return undefined;
|
|
58
|
+
current = current[segment];
|
|
59
|
+
}
|
|
60
|
+
if (current == null || typeof current !== 'object')
|
|
61
|
+
return undefined;
|
|
62
|
+
return current;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=source_resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source_resolver.js","sourceRoot":"","sources":["../../src/source_resolver.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AAClE,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAQ9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CACrC,GAAY,EACZ,MAAmB;IAEnB,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAA6B,CAAC;IACrD,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,CAAY,CAAC;IACjC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC;IACzD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAA4B,CAAC;IAEvD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,OAAO;YACV,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;QACtB,KAAK,MAAM;YACT,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC;QACrB,KAAK,MAAM,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;YACjD,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;YAClD,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI,EAAE,CAAC;YAC5D,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;QACtB,CAAC;QACD,SAAS,CAAC;YACR,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzB,MAAM,QAAQ,GAAG,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;gBAC3C,OAAO,EAAE,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC;YACjC,CAAC;YACD,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;YACjD,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;YAClD,IAAI,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,IAAI,EAAE,CAAC;YAC5D,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;QACtB,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,GAAY,EAAE,IAAY;IAChD,IAAI,OAAO,GAAY,GAAG,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACrE,OAAO,GAAI,OAAmC,CAAC,OAAO,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACrE,OAAO,OAAkC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CursorParams } from './cursor.js';
|
|
2
|
+
import type { FilterInput } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The richer Spatie / JSON:API input shape produced by {@link parseSpatieRequest}.
|
|
5
|
+
* A superset of {@link FilterInput}: it adds cursor pagination
|
|
6
|
+
* ({@link CursorParams}), JSON:API sparse fieldsets (`select`) and relation
|
|
7
|
+
* includes (`include`). Ready to hand to `applyFilter` (offset) or `applyCursor`
|
|
8
|
+
* (keyset) — the extra fields are ignored by whichever runner does not use them.
|
|
9
|
+
*/
|
|
10
|
+
export interface SpatieInput extends FilterInput, CursorParams {
|
|
11
|
+
/** JSON:API sparse fieldsets (`fields[resource]=a,b`), flattened + de-duped. */
|
|
12
|
+
select?: string[];
|
|
13
|
+
/** Relation includes (`include=posts,comments`). */
|
|
14
|
+
include?: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse a decoded, Spatie-laravel-query-builder / JSON:API-style query object
|
|
18
|
+
* into a {@link SpatieInput}. This is the richer, additive counterpart to
|
|
19
|
+
* {@link parseFilterRequest}: it understands the same `filter`/`sort`/`search`
|
|
20
|
+
* shapes plus JSON:API `include`, sparse `fields[resource]` sets, and cursor
|
|
21
|
+
* pagination (`page[after]`/`page[before]`).
|
|
22
|
+
*
|
|
23
|
+
* | Query string | Decoded input | Mapped to |
|
|
24
|
+
* | ------------------------------- | ------------------------------------ | ------------------------------------------ |
|
|
25
|
+
* | `filter[name]=Al` | `{ filter: { name: 'Al' } }` | `equals` column filter |
|
|
26
|
+
* | `filter[id]=1,2,3` | `{ filter: { id: '1,2,3' } }` | `in` column filter |
|
|
27
|
+
* | `filter[age][gte]=18` | `{ filter: { age: { gte: '18' } } }` | operator column filter |
|
|
28
|
+
* | `sort=-createdAt,name` | `{ sort: '-createdAt,name' }` | sort items |
|
|
29
|
+
* | `distinct=city,tier` | `{ distinct: 'city,tier' }` | `distinct: ['city', 'tier']` |
|
|
30
|
+
* | `include=posts,comments` | `{ include: 'posts,comments' }` | `include: ['posts', 'comments']` |
|
|
31
|
+
* | `fields[users]=id,name` | `{ fields: { users: 'id,name' } }` | `select: ['id', 'name']` |
|
|
32
|
+
* | `page[number]=2&page[size]=10` | `{ page: { number, size } }` | offset (`page`/`size`) |
|
|
33
|
+
* | `page[after]=cur&page[size]=10`| `{ page: { after, size } }` | cursor (`after`/`first`) |
|
|
34
|
+
*
|
|
35
|
+
* A pure reshape — no validation or allow-listing here; that happens downstream
|
|
36
|
+
* in `applyFilter` / `applyCursor` against the `FilterConfig`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseSpatieRequest(qs: unknown): SpatieInput;
|
|
39
|
+
//# sourceMappingURL=spatie_parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spatie_parser.d.ts","sourceRoot":"","sources":["../../src/spatie_parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,WAAY,SAAQ,WAAW,EAAE,YAAY;IAC5D,gFAAgF;IAChF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAWD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,OAAO,GAAG,WAAW,CA8B3D"}
|