@eienjs/adonisjs-api-query 2.0.0 → 3.0.1

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 (89) hide show
  1. package/README.md +138 -0
  2. package/build/configure.d.ts +1 -5
  3. package/build/configure.js +1 -2
  4. package/build/index.d.ts +12 -6
  5. package/build/index.js +26 -20
  6. package/build/providers/api_query_provider.d.ts +19 -12
  7. package/build/providers/api_query_provider.js +7 -3
  8. package/build/src/allowed_filter.d.ts +14 -10
  9. package/build/src/allowed_filter.js +34 -12
  10. package/build/src/allowed_include.d.ts +6 -11
  11. package/build/src/allowed_include.js +14 -15
  12. package/build/src/allowed_sort.d.ts +2 -6
  13. package/build/src/allowed_sort.js +1 -2
  14. package/build/src/api_query_builder_request.d.ts +33 -12
  15. package/build/src/api_query_builder_request.js +119 -41
  16. package/build/src/base_filterable.d.ts +45 -0
  17. package/build/src/base_filterable.js +172 -0
  18. package/build/src/bindings/api_query_request.js +3 -3
  19. package/build/src/bindings/filterable_query.js +51 -0
  20. package/build/src/bindings/filters_query.js +21 -23
  21. package/build/src/bindings/includes_query.js +32 -33
  22. package/build/src/bindings/sorts_query.js +20 -23
  23. package/build/src/bindings/validation_query.js +27 -0
  24. package/build/src/decorators/filterable.d.ts +20 -0
  25. package/build/src/decorators/filterable.js +24 -0
  26. package/build/src/define_config.d.ts +1 -5
  27. package/build/src/define_config.js +3 -3
  28. package/build/src/enums/filter_operator.d.ts +2 -5
  29. package/build/src/enums/filter_operator.js +1 -2
  30. package/build/src/enums/sort_direction.d.ts +2 -5
  31. package/build/src/enums/sort_direction.js +1 -2
  32. package/build/src/exceptions/allowed_fields_must_be_called_before_allowed_includes.d.ts +4 -8
  33. package/build/src/exceptions/allowed_fields_must_be_called_before_allowed_includes.js +3 -4
  34. package/build/src/exceptions/filterable_not_configured.d.ts +5 -0
  35. package/build/src/exceptions/filterable_not_configured.js +12 -0
  36. package/build/src/exceptions/invalid_append_query.d.ts +4 -6
  37. package/build/src/exceptions/invalid_append_query.js +5 -2
  38. package/build/src/exceptions/invalid_direction.d.ts +2 -6
  39. package/build/src/exceptions/invalid_direction.js +1 -2
  40. package/build/src/exceptions/invalid_field_query.d.ts +7 -0
  41. package/build/src/exceptions/invalid_field_query.js +17 -0
  42. package/build/src/exceptions/invalid_filter_query.d.ts +2 -6
  43. package/build/src/exceptions/invalid_filter_query.js +1 -2
  44. package/build/src/exceptions/invalid_filter_value.d.ts +2 -6
  45. package/build/src/exceptions/invalid_filter_value.js +1 -2
  46. package/build/src/exceptions/invalid_include_query.d.ts +2 -6
  47. package/build/src/exceptions/invalid_include_query.js +1 -2
  48. package/build/src/exceptions/invalid_query.d.ts +2 -6
  49. package/build/src/exceptions/invalid_query.js +1 -2
  50. package/build/src/exceptions/invalid_sort_query.d.ts +2 -6
  51. package/build/src/exceptions/invalid_sort_query.js +1 -2
  52. package/build/src/exceptions/unknown_included_fields_query.d.ts +2 -6
  53. package/build/src/exceptions/unknown_included_fields_query.js +1 -2
  54. package/build/src/exceptions/vine_not_installed.d.ts +5 -0
  55. package/build/src/exceptions/vine_not_installed.js +12 -0
  56. package/build/src/filters/filters_begin_with_strict.d.ts +2 -6
  57. package/build/src/filters/filters_begin_with_strict.js +2 -3
  58. package/build/src/filters/filters_callback.d.ts +2 -6
  59. package/build/src/filters/filters_callback.js +1 -2
  60. package/build/src/filters/filters_ends_with_strict.d.ts +2 -6
  61. package/build/src/filters/filters_ends_with_strict.js +2 -3
  62. package/build/src/filters/filters_exact.d.ts +2 -6
  63. package/build/src/filters/filters_exact.js +15 -12
  64. package/build/src/filters/filters_operator.d.ts +3 -7
  65. package/build/src/filters/filters_operator.js +10 -11
  66. package/build/src/filters/filters_partial.d.ts +2 -8
  67. package/build/src/filters/filters_partial.js +7 -9
  68. package/build/src/filters/filters_scope.d.ts +12 -0
  69. package/build/src/filters/filters_scope.js +98 -0
  70. package/build/src/filters/filters_trashed.d.ts +2 -6
  71. package/build/src/filters/filters_trashed.js +1 -2
  72. package/build/src/includes/included_callback.d.ts +2 -6
  73. package/build/src/includes/included_callback.js +1 -2
  74. package/build/src/includes/included_count.d.ts +2 -6
  75. package/build/src/includes/included_count.js +2 -2
  76. package/build/src/includes/included_relationship.d.ts +4 -8
  77. package/build/src/includes/included_relationship.js +19 -22
  78. package/build/src/sorts/sorts_callback.d.ts +2 -6
  79. package/build/src/sorts/sorts_callback.js +1 -2
  80. package/build/src/sorts/sorts_field.d.ts +2 -6
  81. package/build/src/sorts/sorts_field.js +1 -2
  82. package/build/src/utils/helpers.js +2 -14
  83. package/build/src/validation/schema_builder.d.ts +3 -0
  84. package/build/src/validation/schema_builder.js +38 -0
  85. package/build/src/validation/vine_resolver.js +13 -0
  86. package/build/stubs/main.d.ts +1 -4
  87. package/build/stubs/main.js +1 -2
  88. package/build/types.d.ts +44 -19
  89. package/package.json +31 -24
package/README.md CHANGED
@@ -39,6 +39,144 @@ export default class MyController {
39
39
  }
40
40
  ```
41
41
 
42
+ ### Using Filterable Classes
43
+
44
+ Instead of configuring allowed filters, sorts, and includes directly in your controller, you can encapsulate them in a dedicated filterable class extending `BaseFilterable`:
45
+
46
+ ```typescript
47
+ // app/filters/user_filterable.ts
48
+ import { AllowedFilter, BaseFilterable } from '@eienjs/adonisjs-api-query';
49
+ import User from '#models/user';
50
+
51
+ export default class UserFilterable extends BaseFilterable.for(() => User) {
52
+ // Allowed filters: columns, AllowedFilter instances, or custom methods
53
+ public filters = [
54
+ 'name',
55
+ 'status',
56
+ AllowedFilter.exact('email'),
57
+ ];
58
+
59
+ // Allowed sorts and default sorts
60
+ public sorts = ['name', 'createdAt'];
61
+
62
+ public defaultSorts = '-createdAt';
63
+
64
+ // Allowed relationship includes
65
+ public includes = ['posts', 'profile'];
66
+
67
+ // Optional setup hook to customize/initialize the query builder
68
+ public setup(query) {
69
+ query.where('isActive', true);
70
+ }
71
+
72
+ // Custom filter method (auto-bound to 'status' filter)
73
+ public filterStatus(query, value: unknown) {
74
+ void query.where('status', value as string);
75
+ }
76
+ }
77
+ ```
78
+
79
+ Attach the filterable class to your Lucid Model using `static $filterable`, `@filterable` decorator, or `Filterable` mixin:
80
+
81
+ ```typescript
82
+ // app/models/user.ts
83
+ import { BaseModel } from '@adonisjs/lucid/orm';
84
+ import UserFilterable from '#filters/user_filterable';
85
+
86
+ export default class User extends BaseModel {
87
+ // Using a lazy thunk prevents circular dependency issues in ESM
88
+ public static $filterable = () => UserFilterable;
89
+ }
90
+ ```
91
+
92
+ Now you can run the filtered query in controllers using `User.$filter(request)`:
93
+
94
+ ```typescript
95
+ // inside a controller
96
+ export default class UsersController {
97
+ public async index({ request }: HttpContext) {
98
+ // Fluent query builder with filters, sorts, includes, and setup automatically applied
99
+ return await User.$filter(request).paginate(1, 15);
100
+ }
101
+ }
102
+ ```
103
+
104
+ You can also use it directly on existing queries via `query.filter(request)`, or query directly from the filterable class:
105
+
106
+ ```typescript
107
+ // On an existing query builder instance
108
+ const admins = await User.query().where('role', 'admin').filter(request);
109
+
110
+ // With an explicit filterable class
111
+ const special = await User.$filter(SpecialUserFilterable, request);
112
+
113
+ // Or directly from the filterable class
114
+ const users = await UserFilterable.$query(request);
115
+ ```
116
+
117
+ ### Request Validation with VineJS (Optional)
118
+
119
+ You can validate incoming API query parameters (`filter`, `sort`, `include`, etc.) using `@vinejs/vine`. Validation is 100% optional: `@vinejs/vine` is declared as an optional peer dependency (`>=4.4.0`).
120
+
121
+ #### 1. Defining Rules on a Filterable Class
122
+
123
+ Declare explicit validation rules using VineJS on your filterable class. You can pass a literal plain object, a `vine.object(...)`, or a compiled `vine.create(...)` directly to `rules`:
124
+
125
+ ```typescript
126
+ import { AllowedFilter, BaseFilterable } from '@eienjs/adonisjs-api-query';
127
+ import vine from '@vinejs/vine';
128
+ import User from '#models/user';
129
+
130
+ export default class UserFilterable extends BaseFilterable.for(() => User) {
131
+ public filters = [
132
+ 'name',
133
+ 'status',
134
+ AllowedFilter.exact('createdAt').rule(vine.date()),
135
+ AllowedFilter.exact('age').rule(vine.number().min(18)),
136
+ ];
137
+
138
+ public sorts = ['name', 'createdAt'];
139
+
140
+ // You can define rules as a literal object, vine.object(), or vine.create():
141
+ public rules = vine.object({
142
+ filter: vine.object({
143
+ name: vine.string().minLength(3).optional(),
144
+ status: vine.enum(['active', 'inactive']).optional(),
145
+ }).optional(),
146
+ sort: vine.string().optional(),
147
+ include: vine.string().optional(),
148
+ });
149
+ }
150
+ ```
151
+
152
+ #### 2. Automatic & Explicit Validation
153
+
154
+ - **Automatic validation during query execution**: When `rules` or `validator` are defined on a filterable class (or on `AllowedFilter.rule(...)`), the query builder automatically executes validation with VineJS before querying the database. If validation fails, it throws a native `ValidationError` (`errors.E_VALIDATION_ERROR`), which AdonisJS automatically transforms into HTTP 422 Unprocessable Entity:
155
+ ```typescript
156
+ // Automatically validates before hitting the database:
157
+ const users = await User.$filter(request).paginate(1, 15);
158
+ ```
159
+
160
+ - **Explicit validation & Controller usage**:
161
+ Validation resides directly inside `ApiQueryBuilderRequest` as the central core of the package:
162
+ ```typescript
163
+ // Validate request explicitly via filterable:
164
+ const payload = await UserFilterable.validate(request);
165
+
166
+ // Validate and get the query builder:
167
+ const users = await UserFilterable.$validateAndQuery(request);
168
+
169
+ // Or validate directly via ApiQueryBuilderRequest in controllers:
170
+ const apiRequest = new ApiQueryBuilderRequest(request);
171
+ const validated = await apiRequest.validate({
172
+ filter: vine.object({ name: vine.string() }).optional(),
173
+ sort: vine.string().optional(),
174
+ });
175
+ ```
176
+
177
+ - **Disabling automatic validation**:
178
+ You can disable automatic validation per-filterable with `public autoValidate = false;` or globally in `config/apiquery.ts` with `autoValidate: false`.
179
+
42
180
  ## Documentation
43
181
 
44
182
  The documentation is available on [EienJS](https://eienjs.com/packages/adonisjs-api-query/getting-started/).
@@ -1,6 +1,2 @@
1
1
  import Configure from "@adonisjs/core/commands/configure";
2
-
3
- //#region configure.d.ts
4
- declare const configure: (command: Configure) => Promise<void>;
5
- //#endregion
6
- export { configure };
2
+ export declare const configure: (command: Configure) => Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import { stubsRoot } from "./stubs/main.js";
2
- //#region configure.ts
2
+ // configure.ts
3
3
  const configure = async (command) => {
4
4
  const codemods = await command.createCodemods();
5
5
  await codemods.updateRcFile((rcFile) => {
@@ -7,5 +7,4 @@ const configure = async (command) => {
7
7
  });
8
8
  await codemods.makeUsingStub(stubsRoot, "config.stub", {});
9
9
  };
10
- //#endregion
11
10
  export { configure };
package/build/index.d.ts CHANGED
@@ -1,32 +1,38 @@
1
1
  import { configure } from "./configure.js";
2
- import { FilterOperator } from "./src/enums/filter_operator.js";
3
2
  import { AllowedFilter } from "./src/allowed_filter.js";
4
3
  import { AllowedInclude } from "./src/allowed_include.js";
5
4
  import { AllowedSort } from "./src/allowed_sort.js";
6
5
  import { ApiQueryBuilderRequest } from "./src/api_query_builder_request.js";
6
+ import { BaseFilterable } from "./src/base_filterable.js";
7
+ import { Filterable, filterable } from "./src/decorators/filterable.js";
7
8
  import { defineConfig } from "./src/define_config.js";
9
+ import { FilterOperator } from "./src/enums/filter_operator.js";
8
10
  import { SortDirection } from "./src/enums/sort_direction.js";
9
11
  import { AllowedFieldsMustBeCalledBeforeAllowedIncludes } from "./src/exceptions/allowed_fields_must_be_called_before_allowed_includes.js";
10
- import { InvalidQuery } from "./src/exceptions/invalid_query.js";
12
+ import { FilterableNotConfigured } from "./src/exceptions/filterable_not_configured.js";
11
13
  import { InvalidAppendQuery } from "./src/exceptions/invalid_append_query.js";
12
14
  import { InvalidDirection } from "./src/exceptions/invalid_direction.js";
15
+ import { InvalidFieldQuery } from "./src/exceptions/invalid_field_query.js";
13
16
  import { InvalidFilterQuery } from "./src/exceptions/invalid_filter_query.js";
14
17
  import { InvalidFilterValue } from "./src/exceptions/invalid_filter_value.js";
15
18
  import { InvalidIncludeQuery } from "./src/exceptions/invalid_include_query.js";
19
+ import { InvalidQuery } from "./src/exceptions/invalid_query.js";
16
20
  import { InvalidSortQuery } from "./src/exceptions/invalid_sort_query.js";
17
21
  import { UnknownIncludedFieldsQuery } from "./src/exceptions/unknown_included_fields_query.js";
18
- import { FiltersExact } from "./src/filters/filters_exact.js";
19
- import { FiltersPartial } from "./src/filters/filters_partial.js";
22
+ import { VineNotInstalledException } from "./src/exceptions/vine_not_installed.js";
20
23
  import { FiltersBeginWithStrict } from "./src/filters/filters_begin_with_strict.js";
21
24
  import { FiltersCallback } from "./src/filters/filters_callback.js";
22
25
  import { FiltersEndsWithStrict } from "./src/filters/filters_ends_with_strict.js";
26
+ import { FiltersExact } from "./src/filters/filters_exact.js";
23
27
  import { FiltersOperator } from "./src/filters/filters_operator.js";
28
+ import { FiltersPartial } from "./src/filters/filters_partial.js";
29
+ import { FiltersScope } from "./src/filters/filters_scope.js";
24
30
  import { FiltersTrashed } from "./src/filters/filters_trashed.js";
25
31
  import { IncludedCallback } from "./src/includes/included_callback.js";
26
32
  import { IncludedCount } from "./src/includes/included_count.js";
27
33
  import { IncludedRelationship } from "./src/includes/included_relationship.js";
28
34
  import { SortsCallback } from "./src/sorts/sorts_callback.js";
29
35
  import { SortsField } from "./src/sorts/sorts_field.js";
36
+ import { buildQueryValidator } from "./src/validation/schema_builder.js";
30
37
  import { stubsRoot } from "./stubs/main.js";
31
- export * from "collect.js";
32
- export { AllowedFieldsMustBeCalledBeforeAllowedIncludes, AllowedFilter, AllowedInclude, AllowedSort, ApiQueryBuilderRequest, FilterOperator, FiltersBeginWithStrict, FiltersCallback, FiltersEndsWithStrict, FiltersExact, FiltersOperator, FiltersPartial, FiltersTrashed, IncludedCallback, IncludedCount, IncludedRelationship, InvalidAppendQuery, InvalidDirection, InvalidFilterQuery, InvalidFilterValue, InvalidIncludeQuery, InvalidQuery, InvalidSortQuery, SortDirection, SortsCallback, SortsField, UnknownIncludedFieldsQuery, configure, defineConfig, stubsRoot };
38
+ export { AllowedFieldsMustBeCalledBeforeAllowedIncludes, AllowedFilter, AllowedInclude, AllowedSort, ApiQueryBuilderRequest, BaseFilterable, FilterOperator, Filterable, FilterableNotConfigured, FiltersBeginWithStrict, FiltersCallback, FiltersEndsWithStrict, FiltersExact, FiltersOperator, FiltersPartial, FiltersScope, FiltersTrashed, IncludedCallback, IncludedCount, IncludedRelationship, InvalidAppendQuery, InvalidDirection, InvalidFieldQuery, InvalidFilterQuery, InvalidFilterValue, InvalidIncludeQuery, InvalidQuery, InvalidSortQuery, SortDirection, SortsCallback, SortsField, UnknownIncludedFieldsQuery, VineNotInstalledException, buildQueryValidator, configure, defineConfig, filterable, stubsRoot };
package/build/index.js CHANGED
@@ -1,32 +1,38 @@
1
- import { stubsRoot } from "./stubs/main.js";
2
1
  import { configure } from "./configure.js";
3
- import { ApiQueryBuilderRequest } from "./src/api_query_builder_request.js";
4
- import { FiltersExact } from "./src/filters/filters_exact.js";
5
- import { FiltersPartial } from "./src/filters/filters_partial.js";
6
- import { FiltersBeginWithStrict } from "./src/filters/filters_begin_with_strict.js";
7
- import { FiltersCallback } from "./src/filters/filters_callback.js";
8
- import { FiltersEndsWithStrict } from "./src/filters/filters_ends_with_strict.js";
9
- import { FilterOperator } from "./src/enums/filter_operator.js";
10
- import { FiltersOperator } from "./src/filters/filters_operator.js";
11
- import { FiltersTrashed } from "./src/filters/filters_trashed.js";
12
2
  import { AllowedFilter } from "./src/allowed_filter.js";
13
- import { IncludedCallback } from "./src/includes/included_callback.js";
14
- import { IncludedCount } from "./src/includes/included_count.js";
15
- import { IncludedRelationship } from "./src/includes/included_relationship.js";
16
3
  import { AllowedInclude } from "./src/allowed_include.js";
17
- import { SortDirection } from "./src/enums/sort_direction.js";
18
- import { InvalidDirection } from "./src/exceptions/invalid_direction.js";
19
- import { SortsCallback } from "./src/sorts/sorts_callback.js";
20
- import { SortsField } from "./src/sorts/sorts_field.js";
21
4
  import { AllowedSort } from "./src/allowed_sort.js";
5
+ import { ApiQueryBuilderRequest } from "./src/api_query_builder_request.js";
6
+ import { BaseFilterable } from "./src/base_filterable.js";
7
+ import { Filterable, filterable } from "./src/decorators/filterable.js";
22
8
  import { defineConfig } from "./src/define_config.js";
9
+ import { FilterOperator } from "./src/enums/filter_operator.js";
10
+ import { SortDirection } from "./src/enums/sort_direction.js";
23
11
  import { AllowedFieldsMustBeCalledBeforeAllowedIncludes } from "./src/exceptions/allowed_fields_must_be_called_before_allowed_includes.js";
24
- import { InvalidQuery } from "./src/exceptions/invalid_query.js";
12
+ import { FilterableNotConfigured } from "./src/exceptions/filterable_not_configured.js";
25
13
  import { InvalidAppendQuery } from "./src/exceptions/invalid_append_query.js";
14
+ import { InvalidDirection } from "./src/exceptions/invalid_direction.js";
15
+ import { InvalidFieldQuery } from "./src/exceptions/invalid_field_query.js";
26
16
  import { InvalidFilterQuery } from "./src/exceptions/invalid_filter_query.js";
27
17
  import { InvalidFilterValue } from "./src/exceptions/invalid_filter_value.js";
28
18
  import { InvalidIncludeQuery } from "./src/exceptions/invalid_include_query.js";
19
+ import { InvalidQuery } from "./src/exceptions/invalid_query.js";
29
20
  import { InvalidSortQuery } from "./src/exceptions/invalid_sort_query.js";
30
21
  import { UnknownIncludedFieldsQuery } from "./src/exceptions/unknown_included_fields_query.js";
31
- export * from "collect.js";
32
- export { AllowedFieldsMustBeCalledBeforeAllowedIncludes, AllowedFilter, AllowedInclude, AllowedSort, ApiQueryBuilderRequest, FilterOperator, FiltersBeginWithStrict, FiltersCallback, FiltersEndsWithStrict, FiltersExact, FiltersOperator, FiltersPartial, FiltersTrashed, IncludedCallback, IncludedCount, IncludedRelationship, InvalidAppendQuery, InvalidDirection, InvalidFilterQuery, InvalidFilterValue, InvalidIncludeQuery, InvalidQuery, InvalidSortQuery, SortDirection, SortsCallback, SortsField, UnknownIncludedFieldsQuery, configure, defineConfig, stubsRoot };
22
+ import { VineNotInstalledException } from "./src/exceptions/vine_not_installed.js";
23
+ import { FiltersBeginWithStrict } from "./src/filters/filters_begin_with_strict.js";
24
+ import { FiltersCallback } from "./src/filters/filters_callback.js";
25
+ import { FiltersEndsWithStrict } from "./src/filters/filters_ends_with_strict.js";
26
+ import { FiltersExact } from "./src/filters/filters_exact.js";
27
+ import { FiltersOperator } from "./src/filters/filters_operator.js";
28
+ import { FiltersPartial } from "./src/filters/filters_partial.js";
29
+ import { FiltersScope } from "./src/filters/filters_scope.js";
30
+ import { FiltersTrashed } from "./src/filters/filters_trashed.js";
31
+ import { IncludedCallback } from "./src/includes/included_callback.js";
32
+ import { IncludedCount } from "./src/includes/included_count.js";
33
+ import { IncludedRelationship } from "./src/includes/included_relationship.js";
34
+ import { SortsCallback } from "./src/sorts/sorts_callback.js";
35
+ import { SortsField } from "./src/sorts/sorts_field.js";
36
+ import { buildQueryValidator } from "./src/validation/schema_builder.js";
37
+ import { stubsRoot } from "./stubs/main.js";
38
+ export { AllowedFieldsMustBeCalledBeforeAllowedIncludes, AllowedFilter, AllowedInclude, AllowedSort, ApiQueryBuilderRequest, BaseFilterable, FilterOperator, Filterable, FilterableNotConfigured, FiltersBeginWithStrict, FiltersCallback, FiltersEndsWithStrict, FiltersExact, FiltersOperator, FiltersPartial, FiltersScope, FiltersTrashed, IncludedCallback, IncludedCount, IncludedRelationship, InvalidAppendQuery, InvalidDirection, InvalidFieldQuery, InvalidFilterQuery, InvalidFilterValue, InvalidIncludeQuery, InvalidQuery, InvalidSortQuery, SortDirection, SortsCallback, SortsField, UnknownIncludedFieldsQuery, VineNotInstalledException, buildQueryValidator, configure, defineConfig, filterable, stubsRoot };
@@ -1,16 +1,14 @@
1
- import { ExtractKeys, HintedString, SortUnionKeyParams } from "../types.js";
2
1
  import { AllowedFilter } from "../src/allowed_filter.js";
3
2
  import { AllowedInclude } from "../src/allowed_include.js";
4
3
  import { AllowedSort } from "../src/allowed_sort.js";
5
4
  import { ApiQueryBuilderRequest } from "../src/api_query_builder_request.js";
6
- import { Collection } from "collect.js";
5
+ import { BaseFilterable } from "../src/base_filterable.js";
6
+ import { ExtractKeys, FilterableConstructor, FilterableTarget, HintedString, SortUnionKeyParams } from "../types.js";
7
7
  import { HttpRequest } from "@adonisjs/core/http";
8
8
  import { LucidModel } from "@adonisjs/lucid/types/model";
9
9
  import { ExtractModelRelations } from "@adonisjs/lucid/types/relations";
10
10
  import { ApplicationService } from "@adonisjs/core/types";
11
-
12
- //#region providers/api_query_provider.d.ts
13
- declare class ApiQueryProvider {
11
+ export default class ApiQueryProvider {
14
12
  protected app: ApplicationService;
15
13
  constructor(app: ApplicationService);
16
14
  boot(): Promise<void>;
@@ -25,11 +23,19 @@ declare module '@adonisjs/lucid/orm' {
25
23
  defaultSort(sorts: (AllowedSort<LucidModel> | string)[]): this;
26
24
  allowedFilters(...filters: (AllowedFilter<LucidModel> | string)[]): this;
27
25
  allowedFilters(filters: (AllowedFilter<LucidModel> | string)[]): this;
28
- allowedIncludes(...includes: (Collection<AllowedInclude<LucidModel>> | string)[]): this;
29
- allowedIncludes(includes: (Collection<AllowedInclude<LucidModel>> | string)[]): this;
26
+ allowedIncludes(...includes: (AllowedInclude<LucidModel> | AllowedInclude<LucidModel>[] | string)[]): this;
27
+ allowedIncludes(includes: (AllowedInclude<LucidModel> | AllowedInclude<LucidModel>[] | string)[]): this;
28
+ filter(request?: HttpRequest | ApiQueryBuilderRequest): this;
29
+ filter(filterable: FilterableConstructor | BaseFilterable, request?: HttpRequest | ApiQueryBuilderRequest): this;
30
+ validate(): Promise<unknown>;
30
31
  }
31
32
  }
32
33
  declare module '@adonisjs/lucid/types/model' {
34
+ interface LucidModel {
35
+ $filterable?: FilterableTarget;
36
+ $filter<Model extends LucidModel>(this: Model, request?: HttpRequest | ApiQueryBuilderRequest): ModelQueryBuilderContract<Model, InstanceType<Model>>;
37
+ $filter<Model extends LucidModel>(this: Model, filterable: FilterableConstructor<Model> | BaseFilterable<Model>, request?: HttpRequest | ApiQueryBuilderRequest): ModelQueryBuilderContract<Model, InstanceType<Model>>;
38
+ }
33
39
  interface ModelQueryBuilderContract<Model extends LucidModel, Result = InstanceType<Model>> {
34
40
  withRequest(request: HttpRequest): this;
35
41
  allowedSorts(...sorts: (AllowedSort<Model> | HintedString<SortUnionKeyParams<Model>>)[]): this;
@@ -38,9 +44,10 @@ declare module '@adonisjs/lucid/types/model' {
38
44
  defaultSort(sorts: (AllowedSort<Model> | HintedString<SortUnionKeyParams<Model>>)[]): this;
39
45
  allowedFilters(...filters: (AllowedFilter<Model> | HintedString<ExtractKeys<ModelAttributes<InstanceType<Model>>>>)[]): this;
40
46
  allowedFilters(filters: (AllowedFilter<Model> | HintedString<ExtractKeys<ModelAttributes<InstanceType<Model>>>>)[]): this;
41
- allowedIncludes(...includes: (Collection<AllowedInclude<Model>> | HintedString<ExtractModelRelations<InstanceType<Model>>>)[]): this;
42
- allowedIncludes(includes: (Collection<AllowedInclude<Model>> | HintedString<ExtractModelRelations<InstanceType<Model>>>)[]): this;
47
+ allowedIncludes(...includes: (AllowedInclude<Model> | AllowedInclude<Model>[] | HintedString<ExtractModelRelations<InstanceType<Model>>>)[]): this;
48
+ allowedIncludes(includes: (AllowedInclude<Model> | AllowedInclude<Model>[] | HintedString<ExtractModelRelations<InstanceType<Model>>>)[]): this;
49
+ filter(request?: HttpRequest | ApiQueryBuilderRequest): this;
50
+ filter(filterable: FilterableConstructor<Model> | BaseFilterable<Model>, request?: HttpRequest | ApiQueryBuilderRequest): this;
51
+ validate(): Promise<unknown>;
43
52
  }
44
- }
45
- //#endregion
46
- export { ApiQueryProvider as default };
53
+ }
@@ -1,7 +1,7 @@
1
1
  import { RuntimeException } from "@adonisjs/core/exceptions";
2
+ import { BaseModel, ModelQueryBuilder } from "@adonisjs/lucid/orm";
2
3
  import { configProvider } from "@adonisjs/core";
3
- import { ModelQueryBuilder } from "@adonisjs/lucid/orm";
4
- //#region providers/api_query_provider.ts
4
+ // providers/api_query_provider.ts
5
5
  var ApiQueryProvider = class {
6
6
  app;
7
7
  constructor(app) {
@@ -12,14 +12,18 @@ var ApiQueryProvider = class {
12
12
  const { extendModelQueryBuilderWithSortsQuery } = await import("../src/bindings/sorts_query.js");
13
13
  const { extendModelQueryBuilderWithFiltersQuery } = await import("../src/bindings/filters_query.js");
14
14
  const { extendModelQueryBuilderWithIncludesQuery } = await import("../src/bindings/includes_query.js");
15
+ const { extendLucidWithFilterable } = await import("../src/bindings/filterable_query.js");
16
+ const { extendModelQueryBuilderWithValidation } = await import("../src/bindings/validation_query.js");
15
17
  const apiQueryConfigProvider = this.app.config.get("apiquery");
16
18
  const config = await configProvider.resolve(this.app, apiQueryConfigProvider);
17
19
  if (!config) throw new RuntimeException("Invalid \"config/apiquery.ts\" file. Make sure you are using the \"defineConfig\" method");
20
+ this.app.config.set("apiquery", config);
18
21
  extendModelQueryBuilderWithRequest(ModelQueryBuilder);
19
22
  extendModelQueryBuilderWithSortsQuery(ModelQueryBuilder, config);
20
23
  extendModelQueryBuilderWithFiltersQuery(ModelQueryBuilder, config);
21
24
  extendModelQueryBuilderWithIncludesQuery(ModelQueryBuilder, config);
25
+ extendLucidWithFilterable(BaseModel, ModelQueryBuilder);
26
+ extendModelQueryBuilderWithValidation(ModelQueryBuilder);
22
27
  }
23
28
  };
24
- //#endregion
25
29
  export { ApiQueryProvider as default };
@@ -1,19 +1,20 @@
1
1
  import { FilterOperator } from "./enums/filter_operator.js";
2
- import { EnumType, ExtractKeys, Filter } from "../types.js";
3
- import { Collection } from "collect.js";
2
+ import { EnumType, ExtractKeys, Filter, ScopeUnionKeyParams } from "../types.js";
4
3
  import { LucidModel, ModelAttributes, ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
5
4
  import { StrictValuesWithoutRaw } from "@adonisjs/lucid/types/querybuilder";
6
-
7
- //#region src/allowed_filter.d.ts
8
- declare class AllowedFilter<Model extends LucidModel> {
5
+ import { SchemaTypes } from "@vinejs/vine/types";
6
+ export declare class AllowedFilter<Model extends LucidModel> {
9
7
  protected name: string;
10
8
  protected filterClass: Filter<Model>;
11
9
  protected internalName: string;
12
- protected ignored: Collection<unknown>;
10
+ protected ignored: unknown[];
13
11
  protected default: unknown;
14
12
  protected $hasDefault: boolean;
15
13
  protected nullable: boolean;
16
- constructor(name: string, filterClass: Filter<Model>, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>);
14
+ protected validationRule?: SchemaTypes;
15
+ constructor(name: string, filterClass: Filter<Model>, internalName?: string);
16
+ rule(rule: SchemaTypes): this;
17
+ getRule(): SchemaTypes | undefined;
17
18
  filter(query: ModelQueryBuilderContract<Model>, value: unknown): void;
18
19
  getFilterClass(): Filter<Model>;
19
20
  getName(): string;
@@ -28,14 +29,17 @@ declare class AllowedFilter<Model extends LucidModel> {
28
29
  unsetDefault(): this;
29
30
  protected resolveValueForFiltering(value: unknown): StrictValuesWithoutRaw | null;
30
31
  static setFilterArrayValueDelimiter(delimiter?: string): void;
32
+ /**
33
+ * Map of filter name to its Vine rule, for every allowed filter that declares one.
34
+ */
35
+ static extractRules(filters: readonly unknown[]): Record<string, SchemaTypes>;
31
36
  static exact<Model extends LucidModel>(name: string, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>, shouldAddRelationConstraint?: boolean, arrayValueDelimiter?: string): AllowedFilter<Model>;
32
37
  static partial<Model extends LucidModel>(name: string, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>, shouldAddRelationConstraint?: boolean, arrayValueDelimiter?: string): AllowedFilter<Model>;
33
38
  static beginsWithStrict<Model extends LucidModel>(name: string, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>, shouldAddRelationConstraint?: boolean, arrayValueDelimiter?: string): AllowedFilter<Model>;
34
39
  static endsWithStrict<Model extends LucidModel>(name: string, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>, shouldAddRelationConstraint?: boolean, arrayValueDelimiter?: string): AllowedFilter<Model>;
35
40
  static callback<Model extends LucidModel>(name: string, callback: (query: ModelQueryBuilderContract<Model>, value: unknown, property: string) => void, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>, arrayValueDelimiter?: string): AllowedFilter<Model>;
36
41
  static trashed<Model extends LucidModel>(name?: string, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>): AllowedFilter<Model>;
42
+ static scope<Model extends LucidModel = LucidModel>(name: string, internalName?: ScopeUnionKeyParams<Model>, arrayValueDelimiter?: string): AllowedFilter<Model>;
37
43
  static custom<Model extends LucidModel>(name: string, filterClass: Filter<Model>, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>, arrayValueDelimiter?: string): AllowedFilter<Model>;
38
44
  static operator<Model extends LucidModel>(name: string, filterOperator: EnumType<typeof FilterOperator>, boolean?: 'and' | 'or', internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>, shouldAddRelationConstraint?: boolean, arrayValueDelimiter?: string): AllowedFilter<Model>;
39
- }
40
- //#endregion
41
- export { AllowedFilter };
45
+ }
@@ -1,30 +1,37 @@
1
1
  import { ApiQueryBuilderRequest } from "./api_query_builder_request.js";
2
- import { FiltersExact } from "./filters/filters_exact.js";
3
- import { FiltersPartial } from "./filters/filters_partial.js";
4
2
  import { FiltersBeginWithStrict } from "./filters/filters_begin_with_strict.js";
5
3
  import { FiltersCallback } from "./filters/filters_callback.js";
6
4
  import { FiltersEndsWithStrict } from "./filters/filters_ends_with_strict.js";
5
+ import { FiltersExact } from "./filters/filters_exact.js";
7
6
  import { FiltersOperator } from "./filters/filters_operator.js";
7
+ import { FiltersPartial } from "./filters/filters_partial.js";
8
+ import { FiltersScope } from "./filters/filters_scope.js";
8
9
  import { FiltersTrashed } from "./filters/filters_trashed.js";
9
- import { Collection } from "collect.js";
10
- //#region src/allowed_filter.ts
10
+ // src/allowed_filter.ts
11
11
  var AllowedFilter = class AllowedFilter {
12
12
  name;
13
13
  filterClass;
14
14
  internalName;
15
- ignored;
15
+ ignored = [];
16
16
  default;
17
17
  $hasDefault = false;
18
18
  nullable = false;
19
+ validationRule;
19
20
  constructor(name, filterClass, internalName) {
20
21
  this.name = name;
21
22
  this.filterClass = filterClass;
22
- this.ignored = new Collection();
23
23
  this.internalName = internalName ?? this.name;
24
24
  }
25
+ rule(rule) {
26
+ this.validationRule = rule;
27
+ return this;
28
+ }
29
+ getRule() {
30
+ return this.validationRule;
31
+ }
25
32
  filter(query, value) {
26
33
  const valueToFilter = this.resolveValueForFiltering(value);
27
- if (!this.nullable && valueToFilter === null) return;
34
+ if (valueToFilter === null && !this.nullable) return;
28
35
  this.filterClass.handle(query, valueToFilter, this.internalName);
29
36
  }
30
37
  getFilterClass() {
@@ -37,11 +44,11 @@ var AllowedFilter = class AllowedFilter {
37
44
  return this.name === filterName;
38
45
  }
39
46
  ignore(...values) {
40
- this.ignored = this.ignored.merge(values).flatten();
47
+ this.ignored.push(...values.flat(Infinity));
41
48
  return this;
42
49
  }
43
50
  getIgnored() {
44
- return this.ignored.toArray();
51
+ return this.ignored;
45
52
  }
46
53
  getInternalName() {
47
54
  return this.internalName;
@@ -69,14 +76,26 @@ var AllowedFilter = class AllowedFilter {
69
76
  }
70
77
  resolveValueForFiltering(value) {
71
78
  if (Array.isArray(value)) {
72
- const remainingProperties = value.map((subValue) => this.resolveValueForFiltering(subValue));
79
+ const remainingProperties = value.map((subValue) => this.resolveValueForFiltering(subValue)).filter((subValue) => subValue !== null);
73
80
  return remainingProperties.length === 0 ? null : remainingProperties;
74
81
  }
75
- return this.ignored.contains(value) ? null : value;
82
+ return this.ignored.includes(value) ? null : value;
76
83
  }
77
84
  static setFilterArrayValueDelimiter(delimiter) {
78
85
  if (delimiter) ApiQueryBuilderRequest.setFilterArrayValueDelimiter(delimiter);
79
86
  }
87
+ /**
88
+ * Map of filter name to its Vine rule, for every allowed filter that declares one.
89
+ */
90
+ static extractRules(filters) {
91
+ const rules = {};
92
+ for (const filter of filters) {
93
+ if (!(filter instanceof this)) continue;
94
+ const rule = filter.getRule();
95
+ if (rule) rules[filter.getName()] = rule;
96
+ }
97
+ return rules;
98
+ }
80
99
  static exact(name, internalName, shouldAddRelationConstraint = true, arrayValueDelimiter) {
81
100
  this.setFilterArrayValueDelimiter(arrayValueDelimiter);
82
101
  return new AllowedFilter(name, new FiltersExact(shouldAddRelationConstraint), internalName);
@@ -100,6 +119,10 @@ var AllowedFilter = class AllowedFilter {
100
119
  static trashed(name = "trashed", internalName) {
101
120
  return new AllowedFilter(name, new FiltersTrashed(), internalName);
102
121
  }
122
+ static scope(name, internalName, arrayValueDelimiter) {
123
+ this.setFilterArrayValueDelimiter(arrayValueDelimiter);
124
+ return new AllowedFilter(name, new FiltersScope(), internalName);
125
+ }
103
126
  static custom(name, filterClass, internalName, arrayValueDelimiter) {
104
127
  this.setFilterArrayValueDelimiter(arrayValueDelimiter);
105
128
  return new AllowedFilter(name, filterClass, internalName);
@@ -109,5 +132,4 @@ var AllowedFilter = class AllowedFilter {
109
132
  return new AllowedFilter(name, new FiltersOperator(shouldAddRelationConstraint, filterOperator, boolean), internalName);
110
133
  }
111
134
  };
112
- //#endregion
113
135
  export { AllowedFilter };
@@ -1,10 +1,7 @@
1
1
  import { Include } from "../types.js";
2
- import { Collection } from "collect.js";
3
2
  import { LucidModel, ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
4
3
  import { ModelRelations } from "@adonisjs/lucid/types/relations";
5
-
6
- //#region src/allowed_include.d.ts
7
- declare class AllowedInclude<Model extends LucidModel> {
4
+ export declare class AllowedInclude<Model extends LucidModel> {
8
5
  protected internalName: string;
9
6
  protected name: string;
10
7
  protected includeClass: Include<Model>;
@@ -12,10 +9,8 @@ declare class AllowedInclude<Model extends LucidModel> {
12
9
  include(query: ModelQueryBuilderContract<Model>): void;
13
10
  getName(): string;
14
11
  isForInclude(includeName: string): boolean;
15
- static relationship<ParentModel extends LucidModel>(name: string, internalName?: string): Collection<AllowedInclude<ParentModel>>;
16
- static count<ParentModel extends LucidModel>(name: string, internalName?: string): Collection<AllowedInclude<ParentModel>>;
17
- static callback<ParentModel extends LucidModel, RelatedModel extends LucidModel>(name: string, callback: (query: ModelRelations<RelatedModel, ParentModel>['builder'] | ModelRelations<RelatedModel, ParentModel>['subQuery']) => void, internalName?: string): Collection<AllowedInclude<ParentModel>>;
18
- static custom<ParentModel extends LucidModel>(name: string, includeClass: Include<ParentModel>, internalName?: string): Collection<AllowedInclude<ParentModel>>;
19
- }
20
- //#endregion
21
- export { AllowedInclude };
12
+ static relationship<ParentModel extends LucidModel>(name: string, internalName?: string): AllowedInclude<ParentModel>[];
13
+ static count<ParentModel extends LucidModel>(name: string, internalName?: string): AllowedInclude<ParentModel>[];
14
+ static callback<ParentModel extends LucidModel, RelatedModel extends LucidModel>(name: string, callback: (query: ModelRelations<RelatedModel, ParentModel>['builder'] | ModelRelations<RelatedModel, ParentModel>['subQuery']) => void, internalName?: string): AllowedInclude<ParentModel>[];
15
+ static custom<ParentModel extends LucidModel>(name: string, includeClass: Include<ParentModel>, internalName?: string): AllowedInclude<ParentModel>[];
16
+ }
@@ -1,9 +1,8 @@
1
1
  import { IncludedCallback } from "./includes/included_callback.js";
2
2
  import { IncludedCount } from "./includes/included_count.js";
3
3
  import { IncludedRelationship } from "./includes/included_relationship.js";
4
- import { Collection } from "collect.js";
5
4
  import app from "@adonisjs/core/services/app";
6
- //#region src/allowed_include.ts
5
+ // src/allowed_include.ts
7
6
  var AllowedInclude = class AllowedInclude {
8
7
  internalName;
9
8
  name;
@@ -24,25 +23,25 @@ var AllowedInclude = class AllowedInclude {
24
23
  }
25
24
  static relationship(name, internalName) {
26
25
  internalName ??= name;
27
- return IncludedRelationship.getIndividualRelationshipPathsFromInclude(internalName).zip(IncludedRelationship.getIndividualRelationshipPathsFromInclude(name).toArray()).reduce((includes, args) => {
28
- const [relationship, alias] = args;
29
- includes = includes.push(new AllowedInclude(alias, new IncludedRelationship(), relationship));
30
- if (!relationship.includes(".")) {
31
- const countSuffix = app.config.get("apiquery.countSuffix", "Count");
32
- for (const countAllowedInclude of this.count(`${alias}${countSuffix}`, `${relationship}${countSuffix}`)) includes = includes.push(countAllowedInclude);
33
- }
34
- return includes;
35
- }, new Collection());
26
+ const relationships = IncludedRelationship.getIndividualRelationshipPathsFromInclude(internalName);
27
+ const aliases = IncludedRelationship.getIndividualRelationshipPathsFromInclude(name);
28
+ const countSuffix = app.config.get("apiquery.countSuffix", "Count");
29
+ const includes = [];
30
+ for (const [index, relationship] of relationships.entries()) {
31
+ const alias = aliases[index];
32
+ includes.push(new AllowedInclude(alias, new IncludedRelationship(), relationship));
33
+ if (!relationship.includes(".")) includes.push(...this.count(`${alias}${countSuffix}`, `${relationship}${countSuffix}`));
34
+ }
35
+ return includes;
36
36
  }
37
37
  static count(name, internalName) {
38
- return new Collection([new AllowedInclude(name, new IncludedCount(), internalName)]);
38
+ return [new AllowedInclude(name, new IncludedCount(), internalName)];
39
39
  }
40
40
  static callback(name, callback, internalName) {
41
- return new Collection([new AllowedInclude(name, new IncludedCallback(callback), internalName)]);
41
+ return [new AllowedInclude(name, new IncludedCallback(callback), internalName)];
42
42
  }
43
43
  static custom(name, includeClass, internalName) {
44
- return new Collection([new AllowedInclude(name, includeClass, internalName)]);
44
+ return [new AllowedInclude(name, includeClass, internalName)];
45
45
  }
46
46
  };
47
- //#endregion
48
47
  export { AllowedInclude };
@@ -1,8 +1,6 @@
1
1
  import { ExtractKeys, Sort } from "../types.js";
2
2
  import { LucidModel, ModelAttributes, ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
3
-
4
- //#region src/allowed_sort.d.ts
5
- declare class AllowedSort<Model extends LucidModel> {
3
+ export declare class AllowedSort<Model extends LucidModel> {
6
4
  protected defaultDirection: string;
7
5
  protected internalName: string;
8
6
  protected name: string;
@@ -17,6 +15,4 @@ declare class AllowedSort<Model extends LucidModel> {
17
15
  static field<Model extends LucidModel>(name: string, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>): AllowedSort<Model>;
18
16
  static custom<Model extends LucidModel>(name: string, sortClass: Sort<Model>, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>): AllowedSort<Model>;
19
17
  static callback<Model extends LucidModel>(name: string, callback: (query: ModelQueryBuilderContract<Model>, isDescending: boolean, property: string) => void, internalName?: ExtractKeys<ModelAttributes<InstanceType<Model>>>): AllowedSort<Model>;
20
- }
21
- //#endregion
22
- export { AllowedSort };
18
+ }
@@ -2,7 +2,7 @@ import { SortDirection } from "./enums/sort_direction.js";
2
2
  import { InvalidDirection } from "./exceptions/invalid_direction.js";
3
3
  import { SortsCallback } from "./sorts/sorts_callback.js";
4
4
  import { SortsField } from "./sorts/sorts_field.js";
5
- //#region src/allowed_sort.ts
5
+ // src/allowed_sort.ts
6
6
  const REMOVE_HYPHEN_STARTING_IN_ANY_POSITION = /^-+/;
7
7
  var AllowedSort = class AllowedSort {
8
8
  defaultDirection;
@@ -46,5 +46,4 @@ var AllowedSort = class AllowedSort {
46
46
  return new AllowedSort(name, new SortsCallback(callback), internalName);
47
47
  }
48
48
  };
49
- //#endregion
50
49
  export { AllowedSort };