@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,35 @@
1
+ import { BaseCommand } from '@adonisjs/core/ace';
2
+ import type { CommandOptions } from '@adonisjs/core/types/ace';
3
+ /**
4
+ * `node ace make:filter-client [entrypoint]` — the idiomatic AdonisJS equivalent
5
+ * of the NestJS `nestjs-filter` codegen bin.
6
+ *
7
+ * Where NestJS ships a standalone `reflect-metadata` bin that reads decorator
8
+ * metadata off entity classes, the Adonis filter declaration is already a plain
9
+ * runtime {@link FilterClientManifest} (`defineFilter(...)` specs), so this is a
10
+ * thin IO wrapper: it loads the app's declared `filters` manifest, hands it to
11
+ * the pure {@link generateFilterClients} core, and writes each emitted typed
12
+ * client to disk. All generation logic lives in `../src/generate_client.ts` and
13
+ * is unit-tested there — this command only does the file IO.
14
+ *
15
+ * The entrypoint module (default `config/filter.js`) must export a `filters`
16
+ * manifest (named export, or default):
17
+ *
18
+ * ```ts
19
+ * // config/filter.ts
20
+ * import { defineFilter } from '@adonis-agora/filter'
21
+ * export const filters = {
22
+ * people: { spec: defineFilter({ filterable: ['name', 'age'] }), fieldTypes: { age: { kind: 'number' } } },
23
+ * }
24
+ * ```
25
+ */
26
+ export default class MakeFilterClient extends BaseCommand {
27
+ #private;
28
+ static commandName: string;
29
+ static description: string;
30
+ static options: CommandOptions;
31
+ entrypoint: string;
32
+ output: string;
33
+ run(): Promise<void>;
34
+ }
35
+ //# sourceMappingURL=make_filter_client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make_filter_client.d.ts","sourceRoot":"","sources":["../../commands/make_filter_client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,WAAW,EAAS,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI/D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;;IACvD,OAAgB,WAAW,SAAwB;IACnD,OAAgB,WAAW,SAC4C;IACvE,OAAgB,OAAO,EAAE,cAAc,CAAsB;IAOrD,UAAU,EAAE,MAAM,CAAC;IAMnB,MAAM,EAAE,MAAM,CAAC;IAER,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAelC;CAuBF"}
@@ -0,0 +1,85 @@
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 { mkdir, writeFile } from 'node:fs/promises';
8
+ import { isAbsolute, join } from 'node:path';
9
+ import { args, BaseCommand, flags } from '@adonisjs/core/ace';
10
+ import { generateFilterClients } from '../src/generate_client.js';
11
+ /**
12
+ * `node ace make:filter-client [entrypoint]` — the idiomatic AdonisJS equivalent
13
+ * of the NestJS `nestjs-filter` codegen bin.
14
+ *
15
+ * Where NestJS ships a standalone `reflect-metadata` bin that reads decorator
16
+ * metadata off entity classes, the Adonis filter declaration is already a plain
17
+ * runtime {@link FilterClientManifest} (`defineFilter(...)` specs), so this is a
18
+ * thin IO wrapper: it loads the app's declared `filters` manifest, hands it to
19
+ * the pure {@link generateFilterClients} core, and writes each emitted typed
20
+ * client to disk. All generation logic lives in `../src/generate_client.ts` and
21
+ * is unit-tested there — this command only does the file IO.
22
+ *
23
+ * The entrypoint module (default `config/filter.js`) must export a `filters`
24
+ * manifest (named export, or default):
25
+ *
26
+ * ```ts
27
+ * // config/filter.ts
28
+ * import { defineFilter } from '@adonis-agora/filter'
29
+ * export const filters = {
30
+ * people: { spec: defineFilter({ filterable: ['name', 'age'] }), fieldTypes: { age: { kind: 'number' } } },
31
+ * }
32
+ * ```
33
+ */
34
+ export default class MakeFilterClient extends BaseCommand {
35
+ static commandName = 'make:filter-client';
36
+ static description = "Generate typed filter clients from the app's declared filter specs";
37
+ static options = { startApp: true };
38
+ async run() {
39
+ const manifest = await this.#loadManifest();
40
+ if (!manifest)
41
+ return;
42
+ const outDir = isAbsolute(this.output) ? this.output : this.app.makePath(this.output);
43
+ await mkdir(outDir, { recursive: true });
44
+ const generated = generateFilterClients(manifest);
45
+ for (const client of generated) {
46
+ const path = join(outDir, client.filename);
47
+ await writeFile(path, client.code, 'utf-8');
48
+ this.logger.action(`create ${path}`).succeeded();
49
+ }
50
+ this.logger.success(`Generated ${generated.length} filter client(s) into ${this.output}.`);
51
+ }
52
+ /** Import the entrypoint module and read its `filters` manifest (named or default export). */
53
+ async #loadManifest() {
54
+ const url = this.app.makeURL(this.entrypoint).href;
55
+ let mod;
56
+ try {
57
+ mod = (await import(url));
58
+ }
59
+ catch (error) {
60
+ this.logger.error(`Could not import filter manifest "${this.entrypoint}": ${String(error)}`);
61
+ this.exitCode = 1;
62
+ return undefined;
63
+ }
64
+ const manifest = (mod.filters ?? mod.default);
65
+ if (!manifest || Object.keys(manifest).length === 0) {
66
+ this.logger.warning(`No \`filters\` manifest exported from "${this.entrypoint}" — nothing to generate.`);
67
+ return undefined;
68
+ }
69
+ return manifest;
70
+ }
71
+ }
72
+ __decorate([
73
+ args.string({
74
+ description: 'Module exporting a `filters` manifest',
75
+ required: false,
76
+ default: 'config/filter.js',
77
+ })
78
+ ], MakeFilterClient.prototype, "entrypoint", void 0);
79
+ __decorate([
80
+ flags.string({
81
+ description: 'Output directory for the generated clients',
82
+ default: 'app/generated/filters',
83
+ })
84
+ ], MakeFilterClient.prototype, "output", void 0);
85
+ //# sourceMappingURL=make_filter_client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"make_filter_client.js","sourceRoot":"","sources":["../../commands/make_filter_client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type Configure from '@adonisjs/core/commands/configure';
2
+ /**
3
+ * `node ace add @adonis-agora/filter` (or `node ace configure @adonis-agora/filter`)
4
+ * — auto-wires the package into an AdonisJS app:
5
+ *
6
+ * 1. registers the service provider in `adonisrc.ts`, which installs the
7
+ * chainable `applyFilterFromRequest` / `filterPaginate` Lucid macros;
8
+ * 2. registers the ace commands barrel, which is what makes `make:filter-client`
9
+ * show up in `node ace list`.
10
+ *
11
+ * There is no config file to publish: a filter policy is a per-model
12
+ * `defineFilterSpec` call in your own code, not global configuration.
13
+ */
14
+ export declare function configure(command: Configure): Promise<void>;
15
+ //# sourceMappingURL=configure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,iBAOjD"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `node ace add @adonis-agora/filter` (or `node ace configure @adonis-agora/filter`)
3
+ * — auto-wires the package into an AdonisJS app:
4
+ *
5
+ * 1. registers the service provider in `adonisrc.ts`, which installs the
6
+ * chainable `applyFilterFromRequest` / `filterPaginate` Lucid macros;
7
+ * 2. registers the ace commands barrel, which is what makes `make:filter-client`
8
+ * show up in `node ace list`.
9
+ *
10
+ * There is no config file to publish: a filter policy is a per-model
11
+ * `defineFilterSpec` call in your own code, not global configuration.
12
+ */
13
+ export async function configure(command) {
14
+ const codemods = await command.createCodemods();
15
+ await codemods.updateRcFile((rcFile) => {
16
+ rcFile.addProvider('@adonis-agora/filter/filter_provider');
17
+ rcFile.addCommand('@adonis-agora/filter/commands');
18
+ });
19
+ }
20
+ //# sourceMappingURL=configure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure.js","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAkB;IAChD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC;IAEhD,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,WAAW,CAAC,sCAAsC,CAAC,CAAC;QAC3D,MAAM,CAAC,UAAU,CAAC,+BAA+B,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { ApplicationService } from '@adonisjs/core/types';
2
+ /**
3
+ * Wires `@adonis-agora/filter`'s chainable Lucid macros into the application.
4
+ *
5
+ * On boot it registers the `applyFilterFromRequest` and `filterPaginate` macros
6
+ * onto Lucid's `ModelQueryBuilder`, so any model query gains the method-call form
7
+ * of the free `applyFilterFromRequest` helper:
8
+ *
9
+ * ```ts
10
+ * const rows = await User.query()
11
+ * .where('tenantId', tenant.id)
12
+ * .applyFilterFromRequest(userFilter, ctx)
13
+ * .orderBy('createdAt', 'desc')
14
+ *
15
+ * const page = await User.query().filterPaginate(userFilter, ctx)
16
+ * ```
17
+ *
18
+ * Register it in `adonisrc.ts` (`providers`). The free functions
19
+ * (`applyFilterFromRequest`, `applyCursorFromRequest`) work without it — this
20
+ * provider only adds the chainable sugar, so `@adonisjs/lucid` is loaded lazily
21
+ * here (not a hard dependency of the package).
22
+ */
23
+ export default class FilterProvider {
24
+ protected app: ApplicationService;
25
+ constructor(app: ApplicationService);
26
+ boot(): Promise<void>;
27
+ }
28
+ //# sourceMappingURL=filter_provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter_provider.d.ts","sourceRoot":"","sources":["../../providers/filter_provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG/D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,OAAO,OAAO,cAAc;IACrB,SAAS,CAAC,GAAG,EAAE,kBAAkB;IAA7C,YAAsB,GAAG,EAAE,kBAAkB,EAAI;IAE3C,IAAI,kBAGT;CACF"}
@@ -0,0 +1,33 @@
1
+ import { registerFilterMacros } from '../src/lucid_macros.js';
2
+ /**
3
+ * Wires `@adonis-agora/filter`'s chainable Lucid macros into the application.
4
+ *
5
+ * On boot it registers the `applyFilterFromRequest` and `filterPaginate` macros
6
+ * onto Lucid's `ModelQueryBuilder`, so any model query gains the method-call form
7
+ * of the free `applyFilterFromRequest` helper:
8
+ *
9
+ * ```ts
10
+ * const rows = await User.query()
11
+ * .where('tenantId', tenant.id)
12
+ * .applyFilterFromRequest(userFilter, ctx)
13
+ * .orderBy('createdAt', 'desc')
14
+ *
15
+ * const page = await User.query().filterPaginate(userFilter, ctx)
16
+ * ```
17
+ *
18
+ * Register it in `adonisrc.ts` (`providers`). The free functions
19
+ * (`applyFilterFromRequest`, `applyCursorFromRequest`) work without it — this
20
+ * provider only adds the chainable sugar, so `@adonisjs/lucid` is loaded lazily
21
+ * here (not a hard dependency of the package).
22
+ */
23
+ export default class FilterProvider {
24
+ app;
25
+ constructor(app) {
26
+ this.app = app;
27
+ }
28
+ async boot() {
29
+ const { ModelQueryBuilder } = await import('@adonisjs/lucid/orm');
30
+ registerFilterMacros(ModelQueryBuilder);
31
+ }
32
+ }
33
+ //# sourceMappingURL=filter_provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filter_provider.js","sourceRoot":"","sources":["../../providers/filter_provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,OAAO,OAAO,cAAc;IACX,GAAG;IAAzB,YAAsB,GAAuB;mBAAvB,GAAG;IAAuB,CAAC;IAEjD,KAAK,CAAC,IAAI;QACR,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;QAClE,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;IAC1C,CAAC;CACF"}
@@ -0,0 +1,70 @@
1
+ import type { RelationSpec } from './filter_spec.js';
2
+ import type { ColumnFilter } from './operators.js';
3
+ import type { ComputedFields, ComputedSource } from './types.js';
4
+ /**
5
+ * The structural slice of a booted Lucid relation the aggregate-subquery
6
+ * compiler reads. A real `@adonisjs/lucid` `HasMany` / `ManyToMany` relation
7
+ * satisfies it — declared locally so this module never hard-imports Lucid. The
8
+ * key-column fields are optional because they differ by relation kind
9
+ * (one-to-many exposes `foreignKeyColumnName`; many-to-many exposes the pivot
10
+ * fields), and only the relevant ones are read per kind.
11
+ */
12
+ export interface LucidRelationLike {
13
+ /** Relation kind — `'hasMany'` / `'manyToMany'` are the to-many kinds we correlate. */
14
+ type: string;
15
+ /** Compute the relation's key columns from the naming strategy (idempotent). */
16
+ boot(): void;
17
+ /** The related (child) model — its `table` is the child table name. */
18
+ relatedModel(): {
19
+ table?: string;
20
+ };
21
+ foreignKeyColumnName?: string;
22
+ localKeyColumnName?: string;
23
+ relatedKeyColumnName?: string;
24
+ pivotTable?: string;
25
+ pivotForeignKey?: string;
26
+ pivotRelatedForeignKey?: string;
27
+ }
28
+ /**
29
+ * The structural slice of a Lucid model the aggregate discovery reads: its table
30
+ * name and a relation accessor. `$getRelation` is typed with `any` for the same
31
+ * reason `QueryBuilderLike.whereHas` is — Lucid types it with a generic
32
+ * literal-union `Name`, and `string` is not assignable to that union, so a
33
+ * narrower signature would make every real model fail to satisfy this interface.
34
+ */
35
+ export interface LucidModelLike {
36
+ table?: string;
37
+ $getRelation(name: any): LucidRelationLike | undefined;
38
+ }
39
+ /**
40
+ * The `EXISTS` / `NOT EXISTS` predicate equivalent to a filter on a discovered
41
+ * `$count` field, when the filter only asks whether the collection is empty:
42
+ * `gt 0`, `gte 1` and `notEquals 0` mean non-empty; `equals 0`, `lte 0` and
43
+ * `lt 1` mean empty. `EXISTS` stops at the first related row where the counting
44
+ * subquery visits all of them. `undefined` for any other filter or source.
45
+ */
46
+ export declare function countExistencePredicate(source: ComputedSource, alias: string, filter: ColumnFilter): string | undefined;
47
+ /**
48
+ * Discover to-many aggregate {@link ComputedSource}s from a Lucid model's
49
+ * relation metadata, for the relations the spec whitelisted. For each declared
50
+ * to-many relation (hasMany / manyToMany) this synthesises:
51
+ *
52
+ * - `<rel>.$count` — always;
53
+ * - `<rel>.$sum|$avg|$min|$max.<col>` — one per column the relation's
54
+ * {@link RelationSpec.aggregates} declares (Lucid does not reflect SQL column
55
+ * types, so the dev asserts numeric-ness by listing the column there).
56
+ *
57
+ * To-one relations (belongsTo / hasOne) are excluded. The whole thing degrades
58
+ * gracefully: a relation that is not on the model, is to-one, or cannot be
59
+ * booted (e.g. no DB adapter yet) is simply skipped, and a model without the
60
+ * introspection capability yields an empty map — so aggregates activate only
61
+ * when the metadata is actually available.
62
+ *
63
+ * The result is merged into the spec's `computed` map, so aggregate fields ARE
64
+ * computed fields with auto-generated correlated-subquery sources — they reuse
65
+ * the exact same runner routing, allow-list bypass (their discovery IS their
66
+ * allow-list, gated by the relation whitelist), injection-safe value binding,
67
+ * and codegen surfacing.
68
+ */
69
+ export declare function discoverAggregateSources(model: LucidModelLike | undefined, relations: Readonly<Record<string, RelationSpec>>): ComputedFields;
70
+ //# sourceMappingURL=aggregate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../src/aggregate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAmB,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGlF;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,IAAI,IAAI,CAAC;IACb,uEAAuE;IACvE,YAAY,IAAI;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAEnC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,iBAAiB,GAAG,SAAS,CAAC;CACxD;AAsBD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,YAAY,GACnB,MAAM,GAAG,SAAS,CA6BpB;AAqFD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,cAAc,GAAG,SAAS,EACjC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,GAChD,cAAc,CA8DhB"}
@@ -0,0 +1,195 @@
1
+ import { normalizeOperator } from './validate-column-filter.js';
2
+ /** Column functions (need a numeric child column); `count` needs none. */
3
+ const COLUMN_FNS = ['sum', 'avg', 'min', 'max'];
4
+ /** Double-quote one identifier segment for Postgres, escaping embedded quotes. */
5
+ function q(ident) {
6
+ return `"${ident.replace(/"/g, '""')}"`;
7
+ }
8
+ /**
9
+ * The correlated `FROM … WHERE <correlation>` clause behind each discovered
10
+ * `$count` source. Keyed by the source function, so a hand-written computed
11
+ * field never has one.
12
+ */
13
+ const countRowSets = new WeakMap();
14
+ /** Is `value` the integer `n`, as a number or as the string a query parameter carries? */
15
+ function isInteger(value, n) {
16
+ return value === n || value === String(n);
17
+ }
18
+ /**
19
+ * The `EXISTS` / `NOT EXISTS` predicate equivalent to a filter on a discovered
20
+ * `$count` field, when the filter only asks whether the collection is empty:
21
+ * `gt 0`, `gte 1` and `notEquals 0` mean non-empty; `equals 0`, `lte 0` and
22
+ * `lt 1` mean empty. `EXISTS` stops at the first related row where the counting
23
+ * subquery visits all of them. `undefined` for any other filter or source.
24
+ */
25
+ export function countExistencePredicate(source, alias, filter) {
26
+ if (typeof source !== 'function')
27
+ return undefined;
28
+ const rowSet = countRowSets.get(source);
29
+ if (!rowSet)
30
+ return undefined;
31
+ const value = filter.value;
32
+ let exists;
33
+ switch (normalizeOperator(filter.operator)) {
34
+ case 'gt':
35
+ case 'notEquals':
36
+ if (!isInteger(value, 0))
37
+ return undefined;
38
+ exists = true;
39
+ break;
40
+ case 'gte':
41
+ if (!isInteger(value, 1))
42
+ return undefined;
43
+ exists = true;
44
+ break;
45
+ case 'equals':
46
+ case 'lte':
47
+ if (!isInteger(value, 0))
48
+ return undefined;
49
+ exists = false;
50
+ break;
51
+ case 'lt':
52
+ if (!isInteger(value, 1))
53
+ return undefined;
54
+ exists = false;
55
+ break;
56
+ default:
57
+ return undefined;
58
+ }
59
+ return `${exists ? 'EXISTS' : 'NOT EXISTS'} (SELECT 1 ${rowSet({ alias })})`;
60
+ }
61
+ /** A `$count` source over the rows `rowSet` (`FROM … WHERE …`) selects. */
62
+ function countSource(rowSet) {
63
+ const source = (ctx) => `(SELECT COUNT(*) ${rowSet(ctx)})`;
64
+ countRowSets.set(source, rowSet);
65
+ return source;
66
+ }
67
+ /**
68
+ * The scalar aggregate expression over an (already-quoted) child column. Empty
69
+ * collection semantics: `sum` coalesces to 0; `avg`/`min`/`max` stay NULL (their
70
+ * natural result over zero rows); `count` is naturally 0.
71
+ */
72
+ function aggregateExpr(fn, quotedColumn) {
73
+ switch (fn) {
74
+ case 'count':
75
+ return 'COUNT(*)';
76
+ case 'sum':
77
+ return `COALESCE(SUM(${quotedColumn}),0)`;
78
+ case 'avg':
79
+ return `AVG(${quotedColumn})`;
80
+ case 'min':
81
+ return `MIN(${quotedColumn})`;
82
+ case 'max':
83
+ return `MAX(${quotedColumn})`;
84
+ }
85
+ }
86
+ /**
87
+ * ONE_TO_MANY correlation: a direct FK on the child row points back at the
88
+ * root's PK. `column` omitted → `$count`.
89
+ *
90
+ * (SELECT <agg> FROM "child" WHERE "child"."fk" = "<alias>"."pk")
91
+ */
92
+ function oneToManySource(fn, childTable, fk, pk, column) {
93
+ if (fn === 'count') {
94
+ return countSource(({ alias }) => `FROM ${q(childTable)} WHERE ${q(childTable)}.${q(fk)} = ${q(alias)}.${q(pk)}`);
95
+ }
96
+ const agg = aggregateExpr(fn, column ? `${q(childTable)}.${q(column)}` : '');
97
+ return ({ alias }) => `(SELECT ${agg} FROM ${q(childTable)} WHERE ${q(childTable)}.${q(fk)} = ${q(alias)}.${q(pk)})`;
98
+ }
99
+ /**
100
+ * MANY_TO_MANY correlation, mediated by a pivot table. `$count` counts pivot
101
+ * rows directly; the column functions JOIN pivot → child INSIDE the scalar
102
+ * subquery to reach the child column.
103
+ *
104
+ * $count: (SELECT COUNT(*) FROM "pivot" WHERE "pivot"."ownerFk" = "<alias>"."ownerPk")
105
+ * $agg: (SELECT <agg> FROM "child"
106
+ * JOIN "pivot" ON "child"."childPk" = "pivot"."inverseFk"
107
+ * WHERE "pivot"."ownerFk" = "<alias>"."ownerPk")
108
+ */
109
+ function manyToManySource(fn, meta, column) {
110
+ if (fn === 'count') {
111
+ return countSource(({ alias }) => `FROM ${q(meta.pivot)} WHERE ${q(meta.pivot)}.${q(meta.ownerFk)} = ${q(alias)}.${q(meta.ownerPk)}`);
112
+ }
113
+ const agg = aggregateExpr(fn, column ? `${q(meta.childTable)}.${q(column)}` : '');
114
+ return ({ alias }) => `(SELECT ${agg} FROM ${q(meta.childTable)} JOIN ${q(meta.pivot)} ON ${q(meta.childTable)}.${q(meta.childPk)} = ${q(meta.pivot)}.${q(meta.inverseFk)} WHERE ${q(meta.pivot)}.${q(meta.ownerFk)} = ${q(alias)}.${q(meta.ownerPk)})`;
115
+ }
116
+ /**
117
+ * Discover to-many aggregate {@link ComputedSource}s from a Lucid model's
118
+ * relation metadata, for the relations the spec whitelisted. For each declared
119
+ * to-many relation (hasMany / manyToMany) this synthesises:
120
+ *
121
+ * - `<rel>.$count` — always;
122
+ * - `<rel>.$sum|$avg|$min|$max.<col>` — one per column the relation's
123
+ * {@link RelationSpec.aggregates} declares (Lucid does not reflect SQL column
124
+ * types, so the dev asserts numeric-ness by listing the column there).
125
+ *
126
+ * To-one relations (belongsTo / hasOne) are excluded. The whole thing degrades
127
+ * gracefully: a relation that is not on the model, is to-one, or cannot be
128
+ * booted (e.g. no DB adapter yet) is simply skipped, and a model without the
129
+ * introspection capability yields an empty map — so aggregates activate only
130
+ * when the metadata is actually available.
131
+ *
132
+ * The result is merged into the spec's `computed` map, so aggregate fields ARE
133
+ * computed fields with auto-generated correlated-subquery sources — they reuse
134
+ * the exact same runner routing, allow-list bypass (their discovery IS their
135
+ * allow-list, gated by the relation whitelist), injection-safe value binding,
136
+ * and codegen surfacing.
137
+ */
138
+ export function discoverAggregateSources(model, relations) {
139
+ if (!model || typeof model.$getRelation !== 'function')
140
+ return {};
141
+ const out = {};
142
+ for (const [relName, relSpec] of Object.entries(relations)) {
143
+ let rel;
144
+ try {
145
+ rel = model.$getRelation(relName);
146
+ if (!rel)
147
+ continue;
148
+ rel.boot();
149
+ }
150
+ catch {
151
+ continue; // Un-bootable relation (no adapter yet, etc.) → skip its aggregates.
152
+ }
153
+ const columns = relSpec.aggregates ?? [];
154
+ const childTable = rel.relatedModel?.().table;
155
+ if (rel.type === 'hasMany') {
156
+ const fk = rel.foreignKeyColumnName;
157
+ const pk = rel.localKeyColumnName;
158
+ if (!childTable || !fk || !pk)
159
+ continue;
160
+ out[`${relName}.$count`] = oneToManySource('count', childTable, fk, pk, undefined);
161
+ for (const col of columns) {
162
+ for (const fn of COLUMN_FNS) {
163
+ out[`${relName}.$${fn}.${col}`] = oneToManySource(fn, childTable, fk, pk, col);
164
+ }
165
+ }
166
+ }
167
+ else if (rel.type === 'manyToMany') {
168
+ const pivot = rel.pivotTable;
169
+ const ownerFk = rel.pivotForeignKey;
170
+ const inverseFk = rel.pivotRelatedForeignKey;
171
+ const ownerPk = rel.localKeyColumnName;
172
+ const childPk = rel.relatedKeyColumnName;
173
+ if (!pivot || !ownerFk || !ownerPk)
174
+ continue;
175
+ out[`${relName}.$count`] = manyToManySource('count', {
176
+ childTable: childTable ?? '',
177
+ childPk: childPk ?? '',
178
+ pivot,
179
+ ownerFk,
180
+ inverseFk: inverseFk ?? '',
181
+ ownerPk,
182
+ }, undefined);
183
+ if (childTable && inverseFk && childPk) {
184
+ for (const col of columns) {
185
+ for (const fn of COLUMN_FNS) {
186
+ out[`${relName}.$${fn}.${col}`] = manyToManySource(fn, { childTable, childPk, pivot, ownerFk, inverseFk, ownerPk }, col);
187
+ }
188
+ }
189
+ }
190
+ }
191
+ // to-one (belongsTo / hasOne) and any other kind → no aggregate keys.
192
+ }
193
+ return out;
194
+ }
195
+ //# sourceMappingURL=aggregate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.js","sourceRoot":"","sources":["../../src/aggregate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Pure, ORM-agnostic parser for to-many aggregate field paths — a verbatim port
3
+ * of the NestJS `nestjs-filter` `aggregate/aggregate-path.ts` (commit 3f21e1f).
4
+ * It only parses the client-facing field *string* into a structured
5
+ * {@link AggregatePath}; turning that into SQL (a correlated subquery) is the
6
+ * Lucid-specific compiler's job (see `filter_spec.ts` aggregate discovery).
7
+ */
8
+ export type AggregateFn = 'count' | 'sum' | 'avg' | 'min' | 'max';
9
+ export interface AggregatePath {
10
+ relation: string;
11
+ fn: AggregateFn;
12
+ column?: string;
13
+ }
14
+ /**
15
+ * Parses a to-many aggregate field path.
16
+ *
17
+ * Grammar (single relation hop only):
18
+ * - `<relation>.$count` → { relation, fn: 'count' }
19
+ * - `<relation>.$<fn>.<childColumn>` → { relation, fn, column } for fn in sum|avg|min|max
20
+ *
21
+ * Returns `null` for anything else so callers can fall through to
22
+ * non-aggregate field handling instead of throwing.
23
+ */
24
+ export declare function parseAggregatePath(path: string): AggregatePath | null;
25
+ //# sourceMappingURL=aggregate_path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate_path.d.ts","sourceRoot":"","sources":["../../src/aggregate_path.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAClE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,WAAW,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAWrE"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Pure, ORM-agnostic parser for to-many aggregate field paths — a verbatim port
3
+ * of the NestJS `nestjs-filter` `aggregate/aggregate-path.ts` (commit 3f21e1f).
4
+ * It only parses the client-facing field *string* into a structured
5
+ * {@link AggregatePath}; turning that into SQL (a correlated subquery) is the
6
+ * Lucid-specific compiler's job (see `filter_spec.ts` aggregate discovery).
7
+ */
8
+ const COLUMN_FNS = new Set(['sum', 'avg', 'min', 'max']);
9
+ /**
10
+ * Parses a to-many aggregate field path.
11
+ *
12
+ * Grammar (single relation hop only):
13
+ * - `<relation>.$count` → { relation, fn: 'count' }
14
+ * - `<relation>.$<fn>.<childColumn>` → { relation, fn, column } for fn in sum|avg|min|max
15
+ *
16
+ * Returns `null` for anything else so callers can fall through to
17
+ * non-aggregate field handling instead of throwing.
18
+ */
19
+ export function parseAggregatePath(path) {
20
+ const parts = path.split('.');
21
+ // <rel>.$<fn> → 2 parts, $count
22
+ // <rel>.$<fn>.<col> → 3 parts, column fns
23
+ if (parts.length < 2 || parts.length > 3)
24
+ return null;
25
+ const [relation, token, column] = parts;
26
+ if (!relation || !token?.startsWith('$'))
27
+ return null;
28
+ const fn = token.slice(1);
29
+ if (fn === 'count')
30
+ return parts.length === 2 ? { relation, fn } : null;
31
+ if (COLUMN_FNS.has(fn))
32
+ return parts.length === 3 && column ? { relation, fn, column } : null;
33
+ return null;
34
+ }
35
+ //# sourceMappingURL=aggregate_path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate_path.js","sourceRoot":"","sources":["../../src/aggregate_path.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,MAAM,UAAU,GAAG,IAAI,GAAG,CAAc,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAEtE;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC9B,2CAA2C;IAC3C,+CAA+C;IAC/C,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IACxC,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAgB,CAAC;IACzC,IAAI,EAAE,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9F,OAAO,IAAI,CAAC;AACd,CAAC"}