@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,134 @@
1
+ import { parseDistinct, parseSort, toColumnFilters } from './parse_request.js';
2
+ function toInt(value) {
3
+ if (typeof value === 'number' && Number.isFinite(value))
4
+ return value;
5
+ if (typeof value === 'string' && value.trim() !== '') {
6
+ const n = Number(value);
7
+ if (Number.isFinite(n))
8
+ return n;
9
+ }
10
+ return undefined;
11
+ }
12
+ /**
13
+ * Parse a decoded, Spatie-laravel-query-builder / JSON:API-style query object
14
+ * into a {@link SpatieInput}. This is the richer, additive counterpart to
15
+ * {@link parseFilterRequest}: it understands the same `filter`/`sort`/`search`
16
+ * shapes plus JSON:API `include`, sparse `fields[resource]` sets, and cursor
17
+ * pagination (`page[after]`/`page[before]`).
18
+ *
19
+ * | Query string | Decoded input | Mapped to |
20
+ * | ------------------------------- | ------------------------------------ | ------------------------------------------ |
21
+ * | `filter[name]=Al` | `{ filter: { name: 'Al' } }` | `equals` column filter |
22
+ * | `filter[id]=1,2,3` | `{ filter: { id: '1,2,3' } }` | `in` column filter |
23
+ * | `filter[age][gte]=18` | `{ filter: { age: { gte: '18' } } }` | operator column filter |
24
+ * | `sort=-createdAt,name` | `{ sort: '-createdAt,name' }` | sort items |
25
+ * | `distinct=city,tier` | `{ distinct: 'city,tier' }` | `distinct: ['city', 'tier']` |
26
+ * | `include=posts,comments` | `{ include: 'posts,comments' }` | `include: ['posts', 'comments']` |
27
+ * | `fields[users]=id,name` | `{ fields: { users: 'id,name' } }` | `select: ['id', 'name']` |
28
+ * | `page[number]=2&page[size]=10` | `{ page: { number, size } }` | offset (`page`/`size`) |
29
+ * | `page[after]=cur&page[size]=10`| `{ page: { after, size } }` | cursor (`after`/`first`) |
30
+ *
31
+ * A pure reshape — no validation or allow-listing here; that happens downstream
32
+ * in `applyFilter` / `applyCursor` against the `FilterConfig`.
33
+ */
34
+ export function parseSpatieRequest(qs) {
35
+ if (qs == null || typeof qs !== 'object')
36
+ return {};
37
+ const src = qs;
38
+ const out = {};
39
+ if (src.filter != null && typeof src.filter === 'object' && !Array.isArray(src.filter)) {
40
+ const filters = [];
41
+ for (const [field, value] of Object.entries(src.filter)) {
42
+ filters.push(...toColumnFilters(field, value));
43
+ }
44
+ if (filters.length > 0)
45
+ out.filters = filters;
46
+ }
47
+ const sort = parseSort(src.sort);
48
+ if (sort.length > 0)
49
+ out.sort = sort;
50
+ const distinct = parseDistinct(src.distinct);
51
+ if (distinct.length > 0)
52
+ out.distinct = distinct;
53
+ if (typeof src.search === 'string' && src.search.length > 0)
54
+ out.search = src.search;
55
+ const include = parseInclude(src.include);
56
+ if (include.length > 0)
57
+ out.include = include;
58
+ const select = parseSparseFieldsets(src.fields);
59
+ if (select.length > 0)
60
+ out.select = select;
61
+ applyPage(src.page, out);
62
+ return out;
63
+ }
64
+ /** Parse `include=posts,comments` (string or array) into a relation-name list. */
65
+ function parseInclude(include) {
66
+ if (typeof include === 'string') {
67
+ return include
68
+ .split(',')
69
+ .map((s) => s.trim())
70
+ .filter((s) => s.length > 0);
71
+ }
72
+ if (Array.isArray(include)) {
73
+ return include.filter((s) => typeof s === 'string' && s.length > 0);
74
+ }
75
+ return [];
76
+ }
77
+ /**
78
+ * Flatten JSON:API sparse fieldsets (`fields[resource]=a,b`) into a single
79
+ * order-preserving, de-duplicated list of column names. Per-resource keys are
80
+ * collapsed because a single SELECT applies to the primary entity.
81
+ */
82
+ function parseSparseFieldsets(fields) {
83
+ if (fields == null || typeof fields !== 'object' || Array.isArray(fields))
84
+ return [];
85
+ const seen = new Set();
86
+ const out = [];
87
+ for (const value of Object.values(fields)) {
88
+ const cols = typeof value === 'string'
89
+ ? value.split(',')
90
+ : Array.isArray(value)
91
+ ? value.filter((s) => typeof s === 'string')
92
+ : [];
93
+ for (const raw of cols) {
94
+ const col = raw.trim();
95
+ if (col.length > 0 && !seen.has(col)) {
96
+ seen.add(col);
97
+ out.push(col);
98
+ }
99
+ }
100
+ }
101
+ return out;
102
+ }
103
+ /**
104
+ * Map a JSON:API `page` object onto `out`: `page[after]`/`page[before]` →
105
+ * cursor (`after`/`first`, `before`/`last`); otherwise `page[number]`/`page[size]`
106
+ * → offset (`page`/`size`). `after` wins if both cursor bounds are present.
107
+ */
108
+ function applyPage(page, out) {
109
+ if (page == null || typeof page !== 'object' || Array.isArray(page))
110
+ return;
111
+ const p = page;
112
+ const after = typeof p.after === 'string' ? p.after : undefined;
113
+ const before = typeof p.before === 'string' ? p.before : undefined;
114
+ const size = toInt(p.size);
115
+ if (after !== undefined || before !== undefined) {
116
+ if (after !== undefined) {
117
+ out.after = after;
118
+ if (size !== undefined)
119
+ out.first = size;
120
+ }
121
+ else if (before !== undefined) {
122
+ out.before = before;
123
+ if (size !== undefined)
124
+ out.last = size;
125
+ }
126
+ return;
127
+ }
128
+ const number = toInt(p.number);
129
+ if (number !== undefined)
130
+ out.page = number;
131
+ if (size !== undefined)
132
+ out.size = size;
133
+ }
134
+ //# sourceMappingURL=spatie_parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spatie_parser.js","sourceRoot":"","sources":["../../src/spatie_parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import type { QueryBuilderLike } from './lucid_adapter.js';
2
+ /** A recorded query-builder call: method name + its scalar args. */
3
+ export interface RecordedCall {
4
+ method: string;
5
+ args: unknown[];
6
+ }
7
+ /**
8
+ * A recording stand-in for a Lucid query builder, published under
9
+ * `@adonis-agora/filter/testing` so consumers can unit-test their own filter
10
+ * definitions without a database. It satisfies {@link QueryBuilderLike}, so any
11
+ * of the adapter/runner helpers (or {@link applyFilterFromRequest}) can be
12
+ * driven against it and the resulting translation asserted.
13
+ *
14
+ * Nested `where`/`orWhere` closures run against child recorders, and a dotted
15
+ * relation-path filter's `whereHas` records the relation name plus runs its
16
+ * nested callback against a child. {@link flatten} collects every leaf call
17
+ * (across all groups and relation subqueries) so tests can assert a translation
18
+ * appeared without caring about the exact grouping nesting.
19
+ */
20
+ export declare class MockQueryBuilder implements QueryBuilderLike {
21
+ calls: RecordedCall[];
22
+ children: MockQueryBuilder[];
23
+ private record;
24
+ private group;
25
+ where(a: any, b?: any, c?: any): this;
26
+ orWhere(cb: (qb: QueryBuilderLike) => void): this;
27
+ /**
28
+ * Record a relation subquery: the `whereHas` call is captured with the
29
+ * relation name (so `find('whereHas')?.args[0]` is the relation), and the
30
+ * nested callback runs against a child recorder whose calls surface via
31
+ * {@link flatten}.
32
+ */
33
+ whereHas(relation: string, cb: (qb: QueryBuilderLike) => void): this;
34
+ whereNot(callback: (qb: QueryBuilderLike) => void): this;
35
+ whereNot(column: string, value: unknown): this;
36
+ whereIn(column: string, values: unknown[]): this;
37
+ whereNotIn(column: string, values: unknown[]): this;
38
+ whereNull(column: string): this;
39
+ whereNotNull(column: string): this;
40
+ whereBetween(column: string, range: [unknown, unknown]): this;
41
+ whereNotBetween(column: string, range: [unknown, unknown]): this;
42
+ whereILike(column: string, value: string): this;
43
+ orWhereILike(column: string, value: string): this;
44
+ orderBy(column: string, direction: 'asc' | 'desc'): this;
45
+ /** Record a raw predicate: `args` are `[sql, bindings]` (bindings default `[]`). */
46
+ whereRaw(sql: string, bindings?: readonly unknown[]): this;
47
+ /** Record a raw ordering: `args` are `[sql, bindings]` (bindings default `[]`). */
48
+ orderByRaw(sql: string, bindings?: readonly unknown[]): this;
49
+ limit(count: number): this;
50
+ /** Record a row skip: `args` are `[n]`. Only the group-by-count aggregation pages this way. */
51
+ offset(n: number): this;
52
+ /** Record a projection: `args` are the select expressions (aggregates included). */
53
+ select(...columns: string[]): this;
54
+ /** Record an aggregation: `args` are `[column]` (e.g. `'* AS count'`). */
55
+ count(column: string): this;
56
+ /** Record a grouping: `args` are the group columns. */
57
+ groupBy(...columns: string[]): this;
58
+ /** Record a DISTINCT projection: `args` are the distinct column names. */
59
+ distinct(...columns: string[]): this;
60
+ /**
61
+ * Every recorded call across this builder and all nested groups and relation
62
+ * subqueries (excludes the anonymous group markers, but keeps `whereHas` since
63
+ * its recorded arg is the relation name, not a marker).
64
+ */
65
+ flatten(): RecordedCall[];
66
+ /** Find the first flattened call matching `method`. */
67
+ find(method: string): RecordedCall | undefined;
68
+ /** Every flattened call matching `method` (handy for asserting relation subqueries). */
69
+ findAll(method: string): RecordedCall[];
70
+ }
71
+ /** Factory for a fresh {@link MockQueryBuilder} — the recording Lucid stand-in. */
72
+ export declare function makeMockQueryBuilder(): MockQueryBuilder;
73
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/testing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * A recording stand-in for a Lucid query builder, published under
3
+ * `@adonis-agora/filter/testing` so consumers can unit-test their own filter
4
+ * definitions without a database. It satisfies {@link QueryBuilderLike}, so any
5
+ * of the adapter/runner helpers (or {@link applyFilterFromRequest}) can be
6
+ * driven against it and the resulting translation asserted.
7
+ *
8
+ * Nested `where`/`orWhere` closures run against child recorders, and a dotted
9
+ * relation-path filter's `whereHas` records the relation name plus runs its
10
+ * nested callback against a child. {@link flatten} collects every leaf call
11
+ * (across all groups and relation subqueries) so tests can assert a translation
12
+ * appeared without caring about the exact grouping nesting.
13
+ */
14
+ export class MockQueryBuilder {
15
+ calls = [];
16
+ children = [];
17
+ record(method, ...args) {
18
+ this.calls.push({ method, args });
19
+ return this;
20
+ }
21
+ group(method, cb) {
22
+ const child = new MockQueryBuilder();
23
+ this.children.push(child);
24
+ this.calls.push({ method, args: ['<group>'] });
25
+ cb(child);
26
+ return this;
27
+ }
28
+ // biome-ignore lint/suspicious/noExplicitAny: structural overloads for the mock.
29
+ where(a, b, c) {
30
+ if (typeof a === 'function')
31
+ return this.group('where', a);
32
+ return c !== undefined ? this.record('where', a, b, c) : this.record('where', a, b);
33
+ }
34
+ orWhere(cb) {
35
+ return this.group('orWhere', cb);
36
+ }
37
+ /**
38
+ * Record a relation subquery: the `whereHas` call is captured with the
39
+ * relation name (so `find('whereHas')?.args[0]` is the relation), and the
40
+ * nested callback runs against a child recorder whose calls surface via
41
+ * {@link flatten}.
42
+ */
43
+ whereHas(relation, cb) {
44
+ const child = new MockQueryBuilder();
45
+ this.children.push(child);
46
+ this.calls.push({ method: 'whereHas', args: [relation] });
47
+ cb(child);
48
+ return this;
49
+ }
50
+ // biome-ignore lint/suspicious/noExplicitAny: structural overloads for the mock.
51
+ whereNot(a, b) {
52
+ if (typeof a === 'function')
53
+ return this.group('whereNot', a);
54
+ return this.record('whereNot', a, b);
55
+ }
56
+ whereIn(column, values) {
57
+ return this.record('whereIn', column, values);
58
+ }
59
+ whereNotIn(column, values) {
60
+ return this.record('whereNotIn', column, values);
61
+ }
62
+ whereNull(column) {
63
+ return this.record('whereNull', column);
64
+ }
65
+ whereNotNull(column) {
66
+ return this.record('whereNotNull', column);
67
+ }
68
+ whereBetween(column, range) {
69
+ return this.record('whereBetween', column, range);
70
+ }
71
+ whereNotBetween(column, range) {
72
+ return this.record('whereNotBetween', column, range);
73
+ }
74
+ whereILike(column, value) {
75
+ return this.record('whereILike', column, value);
76
+ }
77
+ orWhereILike(column, value) {
78
+ return this.record('orWhereILike', column, value);
79
+ }
80
+ orderBy(column, direction) {
81
+ return this.record('orderBy', column, direction);
82
+ }
83
+ /** Record a raw predicate: `args` are `[sql, bindings]` (bindings default `[]`). */
84
+ whereRaw(sql, bindings = []) {
85
+ return this.record('whereRaw', sql, bindings);
86
+ }
87
+ /** Record a raw ordering: `args` are `[sql, bindings]` (bindings default `[]`). */
88
+ orderByRaw(sql, bindings = []) {
89
+ return this.record('orderByRaw', sql, bindings);
90
+ }
91
+ limit(count) {
92
+ return this.record('limit', count);
93
+ }
94
+ /** Record a row skip: `args` are `[n]`. Only the group-by-count aggregation pages this way. */
95
+ offset(n) {
96
+ return this.record('offset', n);
97
+ }
98
+ /** Record a projection: `args` are the select expressions (aggregates included). */
99
+ select(...columns) {
100
+ return this.record('select', ...columns);
101
+ }
102
+ /** Record an aggregation: `args` are `[column]` (e.g. `'* AS count'`). */
103
+ count(column) {
104
+ return this.record('count', column);
105
+ }
106
+ /** Record a grouping: `args` are the group columns. */
107
+ groupBy(...columns) {
108
+ return this.record('groupBy', ...columns);
109
+ }
110
+ /** Record a DISTINCT projection: `args` are the distinct column names. */
111
+ distinct(...columns) {
112
+ return this.record('distinct', ...columns);
113
+ }
114
+ /**
115
+ * Every recorded call across this builder and all nested groups and relation
116
+ * subqueries (excludes the anonymous group markers, but keeps `whereHas` since
117
+ * its recorded arg is the relation name, not a marker).
118
+ */
119
+ flatten() {
120
+ const own = this.calls.filter((c) => c.args[0] !== '<group>');
121
+ return [...own, ...this.children.flatMap((c) => c.flatten())];
122
+ }
123
+ /** Find the first flattened call matching `method`. */
124
+ find(method) {
125
+ return this.flatten().find((c) => c.method === method);
126
+ }
127
+ /** Every flattened call matching `method` (handy for asserting relation subqueries). */
128
+ findAll(method) {
129
+ return this.flatten().filter((c) => c.method === method);
130
+ }
131
+ }
132
+ /** Factory for a fresh {@link MockQueryBuilder} — the recording Lucid stand-in. */
133
+ export function makeMockQueryBuilder() {
134
+ return new MockQueryBuilder();
135
+ }
136
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/testing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,226 @@
1
+ import type { FieldAliases } from './field_aliases.js';
2
+ import type { FullTextSearchOptions, VectorDistanceMetric } from './lucid_adapter.js';
3
+ import type { ColumnFilter } from './operators.js';
4
+ /**
5
+ * A field's classified value kind — mirrors `@adonis-agora/filter-client`'s `FieldTypeKind`.
6
+ *
7
+ * Lives here rather than next to the client codegen because it is no longer codegen-only: the same
8
+ * declaration drives server-side value coercion (see {@link FilterConfig.fieldTypes}). Re-exported
9
+ * from `generate_client.ts` for backwards compatibility.
10
+ */
11
+ export type FilterFieldKind = 'string' | 'number' | 'boolean' | 'date' | 'json' | 'unknown';
12
+ /** A sort directive: field + direction. */
13
+ export interface SortItem {
14
+ field: string;
15
+ direction: 'asc' | 'desc';
16
+ }
17
+ /** Context handed to a {@link ComputedSource} function — the root table alias. */
18
+ export interface ComputedContext {
19
+ /**
20
+ * The root table's SQL alias — for Lucid this is the model's table name (the
21
+ * main table carries no generated alias), taken from {@link FilterConfig.table}.
22
+ * A correlated subquery references the outer row through it, e.g.
23
+ * `` ({ alias }) => `(SELECT COUNT(*) FROM posts WHERE posts.author_id = ${alias}.id)` ``.
24
+ */
25
+ alias: string;
26
+ }
27
+ /**
28
+ * A dev-declared virtual/computed field's SQL source. Two forms:
29
+ *
30
+ * - **verbatim string** — inlined exactly as written (`"first || ' ' || last"`).
31
+ * No token substitution; a correlated subquery needing the outer alias must
32
+ * use the function form.
33
+ * - **function** `({ alias }) => sql` — receives the root table alias
34
+ * ({@link ComputedContext}) so it can emit a correlated subquery referencing
35
+ * the outer row.
36
+ *
37
+ * SAFETY: the source is authored by the developer at declaration time, never by
38
+ * the client. The client only references the declared alias name; its filter
39
+ * VALUE is always a bound parameter. Only this dev expression is inlined.
40
+ */
41
+ export type ComputedSource = string | ((ctx: ComputedContext) => string);
42
+ /** A map of computed-field alias → its dev-declared {@link ComputedSource}. */
43
+ export type ComputedFields = Record<string, ComputedSource>;
44
+ /**
45
+ * Where a request's structured input is read from, for {@link resolveInputFromRequest}:
46
+ *
47
+ * - `'auto'` — query on reads (GET/HEAD), query+body (body wins) on writes.
48
+ * - `'query'` / `'body'` — always that container.
49
+ * - a dot-path (e.g. `'body.filters'`) — the nested object at that path.
50
+ * - a function — a custom extractor receiving the raw request.
51
+ */
52
+ export type InputSource = 'auto' | 'query' | 'body' | (string & {}) | ((req: unknown) => Record<string, unknown> | undefined);
53
+ /**
54
+ * How incoming field-name keys are normalized before matching against the
55
+ * allow-list — a built-in case transform or a custom mapping function.
56
+ */
57
+ export type InputNormalizer = 'camelCase' | 'snakeCase' | ((key: string) => string);
58
+ /**
59
+ * A group-by-count aggregation request: the distinct values of `field` with counts, over the
60
+ * rows the active filters select. `limit` bounds the rows (highest count first — tag cardinality
61
+ * grows with the data, so the unbounded answer is a listing); `offset` pages that bound;
62
+ * `search` narrows to values containing the text, server-side, so a rare value outside the first
63
+ * page stays reachable by typing.
64
+ */
65
+ export interface GroupByCountRequest {
66
+ field: string;
67
+ limit?: number | undefined;
68
+ offset?: number | undefined;
69
+ search?: string | undefined;
70
+ }
71
+ /** Bounds for a group-by-count answer: how many group rows come back, from where, matching what. */
72
+ export interface GroupByCountOptions {
73
+ limit?: number | undefined;
74
+ offset?: number | undefined;
75
+ /** Narrow to groups whose value contains this text (case-insensitive). */
76
+ search?: string | undefined;
77
+ }
78
+ /**
79
+ * The parsed, structured input a {@link applyFilter} call consumes — produced by
80
+ * {@link parseFilterRequest} from a request query string, or built directly.
81
+ */
82
+ export interface FilterInput {
83
+ /** Column filter conditions (with optional AND/OR composition). */
84
+ filters?: ColumnFilter[];
85
+ /** Sort directives, applied in order. */
86
+ sort?: SortItem[];
87
+ /**
88
+ * Select DISTINCT values of the given field(s) — the active filters, search,
89
+ * sort and pagination still apply. Useful for populating a filter dropdown
90
+ * with the distinct values of a column. Fields are alias-resolved and checked
91
+ * against {@link FilterConfig.allowed} exactly like a `where` field; unknown
92
+ * fields are dropped (or rejected under {@link FilterConfig.throwOnInvalid}).
93
+ */
94
+ distinct?: string[];
95
+ /**
96
+ * Free-text search term. Routed through Postgres tsvector full-text search
97
+ * when the policy declares {@link FilterConfig.fullText}, otherwise a portable
98
+ * ILIKE scan across {@link FilterConfig.searchable}.
99
+ */
100
+ search?: string;
101
+ /** 1-based page number for offset pagination. */
102
+ page?: number;
103
+ /** Page size for offset pagination. */
104
+ size?: number;
105
+ /**
106
+ * A group-by-count aggregation replacing entity-row output: the distinct values of one field
107
+ * with counts — what populates a filter dropdown. Parsed from the `groupByCount[field]`,
108
+ * `groupByCount[limit]`, `groupByCount[offset]` and `groupByCount[search]` params the client
109
+ * builder emits.
110
+ */
111
+ groupByCount?: GroupByCountRequest;
112
+ /**
113
+ * A query embedding to rank rows by pgvector *similarity* (distinct from the
114
+ * text `search` above). Applied only when the policy declares a similarity
115
+ * column (see {@link FilterConfig.vectorSimilarity}) — ignored otherwise, so
116
+ * non-similarity requests are unchanged. Typically the controller computes
117
+ * this from an embedding service, not the query string.
118
+ */
119
+ vectorSimilarity?: readonly number[];
120
+ }
121
+ /**
122
+ * Declares a Postgres tsvector full-text search on a policy — the primary
123
+ * `search` path when set. The request's `search` string is matched against the
124
+ * configured document via `websearch_to_tsquery`, optionally ranked by
125
+ * `ts_rank`. Omitting `column`-related fields is a compile error; a policy
126
+ * without this config falls back to the ILIKE {@link FilterConfig.searchable}
127
+ * scan. Additive — carries the same shape as {@link FullTextSearchOptions}
128
+ * minus the per-request `query`.
129
+ */
130
+ export type FullTextSearchConfig = Omit<FullTextSearchOptions, 'query'>;
131
+ /**
132
+ * Declares a pgvector **embedding similarity** ordering on a policy — distinct
133
+ * from full-text `search`. When a request also carries a query embedding
134
+ * ({@link FilterInput.vectorSimilarity}), the runner ranks rows by ascending
135
+ * distance to it. Additive — a policy without this behaves exactly as before.
136
+ */
137
+ export interface VectorSimilarityConfig {
138
+ /** The pgvector column ranked against the query embedding (e.g. `'embedding'`). */
139
+ column: string;
140
+ /** Distance metric → pgvector operator. Default `'cosine'` (`<=>`). */
141
+ metric?: VectorDistanceMetric;
142
+ /** Optional max-distance filter — drop rows farther than this from the embedding. */
143
+ threshold?: number;
144
+ /** Optional top-K truncation — keep only the K nearest rows. */
145
+ topK?: number;
146
+ }
147
+ /**
148
+ * An allow-list of field names — the security boundary for filter/sort. One of:
149
+ *
150
+ * - `'*'` — allow any field (use with care);
151
+ * - `string[]` — allow exactly these field names;
152
+ * - a predicate `(field) => boolean` — allow fields for which it returns true.
153
+ * The predicate form lets a policy express rules a flat list can't (e.g.
154
+ * relation-path whitelisting with a depth cap). It is evaluated against the
155
+ * already alias-resolved target field, never the client-facing alias key.
156
+ */
157
+ export type AllowList = '*' | string[] | ((field: string) => boolean);
158
+ /**
159
+ * Per-call filter policy. The allow-lists are the security boundary: only fields
160
+ * named here can be filtered/sorted/searched, so client input can never probe
161
+ * arbitrary columns.
162
+ */
163
+ export interface FilterConfig {
164
+ /** Columns clients may filter on. `'*'` allows any (use with care). */
165
+ allowed: AllowList;
166
+ /** Columns clients may sort on. Defaults to {@link FilterConfig.allowed}. */
167
+ sortable?: AllowList;
168
+ /**
169
+ * Columns the free-text `search` term scans with a portable ILIKE — the
170
+ * default search path when {@link FilterConfig.fullText} is not set.
171
+ */
172
+ searchable?: string[];
173
+ /**
174
+ * Declared column value kinds, keyed by field. A declared field has its filter value coerced to
175
+ * that kind; a value that can't be coerced is treated exactly like a disallowed field (dropped,
176
+ * or thrown under {@link FilterConfig.throwOnInvalid}). Guards the column from an uncastable
177
+ * value that Postgres would otherwise reject at query time as a 500. Undeclared fields are
178
+ * passed through uncoerced.
179
+ */
180
+ fieldTypes?: Readonly<Record<string, {
181
+ kind?: FilterFieldKind;
182
+ }>>;
183
+ /**
184
+ * Opt-in Postgres tsvector full-text search. When set, the request `search`
185
+ * string routes through `websearch_to_tsquery`/`@@` (and optional `ts_rank`)
186
+ * instead of the ILIKE {@link FilterConfig.searchable} scan. Omitted → ILIKE.
187
+ */
188
+ fullText?: FullTextSearchConfig;
189
+ /** Default page size when none is given. Default 25. */
190
+ defaultSize?: number;
191
+ /** Hard cap on page size. Default 100. */
192
+ maxSize?: number;
193
+ /** Throw `InvalidColumnFilterError` on a disallowed/invalid field instead of dropping it. Default false (drop). */
194
+ throwOnInvalid?: boolean;
195
+ /**
196
+ * Declarative field-name remapping applied to filter and sort fields BEFORE
197
+ * allow-listing — an alias key resolves to its target column, which is what
198
+ * the allow-list, validation and query builder then see. Aliases do not
199
+ * cascade. See {@link resolveFieldAlias}.
200
+ */
201
+ aliases?: FieldAliases;
202
+ /**
203
+ * Opt-in pgvector embedding-similarity ordering (distinct from text `search`).
204
+ * When set and the request carries a query embedding
205
+ * ({@link FilterInput.vectorSimilarity}), rows are ranked nearest-first by
206
+ * distance to it. Omitted → no similarity ordering (default).
207
+ */
208
+ vectorSimilarity?: VectorSimilarityConfig;
209
+ /**
210
+ * Virtual/computed fields — a map of alias → dev-declared SQL expression
211
+ * ({@link ComputedSource}). A declared alias becomes filterable and sortable
212
+ * as if it were a real column; its declaration IS its allow-list (computed
213
+ * fields bypass the column `allowed`/`sortable` lists — they are not real
214
+ * columns). The client value is always parameterized; only the dev expression
215
+ * is inlined.
216
+ */
217
+ computed?: ComputedFields;
218
+ /**
219
+ * The root table's SQL alias, surfaced to {@link ComputedSource} functions as
220
+ * {@link ComputedContext.alias}. For Lucid this is the model's table name.
221
+ * Required for function-form computed fields (and to-many aggregate fields)
222
+ * whose correlated subqueries must reference the outer row.
223
+ */
224
+ table?: string;
225
+ }
226
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
1
+ import { type ColumnFilter, type FilterOperator } from './operators.js';
2
+ /**
3
+ * Normalizes an operator string to its canonical {@link FilterOperator},
4
+ * resolving SQL-symbol aliases (`=` -> `equals`, `!=` -> `notEquals`, ...).
5
+ * Canonical operators (and unknown strings) pass through unchanged.
6
+ */
7
+ export declare function normalizeOperator(operator: string): FilterOperator;
8
+ /**
9
+ * Maximum allowed nesting depth for AND/OR filter composition.
10
+ * Prevents stack overflow from maliciously deep payloads (DoS).
11
+ */
12
+ export declare const MAX_FILTER_DEPTH = 10;
13
+ export declare class InvalidColumnFilterError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ /**
17
+ * Validates a ColumnFilter at runtime:
18
+ * - field must be a non-empty string with no SQL-unsafe characters
19
+ * - operator must be a known FilterOperator
20
+ * - value is required for non-unary operators
21
+ * - 'between' requires a 2-element array
22
+ * - 'in'/'isAnyOf' require arrays
23
+ * - AND/OR arrays are validated recursively
24
+ */
25
+ export declare function validateColumnFilter(filter: ColumnFilter, depth?: number): void;
26
+ /**
27
+ * Validates an array of ColumnFilter objects.
28
+ */
29
+ export declare function validateColumnFilters(filters: ColumnFilter[]): void;
30
+ //# sourceMappingURL=validate-column-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-column-filter.d.ts","sourceRoot":"","sources":["../../src/validate-column-filter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,cAAc,EAEpB,MAAM,gBAAgB,CAAC;AAIxB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAElE;AAyBD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,SAAI,GAAG,IAAI,CAsG1E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,IAAI,CAOnE"}