@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,73 @@
1
+ import type { FilterFieldKind } from './types.js';
2
+ /**
3
+ * A decorator that works under both flavours — the legacy `(prototype, key, descriptor)` form
4
+ * AdonisJS compiles today (`experimentalDecorators`, which Lucid's `@column` requires) and the
5
+ * TC39 standard `(value, context)` form TypeScript 5 emits when that flag is off. Which one runs
6
+ * is decided at call time, from the shape of the second argument.
7
+ */
8
+ export interface DualDecorator {
9
+ (target: object, propertyKey: string | symbol, descriptor?: PropertyDescriptor): void;
10
+ (value: unknown, context: StandardDecoratorContext): void;
11
+ }
12
+ /** The slice of a TC39 decorator context this library reads. */
13
+ export interface StandardDecoratorContext {
14
+ kind: string;
15
+ name: string | symbol;
16
+ metadata?: object;
17
+ }
18
+ /**
19
+ * Bind a filter method to the request key(s) it answers, instead of deriving the key from the
20
+ * method's name.
21
+ *
22
+ * Worth reaching for exactly twice: when the key cannot be a method name (`team.name`), and when
23
+ * one method should answer several keys. It also pins the public contract — with a binding, the
24
+ * key survives renaming the method.
25
+ *
26
+ * ```ts
27
+ * export default class UserFilter extends BaseModelFilter {
28
+ * @filterFor('team.name')
29
+ * byTeamName(value: string) {
30
+ * this.$query.whereHas('team', (team) => team.whereILike('name', `%${value}%`))
31
+ * }
32
+ *
33
+ * @filterFor('q', 'query')
34
+ * freeText(value: string) { … }
35
+ * }
36
+ * ```
37
+ *
38
+ * A bound method answers **only** its declared keys: `?filter[byTeamName]=…` stops working, which
39
+ * is the point — the method name becomes an implementation detail.
40
+ */
41
+ export declare function filterFor(...keys: string[]): DualDecorator;
42
+ /**
43
+ * Mark a model column as filterable — the allow-list, declared where the column is, stacked under
44
+ * Lucid's own `@column()`.
45
+ *
46
+ * ```ts
47
+ * export default class User extends compose(BaseModel, Filterable) {
48
+ * @column()
49
+ * @filterable()
50
+ * declare status: string
51
+ *
52
+ * @column()
53
+ * @filterable('number')
54
+ * declare age: number
55
+ *
56
+ * @column({ serializeAs: null })
57
+ * declare passwordHash: string // undecorated — unreachable, as it should be
58
+ * }
59
+ * ```
60
+ *
61
+ * A filter class picks these up through its `static model`. Anything the filter declares itself
62
+ * still wins outright, so a stricter filter over a shared model can narrow the surface:
63
+ * `static filterable = ['status']` exposes that column and no other.
64
+ *
65
+ * The optional kind is the same one the colocated `filterable` map takes: it drives value
66
+ * coercion and the generated client's types.
67
+ */
68
+ export declare function filterable(kind?: FilterFieldKind): DualDecorator;
69
+ /** Mark a model column as sortable — `?sort=-createdAt` reaches it, nothing else does. */
70
+ export declare function sortable(): DualDecorator;
71
+ /** Mark a model column as scanned by the free-text `?search=` term. */
72
+ export declare function searchable(): DualDecorator;
73
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/decorators.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,UAAU,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACtF,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,GAAG,IAAI,CAAC;CAC3D;AAED,gEAAgE;AAChE,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAwCD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAmB1D;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE,eAAe,GAAG,aAAa,CAKhE;AAED,0FAA0F;AAC1F,wBAAgB,QAAQ,IAAI,aAAa,CAKxC;AAED,uEAAuE;AACvE,wBAAgB,UAAU,IAAI,aAAa,CAK1C"}
@@ -0,0 +1,118 @@
1
+ import { legacyRecord, standardRecord } from './decorator_metadata.js';
2
+ import { isReservedKey } from './filter_class.js';
3
+ import { FilterDefinitionError } from './filter_spec.js';
4
+ /** Standard decorators are handed a context object; legacy ones a property key. */
5
+ function isStandardContext(value) {
6
+ return typeof value === 'object' && value !== null && 'kind' in value;
7
+ }
8
+ /**
9
+ * Resolve where a decorator should write, whichever flavour invoked it: the declared member's
10
+ * name, and the record for the class that declared it.
11
+ */
12
+ function target(what, expectedKinds, first, second) {
13
+ if (isStandardContext(second)) {
14
+ if (!expectedKinds.includes(second.kind)) {
15
+ throw new FilterDefinitionError(`${what} goes on ${expectedKinds.join(' or ')}, not on a ${second.kind}.`);
16
+ }
17
+ if (!second.metadata) {
18
+ throw new FilterDefinitionError(`${what} needs decorator metadata, which this runtime does not provide. Polyfill \`Symbol.metadata\` (\`Symbol.metadata ??= Symbol('Symbol.metadata')\`), or compile with \`experimentalDecorators\` as AdonisJS does.`);
19
+ }
20
+ return { name: String(second.name), record: standardRecord(second.metadata) };
21
+ }
22
+ if (typeof first === 'function') {
23
+ throw new FilterDefinitionError(`${what} goes on an instance member, not a static one.`);
24
+ }
25
+ if (typeof first !== 'object' || first === null) {
26
+ throw new FilterDefinitionError(`${what} was applied to something that is not a class member.`);
27
+ }
28
+ return { name: String(second), record: legacyRecord(first) };
29
+ }
30
+ /**
31
+ * Bind a filter method to the request key(s) it answers, instead of deriving the key from the
32
+ * method's name.
33
+ *
34
+ * Worth reaching for exactly twice: when the key cannot be a method name (`team.name`), and when
35
+ * one method should answer several keys. It also pins the public contract — with a binding, the
36
+ * key survives renaming the method.
37
+ *
38
+ * ```ts
39
+ * export default class UserFilter extends BaseModelFilter {
40
+ * @filterFor('team.name')
41
+ * byTeamName(value: string) {
42
+ * this.$query.whereHas('team', (team) => team.whereILike('name', `%${value}%`))
43
+ * }
44
+ *
45
+ * @filterFor('q', 'query')
46
+ * freeText(value: string) { … }
47
+ * }
48
+ * ```
49
+ *
50
+ * A bound method answers **only** its declared keys: `?filter[byTeamName]=…` stops working, which
51
+ * is the point — the method name becomes an implementation detail.
52
+ */
53
+ export function filterFor(...keys) {
54
+ if (keys.length === 0) {
55
+ throw new FilterDefinitionError('@filterFor() needs at least one request key.');
56
+ }
57
+ for (const key of keys) {
58
+ if (typeof key !== 'string' || key.length === 0) {
59
+ throw new FilterDefinitionError('@filterFor() keys must be non-empty strings.');
60
+ }
61
+ if (isReservedKey(key)) {
62
+ throw new FilterDefinitionError(`@filterFor('${key}') — '${key}' is a key the wire format itself owns (page, sort, search, …), so it never reaches a method. Bind another key.`);
63
+ }
64
+ }
65
+ return ((first, second) => {
66
+ const { name, record } = target('@filterFor()', ['method'], first, second);
67
+ record.filterFor = { ...record.filterFor, [name]: [...keys] };
68
+ });
69
+ }
70
+ /** The member kinds a column decorator may sit on. */
71
+ const COLUMN_KINDS = ['field', 'accessor', 'getter', 'setter'];
72
+ /**
73
+ * Mark a model column as filterable — the allow-list, declared where the column is, stacked under
74
+ * Lucid's own `@column()`.
75
+ *
76
+ * ```ts
77
+ * export default class User extends compose(BaseModel, Filterable) {
78
+ * @column()
79
+ * @filterable()
80
+ * declare status: string
81
+ *
82
+ * @column()
83
+ * @filterable('number')
84
+ * declare age: number
85
+ *
86
+ * @column({ serializeAs: null })
87
+ * declare passwordHash: string // undecorated — unreachable, as it should be
88
+ * }
89
+ * ```
90
+ *
91
+ * A filter class picks these up through its `static model`. Anything the filter declares itself
92
+ * still wins outright, so a stricter filter over a shared model can narrow the surface:
93
+ * `static filterable = ['status']` exposes that column and no other.
94
+ *
95
+ * The optional kind is the same one the colocated `filterable` map takes: it drives value
96
+ * coercion and the generated client's types.
97
+ */
98
+ export function filterable(kind) {
99
+ return ((first, second) => {
100
+ const { name, record } = target('@filterable()', COLUMN_KINDS, first, second);
101
+ record.filterable = { ...record.filterable, [name]: kind ?? null };
102
+ });
103
+ }
104
+ /** Mark a model column as sortable — `?sort=-createdAt` reaches it, nothing else does. */
105
+ export function sortable() {
106
+ return ((first, second) => {
107
+ const { name, record } = target('@sortable()', COLUMN_KINDS, first, second);
108
+ record.sortable = [...(record.sortable ?? []), name];
109
+ });
110
+ }
111
+ /** Mark a model column as scanned by the free-text `?search=` term. */
112
+ export function searchable() {
113
+ return ((first, second) => {
114
+ const { name, record } = target('@searchable()', COLUMN_KINDS, first, second);
115
+ record.searchable = [...(record.searchable ?? []), name];
116
+ });
117
+ }
118
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/decorators.ts"],"names":[],"mappings":"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"}
@@ -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,OAAO,GAAyB,UAAU;IAClF,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,30 @@
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
+ /** Resolves every distinct field name through the alias map (returns a new array). */
29
+ export declare function remapDistinctAliases(fields: string[], aliases: FieldAliases): string[];
30
+ //# 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;AAED,sFAAsF;AACtF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,EAAE,CAEtF"}
@@ -0,0 +1,65 @@
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
+ /** Resolves every distinct field name through the alias map (returns a new array). */
62
+ export function remapDistinctAliases(fields, aliases) {
63
+ return fields.map((field) => resolveFieldAlias(aliases, field));
64
+ }
65
+ //# 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;AAED,sFAAsF;AACtF,MAAM,UAAU,oBAAoB,CAAC,MAAgB,EAAE,OAAqB;IAC1E,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { BaseModelFilter } from './base_model_filter.js';
2
+ import { type FilterSpec } from './filter_spec.js';
3
+ /**
4
+ * A filter class as the container hands it back: constructible, extending {@link BaseModelFilter}
5
+ * — and carrying that base's static declarations (`filterable`, `sortable`, `blacklist`, …).
6
+ *
7
+ * Spelled as an intersection with `typeof BaseModelFilter` rather than an index signature: a
8
+ * concrete `class UserFilter extends BaseModelFilter` has no index signature, so
9
+ * `Record<string, unknown>` here would make every real filter class *unassignable* to this type —
10
+ * `static $filter = () => UserFilter` on a model would not compile.
11
+ */
12
+ export type FilterClass = new (...args: any[]) => BaseModelFilter;
13
+ /**
14
+ * Is this a filter class (as opposed to a `defineFilter` spec)? Checked structurally — a class
15
+ * that extends {@link BaseModelFilter} — so the two forms can share one entry point.
16
+ */
17
+ export declare function isFilterClass(value: unknown): value is FilterClass;
18
+ /**
19
+ * The request keys a class owns with a method of its own, walking the prototype chain up to (but
20
+ * not including) {@link BaseModelFilter} — so a shared abstract filter's methods are inherited
21
+ * like any other. `setup`, the constructor, `$`-prefixed members and anything on the class's
22
+ * `blacklist` are excluded.
23
+ *
24
+ * Memoized per class: the shape of a class does not change between requests.
25
+ */
26
+ export declare function dispatchKeys(cls: FilterClass): ReadonlySet<string>;
27
+ /**
28
+ * Compile a filter class's static declarations into the {@link FilterSpec} the runner consumes —
29
+ * the same object `defineFilter` produces, so a class and a spec take the identical code path
30
+ * through allow-listing, search, sort and pagination.
31
+ *
32
+ * A class that declares no `filterable` is method-only: nothing reaches SQL except through the
33
+ * methods it wrote, which is the tightest allow-list there is.
34
+ *
35
+ * Memoized per class — the declaration is static, so it is compiled once per process.
36
+ */
37
+ export declare function specFromFilterClass(cls: FilterClass): FilterSpec;
38
+ /**
39
+ * Find the method a request key maps to, honouring the class's `camelCase` (a `snake_case` key
40
+ * matches a camelCase method — on by default) and `dropId` (`companyId` matches `company`) knobs.
41
+ * Returns the method name, or `undefined` when the class does not own the key.
42
+ */
43
+ export declare function methodForKey(cls: FilterClass, key: string): string | undefined;
44
+ /** Is this key one the wire format owns (so it is never dispatched to a method)? */
45
+ export declare function isReservedKey(key: string): boolean;
46
+ //# sourceMappingURL=filter_class.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter_class.d.ts","sourceRoot":"","sources":["../../src/filter_class.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGjE;;;;;;;;GAQG;AAEH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,eAAe,CAAC;AAkDlE;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAQlE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CA0BlE;AAkCD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,CA8BhE;AAoDD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAuB9E;AAcD,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD"}
@@ -0,0 +1,244 @@
1
+ import { BaseFilter } from './base_filter.js';
2
+ import { BaseModelFilter } from './base_model_filter.js';
3
+ import { readDecorators } from './decorator_metadata.js';
4
+ import { defineFilter } from './filter_spec.js';
5
+ /**
6
+ * A filter class's static side. Read through a cast rather than declared on {@link FilterClass}
7
+ * itself: an index signature there would make every concrete `class UserFilter extends
8
+ * BaseModelFilter` *unassignable* to the type — `static $filter = () => UserFilter` on a model
9
+ * would stop compiling.
10
+ */
11
+ function statics(cls) {
12
+ return cls;
13
+ }
14
+ /** Request keys the wire format owns — never dispatched to a method of the same name. */
15
+ const RESERVED_KEYS = new Set([
16
+ 'filter',
17
+ 'filters',
18
+ 'sort',
19
+ 'search',
20
+ 'page',
21
+ 'size',
22
+ 'distinct',
23
+ 'include',
24
+ 'select',
25
+ 'after',
26
+ 'before',
27
+ 'first',
28
+ 'last',
29
+ 'limit',
30
+ 'offset',
31
+ ]);
32
+ /** Names on the class that are machinery, not request keys. */
33
+ const RESERVED_METHODS = new Set(['constructor', 'setup', 'input']);
34
+ /**
35
+ * Is this a filter class (as opposed to a `defineFilter` spec)? Checked structurally — a class
36
+ * that extends {@link BaseModelFilter} — so the two forms can share one entry point.
37
+ */
38
+ export function isFilterClass(value) {
39
+ if (typeof value !== 'function')
40
+ return false;
41
+ let proto = Object.getPrototypeOf(value);
42
+ while (proto !== null && proto !== Function.prototype) {
43
+ if (proto === BaseModelFilter)
44
+ return true;
45
+ proto = Object.getPrototypeOf(proto);
46
+ }
47
+ return false;
48
+ }
49
+ /**
50
+ * The request keys a class owns with a method of its own, walking the prototype chain up to (but
51
+ * not including) {@link BaseModelFilter} — so a shared abstract filter's methods are inherited
52
+ * like any other. `setup`, the constructor, `$`-prefixed members and anything on the class's
53
+ * `blacklist` are excluded.
54
+ *
55
+ * Memoized per class: the shape of a class does not change between requests.
56
+ */
57
+ export function dispatchKeys(cls) {
58
+ const cached = KEY_CACHE.get(cls);
59
+ if (cached)
60
+ return cached;
61
+ const blacklist = new Set(statics(cls).blacklist ?? []);
62
+ const bound = explicitBindings(cls).methods;
63
+ const keys = new Set();
64
+ let proto = cls.prototype;
65
+ // Stop at either base: members the bases themselves define (`input`, `setup`) are machinery,
66
+ // never dispatchable keys — for model and custom filters alike.
67
+ while (proto !== null &&
68
+ proto !== BaseModelFilter.prototype &&
69
+ proto !== BaseFilter.prototype &&
70
+ proto !== Object.prototype) {
71
+ for (const name of Object.getOwnPropertyNames(proto)) {
72
+ if (RESERVED_METHODS.has(name) || blacklist.has(name) || bound.has(name))
73
+ continue;
74
+ if (name.startsWith('$') || name.startsWith('_'))
75
+ continue;
76
+ const descriptor = Object.getOwnPropertyDescriptor(proto, name);
77
+ if (typeof descriptor?.value === 'function')
78
+ keys.add(name);
79
+ }
80
+ proto = Object.getPrototypeOf(proto);
81
+ }
82
+ KEY_CACHE.set(cls, keys);
83
+ return keys;
84
+ }
85
+ /**
86
+ * The `@filterFor` bindings a class declares: request key → method, and the set of methods that
87
+ * are bound (and so no longer answer to their own name).
88
+ *
89
+ * Memoized per class alongside the dispatch keys — decorators run at class-definition time, long
90
+ * before the first request reaches the class.
91
+ */
92
+ function explicitBindings(cls) {
93
+ const cached = BINDING_CACHE.get(cls);
94
+ if (cached)
95
+ return cached;
96
+ const keys = new Map();
97
+ const methods = new Set();
98
+ const blacklist = new Set(statics(cls).blacklist ?? []);
99
+ for (const [method, bound] of Object.entries(readDecorators(cls).filterFor ?? {})) {
100
+ if (blacklist.has(method))
101
+ continue;
102
+ methods.add(method);
103
+ for (const key of bound)
104
+ keys.set(key, method);
105
+ }
106
+ const bindings = { keys, methods };
107
+ BINDING_CACHE.set(cls, bindings);
108
+ return bindings;
109
+ }
110
+ const BINDING_CACHE = new WeakMap();
111
+ const KEY_CACHE = new WeakMap();
112
+ const SPEC_CACHE = new WeakMap();
113
+ /**
114
+ * Compile a filter class's static declarations into the {@link FilterSpec} the runner consumes —
115
+ * the same object `defineFilter` produces, so a class and a spec take the identical code path
116
+ * through allow-listing, search, sort and pagination.
117
+ *
118
+ * A class that declares no `filterable` is method-only: nothing reaches SQL except through the
119
+ * methods it wrote, which is the tightest allow-list there is.
120
+ *
121
+ * Memoized per class — the declaration is static, so it is compiled once per process.
122
+ */
123
+ export function specFromFilterClass(cls) {
124
+ const cached = SPEC_CACHE.get(cls);
125
+ if (cached)
126
+ return cached;
127
+ const declared = declaredByModel(cls);
128
+ const spec = defineFilter({
129
+ ...declared,
130
+ filterable: statics(cls).filterable ?? declared.filterable ?? [],
131
+ ...pick(cls, [
132
+ 'model',
133
+ 'sortable',
134
+ 'searchable',
135
+ 'relations',
136
+ 'aliases',
137
+ 'computed',
138
+ 'fieldTypes',
139
+ 'fullText',
140
+ 'vectorSimilarity',
141
+ 'tenant',
142
+ 'defaultFilters',
143
+ 'defaultSort',
144
+ 'defaultSize',
145
+ 'maxSize',
146
+ 'maxDepth',
147
+ 'table',
148
+ 'throwOnInvalid',
149
+ ]),
150
+ });
151
+ SPEC_CACHE.set(cls, spec);
152
+ return spec;
153
+ }
154
+ /**
155
+ * What the model itself declares through `@filterable` / `@sortable` / `@searchable` on its
156
+ * columns, read off the filter's `static model`.
157
+ *
158
+ * These are **fallbacks**: a static on the filter class replaces the corresponding list outright,
159
+ * so a stricter filter over a shared model can narrow what the model opens up. Only `fieldTypes`
160
+ * merges per field — it is metadata about a column, not a decision about who may reach it.
161
+ */
162
+ function declaredByModel(cls) {
163
+ const model = statics(cls).model;
164
+ if (!model)
165
+ return {};
166
+ const declared = readDecorators(model);
167
+ const out = {};
168
+ if (declared.filterable) {
169
+ out.filterable = Object.keys(declared.filterable);
170
+ const fieldTypes = {};
171
+ for (const [column, kind] of Object.entries(declared.filterable)) {
172
+ if (kind !== null)
173
+ fieldTypes[column] = { kind };
174
+ }
175
+ const merged = { ...fieldTypes, ...statics(cls).fieldTypes };
176
+ if (Object.keys(merged).length > 0)
177
+ out.fieldTypes = merged;
178
+ }
179
+ if (declared.sortable)
180
+ out.sortable = [...new Set(declared.sortable)];
181
+ if (declared.searchable)
182
+ out.searchable = [...new Set(declared.searchable)];
183
+ return out;
184
+ }
185
+ function pick(cls, names) {
186
+ const out = {};
187
+ const statics = cls;
188
+ for (const name of names) {
189
+ const value = statics[name];
190
+ if (value !== undefined)
191
+ out[name] = value;
192
+ }
193
+ return out;
194
+ }
195
+ /**
196
+ * Find the method a request key maps to, honouring the class's `camelCase` (a `snake_case` key
197
+ * matches a camelCase method — on by default) and `dropId` (`companyId` matches `company`) knobs.
198
+ * Returns the method name, or `undefined` when the class does not own the key.
199
+ */
200
+ export function methodForKey(cls, key) {
201
+ if (typeof key !== 'string' || key.length === 0 || RESERVED_KEYS.has(key))
202
+ return undefined;
203
+ const bound = explicitBindings(cls).keys.get(key);
204
+ if (bound !== undefined)
205
+ return bound;
206
+ const keys = dispatchKeys(cls);
207
+ if (keys.has(key))
208
+ return key;
209
+ const knobs = statics(cls);
210
+ if (knobs.camelCase !== false) {
211
+ const camel = toCamelCase(key);
212
+ if (keys.has(camel))
213
+ return camel;
214
+ if (knobs.dropId === true) {
215
+ const dropped = stripId(camel);
216
+ if (dropped.length > 0 && keys.has(dropped))
217
+ return dropped;
218
+ }
219
+ }
220
+ if (knobs.dropId === true) {
221
+ const dropped = stripId(key);
222
+ if (dropped.length > 0 && keys.has(dropped))
223
+ return dropped;
224
+ }
225
+ return undefined;
226
+ }
227
+ function toCamelCase(key) {
228
+ const result = key.replace(/[_-](\w)/g, (_, char) => char.toUpperCase());
229
+ return result.charAt(0).toLowerCase() + result.slice(1);
230
+ }
231
+ function stripId(key) {
232
+ if (key === 'id')
233
+ return '';
234
+ if (key.endsWith('Id'))
235
+ return key.slice(0, -2);
236
+ if (key.endsWith('_id'))
237
+ return key.slice(0, -3);
238
+ return key;
239
+ }
240
+ /** Is this key one the wire format owns (so it is never dispatched to a method)? */
241
+ export function isReservedKey(key) {
242
+ return RESERVED_KEYS.has(key);
243
+ }
244
+ //# sourceMappingURL=filter_class.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter_class.js","sourceRoot":"","sources":["../../src/filter_class.ts"],"names":[],"mappings":"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"}