@adonis-agora/filter 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/commands/main.d.ts +5 -3
  3. package/dist/commands/main.d.ts.map +1 -1
  4. package/dist/commands/main.js +4 -2
  5. package/dist/commands/main.js.map +1 -1
  6. package/dist/commands/make_filter.d.ts +18 -0
  7. package/dist/commands/make_filter.d.ts.map +1 -0
  8. package/dist/commands/make_filter.js +32 -0
  9. package/dist/commands/make_filter.js.map +1 -0
  10. package/dist/src/apply_from_request.d.ts +8 -0
  11. package/dist/src/apply_from_request.d.ts.map +1 -1
  12. package/dist/src/apply_from_request.js +70 -21
  13. package/dist/src/apply_from_request.js.map +1 -1
  14. package/dist/src/base_model_filter.d.ts +109 -0
  15. package/dist/src/base_model_filter.d.ts.map +1 -0
  16. package/dist/src/base_model_filter.js +89 -0
  17. package/dist/src/base_model_filter.js.map +1 -0
  18. package/dist/src/decorator_metadata.d.ts +41 -0
  19. package/dist/src/decorator_metadata.d.ts.map +1 -0
  20. package/dist/src/decorator_metadata.js +83 -0
  21. package/dist/src/decorator_metadata.js.map +1 -0
  22. package/dist/src/decorators.d.ts +73 -0
  23. package/dist/src/decorators.d.ts.map +1 -0
  24. package/dist/src/decorators.js +118 -0
  25. package/dist/src/decorators.js.map +1 -0
  26. package/dist/src/filter_class.d.ts +46 -0
  27. package/dist/src/filter_class.d.ts.map +1 -0
  28. package/dist/src/filter_class.js +238 -0
  29. package/dist/src/filter_class.js.map +1 -0
  30. package/dist/src/filterable_mixin.d.ts +49 -0
  31. package/dist/src/filterable_mixin.d.ts.map +1 -0
  32. package/dist/src/filterable_mixin.js +46 -0
  33. package/dist/src/filterable_mixin.js.map +1 -0
  34. package/dist/src/index.d.ts +8 -1
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/index.js +5 -1
  37. package/dist/src/index.js.map +1 -1
  38. package/dist/src/lucid_macros.d.ts +31 -0
  39. package/dist/src/lucid_macros.d.ts.map +1 -1
  40. package/dist/src/lucid_macros.js +28 -4
  41. package/dist/src/lucid_macros.js.map +1 -1
  42. package/dist/src/pagination_stash.d.ts +4 -0
  43. package/dist/src/pagination_stash.d.ts.map +1 -0
  44. package/dist/src/pagination_stash.js +19 -0
  45. package/dist/src/pagination_stash.js.map +1 -0
  46. package/dist/stubs/main.d.ts +3 -0
  47. package/dist/stubs/main.d.ts.map +1 -0
  48. package/dist/stubs/main.js +4 -0
  49. package/dist/stubs/main.js.map +1 -0
  50. package/dist/stubs/make/filter/main.stub +45 -0
  51. package/package.json +5 -3
  52. package/stubs/main.ts +4 -0
  53. package/stubs/make/filter/main.stub +45 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,80 @@
1
1
  # @adonis-agora/filter
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#29](https://github.com/DavideCarvalho/adonis-agora-filter/pull/29) [`2fd4700`](https://github.com/DavideCarvalho/adonis-agora-filter/commit/2fd47000b87b025edf4c117ef97b1d9b731731a0) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Filter classes — the `adonis-lucid-filter` authoring shape, with the whole pipeline behind it.
8
+
9
+ A filter can now be a class:
10
+
11
+ ```ts
12
+ @inject()
13
+ export default class UserFilter extends BaseModelFilter {
14
+ declare $query: ModelQueryBuilderContract<typeof User>;
15
+ constructor(private tenants: TenantsService) {
16
+ super();
17
+ }
18
+
19
+ static model = User;
20
+ static filterable = ["name", "email", "status"];
21
+ static sortable = ["name", "createdAt"];
22
+ static searchable = ["name", "email"];
23
+
24
+ setup() {
25
+ this.$query.where("tenantId", this.tenants.current(this.$ctx));
26
+ }
27
+
28
+ fullName(value: string) {
29
+ this.$query.whereRaw("first_name || ' ' || last_name ilike ?", [
30
+ `%${value}%`,
31
+ ]);
32
+ }
33
+ }
34
+ ```
35
+
36
+ - **`BaseModelFilter`** — a method per request key, the builder on `this.$query`, a `setup()` that
37
+ runs before anything the request asked for. Writing the method is what exposes the key, so it
38
+ needs no allow-list entry; plain columns stay declarative in the statics.
39
+ - **Container-resolved** — the class is constructed through the request's IoC resolver, so
40
+ `@inject()` on the constructor works exactly as it does in a controller.
41
+ - **`Filterable` model mixin** — `static $filter = () => UserFilter`, then
42
+ `User.filterPaginate(ctx)` for the whole endpoint, or `const { query } = await User.filter(ctx)`
43
+ to keep composing and `query.filterPaginate()` to page it with what the request asked for.
44
+ - **The macros take a class** too, and `filterPaginate()` with no arguments pages a query a
45
+ previous call already filtered.
46
+ - **`node ace make:filter user`** scaffolds the class.
47
+ - Key matching follows the same conventions as `adonis-lucid-filter`: `static blacklist`,
48
+ `static dropId`, `static camelCase`, and a bare top-level key (`?minAge=21`) reaching a method of
49
+ that name. Keys the wire format owns (`sort`, `page`, `search`, …) are never dispatched.
50
+
51
+ Everything compiles to the same `FilterSpec` the declarative `defineFilter` produces, so classes
52
+ and specs share one runner — same allow-listing, operators, search, sort and page clamping.
53
+
54
+ Note for the class form: `applyFilterFromRequest(query, UserFilter, ctx)` and `User.filter(ctx)`
55
+ resolve to `{ page, size }` and `{ query, page, size }` respectively, never to the builder as the
56
+ promise's own value — a Lucid query builder is thenable, so a promise resolving to one would run
57
+ the query instead of handing it back.
58
+
59
+ - [#30](https://github.com/DavideCarvalho/adonis-agora-filter/pull/30) [`9796b85`](https://github.com/DavideCarvalho/adonis-agora-filter/commit/9796b851b078d6d1aa977bbe5a396058d8059d55) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Decorators for the class form, and a `FilterClass` type fix.
60
+
61
+ `@filterFor('team.name')` binds a filter method to the request key(s) it answers, for keys a method
62
+ name could never spell and for one method answering several keys; a bound method stops answering to
63
+ its own name, so the public key survives a rename. A key the wire format owns (`sort`, `page`, …)
64
+ is refused when the class is defined.
65
+
66
+ `@filterable()`, `@sortable()` and `@searchable()` declare a model's filterable surface on the
67
+ columns themselves, stacked under Lucid's `@column()`; a filter picks them up through its
68
+ `static model`, and a static on the filter still replaces the list outright so a stricter filter can
69
+ narrow a shared model. `@filterable('number')` also declares the field's kind.
70
+
71
+ Both work under legacy (`experimentalDecorators`, what AdonisJS compiles) and standard TC39
72
+ decorators — the flavour is detected at call time.
73
+
74
+ Fixes `FilterClass`, which intersected `Record<string, unknown>`: no concrete
75
+ `class UserFilter extends BaseModelFilter` satisfied that, so `static $filter = () => UserFilter`
76
+ on a model did not typecheck. A type test over a real Lucid model now covers it.
77
+
3
78
  ## 0.7.1
4
79
 
5
80
  ### Patch Changes
@@ -1,17 +1,19 @@
1
1
  import { ListLoader } from '@adonisjs/core/ace';
2
+ import MakeFilter from './make_filter.js';
2
3
  import MakeFilterClient from './make_filter_client.js';
3
4
  /**
4
5
  * The commands barrel for `@adonis-agora/filter`. An app registers it in its
5
6
  * `adonisrc` via `rcFile.addCommand('@adonis-agora/filter/commands')`. A
6
7
  * {@link ListLoader} exposes the `make:filter-client` command's metadata and
7
- * constructor to the ace kernel.
8
+ * constructors to the ace kernel: `make:filter` (scaffold a filter class) and
9
+ * `make:filter-client` (emit the typed front-end builders).
8
10
  *
9
11
  * `@adonisjs/core` is an *optional* peer of this otherwise framework-free core —
10
12
  * only this `./commands` subpath imports it, so the main entrypoint stays
11
13
  * dependency-free.
12
14
  */
13
- declare const loader: ListLoader<typeof MakeFilterClient>;
15
+ declare const loader: ListLoader<typeof MakeFilter | typeof MakeFilterClient>;
14
16
  export declare const getMetaData: () => Promise<import("@adonisjs/core/types/ace").CommandMetaData[]>;
15
- export declare const getCommand: (metaData: import("@adonisjs/core/types/ace").CommandMetaData) => Promise<typeof MakeFilterClient | null>;
17
+ export declare const getCommand: (metaData: import("@adonisjs/core/types/ace").CommandMetaData) => Promise<typeof MakeFilter | typeof MakeFilterClient | null>;
16
18
  export default loader;
17
19
  //# sourceMappingURL=main.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../commands/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;;;GASG;AACH,QAAA,MAAM,MAAM,qCAAqC,CAAC;AAElD,eAAO,MAAM,WAAW,qEAAkC,CAAC;AAC3D,eAAO,MAAM,UAAU,2GAAiC,CAAC;AAEzD,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../commands/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,UAAU,MAAM,kBAAkB,CAAC;AAC1C,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,QAAA,MAAM,MAAM,yDAAiD,CAAC;AAE9D,eAAO,MAAM,WAAW,qEAAkC,CAAC;AAC3D,eAAO,MAAM,UAAU,+HAAiC,CAAC;AAEzD,eAAe,MAAM,CAAC"}
@@ -1,16 +1,18 @@
1
1
  import { ListLoader } from '@adonisjs/core/ace';
2
+ import MakeFilter from './make_filter.js';
2
3
  import MakeFilterClient from './make_filter_client.js';
3
4
  /**
4
5
  * The commands barrel for `@adonis-agora/filter`. An app registers it in its
5
6
  * `adonisrc` via `rcFile.addCommand('@adonis-agora/filter/commands')`. A
6
7
  * {@link ListLoader} exposes the `make:filter-client` command's metadata and
7
- * constructor to the ace kernel.
8
+ * constructors to the ace kernel: `make:filter` (scaffold a filter class) and
9
+ * `make:filter-client` (emit the typed front-end builders).
8
10
  *
9
11
  * `@adonisjs/core` is an *optional* peer of this otherwise framework-free core —
10
12
  * only this `./commands` subpath imports it, so the main entrypoint stays
11
13
  * dependency-free.
12
14
  */
13
- const loader = new ListLoader([MakeFilterClient]);
15
+ const loader = new ListLoader([MakeFilter, MakeFilterClient]);
14
16
  export const getMetaData = loader.getMetaData.bind(loader);
15
17
  export const getCommand = loader.getCommand.bind(loader);
16
18
  export default loader;
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../../commands/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;;;GASG;AACH,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAElD,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../commands/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,UAAU,MAAM,kBAAkB,CAAC;AAC1C,OAAO,gBAAgB,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAe,MAAM,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { CommandOptions } from '@adonisjs/core/types/ace';
3
+ /**
4
+ * `node ace make:filter <name>` — scaffold a filter class under `app/filters/`.
5
+ *
6
+ * The generated class extends `BaseModelFilter`: allow-lists as statics for the plain columns, a
7
+ * `setup()` for the scope the client cannot relax, and a method per key that needs SQL of its own.
8
+ * It is `@inject()`ed, so a constructor dependency resolves through the container exactly as a
9
+ * controller's does.
10
+ */
11
+ export default class MakeFilter extends BaseCommand {
12
+ static commandName: string;
13
+ static description: string;
14
+ static options: CommandOptions;
15
+ name: string;
16
+ run(): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=make_filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make_filter.d.ts","sourceRoot":"","sources":["../../commands/make_filter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAQ,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IACjD,OAAgB,WAAW,SAAiB;IAC5C,OAAgB,WAAW,SAA+B;IAC1D,OAAgB,OAAO,EAAE,cAAc,CAA+B;IAG9D,IAAI,EAAE,MAAM,CAAC;IAEN,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAOpC"}
@@ -0,0 +1,32 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { BaseCommand, args } from '@adonisjs/core/ace';
8
+ import { stubsRoot } from '../stubs/main.js';
9
+ /**
10
+ * `node ace make:filter <name>` — scaffold a filter class under `app/filters/`.
11
+ *
12
+ * The generated class extends `BaseModelFilter`: allow-lists as statics for the plain columns, a
13
+ * `setup()` for the scope the client cannot relax, and a method per key that needs SQL of its own.
14
+ * It is `@inject()`ed, so a constructor dependency resolves through the container exactly as a
15
+ * controller's does.
16
+ */
17
+ export default class MakeFilter extends BaseCommand {
18
+ static commandName = 'make:filter';
19
+ static description = 'Create a new filter class';
20
+ static options = { allowUnknownFlags: true };
21
+ async run() {
22
+ const codemods = await this.createCodemods();
23
+ await codemods.makeUsingStub(stubsRoot, 'make/filter/main.stub', {
24
+ flags: this.parsed.flags,
25
+ entity: this.app.generators.createEntity(this.name),
26
+ });
27
+ }
28
+ }
29
+ __decorate([
30
+ args.string({ description: 'Name of the filter (e.g. user)' })
31
+ ], MakeFilter.prototype, "name", void 0);
32
+ //# sourceMappingURL=make_filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make_filter.js","sourceRoot":"","sources":["../../commands/make_filter.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IACjD,MAAM,CAAU,WAAW,GAAG,aAAa,CAAC;IAC5C,MAAM,CAAU,WAAW,GAAG,2BAA2B,CAAC;IAC1D,MAAM,CAAU,OAAO,GAAmB,EAAE,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAK7D,KAAK,CAAC,GAAG;QAChB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC7C,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,uBAAuB,EAAE;YAC/D,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;YACxB,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;SACpD,CAAC,CAAC;IACL,CAAC;;AARO;IADP,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,gCAAgC,EAAE,CAAC;wCAC1C"}
@@ -1,4 +1,5 @@
1
1
  import type { ResolvedCursor } from './cursor.js';
2
+ import { type FilterClass } from './filter_class.js';
2
3
  import { type FilterSpec } from './filter_spec.js';
3
4
  import { type QueryBuilderLike } from './lucid_adapter.js';
4
5
  import { type ResolvedPagination } from './runner.js';
@@ -59,6 +60,13 @@ export interface ApplyCursorFromRequestOptions extends ApplyFromRequestOptions {
59
60
  * ```
60
61
  */
61
62
  export declare function applyFilterFromRequest(query: QueryBuilderLike, spec: FilterSpec, ctx: FilterRequestContext | undefined, options?: ApplyFromRequestOptions): ResolvedPagination;
63
+ /**
64
+ * The class form: the filter is resolved through the IoC container (so an `@inject()`ed
65
+ * constructor works), its `setup()` runs before anything the request asked for, and every request
66
+ * key the class owns a method for is dispatched to that method instead of the declarative path.
67
+ * Resolution is async, so this overload returns a promise.
68
+ */
69
+ export declare function applyFilterFromRequest(query: QueryBuilderLike, filter: FilterClass, ctx: FilterRequestContext | undefined, options?: ApplyFromRequestOptions): Promise<ResolvedPagination>;
62
70
  /**
63
71
  * The keyset (cursor) counterpart of {@link applyFilterFromRequest}. Sources the
64
72
  * request filters and the `after`/`before`/`first`/`last` cursor params from ctx,
@@ -1 +1 @@
1
- {"version":3,"file":"apply_from_request.d.ts","sourceRoot":"","sources":["../../src/apply_from_request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,KAAK,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;AAG/E,OAAO,EAAqB,KAAK,kBAAkB,EAA4B,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;AA6CD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,oBAAoB,GAAG,SAAS,EACrC,OAAO,GAAE,uBAA4B,GACpC,kBAAkB,CAQpB;AA8BD;;;;;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"}
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,EAAE,KAAK,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;AAG/E,OAAO,EAAqB,KAAK,kBAAkB,EAA4B,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;AA6CD;;;;;;;;;;;;;;;;;;;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"}
@@ -1,3 +1,4 @@
1
+ import { isFilterClass, methodForKey, specFromFilterClass, } from './filter_class.js';
1
2
  import { specToFilterConfig } from './filter_spec.js';
2
3
  import { applyColumnFilters } from './lucid_adapter.js';
3
4
  import { parseFilterRequest } from './parse_request.js';
@@ -38,27 +39,13 @@ function withDefaultSort(input, spec) {
38
39
  }
39
40
  return input;
40
41
  }
41
- /**
42
- * Apply a {@link FilterSpec} to a Lucid query from a request context, returning
43
- * the resolved offset pagination — the single explicit call an AdonisJS
44
- * controller makes (the idiomatic replacement for the NestJS
45
- * `ApplyFilterInterceptor` + `@ApplyFilter` param decorator).
46
- *
47
- * It resolves the request input from `ctx.request.qs()`, injects the server
48
- * scope (tenant + default filters) that is never exposed to the allow-list, and
49
- * delegates the allow-listed filter/sort/search + pagination resolution to
50
- * {@link applyFilter}.
51
- *
52
- * ```ts
53
- * const spec = defineFilter({ filterable: ['name', 'age'], tenant: { column: 'tenantId', resolve: (ctx) => ctx.auth.user.tenantId } })
54
- *
55
- * // in a controller:
56
- * const query = User.query()
57
- * const { page, size } = applyFilterFromRequest(query, spec, ctx)
58
- * return query.paginate(page, size)
59
- * ```
60
- */
61
- export function applyFilterFromRequest(query, spec, ctx, options = {}) {
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) {
62
49
  const parsed = options.input ?? parseFilterRequest(rawQs(ctx));
63
50
  const withVector = options.vectorSimilarity !== undefined
64
51
  ? { ...parsed, vectorSimilarity: options.vectorSimilarity }
@@ -66,6 +53,68 @@ export function applyFilterFromRequest(query, spec, ctx, options = {}) {
66
53
  applyServerScope(query, spec, ctx);
67
54
  return applyFilter(query, withDefaultSort(withVector, spec), specToFilterConfig(spec));
68
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
+ }
69
118
  /** Parse cursor (keyset) params from a decoded query string (Spatie `page[...]` shapes). */
70
119
  function parseCursorParams(qs) {
71
120
  const out = {};
@@ -1 +1 @@
1
- {"version":3,"file":"apply_from_request.js","sourceRoot":"","sources":["../../src/apply_from_request.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apply_from_request.js","sourceRoot":"","sources":["../../src/apply_from_request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import type { LucidModelLike } from './aggregate.js';
2
+ import type { FieldAliases } from './field_aliases.js';
3
+ import type { FilterableInput, RelationColumns, RelationSpec, TenantScopeSpec } from './filter_spec.js';
4
+ import type { FilterFieldTypeInfo } from './generate_client.js';
5
+ import type { QueryBuilderLike } from './lucid_adapter.js';
6
+ import type { ColumnFilter } from './operators.js';
7
+ import type { ComputedFields, FilterInput, FullTextSearchConfig, SortItem, VectorSimilarityConfig } from './types.js';
8
+ /**
9
+ * The class-authoring form of a filter — the counterpart of `defineFilter`, in the shape
10
+ * AdonisJS developers already know from `adonis-lucid-filter`: **a method per request key**,
11
+ * with the query builder on `this.$query`.
12
+ *
13
+ * A method's name IS the key it owns. `?filter[fullName]=silva` calls `fullName('silva')`, and
14
+ * because you wrote the method, the key needs no allow-list entry — writing the code is the
15
+ * decision to expose it. Plain columns that need no custom SQL stay declarative: list them in
16
+ * `static filterable` and the runner applies the operators, search, sort and pagination for you.
17
+ *
18
+ * The class is resolved through the IoC container, so an `@inject()`ed constructor works: a
19
+ * filter can depend on a service exactly like a controller does.
20
+ *
21
+ * ```ts
22
+ * @inject()
23
+ * export default class UserFilter extends BaseModelFilter {
24
+ * declare $query: ModelQueryBuilderContract<typeof User>
25
+ *
26
+ * constructor(private tenants: TenantsService) { super() }
27
+ *
28
+ * static model = User
29
+ * static filterable = ['name', 'email', 'status']
30
+ * static sortable = ['name', 'createdAt']
31
+ * static searchable = ['name', 'email']
32
+ *
33
+ * // always applied, before anything the request asked for
34
+ * setup() {
35
+ * this.$query.where('tenantId', this.tenants.current(this.$ctx))
36
+ * }
37
+ *
38
+ * // the one key this class owns itself
39
+ * fullName(value: string) {
40
+ * this.$query.whereILike('full_name', `%${value}%`)
41
+ * }
42
+ * }
43
+ * ```
44
+ */
45
+ export declare abstract class BaseModelFilter<Q extends QueryBuilderLike = QueryBuilderLike> {
46
+ /**
47
+ * The query builder being filtered — the one the caller created. Methods mutate it directly;
48
+ * nothing here constructs or executes a query.
49
+ */
50
+ $query: Q;
51
+ /** The raw decoded request input the dispatch reads (query string, or the body on a POST). */
52
+ $input: Readonly<Record<string, unknown>>;
53
+ /** The parsed input — filters, sort, search, pagination — after the wire format is read. */
54
+ $parsed: FilterInput;
55
+ /** The request context handed to the call (a real `HttpContext` in an AdonisJS app). */
56
+ $ctx: unknown;
57
+ /** The owning Lucid model. Unlocks to-many aggregates and supplies the root table name. */
58
+ static model?: LucidModelLike;
59
+ /** Columns clients may filter on with the standard operators. `'*'` allows any base column. */
60
+ static filterable?: FilterableInput;
61
+ /** Columns clients may sort on. Defaults to `filterable`. */
62
+ static sortable?: RelationColumns;
63
+ /** Columns the free-text `search` term scans with a portable ILIKE. */
64
+ static searchable?: string[];
65
+ /** Whitelisted relations and the columns reachable through them. */
66
+ static relations?: Record<string, RelationSpec>;
67
+ /** Client-alias → target-field remapping. */
68
+ static aliases?: FieldAliases;
69
+ /** Virtual fields — alias → SQL expression. */
70
+ static computed?: ComputedFields;
71
+ /** Per-field value kinds, driving coercion and typed client codegen. */
72
+ static fieldTypes?: Record<string, FilterFieldTypeInfo>;
73
+ /** Opt-in Postgres tsvector search for the `search` term. */
74
+ static fullText?: FullTextSearchConfig;
75
+ /** Opt-in pgvector embedding-similarity ordering. */
76
+ static vectorSimilarity?: VectorSimilarityConfig;
77
+ /** Auto-scope a column to the tenant resolved from ctx (the declarative form of `setup`). */
78
+ static tenant?: TenantScopeSpec;
79
+ /** Server-declared filters always AND-combined with the request. */
80
+ static defaultFilters?: ColumnFilter[];
81
+ /** Sort applied when the request carries none. */
82
+ static defaultSort?: SortItem[];
83
+ /** Page size when the request asks for none. Default 25. */
84
+ static defaultSize?: number;
85
+ /** Hard cap on page size. Default 100. */
86
+ static maxSize?: number;
87
+ /** Max relation-path hops. Defaults to the deepest declared relation nesting. */
88
+ static maxDepth?: number;
89
+ /** Root table name — defaults to `model.table`. */
90
+ static table?: string;
91
+ /** Throw `InvalidColumnFilterError` on a disallowed field instead of dropping it. */
92
+ static throwOnInvalid?: boolean;
93
+ /** Method names that must never be dispatched from request input, whatever the key says. */
94
+ static blacklist?: string[];
95
+ /** Strip a trailing `Id` from an input key before looking for a method (`companyId` → `company`). */
96
+ static dropId?: boolean;
97
+ /** Match `snake_case` input keys against camelCase methods (`first_name` → `firstName`). Default true. */
98
+ static camelCase?: boolean;
99
+ /**
100
+ * Runs on every filter call, before the request's own filters — the place for a scope the
101
+ * client cannot relax (a tenant, a soft-delete guard, an ownership check).
102
+ */
103
+ setup?(): void | Promise<void>;
104
+ /** The whole raw input, one key of it, or a fallback when the key is absent. */
105
+ input(): Readonly<Record<string, unknown>>;
106
+ input(key: string): unknown;
107
+ input(key: string, fallback: unknown): unknown;
108
+ }
109
+ //# 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":"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"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * The class-authoring form of a filter — the counterpart of `defineFilter`, in the shape
3
+ * AdonisJS developers already know from `adonis-lucid-filter`: **a method per request key**,
4
+ * with the query builder on `this.$query`.
5
+ *
6
+ * A method's name IS the key it owns. `?filter[fullName]=silva` calls `fullName('silva')`, and
7
+ * because you wrote the method, the key needs no allow-list entry — writing the code is the
8
+ * decision to expose it. Plain columns that need no custom SQL stay declarative: list them in
9
+ * `static filterable` and the runner applies the operators, search, sort and pagination for you.
10
+ *
11
+ * The class is resolved through the IoC container, so an `@inject()`ed constructor works: a
12
+ * filter can depend on a service exactly like a controller does.
13
+ *
14
+ * ```ts
15
+ * @inject()
16
+ * export default class UserFilter extends BaseModelFilter {
17
+ * declare $query: ModelQueryBuilderContract<typeof User>
18
+ *
19
+ * constructor(private tenants: TenantsService) { super() }
20
+ *
21
+ * static model = User
22
+ * static filterable = ['name', 'email', 'status']
23
+ * static sortable = ['name', 'createdAt']
24
+ * static searchable = ['name', 'email']
25
+ *
26
+ * // always applied, before anything the request asked for
27
+ * setup() {
28
+ * this.$query.where('tenantId', this.tenants.current(this.$ctx))
29
+ * }
30
+ *
31
+ * // the one key this class owns itself
32
+ * fullName(value: string) {
33
+ * this.$query.whereILike('full_name', `%${value}%`)
34
+ * }
35
+ * }
36
+ * ```
37
+ */
38
+ export class BaseModelFilter {
39
+ // ── the declarative half: everything a plain column needs, without writing a method ──────────
40
+ /** The owning Lucid model. Unlocks to-many aggregates and supplies the root table name. */
41
+ static model;
42
+ /** Columns clients may filter on with the standard operators. `'*'` allows any base column. */
43
+ static filterable;
44
+ /** Columns clients may sort on. Defaults to `filterable`. */
45
+ static sortable;
46
+ /** Columns the free-text `search` term scans with a portable ILIKE. */
47
+ static searchable;
48
+ /** Whitelisted relations and the columns reachable through them. */
49
+ static relations;
50
+ /** Client-alias → target-field remapping. */
51
+ static aliases;
52
+ /** Virtual fields — alias → SQL expression. */
53
+ static computed;
54
+ /** Per-field value kinds, driving coercion and typed client codegen. */
55
+ static fieldTypes;
56
+ /** Opt-in Postgres tsvector search for the `search` term. */
57
+ static fullText;
58
+ /** Opt-in pgvector embedding-similarity ordering. */
59
+ static vectorSimilarity;
60
+ /** Auto-scope a column to the tenant resolved from ctx (the declarative form of `setup`). */
61
+ static tenant;
62
+ /** Server-declared filters always AND-combined with the request. */
63
+ static defaultFilters;
64
+ /** Sort applied when the request carries none. */
65
+ static defaultSort;
66
+ /** Page size when the request asks for none. Default 25. */
67
+ static defaultSize;
68
+ /** Hard cap on page size. Default 100. */
69
+ static maxSize;
70
+ /** Max relation-path hops. Defaults to the deepest declared relation nesting. */
71
+ static maxDepth;
72
+ /** Root table name — defaults to `model.table`. */
73
+ static table;
74
+ /** Throw `InvalidColumnFilterError` on a disallowed field instead of dropping it. */
75
+ static throwOnInvalid;
76
+ // ── dispatch controls (the `adonis-lucid-filter` knobs) ──────────────────────────────────────
77
+ /** Method names that must never be dispatched from request input, whatever the key says. */
78
+ static blacklist;
79
+ /** Strip a trailing `Id` from an input key before looking for a method (`companyId` → `company`). */
80
+ static dropId;
81
+ /** Match `snake_case` input keys against camelCase methods (`first_name` → `firstName`). Default true. */
82
+ static camelCase;
83
+ input(key, fallback) {
84
+ if (key === undefined)
85
+ return this.$input;
86
+ return Object.hasOwn(this.$input, key) ? this.$input[key] : fallback;
87
+ }
88
+ }
89
+ //# 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":"AAmBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,OAAgB,eAAe;IAgBnC,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;IAY3B,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,41 @@
1
+ import type { FilterFieldKind } from './types.js';
2
+ /**
3
+ * What the decorators record about a class, per declaration site.
4
+ *
5
+ * Two storage channels back this, because two decorator flavours exist in the wild:
6
+ *
7
+ * - **legacy** (`experimentalDecorators`, what AdonisJS uses today — Lucid's `@column` requires
8
+ * it) hands the decorator the *prototype*, so the record is kept in a `WeakMap` keyed by it;
9
+ * - **standard** (TC39 stage 3, TypeScript 5's default when the flag is off) hands the decorator
10
+ * a context object carrying a per-class `metadata` bag, so the record is kept in there.
11
+ *
12
+ * Both are read back the same way, so a class works under either flavour — and inheritance falls
13
+ * out of the walk: prototype chain for the first, the metadata object's own prototype chain
14
+ * (which the standard emit links to the base class's) for the second.
15
+ */
16
+ export interface FilterDecoratorRecord {
17
+ /** Method name → the request keys `@filterFor` bound it to. */
18
+ filterFor?: Record<string, string[]>;
19
+ /** Column name → the kind `@filterable` declared, or `null` when it declared none. */
20
+ filterable?: Record<string, FilterFieldKind | null>;
21
+ /** Columns marked `@sortable`. */
22
+ sortable?: string[];
23
+ /** Columns marked `@searchable`. */
24
+ searchable?: string[];
25
+ }
26
+ /** The record a legacy decorator writes into, created on first use. */
27
+ export declare function legacyRecord(prototype: object): FilterDecoratorRecord;
28
+ /**
29
+ * The record a standard decorator writes into. The bag a subclass receives *inherits* from its
30
+ * base's, so the record is defined as an **own** property — writing through the inherited one
31
+ * would leak a subclass's bindings back onto the parent.
32
+ */
33
+ export declare function standardRecord(metadata: object): FilterDecoratorRecord;
34
+ /**
35
+ * The merged declaration for a class — its own decorators layered over everything it inherits.
36
+ *
37
+ * Not memoized: decorators run at class-definition time, but a model's columns and a filter's
38
+ * methods are read once per compiled spec, which is itself cached by the caller.
39
+ */
40
+ export declare function readDecorators(cls: object): FilterDecoratorRecord;
41
+ //# sourceMappingURL=decorator_metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorator_metadata.d.ts","sourceRoot":"","sources":["../../src/decorator_metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,qBAAqB;IACpC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACrC,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC,CAAC;IACpD,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAoBD,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,CAMrE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAUtE;AA6BD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,qBAAqB,CASjE"}