@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.
Files changed (168) hide show
  1. package/CHANGELOG.md +417 -0
  2. package/LICENSE +21 -0
  3. package/README.md +75 -0
  4. package/dist/commands/main.d.ts +19 -0
  5. package/dist/commands/main.d.ts.map +1 -0
  6. package/dist/commands/main.js +19 -0
  7. package/dist/commands/main.js.map +1 -0
  8. package/dist/commands/make_filter.d.ts +18 -0
  9. package/dist/commands/make_filter.d.ts.map +1 -0
  10. package/dist/commands/make_filter.js +32 -0
  11. package/dist/commands/make_filter.js.map +1 -0
  12. package/dist/commands/make_filter_client.d.ts +35 -0
  13. package/dist/commands/make_filter_client.d.ts.map +1 -0
  14. package/dist/commands/make_filter_client.js +85 -0
  15. package/dist/commands/make_filter_client.js.map +1 -0
  16. package/dist/configure.d.ts +15 -0
  17. package/dist/configure.d.ts.map +1 -0
  18. package/dist/configure.js +20 -0
  19. package/dist/configure.js.map +1 -0
  20. package/dist/providers/filter_provider.d.ts +28 -0
  21. package/dist/providers/filter_provider.d.ts.map +1 -0
  22. package/dist/providers/filter_provider.js +33 -0
  23. package/dist/providers/filter_provider.js.map +1 -0
  24. package/dist/src/aggregate.d.ts +70 -0
  25. package/dist/src/aggregate.d.ts.map +1 -0
  26. package/dist/src/aggregate.js +195 -0
  27. package/dist/src/aggregate.js.map +1 -0
  28. package/dist/src/aggregate_path.d.ts +25 -0
  29. package/dist/src/aggregate_path.d.ts.map +1 -0
  30. package/dist/src/aggregate_path.js +35 -0
  31. package/dist/src/aggregate_path.js.map +1 -0
  32. package/dist/src/apply_from_request.d.ts +85 -0
  33. package/dist/src/apply_from_request.d.ts.map +1 -0
  34. package/dist/src/apply_from_request.js +164 -0
  35. package/dist/src/apply_from_request.js.map +1 -0
  36. package/dist/src/base_filter.d.ts +49 -0
  37. package/dist/src/base_filter.d.ts.map +1 -0
  38. package/dist/src/base_filter.js +33 -0
  39. package/dist/src/base_filter.js.map +1 -0
  40. package/dist/src/base_model_filter.d.ts +90 -0
  41. package/dist/src/base_model_filter.d.ts.map +1 -0
  42. package/dist/src/base_model_filter.js +85 -0
  43. package/dist/src/base_model_filter.js.map +1 -0
  44. package/dist/src/coerce_value.d.ts +26 -0
  45. package/dist/src/coerce_value.d.ts.map +1 -0
  46. package/dist/src/coerce_value.js +82 -0
  47. package/dist/src/coerce_value.js.map +1 -0
  48. package/dist/src/cursor.d.ts +92 -0
  49. package/dist/src/cursor.d.ts.map +1 -0
  50. package/dist/src/cursor.js +107 -0
  51. package/dist/src/cursor.js.map +1 -0
  52. package/dist/src/custom_filter.d.ts +36 -0
  53. package/dist/src/custom_filter.d.ts.map +1 -0
  54. package/dist/src/custom_filter.js +127 -0
  55. package/dist/src/custom_filter.js.map +1 -0
  56. package/dist/src/decorator_metadata.d.ts +41 -0
  57. package/dist/src/decorator_metadata.d.ts.map +1 -0
  58. package/dist/src/decorator_metadata.js +83 -0
  59. package/dist/src/decorator_metadata.js.map +1 -0
  60. package/dist/src/decorators.d.ts +73 -0
  61. package/dist/src/decorators.d.ts.map +1 -0
  62. package/dist/src/decorators.js +118 -0
  63. package/dist/src/decorators.js.map +1 -0
  64. package/dist/src/escape-like.d.ts +10 -0
  65. package/dist/src/escape-like.d.ts.map +1 -0
  66. package/dist/src/escape-like.js +15 -0
  67. package/dist/src/escape-like.js.map +1 -0
  68. package/dist/src/field_aliases.d.ts +30 -0
  69. package/dist/src/field_aliases.d.ts.map +1 -0
  70. package/dist/src/field_aliases.js +65 -0
  71. package/dist/src/field_aliases.js.map +1 -0
  72. package/dist/src/filter_class.d.ts +46 -0
  73. package/dist/src/filter_class.d.ts.map +1 -0
  74. package/dist/src/filter_class.js +244 -0
  75. package/dist/src/filter_class.js.map +1 -0
  76. package/dist/src/filter_spec.d.ts +231 -0
  77. package/dist/src/filter_spec.d.ts.map +1 -0
  78. package/dist/src/filter_spec.js +182 -0
  79. package/dist/src/filter_spec.js.map +1 -0
  80. package/dist/src/filterable_mixin.d.ts +49 -0
  81. package/dist/src/filterable_mixin.d.ts.map +1 -0
  82. package/dist/src/filterable_mixin.js +47 -0
  83. package/dist/src/filterable_mixin.js.map +1 -0
  84. package/dist/src/generate_client.d.ts +107 -0
  85. package/dist/src/generate_client.d.ts.map +1 -0
  86. package/dist/src/generate_client.js +164 -0
  87. package/dist/src/generate_client.js.map +1 -0
  88. package/dist/src/group_by_count.d.ts +67 -0
  89. package/dist/src/group_by_count.d.ts.map +1 -0
  90. package/dist/src/group_by_count.js +71 -0
  91. package/dist/src/group_by_count.js.map +1 -0
  92. package/dist/src/in_memory.d.ts +279 -0
  93. package/dist/src/in_memory.d.ts.map +1 -0
  94. package/dist/src/in_memory.js +810 -0
  95. package/dist/src/in_memory.js.map +1 -0
  96. package/dist/src/index.d.ts +51 -0
  97. package/dist/src/index.d.ts.map +1 -0
  98. package/dist/src/index.js +33 -0
  99. package/dist/src/index.js.map +1 -0
  100. package/dist/src/lucid_adapter.d.ts +311 -0
  101. package/dist/src/lucid_adapter.d.ts.map +1 -0
  102. package/dist/src/lucid_adapter.js +469 -0
  103. package/dist/src/lucid_adapter.js.map +1 -0
  104. package/dist/src/lucid_macros.d.ts +87 -0
  105. package/dist/src/lucid_macros.d.ts.map +1 -0
  106. package/dist/src/lucid_macros.js +67 -0
  107. package/dist/src/lucid_macros.js.map +1 -0
  108. package/dist/src/normalizer.d.ts +21 -0
  109. package/dist/src/normalizer.d.ts.map +1 -0
  110. package/dist/src/normalizer.js +61 -0
  111. package/dist/src/normalizer.js.map +1 -0
  112. package/dist/src/operators.d.ts +37 -0
  113. package/dist/src/operators.d.ts.map +1 -0
  114. package/dist/src/operators.js +43 -0
  115. package/dist/src/operators.js.map +1 -0
  116. package/dist/src/paginate.d.ts +12 -0
  117. package/dist/src/paginate.d.ts.map +1 -0
  118. package/dist/src/paginate.js +88 -0
  119. package/dist/src/paginate.js.map +1 -0
  120. package/dist/src/pagination_stash.d.ts +4 -0
  121. package/dist/src/pagination_stash.d.ts.map +1 -0
  122. package/dist/src/pagination_stash.js +19 -0
  123. package/dist/src/pagination_stash.js.map +1 -0
  124. package/dist/src/parse_request.d.ts +50 -0
  125. package/dist/src/parse_request.d.ts.map +1 -0
  126. package/dist/src/parse_request.js +220 -0
  127. package/dist/src/parse_request.js.map +1 -0
  128. package/dist/src/runner.d.ts +65 -0
  129. package/dist/src/runner.d.ts.map +1 -0
  130. package/dist/src/runner.js +371 -0
  131. package/dist/src/runner.js.map +1 -0
  132. package/dist/src/source_resolver.d.ts +18 -0
  133. package/dist/src/source_resolver.d.ts.map +1 -0
  134. package/dist/src/source_resolver.js +64 -0
  135. package/dist/src/source_resolver.js.map +1 -0
  136. package/dist/src/spatie_parser.d.ts +39 -0
  137. package/dist/src/spatie_parser.d.ts.map +1 -0
  138. package/dist/src/spatie_parser.js +134 -0
  139. package/dist/src/spatie_parser.js.map +1 -0
  140. package/dist/src/testing.d.ts +73 -0
  141. package/dist/src/testing.d.ts.map +1 -0
  142. package/dist/src/testing.js +136 -0
  143. package/dist/src/testing.js.map +1 -0
  144. package/dist/src/types.d.ts +226 -0
  145. package/dist/src/types.d.ts.map +1 -0
  146. package/dist/src/types.js +2 -0
  147. package/dist/src/types.js.map +1 -0
  148. package/dist/src/validate-column-filter.d.ts +30 -0
  149. package/dist/src/validate-column-filter.d.ts.map +1 -0
  150. package/dist/src/validate-column-filter.js +148 -0
  151. package/dist/src/validate-column-filter.js.map +1 -0
  152. package/dist/src/value-shape.d.ts +18 -0
  153. package/dist/src/value-shape.d.ts.map +1 -0
  154. package/dist/src/value-shape.js +36 -0
  155. package/dist/src/value-shape.js.map +1 -0
  156. package/dist/stubs/main.d.ts +3 -0
  157. package/dist/stubs/main.d.ts.map +1 -0
  158. package/dist/stubs/main.js +4 -0
  159. package/dist/stubs/main.js.map +1 -0
  160. package/dist/stubs/make/filter/main.stub +45 -0
  161. package/package.json +102 -0
  162. package/skills/filter-basics/SKILL.md +285 -0
  163. package/skills/filter-codegen/SKILL.md +246 -0
  164. package/skills/filter-definitions/SKILL.md +340 -0
  165. package/skills/filter-querying/SKILL.md +319 -0
  166. package/skills/filter-safety/SKILL.md +304 -0
  167. package/stubs/main.ts +4 -0
  168. package/stubs/make/filter/main.stub +45 -0
@@ -0,0 +1,85 @@
1
+ import type { ResolvedCursor } from './cursor.js';
2
+ import { type FilterClass } from './filter_class.js';
3
+ import { type FilterSpec } from './filter_spec.js';
4
+ import { type QueryBuilderLike } from './lucid_adapter.js';
5
+ import { type ResolvedPagination } from './runner.js';
6
+ import type { FilterInput } from './types.js';
7
+ /**
8
+ * The structural slice of an AdonisJS `HttpContext` these helpers read. Only
9
+ * `request.qs()` (the decoded query string) is used to source the raw input;
10
+ * the whole ctx is also handed to a spec's tenant resolver. Declared
11
+ * structurally so the package never hard-imports `@adonisjs/core` — a real
12
+ * `HttpContext` satisfies it, and tests can pass a plain object.
13
+ */
14
+ export interface FilterRequestContext {
15
+ request?: {
16
+ qs?: () => Record<string, unknown>;
17
+ };
18
+ [key: string]: any;
19
+ }
20
+ /** Options shared by the request-driven helpers. */
21
+ export interface ApplyFromRequestOptions {
22
+ /**
23
+ * Pre-parsed input to use instead of reading `ctx.request.qs()`. Useful for
24
+ * non-HTTP callers and tests; when omitted the query string is parsed via
25
+ * {@link parseFilterRequest}.
26
+ */
27
+ input?: FilterInput;
28
+ /**
29
+ * Query embedding for pgvector embedding-similarity ranking (distinct from the
30
+ * text `search`). The idiomatic path: the controller computes it from an
31
+ * embedding service and passes it here (rather than shipping a large float
32
+ * array through the query string). Ignored unless the spec declares a
33
+ * `vectorSimilarity` column; merged over any `input.vectorSimilarity`.
34
+ */
35
+ vectorSimilarity?: readonly number[];
36
+ }
37
+ /** Options for {@link applyCursorFromRequest}. */
38
+ export interface ApplyCursorFromRequestOptions extends ApplyFromRequestOptions {
39
+ /** Primary-key column appended to the keyset as a stable tiebreaker. Default `'id'`. */
40
+ primaryKey?: string;
41
+ }
42
+ /**
43
+ * Apply the server-side, non-client-tamperable scope to the query BEFORE the
44
+ * allow-listed request filters: the tenant constraint (when the spec declares
45
+ * one and a tenant id resolves from ctx) and any `defaultFilters`. These bypass
46
+ * the allow-list on purpose — they are trusted server policy, mirroring how the
47
+ * NestJS runner applied `@TenantScoped` via the adapter's auto-field path.
48
+ */
49
+ export declare function applyServerScope(query: QueryBuilderLike, spec: FilterSpec, ctx: FilterRequestContext | undefined): void;
50
+ /**
51
+ * Apply a {@link FilterSpec} to a Lucid query from a request context, returning
52
+ * the resolved offset pagination — the single explicit call an AdonisJS
53
+ * controller makes (the idiomatic replacement for the NestJS
54
+ * `ApplyFilterInterceptor` + `@ApplyFilter` param decorator).
55
+ *
56
+ * It resolves the request input from `ctx.request.qs()`, injects the server
57
+ * scope (tenant + default filters) that is never exposed to the allow-list, and
58
+ * delegates the allow-listed filter/sort/search + pagination resolution to
59
+ * {@link applyFilter}.
60
+ *
61
+ * ```ts
62
+ * const spec = defineFilter({ filterable: ['name', 'age'], tenant: { column: 'tenantId', resolve: (ctx) => ctx.auth.user.tenantId } })
63
+ *
64
+ * // in a controller:
65
+ * const query = User.query()
66
+ * const { page, size } = applyFilterFromRequest(query, spec, ctx)
67
+ * return query.paginate(page, size)
68
+ * ```
69
+ */
70
+ export declare function applyFilterFromRequest(query: QueryBuilderLike, spec: FilterSpec, ctx: FilterRequestContext | undefined, options?: ApplyFromRequestOptions): ResolvedPagination;
71
+ /**
72
+ * The class form: the filter is resolved through the IoC container (so an `@inject()`ed
73
+ * constructor works), its `setup()` runs before anything the request asked for, and every request
74
+ * key the class owns a method for is dispatched to that method instead of the declarative path.
75
+ * Resolution is async, so this overload returns a promise.
76
+ */
77
+ export declare function applyFilterFromRequest(query: QueryBuilderLike, filter: FilterClass, ctx: FilterRequestContext | undefined, options?: ApplyFromRequestOptions): Promise<ResolvedPagination>;
78
+ /**
79
+ * The keyset (cursor) counterpart of {@link applyFilterFromRequest}. Sources the
80
+ * request filters and the `after`/`before`/`first`/`last` cursor params from ctx,
81
+ * injects the same server scope, and delegates to {@link applyCursor}. Feed the
82
+ * fetched rows and the returned {@link ResolvedCursor} to `buildCursorPage`.
83
+ */
84
+ export declare function applyCursorFromRequest(query: QueryBuilderLike, spec: FilterSpec, ctx: FilterRequestContext | undefined, options?: ApplyCursorFromRequestOptions): ResolvedCursor;
85
+ //# sourceMappingURL=apply_from_request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply_from_request.d.ts","sourceRoot":"","sources":["../../src/apply_from_request.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EACL,KAAK,WAAW,EAIjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAsB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG/E,OAAO,EAA+C,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAEjD,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,oDAAoD;AACpD,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,kDAAkD;AAClD,MAAM,WAAW,6BAA8B,SAAQ,uBAAuB;IAC5E,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AASD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,oBAAoB,GAAG,SAAS,GACpC,IAAI,CAeN;AAUD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,oBAAoB,GAAG,SAAS,EACrC,OAAO,CAAC,EAAE,uBAAuB,GAChC,kBAAkB,CAAC;AACtB;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,gBAAgB,EACvB,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,oBAAoB,GAAG,SAAS,EACrC,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAyI/B;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,oBAAoB,GAAG,SAAS,EACrC,OAAO,GAAE,6BAAkC,GAC1C,cAAc,CAahB"}
@@ -0,0 +1,164 @@
1
+ import { isFilterClass, methodForKey, specFromFilterClass, } from './filter_class.js';
2
+ import { specToFilterConfig } from './filter_spec.js';
3
+ import { applyColumnFilters } from './lucid_adapter.js';
4
+ import { parseFilterRequest } from './parse_request.js';
5
+ import { applyCursor, applyFilter } from './runner.js';
6
+ /** Read the decoded query string off the ctx (empty object when absent). */
7
+ function rawQs(ctx) {
8
+ const qs = ctx?.request?.qs;
9
+ if (typeof qs === 'function')
10
+ return qs.call(ctx?.request) ?? {};
11
+ return {};
12
+ }
13
+ /**
14
+ * Apply the server-side, non-client-tamperable scope to the query BEFORE the
15
+ * allow-listed request filters: the tenant constraint (when the spec declares
16
+ * one and a tenant id resolves from ctx) and any `defaultFilters`. These bypass
17
+ * the allow-list on purpose — they are trusted server policy, mirroring how the
18
+ * NestJS runner applied `@TenantScoped` via the adapter's auto-field path.
19
+ */
20
+ export function applyServerScope(query, spec, ctx) {
21
+ const serverFilters = [];
22
+ if (spec.tenant) {
23
+ const tenantId = spec.tenant.resolve(ctx);
24
+ if (tenantId !== undefined && tenantId !== null) {
25
+ serverFilters.push({ field: spec.tenant.column, operator: 'equals', value: tenantId });
26
+ }
27
+ }
28
+ if (spec.defaultFilters.length > 0) {
29
+ serverFilters.push(...spec.defaultFilters);
30
+ }
31
+ if (serverFilters.length > 0) {
32
+ applyColumnFilters(query, serverFilters);
33
+ }
34
+ }
35
+ /** Fall back to the spec's `defaultSort` when the request supplied no sort. */
36
+ function withDefaultSort(input, spec) {
37
+ if ((!input.sort || input.sort.length === 0) && spec.defaultSort.length > 0) {
38
+ return { ...input, sort: [...spec.defaultSort] };
39
+ }
40
+ return input;
41
+ }
42
+ export function applyFilterFromRequest(query, specOrClass, ctx, options = {}) {
43
+ if (isFilterClass(specOrClass)) {
44
+ return applyFilterClass(query, specOrClass, ctx, options);
45
+ }
46
+ return applySpec(query, specOrClass, ctx, options);
47
+ }
48
+ function applySpec(query, spec, ctx, options) {
49
+ const parsed = options.input ?? parseFilterRequest(rawQs(ctx));
50
+ const withVector = options.vectorSimilarity !== undefined
51
+ ? { ...parsed, vectorSimilarity: options.vectorSimilarity }
52
+ : parsed;
53
+ applyServerScope(query, spec, ctx);
54
+ return applyFilter(query, withDefaultSort(withVector, spec), specToFilterConfig(spec));
55
+ }
56
+ /**
57
+ * Resolve a filter class through the request's IoC container when there is one — the same
58
+ * resolver a controller is constructed with, so `@inject()` on the filter behaves identically —
59
+ * and fall back to plain construction outside an AdonisJS request (a test, a script).
60
+ */
61
+ async function resolveFilter(cls, ctx) {
62
+ const resolver = ctx?.containerResolver;
63
+ if (resolver && typeof resolver.make === 'function') {
64
+ return (await resolver.make(cls));
65
+ }
66
+ return new cls();
67
+ }
68
+ /**
69
+ * Run a filter class against the query: bind the per-request state onto the instance, run
70
+ * `setup()`, apply everything the class declared statically through the ordinary spec path, then
71
+ * hand each key the class owns to its own method.
72
+ *
73
+ * The split matters: a key with a method is removed from the declarative input **before** the
74
+ * runner sees it, so `fullName` never becomes `where "fullName" = ?` on a column that may not
75
+ * exist — the method is the only thing that touches it.
76
+ */
77
+ async function applyFilterClass(query, cls, ctx, options) {
78
+ const raw = rawQs(ctx);
79
+ const parsed = options.input ?? parseFilterRequest(raw);
80
+ const instance = await resolveFilter(cls, ctx);
81
+ const owned = [];
82
+ const rest = [];
83
+ for (const condition of parsed.filters ?? []) {
84
+ const method = methodForKey(cls, condition.field);
85
+ if (method === undefined) {
86
+ rest.push(condition);
87
+ continue;
88
+ }
89
+ owned.push({ method, value: condition.value, operator: condition.operator });
90
+ }
91
+ // A bare top-level key (`?minAge=21`) reaches a method of the same name too — the shape
92
+ // `adonis-lucid-filter` dispatches on — as long as the wire format does not own the key.
93
+ for (const [key, value] of Object.entries(raw)) {
94
+ const method = methodForKey(cls, key);
95
+ if (method !== undefined && !owned.some((entry) => entry.method === method)) {
96
+ owned.push({ method, value, operator: 'equals' });
97
+ }
98
+ }
99
+ Object.assign(instance, {
100
+ $query: query,
101
+ $input: raw,
102
+ $parsed: parsed,
103
+ $ctx: ctx,
104
+ });
105
+ await instance.setup?.();
106
+ const pagination = applySpec(query, specFromFilterClass(cls), ctx, {
107
+ ...options,
108
+ input: { ...parsed, filters: rest },
109
+ });
110
+ for (const { method, value, operator } of owned) {
111
+ const fn = instance[method];
112
+ if (typeof fn === 'function') {
113
+ await fn.call(instance, value, operator);
114
+ }
115
+ }
116
+ return pagination;
117
+ }
118
+ /** Parse cursor (keyset) params from a decoded query string (Spatie `page[...]` shapes). */
119
+ function parseCursorParams(qs) {
120
+ const out = {};
121
+ const page = qs.page != null && typeof qs.page === 'object' && !Array.isArray(qs.page)
122
+ ? qs.page
123
+ : undefined;
124
+ const after = qs.after ?? page?.after;
125
+ const before = qs.before ?? page?.before;
126
+ if (typeof after === 'string' && after.length > 0)
127
+ out.after = after;
128
+ else if (typeof before === 'string' && before.length > 0)
129
+ out.before = before;
130
+ const first = qs.first ?? page?.first;
131
+ const last = qs.last ?? page?.last;
132
+ const toInt = (v) => {
133
+ const n = typeof v === 'number' ? v : typeof v === 'string' && v.trim() !== '' ? Number(v) : Number.NaN;
134
+ return Number.isFinite(n) ? n : undefined;
135
+ };
136
+ const f = toInt(first);
137
+ const l = toInt(last);
138
+ if (f !== undefined)
139
+ out.first = f;
140
+ if (l !== undefined)
141
+ out.last = l;
142
+ return out;
143
+ }
144
+ /**
145
+ * The keyset (cursor) counterpart of {@link applyFilterFromRequest}. Sources the
146
+ * request filters and the `after`/`before`/`first`/`last` cursor params from ctx,
147
+ * injects the same server scope, and delegates to {@link applyCursor}. Feed the
148
+ * fetched rows and the returned {@link ResolvedCursor} to `buildCursorPage`.
149
+ */
150
+ export function applyCursorFromRequest(query, spec, ctx, options = {}) {
151
+ const qs = rawQs(ctx);
152
+ const parsed = options.input ?? parseFilterRequest(qs);
153
+ const full = {
154
+ ...withDefaultSort(parsed, spec),
155
+ ...parseCursorParams(qs),
156
+ };
157
+ applyServerScope(query, spec, ctx);
158
+ const config = {
159
+ ...specToFilterConfig(spec),
160
+ ...(options.primaryKey !== undefined && { primaryKey: options.primaryKey }),
161
+ };
162
+ return applyCursor(query, full, config);
163
+ }
164
+ //# sourceMappingURL=apply_from_request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply_from_request.js","sourceRoot":"","sources":["../../src/apply_from_request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { FilterInput } from './types.js';
2
+ /**
3
+ * The class-authoring form of a filter over ANY backend — the generic base behind
4
+ * {@link import('./base_model_filter.js').BaseModelFilter} (Lucid) and custom backends
5
+ * (in-memory stores, engine clients, predicate bags) alike: **one style that does everything**.
6
+ *
7
+ * A method's name IS the key it owns, `setup()` runs on every call before anything the request
8
+ * asked for, and the class is resolved through the IoC container so an `@inject()`ed constructor
9
+ * works — exactly the shape AdonisJS developers know from `adonis-lucid-filter`. What differs per
10
+ * backend is only what `$query` IS: a Lucid builder for models, a draft the methods narrow for
11
+ * anything else.
12
+ *
13
+ * ```ts
14
+ * export class RunFilter extends BaseFilter<RunQueryDraft> {
15
+ * declare $query: RunQueryDraft;
16
+ *
17
+ * status(value: unknown, operator: string) {
18
+ * const values = (Array.isArray(value) ? value : [value]).map(String);
19
+ * this.$query.narrow(values.length === 1 ? { status: values[0] } : { statuses: values });
20
+ * }
21
+ * }
22
+ *
23
+ * const draft = new RunQueryDraft();
24
+ * await applyCustomFilter(draft, RunFilter, ctx);
25
+ * ```
26
+ */
27
+ export declare abstract class BaseFilter<TQuery> {
28
+ /**
29
+ * The query being filtered — the one the caller created. Methods mutate it directly; nothing
30
+ * here constructs or executes a query.
31
+ */
32
+ $query: TQuery;
33
+ /** The raw decoded request input the dispatch reads (query string, or the body on a POST). */
34
+ $input: Readonly<Record<string, unknown>>;
35
+ /** The parsed input — filters, sort, search, pagination — after the wire format is read. */
36
+ $parsed: FilterInput;
37
+ /** The request context handed to the call (a real `HttpContext` in an AdonisJS app). */
38
+ $ctx: unknown;
39
+ /**
40
+ * Runs on every filter call, before anything the request asked for — the place for defaults
41
+ * and for constraints the client must not be able to relax.
42
+ */
43
+ setup?(): void | Promise<void>;
44
+ /** The whole raw input, one key of it, or a fallback when the key is absent. */
45
+ input(): Readonly<Record<string, unknown>>;
46
+ input(key: string): unknown;
47
+ input(key: string, fallback: unknown): unknown;
48
+ }
49
+ //# sourceMappingURL=base_filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base_filter.d.ts","sourceRoot":"","sources":["../../src/base_filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,8BAAsB,UAAU,CAAC,MAAM;IACrC;;;OAGG;IACK,MAAM,EAAE,MAAM,CAAC;IAEvB,8FAA8F;IACtF,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAElD,4FAA4F;IACpF,OAAO,EAAE,WAAW,CAAC;IAE7B,wFAAwF;IAChF,IAAI,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/B,gFAAgF;IAChF,KAAK,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3C,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC;CAKhD"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The class-authoring form of a filter over ANY backend — the generic base behind
3
+ * {@link import('./base_model_filter.js').BaseModelFilter} (Lucid) and custom backends
4
+ * (in-memory stores, engine clients, predicate bags) alike: **one style that does everything**.
5
+ *
6
+ * A method's name IS the key it owns, `setup()` runs on every call before anything the request
7
+ * asked for, and the class is resolved through the IoC container so an `@inject()`ed constructor
8
+ * works — exactly the shape AdonisJS developers know from `adonis-lucid-filter`. What differs per
9
+ * backend is only what `$query` IS: a Lucid builder for models, a draft the methods narrow for
10
+ * anything else.
11
+ *
12
+ * ```ts
13
+ * export class RunFilter extends BaseFilter<RunQueryDraft> {
14
+ * declare $query: RunQueryDraft;
15
+ *
16
+ * status(value: unknown, operator: string) {
17
+ * const values = (Array.isArray(value) ? value : [value]).map(String);
18
+ * this.$query.narrow(values.length === 1 ? { status: values[0] } : { statuses: values });
19
+ * }
20
+ * }
21
+ *
22
+ * const draft = new RunQueryDraft();
23
+ * await applyCustomFilter(draft, RunFilter, ctx);
24
+ * ```
25
+ */
26
+ export class BaseFilter {
27
+ input(key, fallback) {
28
+ if (key === undefined)
29
+ return this.$input;
30
+ return Object.hasOwn(this.$input, key) ? this.$input[key] : fallback;
31
+ }
32
+ }
33
+ //# sourceMappingURL=base_filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base_filter.js","sourceRoot":"","sources":["../../src/base_filter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,OAAgB,UAAU;IA0B9B,KAAK,CAAC,GAAY,EAAE,QAAkB;QACpC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC;QAC1C,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IACvE,CAAC;CACF"}
@@ -0,0 +1,90 @@
1
+ import type { LucidModelLike } from './aggregate.js';
2
+ import { BaseFilter } from './base_filter.js';
3
+ import type { FieldAliases } from './field_aliases.js';
4
+ import type { FilterableInput, RelationColumns, RelationSpec, TenantScopeSpec } from './filter_spec.js';
5
+ import type { FilterFieldTypeInfo } from './generate_client.js';
6
+ import type { QueryBuilderLike } from './lucid_adapter.js';
7
+ import type { ColumnFilter } from './operators.js';
8
+ import type { ComputedFields, FullTextSearchConfig, SortItem, VectorSimilarityConfig } from './types.js';
9
+ /**
10
+ * The class-authoring form of a filter — the counterpart of `defineFilter`, in the shape
11
+ * AdonisJS developers already know from `adonis-lucid-filter`: **a method per request key**,
12
+ * with the query builder on `this.$query`.
13
+ *
14
+ * A method's name IS the key it owns. `?filter[fullName]=silva` calls `fullName('silva')`, and
15
+ * because you wrote the method, the key needs no allow-list entry — writing the code is the
16
+ * decision to expose it. Plain columns that need no custom SQL stay declarative: list them in
17
+ * `static filterable` and the runner applies the operators, search, sort and pagination for you.
18
+ *
19
+ * The class is resolved through the IoC container, so an `@inject()`ed constructor works: a
20
+ * filter can depend on a service exactly like a controller does.
21
+ *
22
+ * ```ts
23
+ * @inject()
24
+ * export default class UserFilter extends BaseModelFilter {
25
+ * declare $query: ModelQueryBuilderContract<typeof User>
26
+ *
27
+ * constructor(private tenants: TenantsService) { super() }
28
+ *
29
+ * static model = User
30
+ * static filterable = ['name', 'email', 'status']
31
+ * static sortable = ['name', 'createdAt']
32
+ * static searchable = ['name', 'email']
33
+ *
34
+ * // always applied, before anything the request asked for
35
+ * setup() {
36
+ * this.$query.where('tenantId', this.tenants.current(this.$ctx))
37
+ * }
38
+ *
39
+ * // the one key this class owns itself
40
+ * fullName(value: string) {
41
+ * this.$query.whereILike('full_name', `%${value}%`)
42
+ * }
43
+ * }
44
+ * ```
45
+ */
46
+ export declare abstract class BaseModelFilter<Q extends QueryBuilderLike = QueryBuilderLike> extends BaseFilter<Q> {
47
+ /** The owning Lucid model. Unlocks to-many aggregates and supplies the root table name. */
48
+ static model?: LucidModelLike;
49
+ /** Columns clients may filter on with the standard operators. `'*'` allows any base column. */
50
+ static filterable?: FilterableInput;
51
+ /** Columns clients may sort on. Defaults to `filterable`. */
52
+ static sortable?: RelationColumns;
53
+ /** Columns the free-text `search` term scans with a portable ILIKE. */
54
+ static searchable?: string[];
55
+ /** Whitelisted relations and the columns reachable through them. */
56
+ static relations?: Record<string, RelationSpec>;
57
+ /** Client-alias → target-field remapping. */
58
+ static aliases?: FieldAliases;
59
+ /** Virtual fields — alias → SQL expression. */
60
+ static computed?: ComputedFields;
61
+ /** Per-field value kinds, driving coercion and typed client codegen. */
62
+ static fieldTypes?: Record<string, FilterFieldTypeInfo>;
63
+ /** Opt-in Postgres tsvector search for the `search` term. */
64
+ static fullText?: FullTextSearchConfig;
65
+ /** Opt-in pgvector embedding-similarity ordering. */
66
+ static vectorSimilarity?: VectorSimilarityConfig;
67
+ /** Auto-scope a column to the tenant resolved from ctx (the declarative form of `setup`). */
68
+ static tenant?: TenantScopeSpec;
69
+ /** Server-declared filters always AND-combined with the request. */
70
+ static defaultFilters?: ColumnFilter[];
71
+ /** Sort applied when the request carries none. */
72
+ static defaultSort?: SortItem[];
73
+ /** Page size when the request asks for none. Default 25. */
74
+ static defaultSize?: number;
75
+ /** Hard cap on page size. Default 100. */
76
+ static maxSize?: number;
77
+ /** Max relation-path hops. Defaults to the deepest declared relation nesting. */
78
+ static maxDepth?: number;
79
+ /** Root table name — defaults to `model.table`. */
80
+ static table?: string;
81
+ /** Throw `InvalidColumnFilterError` on a disallowed field instead of dropping it. */
82
+ static throwOnInvalid?: boolean;
83
+ /** Method names that must never be dispatched from request input, whatever the key says. */
84
+ static blacklist?: string[];
85
+ /** Strip a trailing `Id` from an input key before looking for a method (`companyId` → `company`). */
86
+ static dropId?: boolean;
87
+ /** Match `snake_case` input keys against camelCase methods (`first_name` → `firstName`). Default true. */
88
+ static camelCase?: boolean;
89
+ }
90
+ //# sourceMappingURL=base_model_filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base_model_filter.d.ts","sourceRoot":"","sources":["../../src/base_model_filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,YAAY,EACZ,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EACV,cAAc,EACd,oBAAoB,EACpB,QAAQ,EACR,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,8BAAsB,eAAe,CACnC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB,CAC7C,SAAQ,UAAU,CAAC,CAAC,CAAC;IAGrB,2FAA2F;IAC3F,MAAM,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;IAC9B,+FAA+F;IAC/F,MAAM,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC;IACpC,6DAA6D;IAC7D,MAAM,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAC;IAClC,uEAAuE;IACvE,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,oEAAoE;IACpE,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChD,6CAA6C;IAC7C,MAAM,CAAC,OAAO,CAAC,EAAE,YAAY,CAAC;IAC9B,+CAA+C;IAC/C,MAAM,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IACjC,wEAAwE;IACxE,MAAM,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACxD,6DAA6D;IAC7D,MAAM,CAAC,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IACvC,qDAAqD;IACrD,MAAM,CAAC,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IACjD,6FAA6F;IAC7F,MAAM,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;IAChC,oEAAoE;IACpE,MAAM,CAAC,cAAc,CAAC,EAAE,YAAY,EAAE,CAAC;IACvC,kDAAkD;IAClD,MAAM,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;IAChC,4DAA4D;IAC5D,MAAM,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC5B,0CAA0C;IAC1C,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IACzB,mDAAmD;IACnD,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,MAAM,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAIhC,4FAA4F;IAC5F,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,qGAAqG;IACrG,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IACxB,0GAA0G;IAC1G,MAAM,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC5B"}
@@ -0,0 +1,85 @@
1
+ import { BaseFilter } from './base_filter.js';
2
+ /**
3
+ * The class-authoring form of a filter — the counterpart of `defineFilter`, in the shape
4
+ * AdonisJS developers already know from `adonis-lucid-filter`: **a method per request key**,
5
+ * with the query builder on `this.$query`.
6
+ *
7
+ * A method's name IS the key it owns. `?filter[fullName]=silva` calls `fullName('silva')`, and
8
+ * because you wrote the method, the key needs no allow-list entry — writing the code is the
9
+ * decision to expose it. Plain columns that need no custom SQL stay declarative: list them in
10
+ * `static filterable` and the runner applies the operators, search, sort and pagination for you.
11
+ *
12
+ * The class is resolved through the IoC container, so an `@inject()`ed constructor works: a
13
+ * filter can depend on a service exactly like a controller does.
14
+ *
15
+ * ```ts
16
+ * @inject()
17
+ * export default class UserFilter extends BaseModelFilter {
18
+ * declare $query: ModelQueryBuilderContract<typeof User>
19
+ *
20
+ * constructor(private tenants: TenantsService) { super() }
21
+ *
22
+ * static model = User
23
+ * static filterable = ['name', 'email', 'status']
24
+ * static sortable = ['name', 'createdAt']
25
+ * static searchable = ['name', 'email']
26
+ *
27
+ * // always applied, before anything the request asked for
28
+ * setup() {
29
+ * this.$query.where('tenantId', this.tenants.current(this.$ctx))
30
+ * }
31
+ *
32
+ * // the one key this class owns itself
33
+ * fullName(value: string) {
34
+ * this.$query.whereILike('full_name', `%${value}%`)
35
+ * }
36
+ * }
37
+ * ```
38
+ */
39
+ export class BaseModelFilter extends BaseFilter {
40
+ // ── the declarative half: everything a plain column needs, without writing a method ──────────
41
+ /** The owning Lucid model. Unlocks to-many aggregates and supplies the root table name. */
42
+ static model;
43
+ /** Columns clients may filter on with the standard operators. `'*'` allows any base column. */
44
+ static filterable;
45
+ /** Columns clients may sort on. Defaults to `filterable`. */
46
+ static sortable;
47
+ /** Columns the free-text `search` term scans with a portable ILIKE. */
48
+ static searchable;
49
+ /** Whitelisted relations and the columns reachable through them. */
50
+ static relations;
51
+ /** Client-alias → target-field remapping. */
52
+ static aliases;
53
+ /** Virtual fields — alias → SQL expression. */
54
+ static computed;
55
+ /** Per-field value kinds, driving coercion and typed client codegen. */
56
+ static fieldTypes;
57
+ /** Opt-in Postgres tsvector search for the `search` term. */
58
+ static fullText;
59
+ /** Opt-in pgvector embedding-similarity ordering. */
60
+ static vectorSimilarity;
61
+ /** Auto-scope a column to the tenant resolved from ctx (the declarative form of `setup`). */
62
+ static tenant;
63
+ /** Server-declared filters always AND-combined with the request. */
64
+ static defaultFilters;
65
+ /** Sort applied when the request carries none. */
66
+ static defaultSort;
67
+ /** Page size when the request asks for none. Default 25. */
68
+ static defaultSize;
69
+ /** Hard cap on page size. Default 100. */
70
+ static maxSize;
71
+ /** Max relation-path hops. Defaults to the deepest declared relation nesting. */
72
+ static maxDepth;
73
+ /** Root table name — defaults to `model.table`. */
74
+ static table;
75
+ /** Throw `InvalidColumnFilterError` on a disallowed field instead of dropping it. */
76
+ static throwOnInvalid;
77
+ // ── dispatch controls (the `adonis-lucid-filter` knobs) ──────────────────────────────────────
78
+ /** Method names that must never be dispatched from request input, whatever the key says. */
79
+ static blacklist;
80
+ /** Strip a trailing `Id` from an input key before looking for a method (`companyId` → `company`). */
81
+ static dropId;
82
+ /** Match `snake_case` input keys against camelCase methods (`first_name` → `firstName`). Default true. */
83
+ static camelCase;
84
+ }
85
+ //# sourceMappingURL=base_model_filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base_model_filter.js","sourceRoot":"","sources":["../../src/base_model_filter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAkB9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,OAAgB,eAEpB,SAAQ,UAAa;IACrB,gGAAgG;IAEhG,2FAA2F;IAC3F,MAAM,CAAC,KAAK,CAAkB;IAC9B,+FAA+F;IAC/F,MAAM,CAAC,UAAU,CAAmB;IACpC,6DAA6D;IAC7D,MAAM,CAAC,QAAQ,CAAmB;IAClC,uEAAuE;IACvE,MAAM,CAAC,UAAU,CAAY;IAC7B,oEAAoE;IACpE,MAAM,CAAC,SAAS,CAAgC;IAChD,6CAA6C;IAC7C,MAAM,CAAC,OAAO,CAAgB;IAC9B,+CAA+C;IAC/C,MAAM,CAAC,QAAQ,CAAkB;IACjC,wEAAwE;IACxE,MAAM,CAAC,UAAU,CAAuC;IACxD,6DAA6D;IAC7D,MAAM,CAAC,QAAQ,CAAwB;IACvC,qDAAqD;IACrD,MAAM,CAAC,gBAAgB,CAA0B;IACjD,6FAA6F;IAC7F,MAAM,CAAC,MAAM,CAAmB;IAChC,oEAAoE;IACpE,MAAM,CAAC,cAAc,CAAkB;IACvC,kDAAkD;IAClD,MAAM,CAAC,WAAW,CAAc;IAChC,4DAA4D;IAC5D,MAAM,CAAC,WAAW,CAAU;IAC5B,0CAA0C;IAC1C,MAAM,CAAC,OAAO,CAAU;IACxB,iFAAiF;IACjF,MAAM,CAAC,QAAQ,CAAU;IACzB,mDAAmD;IACnD,MAAM,CAAC,KAAK,CAAU;IACtB,qFAAqF;IACrF,MAAM,CAAC,cAAc,CAAW;IAEhC,gGAAgG;IAEhG,4FAA4F;IAC5F,MAAM,CAAC,SAAS,CAAY;IAC5B,qGAAqG;IACrG,MAAM,CAAC,MAAM,CAAW;IACxB,0GAA0G;IAC1G,MAAM,CAAC,SAAS,CAAW;CAC5B"}
@@ -0,0 +1,26 @@
1
+ import type { FilterFieldKind } from './types.js';
2
+ /** Outcome of coercing one filter value against its declared {@link FilterFieldKind}. */
3
+ export type CoerceResult = {
4
+ ok: true;
5
+ value: unknown;
6
+ } | {
7
+ ok: false;
8
+ reason: string;
9
+ };
10
+ /**
11
+ * Coerce a raw filter value to its declared column kind, or reject it.
12
+ *
13
+ * Why this exists: a filter value arriving over a query string is ALWAYS a string
14
+ * (`?filter[dayOfWeek][equals]=3` yields `'3'`). Postgres papers over the benign cases with an
15
+ * implicit cast — `day_of_week = '3'` and `is_recurring = 'false'` both work — so the gap is
16
+ * invisible until a client sends something that ISN'T castable: `is_recurring = 'xyz'` raises
17
+ * `invalid input syntax for type boolean` at the database, which surfaces as a 500 on a public
18
+ * endpoint from pure user input. The allow-list guards which FIELD may be filtered; this guards
19
+ * what VALUE may reach the column.
20
+ *
21
+ * Rejection is not an exception here: the caller maps it onto the spec's existing `throwOnInvalid`
22
+ * semantics, so a bad value behaves exactly like a disallowed field — dropped by default, or a
23
+ * loud `InvalidColumnFilterError` (→ 400) when the spec asks for it.
24
+ */
25
+ export declare function coerceFilterValue(value: unknown, kind: FilterFieldKind): CoerceResult;
26
+ //# sourceMappingURL=coerce_value.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coerce_value.d.ts","sourceRoot":"","sources":["../../src/coerce_value.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,GAAG,YAAY,CAgErF"}