@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,10 @@
1
+ /**
2
+ * Escapes special characters in a LIKE pattern value.
3
+ *
4
+ * @param value - The raw string to escape.
5
+ * @param dialect - The SQL dialect to target.
6
+ * - `'standard'` (default): escapes `%`, `_`, and `\` with a backslash.
7
+ * - `'mssql'`: escapes `%`, `_`, and `[` using bracket notation (`[$&]`).
8
+ */
9
+ export declare function escapeLike(value: string, dialect?: 'standard' | 'mssql'): string;
10
+ //# sourceMappingURL=escape-like.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-like.d.ts","sourceRoot":"","sources":["../../src/escape-like.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,UAAU,GAAG,OAAoB,GAAG,MAAM,CAK5F"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Escapes special characters in a LIKE pattern value.
3
+ *
4
+ * @param value - The raw string to escape.
5
+ * @param dialect - The SQL dialect to target.
6
+ * - `'standard'` (default): escapes `%`, `_`, and `\` with a backslash.
7
+ * - `'mssql'`: escapes `%`, `_`, and `[` using bracket notation (`[$&]`).
8
+ */
9
+ export function escapeLike(value, dialect = 'standard') {
10
+ if (dialect === 'mssql') {
11
+ return value.replace(/[%_\[]/g, '[$&]');
12
+ }
13
+ return value.replace(/[%_\\]/g, '\\$&');
14
+ }
15
+ //# sourceMappingURL=escape-like.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"escape-like.js","sourceRoot":"","sources":["../../src/escape-like.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,UAAgC,UAAU;IAClF,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { ColumnFilter } from './operators.js';
2
+ import type { SortItem } from './types.js';
3
+ /** A declarative field-name remapping: client-facing alias → resolved target column. */
4
+ export type FieldAliases = Record<string, string>;
5
+ /**
6
+ * Resolves a client-supplied field name to its declared `aliases` target, or
7
+ * returns `field` unchanged when no alias applies.
8
+ *
9
+ * This is the single choke point for alias resolution — call it wherever a
10
+ * client-supplied field name is about to be resolved against the entity (column
11
+ * filters and sort). It must run FIRST, before any validation (allow-listing,
12
+ * field-charset checks) — those all evaluate the resolved target, never the
13
+ * alias key.
14
+ *
15
+ * Aliases do not cascade: the returned target is never re-run through the alias
16
+ * map, even when it happens to also be a declared alias key itself — this is
17
+ * what makes alias cycles structurally impossible.
18
+ */
19
+ export declare function resolveFieldAlias(aliases: FieldAliases | undefined, field: string): string;
20
+ /**
21
+ * Returns a copy of a {@link ColumnFilter} with its own field and every nested
22
+ * AND/OR child field resolved through the alias map. Pure — the input tree is
23
+ * left untouched.
24
+ */
25
+ export declare function remapFilterAliases(filter: ColumnFilter, aliases: FieldAliases): ColumnFilter;
26
+ /** Resolves every sort directive's field through the alias map (returns a new array). */
27
+ export declare function remapSortAliases(sorts: SortItem[], aliases: FieldAliases): SortItem[];
28
+ //# sourceMappingURL=field_aliases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field_aliases.d.ts","sourceRoot":"","sources":["../../src/field_aliases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAkB3C,wFAAwF;AACxF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAK1F;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,YAAY,CAS5F;AAED,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,YAAY,GAAG,QAAQ,EAAE,CAKrF"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Field names that would resolve to inherited `Object.prototype` members
3
+ * (`__proto__`, `constructor`, `prototype`, `toString`, `valueOf`) if looked up
4
+ * on a plain-object `aliases` map without an own-property check. A
5
+ * client-supplied field name is never trusted as an alias-map lookup key without
6
+ * this guard, even though the `Object.hasOwn` check below already excludes
7
+ * inherited properties; kept as explicit defense-in-depth.
8
+ */
9
+ const BLOCKED_ALIAS_KEYS = new Set([
10
+ '__proto__',
11
+ 'constructor',
12
+ 'prototype',
13
+ 'toString',
14
+ 'valueOf',
15
+ ]);
16
+ /**
17
+ * Resolves a client-supplied field name to its declared `aliases` target, or
18
+ * returns `field` unchanged when no alias applies.
19
+ *
20
+ * This is the single choke point for alias resolution — call it wherever a
21
+ * client-supplied field name is about to be resolved against the entity (column
22
+ * filters and sort). It must run FIRST, before any validation (allow-listing,
23
+ * field-charset checks) — those all evaluate the resolved target, never the
24
+ * alias key.
25
+ *
26
+ * Aliases do not cascade: the returned target is never re-run through the alias
27
+ * map, even when it happens to also be a declared alias key itself — this is
28
+ * what makes alias cycles structurally impossible.
29
+ */
30
+ export function resolveFieldAlias(aliases, field) {
31
+ if (!aliases || BLOCKED_ALIAS_KEYS.has(field) || !Object.hasOwn(aliases, field)) {
32
+ return field;
33
+ }
34
+ return aliases[field] ?? field;
35
+ }
36
+ /**
37
+ * Returns a copy of a {@link ColumnFilter} with its own field and every nested
38
+ * AND/OR child field resolved through the alias map. Pure — the input tree is
39
+ * left untouched.
40
+ */
41
+ export function remapFilterAliases(filter, aliases) {
42
+ const next = {
43
+ field: resolveFieldAlias(aliases, filter.field),
44
+ operator: filter.operator,
45
+ };
46
+ if (filter.value !== undefined)
47
+ next.value = filter.value;
48
+ if (filter.AND)
49
+ next.AND = filter.AND.map((f) => remapFilterAliases(f, aliases));
50
+ if (filter.OR)
51
+ next.OR = filter.OR.map((f) => remapFilterAliases(f, aliases));
52
+ return next;
53
+ }
54
+ /** Resolves every sort directive's field through the alias map (returns a new array). */
55
+ export function remapSortAliases(sorts, aliases) {
56
+ return sorts.map((sort) => ({
57
+ field: resolveFieldAlias(aliases, sort.field),
58
+ direction: sort.direction,
59
+ }));
60
+ }
61
+ //# sourceMappingURL=field_aliases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field_aliases.js","sourceRoot":"","sources":["../../src/field_aliases.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,WAAW;IACX,aAAa;IACb,WAAW;IACX,UAAU;IACV,SAAS;CACV,CAAC,CAAC;AAKH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAiC,EAAE,KAAa;IAChF,IAAI,CAAC,OAAO,IAAI,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC;QAChF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;AACjC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAoB,EAAE,OAAqB;IAC5E,MAAM,IAAI,GAAiB;QACzB,KAAK,EAAE,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;QAC/C,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC;IACF,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC1D,IAAI,MAAM,CAAC,GAAG;QAAE,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACjF,IAAI,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9E,OAAO,IAAI,CAAC;AACd,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,gBAAgB,CAAC,KAAiB,EAAE,OAAqB;IACvE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC1B,KAAK,EAAE,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC;QAC7C,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,146 @@
1
+ import type { FieldAliases } from './field_aliases.js';
2
+ import type { ColumnFilter } from './operators.js';
3
+ import type { FilterConfig, FullTextSearchConfig, SortItem, VectorSimilarityConfig } from './types.js';
4
+ /**
5
+ * Thrown when a {@link defineFilter} declaration is itself invalid (a developer
6
+ * error surfaced at wiring time, not a bad client request). Request-time
7
+ * violations (a disallowed field under `throwOnInvalid`) still surface as
8
+ * `InvalidColumnFilterError` from the runner — this is the one new typed error,
9
+ * for the declaration boundary the NestJS `@Filterable` decorator guarded.
10
+ */
11
+ export declare class FilterDefinitionError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ /** Column-level allow-list for a relation: `'*'` (any column) or a bare-name list. */
15
+ export type RelationColumns = string[] | '*';
16
+ /**
17
+ * The filterable/sortable declaration for one whitelisted relation, keyed by the
18
+ * relation name on the owning model. Column names are **bare** (unprefixed) — the
19
+ * request field is the dotted path `relation.column`. Nesting `relations` again
20
+ * whitelists deeper paths (`relation.child.column`), bounded by `maxDepth`.
21
+ */
22
+ export interface RelationSpec {
23
+ /** Columns of the related model clients may filter on. Defaults to `'*'`. */
24
+ filterable?: RelationColumns;
25
+ /** Columns of the related model clients may sort on. Defaults to `filterable`. */
26
+ sortable?: RelationColumns;
27
+ /** Further whitelisted relations reachable from this one (one hop deeper). */
28
+ relations?: Record<string, RelationSpec>;
29
+ }
30
+ /**
31
+ * Reads the current tenant id from the request context (the AdonisJS
32
+ * `HttpContext`, or any object the caller passes). A `null`/`undefined` result
33
+ * means "no tenant in context" — tenant scoping is then skipped (opt-in, exactly
34
+ * like the NestJS `@TenantScoped` no-op when no tenant resolves).
35
+ */
36
+ export type TenantResolver = (ctx: unknown) => string | number | null | undefined;
37
+ /** Tenant auto-scope: constrain `column` to the tenant id resolved from ctx. */
38
+ export interface TenantScopeSpec {
39
+ /** The model column constrained to the current tenant id (e.g. `'tenantId'`). */
40
+ column: string;
41
+ /** Resolves the tenant id from the request ctx. Nullish → scope is skipped. */
42
+ resolve: TenantResolver;
43
+ }
44
+ /**
45
+ * The declarative filter definition — the idiomatic AdonisJS reimplementation of
46
+ * everything the NestJS `@Filterable`/`@Relations`/`@TenantScoped` decorators
47
+ * encoded, as a plain options object (no decorators, no metadata reflection).
48
+ */
49
+ export interface DefineFilterOptions {
50
+ /** Columns clients may filter on. `'*'` allows any base column. */
51
+ filterable: RelationColumns;
52
+ /** Columns clients may sort on. Defaults to {@link DefineFilterOptions.filterable}. */
53
+ sortable?: RelationColumns;
54
+ /**
55
+ * Columns the free-text `search` term scans with a portable ILIKE — the
56
+ * default search path when {@link DefineFilterOptions.fullText} is not set.
57
+ */
58
+ searchable?: string[];
59
+ /**
60
+ * Opt-in Postgres tsvector full-text search. When set, the request `search`
61
+ * string routes through `websearch_to_tsquery`/`@@` (and optional `ts_rank`)
62
+ * instead of the ILIKE `searchable` scan. Column(s) + language + rank.
63
+ */
64
+ fullText?: FullTextSearchConfig;
65
+ /** Whitelisted relations and their nested filterable/sortable columns. */
66
+ relations?: Record<string, RelationSpec>;
67
+ /**
68
+ * Maximum relation-path depth (number of relation hops; a base column is
69
+ * depth 0, `posts.title` is depth 1, `posts.comments.body` is depth 2).
70
+ * Defaults to the deepest declared relation nesting. An explicit smaller value
71
+ * caps paths even when a deeper relation is declared.
72
+ */
73
+ maxDepth?: number;
74
+ /** Client-alias → resolved-target field remapping (see {@link resolveFieldAlias}). */
75
+ aliases?: FieldAliases;
76
+ /**
77
+ * Opt-in pgvector embedding-similarity ordering (distinct from text `search`):
78
+ * declares the vector column (and metric / threshold / top-K) rows are ranked
79
+ * by when a request carries a query embedding. Additive — a spec without this
80
+ * is unchanged.
81
+ */
82
+ vectorSimilarity?: VectorSimilarityConfig;
83
+ /** Opt-in tenant auto-scope read from ctx. */
84
+ tenant?: TenantScopeSpec;
85
+ /**
86
+ * Server-declared filters always applied (in real column terms, AND-combined
87
+ * with the request filters). Not subject to the allow-list — they are trusted
88
+ * server policy, never client input.
89
+ */
90
+ defaultFilters?: ColumnFilter[];
91
+ /** Sort applied when the request supplies none (stable default ordering). */
92
+ defaultSort?: SortItem[];
93
+ /** Default page size when the request gives none. Default 25 (in the runner). */
94
+ defaultSize?: number;
95
+ /** Hard cap on page size. Default 100 (in the runner). */
96
+ maxSize?: number;
97
+ /** Throw `InvalidColumnFilterError` on a disallowed field instead of dropping it. */
98
+ throwOnInvalid?: boolean;
99
+ }
100
+ /**
101
+ * A resolved, reusable, frozen filter definition produced by {@link defineFilter}.
102
+ * Build it once (module scope) and hand it to {@link applyFilterFromRequest} on
103
+ * every request. The `isFilterable`/`isSortable` predicates are the allow-list
104
+ * boundary — relation-path and depth aware — that the runner enforces.
105
+ */
106
+ export interface FilterSpec {
107
+ readonly filterable: RelationColumns;
108
+ readonly sortable: RelationColumns;
109
+ readonly searchable: readonly string[];
110
+ readonly fullText: FullTextSearchConfig | undefined;
111
+ readonly relations: Readonly<Record<string, RelationSpec>>;
112
+ readonly maxDepth: number;
113
+ readonly aliases: FieldAliases | undefined;
114
+ readonly vectorSimilarity: VectorSimilarityConfig | undefined;
115
+ readonly tenant: TenantScopeSpec | undefined;
116
+ readonly defaultFilters: readonly ColumnFilter[];
117
+ readonly defaultSort: readonly SortItem[];
118
+ readonly defaultSize: number | undefined;
119
+ readonly maxSize: number | undefined;
120
+ readonly throwOnInvalid: boolean;
121
+ /** Is this (possibly relation-dotted, alias-resolved) field filterable? */
122
+ isFilterable(field: string): boolean;
123
+ /** Is this (possibly relation-dotted, alias-resolved) field sortable? */
124
+ isSortable(field: string): boolean;
125
+ }
126
+ /**
127
+ * Build a reusable {@link FilterSpec} from a declarative {@link DefineFilterOptions}.
128
+ *
129
+ * This is the AdonisJS-idiomatic replacement for the NestJS decorator stack:
130
+ * instead of `@Filterable`/`@Relations`/`@TenantScoped` metadata read by an
131
+ * interceptor, the definition is an explicit, framework-free config object built
132
+ * once and passed explicitly to {@link applyFilterFromRequest}. It captures the
133
+ * same feature set the decorators encoded — filterable/sortable allow-listing,
134
+ * a relation whitelist with a depth cap, field aliases, tenant scoping, and
135
+ * default filters/sort.
136
+ */
137
+ export declare function defineFilter(options: DefineFilterOptions): FilterSpec;
138
+ /**
139
+ * Project a {@link FilterSpec} onto the per-call {@link FilterConfig} the runner's
140
+ * {@link applyFilter}/{@link applyCursor} consume. The allow-lists become
141
+ * predicates (so relation-path + depth rules survive), and `defaultSort` fields
142
+ * are unioned into the sortable predicate so a server-declared default ordering
143
+ * is never dropped by the client-facing sort allow-list.
144
+ */
145
+ export declare function specToFilterConfig(spec: FilterSpec): FilterConfig;
146
+ //# sourceMappingURL=filter_spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter_spec.d.ts","sourceRoot":"","sources":["../../src/filter_spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Thrown when a {@link defineFilter} declaration is itself invalid (a developer
3
+ * error surfaced at wiring time, not a bad client request). Request-time
4
+ * violations (a disallowed field under `throwOnInvalid`) still surface as
5
+ * `InvalidColumnFilterError` from the runner — this is the one new typed error,
6
+ * for the declaration boundary the NestJS `@Filterable` decorator guarded.
7
+ */
8
+ export class FilterDefinitionError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'FilterDefinitionError';
12
+ }
13
+ }
14
+ /** Blocked path segments that could otherwise index inherited prototype members. */
15
+ const BLOCKED_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype', 'toString', 'valueOf']);
16
+ /** Deepest relation nesting declared under `relations` (0 when none). */
17
+ function declaredDepth(relations) {
18
+ if (!relations)
19
+ return 0;
20
+ let max = 0;
21
+ for (const rel of Object.values(relations)) {
22
+ max = Math.max(max, 1 + declaredDepth(rel.relations));
23
+ }
24
+ return max;
25
+ }
26
+ /** Does an allow-list admit a bare column name? `undefined` → treated as `'*'`. */
27
+ function columnAllowed(list, column) {
28
+ if (list === undefined || list === '*')
29
+ return true;
30
+ return list.includes(column);
31
+ }
32
+ /**
33
+ * Resolve a (possibly dotted) field path against the base allow-lists and the
34
+ * relation whitelist, enforcing `maxDepth`. Pure — the client-supplied `field`
35
+ * is only ever read, and each segment is guarded against prototype-pollution
36
+ * lookups before it indexes the relations map.
37
+ */
38
+ function pathAllowed(field, kind, baseFilterable, baseSortable, relations, maxDepth) {
39
+ const segments = field.split('.');
40
+ // Base column (no relation hop).
41
+ if (segments.length === 1) {
42
+ const seg = segments[0];
43
+ if (seg.length === 0 || BLOCKED_SEGMENTS.has(seg))
44
+ return false;
45
+ return columnAllowed(kind === 'filterable' ? baseFilterable : baseSortable, seg);
46
+ }
47
+ // Relation path — bounded by maxDepth (hops = segments - 1).
48
+ if (segments.length - 1 > maxDepth)
49
+ return false;
50
+ let node = relations;
51
+ for (let i = 0; i < segments.length - 1; i++) {
52
+ const seg = segments[i];
53
+ if (seg.length === 0 || BLOCKED_SEGMENTS.has(seg))
54
+ return false;
55
+ if (!node || !Object.hasOwn(node, seg))
56
+ return false;
57
+ const rel = node[seg];
58
+ // Last hop: the final segment is the leaf column on this relation.
59
+ if (i === segments.length - 2) {
60
+ const leaf = segments[segments.length - 1];
61
+ if (leaf.length === 0 || BLOCKED_SEGMENTS.has(leaf))
62
+ return false;
63
+ const list = kind === 'filterable' ? rel.filterable : (rel.sortable ?? rel.filterable);
64
+ return columnAllowed(list, leaf);
65
+ }
66
+ node = rel.relations;
67
+ }
68
+ return false;
69
+ }
70
+ /**
71
+ * Build a reusable {@link FilterSpec} from a declarative {@link DefineFilterOptions}.
72
+ *
73
+ * This is the AdonisJS-idiomatic replacement for the NestJS decorator stack:
74
+ * instead of `@Filterable`/`@Relations`/`@TenantScoped` metadata read by an
75
+ * interceptor, the definition is an explicit, framework-free config object built
76
+ * once and passed explicitly to {@link applyFilterFromRequest}. It captures the
77
+ * same feature set the decorators encoded — filterable/sortable allow-listing,
78
+ * a relation whitelist with a depth cap, field aliases, tenant scoping, and
79
+ * default filters/sort.
80
+ */
81
+ export function defineFilter(options) {
82
+ if (!options || options.filterable === undefined) {
83
+ throw new FilterDefinitionError('defineFilter requires a `filterable` allow-list.');
84
+ }
85
+ if (options.maxDepth !== undefined &&
86
+ (!Number.isInteger(options.maxDepth) || options.maxDepth < 0)) {
87
+ throw new FilterDefinitionError('`maxDepth` must be a non-negative integer.');
88
+ }
89
+ const relations = options.relations ?? {};
90
+ const filterable = options.filterable;
91
+ const sortable = options.sortable ?? options.filterable;
92
+ const maxDepth = options.maxDepth ?? declaredDepth(relations);
93
+ const spec = {
94
+ filterable,
95
+ sortable,
96
+ searchable: options.searchable ?? [],
97
+ fullText: options.fullText,
98
+ relations,
99
+ maxDepth,
100
+ aliases: options.aliases,
101
+ vectorSimilarity: options.vectorSimilarity,
102
+ tenant: options.tenant,
103
+ defaultFilters: options.defaultFilters ?? [],
104
+ defaultSort: options.defaultSort ?? [],
105
+ defaultSize: options.defaultSize,
106
+ maxSize: options.maxSize,
107
+ throwOnInvalid: options.throwOnInvalid ?? false,
108
+ isFilterable(field) {
109
+ return pathAllowed(field, 'filterable', filterable, sortable, relations, maxDepth);
110
+ },
111
+ isSortable(field) {
112
+ return pathAllowed(field, 'sortable', filterable, sortable, relations, maxDepth);
113
+ },
114
+ };
115
+ return Object.freeze(spec);
116
+ }
117
+ /**
118
+ * Project a {@link FilterSpec} onto the per-call {@link FilterConfig} the runner's
119
+ * {@link applyFilter}/{@link applyCursor} consume. The allow-lists become
120
+ * predicates (so relation-path + depth rules survive), and `defaultSort` fields
121
+ * are unioned into the sortable predicate so a server-declared default ordering
122
+ * is never dropped by the client-facing sort allow-list.
123
+ */
124
+ export function specToFilterConfig(spec) {
125
+ const defaultSortFields = new Set(spec.defaultSort.map((s) => s.field));
126
+ const allowed = (field) => spec.isFilterable(field);
127
+ const sortable = (field) => spec.isSortable(field) || defaultSortFields.has(field);
128
+ return {
129
+ allowed,
130
+ sortable,
131
+ ...(spec.searchable.length > 0 && { searchable: [...spec.searchable] }),
132
+ ...(spec.fullText && { fullText: spec.fullText }),
133
+ ...(spec.aliases && { aliases: spec.aliases }),
134
+ ...(spec.vectorSimilarity && { vectorSimilarity: spec.vectorSimilarity }),
135
+ ...(spec.defaultSize !== undefined && { defaultSize: spec.defaultSize }),
136
+ ...(spec.maxSize !== undefined && { maxSize: spec.maxSize }),
137
+ throwOnInvalid: spec.throwOnInvalid,
138
+ };
139
+ }
140
+ //# sourceMappingURL=filter_spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter_spec.js","sourceRoot":"","sources":["../../src/filter_spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ import type { FilterSpec } from './filter_spec.js';
2
+ /**
3
+ * Codegen for the typed client — the AdonisJS-idiomatic port of the NestJS
4
+ * `nestjs-filter` codegen. Where the NestJS package ships a standalone
5
+ * `reflect-metadata` bin that reads decorator metadata off entity classes and
6
+ * emits `filterQuery: () => filterQueryTyped<Fields, TypeMap>()` members into a
7
+ * generated `api.ts`, the Adonis port has no decorators: the filter declaration
8
+ * is already a plain runtime {@link FilterSpec} produced by `defineFilter(...)`.
9
+ *
10
+ * So generation is a **pure function of the spec** — string in, string out, no
11
+ * AST walk, no metadata reflection, no framework. The ace command
12
+ * (`make:filter-client`) is the thin IO wrapper that loads the app's declared
13
+ * specs and writes the emitted module to disk. Keeping the generator pure keeps
14
+ * it trivially testable (snapshot/string assertions) and framework-free, exactly
15
+ * like the rest of this core.
16
+ *
17
+ * The emitted module targets `@adonis-agora/filter-client`'s `filterQueryTyped<Fields,
18
+ * FieldTypes>()` factory — the same client the NestJS codegen emitted against —
19
+ * so the two ecosystems share one browser runtime.
20
+ */
21
+ /** A field's classified value kind — mirrors `@adonis-agora/filter-client`'s `FieldTypeKind`. */
22
+ export type FilterFieldKind = 'string' | 'number' | 'boolean' | 'date' | 'json' | 'unknown';
23
+ /**
24
+ * Optional per-field type information. A {@link FilterSpec} carries the
25
+ * allow-list but not column value types (Lucid models aren't reflected here), so
26
+ * the caller supplies types to unlock the client's type-aware operator narrowing.
27
+ * Without it the emitted client is still field-name-safe, just operator-permissive.
28
+ */
29
+ export interface FilterFieldTypeInfo {
30
+ /** Classified value kind. Ignored when {@link typeRef} or {@link enumValues} is set. */
31
+ kind?: FilterFieldKind;
32
+ /** Enum literal members — emitted as a union (`"A" | "B"` or `1 | 2`). Wins over {@link kind}. */
33
+ enumValues?: readonly (string | number)[];
34
+ /** A named TS type (e.g. `'Role'`) — emitted verbatim. Wins over everything. */
35
+ typeRef?: string;
36
+ /** Nullable column — appends `| null` to the emitted type. */
37
+ nullable?: boolean;
38
+ }
39
+ /** Options for {@link generateFilterClient}. */
40
+ export interface GenerateFilterClientOptions {
41
+ /**
42
+ * Base name for the emitted identifiers — e.g. `'people'` yields
43
+ * `PeopleFilterFields`, `PeopleFilterFieldTypes`, `peopleFilterMeta` and
44
+ * `peopleFilterQuery()`. Non-identifier characters are stripped.
45
+ */
46
+ name: string;
47
+ /** Per-field value types keyed by (dotted) field path. Unlocks operator narrowing. */
48
+ fieldTypes?: Record<string, FilterFieldTypeInfo>;
49
+ /** Import specifier for `filterQueryTyped`. Default `'@adonis-agora/filter-client'`. */
50
+ clientModule?: string;
51
+ /** Cap relation-path depth of the emitted field union. Default {@link FilterSpec.maxDepth}. */
52
+ maxDepth?: number;
53
+ /** Emit the "generated — do not edit" banner. Default `true`. */
54
+ banner?: boolean;
55
+ }
56
+ /** Enumerate the filterable field paths a spec admits, capped at `maxDepth` relation hops. */
57
+ export declare function filterableFieldPaths(spec: FilterSpec, maxDepth?: number): string[];
58
+ /** Enumerate the sortable field paths a spec admits, capped at `maxDepth` relation hops. */
59
+ export declare function sortableFieldPaths(spec: FilterSpec, maxDepth?: number): string[];
60
+ /**
61
+ * Generate a typed filter client module for one {@link FilterSpec} — a pure
62
+ * string transform. The emitted module exports, for a spec named `people`:
63
+ *
64
+ * - `type PeopleFilterFields` — the union of filterable field paths (the security
65
+ * boundary, as concrete string literals);
66
+ * - `interface PeopleFilterFieldTypes` — the per-field value-type map (only when
67
+ * `fieldTypes` is supplied), which drives the client's operator/value narrowing;
68
+ * - `const peopleFilterMeta` — runtime metadata (filterable/sortable/searchable
69
+ * fields, whitelisted relations, per-field kinds, and the default sort / page
70
+ * size / max size + cursor keyset the runner paginates by);
71
+ * - `function peopleFilterQuery()` — a `filterQueryTyped<Fields, FieldTypes>()`
72
+ * factory returning a type-safe builder scoped to this spec.
73
+ */
74
+ export declare function generateFilterClient(spec: FilterSpec, options: GenerateFilterClientOptions): string;
75
+ /**
76
+ * One entry in a {@link FilterClientManifest}: a declared spec plus the optional
77
+ * type info / codegen overrides the emitter needs. This is what an app exports
78
+ * from `config/filter.ts` (as `filters`) for the `make:filter-client` command to
79
+ * consume — the specs are already `defineFilter(...)` results, so no reflection.
80
+ */
81
+ export interface FilterClientEntry {
82
+ /** The `defineFilter(...)` result to generate a client for. */
83
+ spec: FilterSpec;
84
+ /** Per-field value types (see {@link GenerateFilterClientOptions.fieldTypes}). */
85
+ fieldTypes?: Record<string, FilterFieldTypeInfo>;
86
+ /** Override the client import specifier for this entry. */
87
+ clientModule?: string;
88
+ /** Override the relation-path depth cap for this entry. */
89
+ maxDepth?: number;
90
+ }
91
+ /** A map of client name → declared spec, keyed by the emitted client's base name. */
92
+ export type FilterClientManifest = Record<string, FilterClientEntry>;
93
+ /** A single generated client: its name, its AdonisJS-conventional file name, and its code. */
94
+ export interface GeneratedFilterClient {
95
+ name: string;
96
+ /** `snake_case`d file name, e.g. `blog_post_filter_client.ts`. */
97
+ filename: string;
98
+ code: string;
99
+ }
100
+ /**
101
+ * Expand a whole {@link FilterClientManifest} into per-spec generated modules —
102
+ * the pure core the `make:filter-client` ace command writes to disk. Kept pure
103
+ * (manifest in, modules out) so the command stays a thin IO wrapper and the
104
+ * expansion is itself testable without a running app.
105
+ */
106
+ export declare function generateFilterClients(manifest: FilterClientManifest): GeneratedFilterClient[];
107
+ //# sourceMappingURL=generate_client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate_client.d.ts","sourceRoot":"","sources":["../../src/generate_client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,kBAAkB,CAAC;AAEjE;;;;;;;;;;;;;;;;;;GAkBG;AAEH,iGAAiG;AACjG,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAE5F;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,wFAAwF;IACxF,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,kGAAkG;IAClG,UAAU,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACjD,wFAAwF;IACxF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,SAAgB,GAAG,MAAM,EAAE,CAEzF;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,SAAgB,GAAG,MAAM,EAAE,CAEvF;AAqFD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,2BAA2B,GACnC,MAAM,CAiFR;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,IAAI,EAAE,UAAU,CAAC;IACjB,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACjD,2DAA2D;IAC3D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qFAAqF;AACrF,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAErE,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,qBAAqB,EAAE,CAW7F"}