@eienjs/adonisjs-api-query 1.1.1 → 3.0.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 (120) hide show
  1. package/{LICENSE.md → LICENSE} +1 -1
  2. package/README.md +166 -2
  3. package/build/configure.d.ts +2 -2
  4. package/build/configure.js +10 -0
  5. package/build/index.d.ts +38 -31
  6. package/build/index.js +38 -154
  7. package/build/providers/api_query_provider.d.ts +48 -37
  8. package/build/providers/api_query_provider.js +26 -27
  9. package/build/src/allowed_filter.d.ts +44 -36
  10. package/build/src/allowed_filter.js +135 -0
  11. package/build/src/allowed_include.d.ts +15 -16
  12. package/build/src/allowed_include.js +47 -0
  13. package/build/src/allowed_sort.d.ts +17 -17
  14. package/build/src/allowed_sort.js +49 -0
  15. package/build/src/api_query_builder_request.d.ts +55 -30
  16. package/build/src/api_query_builder_request.js +200 -0
  17. package/build/src/base_filterable.d.ts +45 -0
  18. package/build/src/base_filterable.js +172 -0
  19. package/build/src/bindings/api_query_request.js +13 -0
  20. package/build/src/bindings/filterable_query.js +51 -0
  21. package/build/src/bindings/filters_query.js +38 -0
  22. package/build/src/bindings/includes_query.js +50 -0
  23. package/build/src/bindings/sorts_query.js +42 -0
  24. package/build/src/bindings/validation_query.js +27 -0
  25. package/build/src/decorators/filterable.d.ts +20 -0
  26. package/build/src/decorators/filterable.js +24 -0
  27. package/build/src/define_config.d.ts +3 -3
  28. package/build/src/define_config.js +25 -0
  29. package/build/src/enums/filter_operator.d.ts +8 -9
  30. package/build/src/enums/filter_operator.js +11 -0
  31. package/build/src/enums/sort_direction.d.ts +3 -4
  32. package/build/src/enums/sort_direction.js +6 -0
  33. package/build/src/exceptions/allowed_fields_must_be_called_before_allowed_includes.d.ts +5 -5
  34. package/build/src/exceptions/allowed_fields_must_be_called_before_allowed_includes.js +10 -0
  35. package/build/src/exceptions/filterable_not_configured.d.ts +5 -0
  36. package/build/src/exceptions/filterable_not_configured.js +12 -0
  37. package/build/src/exceptions/invalid_append_query.d.ts +6 -4
  38. package/build/src/exceptions/invalid_append_query.js +17 -0
  39. package/build/src/exceptions/invalid_direction.d.ts +3 -3
  40. package/build/src/exceptions/invalid_direction.js +9 -0
  41. package/build/src/exceptions/invalid_field_query.d.ts +6 -4
  42. package/build/src/exceptions/invalid_field_query.js +17 -0
  43. package/build/src/exceptions/invalid_filter_query.d.ts +6 -6
  44. package/build/src/exceptions/invalid_filter_query.js +17 -0
  45. package/build/src/exceptions/invalid_filter_value.d.ts +3 -3
  46. package/build/src/exceptions/invalid_filter_value.js +8 -0
  47. package/build/src/exceptions/invalid_include_query.d.ts +6 -6
  48. package/build/src/exceptions/invalid_include_query.js +21 -0
  49. package/build/src/exceptions/invalid_query.d.ts +3 -3
  50. package/build/{chunk-XIRHVLVQ.js → src/exceptions/invalid_query.js} +3 -7
  51. package/build/src/exceptions/invalid_sort_query.d.ts +6 -6
  52. package/build/src/exceptions/invalid_sort_query.js +17 -0
  53. package/build/src/exceptions/unknown_included_fields_query.d.ts +4 -4
  54. package/build/src/exceptions/unknown_included_fields_query.js +12 -0
  55. package/build/src/exceptions/vine_not_installed.d.ts +5 -0
  56. package/build/src/exceptions/vine_not_installed.js +12 -0
  57. package/build/src/filters/filters_begin_with_strict.d.ts +6 -6
  58. package/build/src/filters/filters_begin_with_strict.js +8 -0
  59. package/build/src/filters/filters_callback.d.ts +6 -6
  60. package/build/src/filters/filters_callback.js +11 -0
  61. package/build/src/filters/filters_ends_with_strict.d.ts +6 -6
  62. package/build/src/filters/filters_ends_with_strict.js +8 -0
  63. package/build/src/filters/filters_exact.d.ts +11 -11
  64. package/build/src/filters/filters_exact.js +50 -0
  65. package/build/src/filters/filters_operator.d.ts +13 -13
  66. package/build/src/filters/filters_operator.js +52 -0
  67. package/build/src/filters/filters_partial.d.ts +9 -11
  68. package/build/src/filters/filters_partial.js +37 -0
  69. package/build/src/filters/filters_scope.d.ts +12 -0
  70. package/build/src/filters/filters_scope.js +98 -0
  71. package/build/src/filters/filters_trashed.d.ts +5 -5
  72. package/build/src/filters/filters_trashed.js +13 -0
  73. package/build/src/includes/included_callback.d.ts +7 -7
  74. package/build/src/includes/included_callback.js +11 -0
  75. package/build/src/includes/included_count.d.ts +4 -4
  76. package/build/src/includes/included_count.js +11 -0
  77. package/build/src/includes/included_relationship.d.ts +8 -8
  78. package/build/src/includes/included_relationship.js +37 -0
  79. package/build/src/sorts/sorts_callback.d.ts +6 -6
  80. package/build/src/sorts/sorts_callback.js +11 -0
  81. package/build/src/sorts/sorts_field.d.ts +4 -4
  82. package/build/src/sorts/sorts_field.js +7 -0
  83. package/build/src/utils/helpers.js +14 -0
  84. package/build/src/validation/schema_builder.d.ts +3 -0
  85. package/build/src/validation/schema_builder.js +38 -0
  86. package/build/src/validation/vine_resolver.js +13 -0
  87. package/build/stubs/main.d.ts +1 -1
  88. package/build/stubs/main.js +3 -0
  89. package/build/types.d.ts +116 -0
  90. package/build/types.js +1 -0
  91. package/package.json +101 -88
  92. package/build/api_query_request-QXOB4BN6.js +0 -18
  93. package/build/api_query_request-QXOB4BN6.js.map +0 -1
  94. package/build/chunk-3ITWUZL5.js +0 -108
  95. package/build/chunk-3ITWUZL5.js.map +0 -1
  96. package/build/chunk-54HNQTDY.js +0 -348
  97. package/build/chunk-54HNQTDY.js.map +0 -1
  98. package/build/chunk-IV3B5WO7.js +0 -160
  99. package/build/chunk-IV3B5WO7.js.map +0 -1
  100. package/build/chunk-MKQ3HMMA.js +0 -198
  101. package/build/chunk-MKQ3HMMA.js.map +0 -1
  102. package/build/chunk-XIRHVLVQ.js.map +0 -1
  103. package/build/filters_query-LCFH2F2X.js +0 -55
  104. package/build/filters_query-LCFH2F2X.js.map +0 -1
  105. package/build/includes_query-PVONWQMC.js +0 -70
  106. package/build/includes_query-PVONWQMC.js.map +0 -1
  107. package/build/index.js.map +0 -1
  108. package/build/metafile-esm.json +0 -1
  109. package/build/providers/api_query_provider.js.map +0 -1
  110. package/build/sorts_query-NJTC5UXX.js +0 -63
  111. package/build/sorts_query-NJTC5UXX.js.map +0 -1
  112. package/build/src/bindings/api_query_request.d.ts +0 -2
  113. package/build/src/bindings/filters_query.d.ts +0 -3
  114. package/build/src/bindings/includes_query.d.ts +0 -3
  115. package/build/src/bindings/sorts_query.d.ts +0 -3
  116. package/build/src/types.d.ts +0 -90
  117. package/build/src/types.js +0 -1
  118. package/build/src/types.js.map +0 -1
  119. package/build/src/utils/helpers.d.ts +0 -3
  120. /package/build/{config.stub → stubs/config.stub} +0 -0
@@ -0,0 +1,200 @@
1
+ import { strAfterLast, strBeforeLast } from "./utils/helpers.js";
2
+ import { buildQueryValidator } from "./validation/schema_builder.js";
3
+ import { HttpContext } from "@adonisjs/core/http";
4
+ import app from "@adonisjs/core/services/app";
5
+ // src/api_query_builder_request.ts
6
+ const UNSAFE_OBJECT_KEYS = /* @__PURE__ */ new Set([
7
+ "__proto__",
8
+ "constructor",
9
+ "prototype"
10
+ ]);
11
+ const isPlainObject = (value) => {
12
+ if (typeof value !== "object" || value === null) return false;
13
+ const prototype = Object.getPrototypeOf(value);
14
+ return prototype === Object.prototype || prototype === null;
15
+ };
16
+ var ApiQueryBuilderRequest = class ApiQueryBuilderRequest {
17
+ static includesArrayValueDelimiter = ",";
18
+ static appendsArrayValueDelimiter = ",";
19
+ static fieldsArrayValueDelimiter = ",";
20
+ static sortsArrayValueDelimiter = ",";
21
+ static filterArrayValueDelimiter = ",";
22
+ _request;
23
+ _rules;
24
+ _filterRules = {};
25
+ _autoValidate;
26
+ _validation;
27
+ _validatedData;
28
+ constructor(request) {
29
+ this._request = request ?? HttpContext.getOrFail().request;
30
+ }
31
+ getRequest() {
32
+ return this._request;
33
+ }
34
+ all() {
35
+ return this._request.all();
36
+ }
37
+ setRules(rules) {
38
+ this._rules = rules;
39
+ return this;
40
+ }
41
+ getRules() {
42
+ return this._rules;
43
+ }
44
+ setFilterRules(rules) {
45
+ this._filterRules = rules;
46
+ return this;
47
+ }
48
+ getFilterRules() {
49
+ return this._filterRules;
50
+ }
51
+ setAutoValidate(autoValidate) {
52
+ this._autoValidate = autoValidate;
53
+ return this;
54
+ }
55
+ shouldAutoValidate() {
56
+ return this._autoValidate ?? app.config.get("apiquery.autoValidate", true);
57
+ }
58
+ hasValidation() {
59
+ return this._rules !== void 0 || Object.keys(this._filterRules).length > 0;
60
+ }
61
+ async toValidator() {
62
+ return buildQueryValidator(this._rules, this._filterRules);
63
+ }
64
+ /**
65
+ * Validates the request once (the result is memoized) and returns the validated data.
66
+ * Returns the raw request data when no rules are configured.
67
+ */
68
+ async validate(rules) {
69
+ if (rules !== void 0) this.setRules(rules);
70
+ if (!this.hasValidation()) return this.all();
71
+ this._validation ??= this.runValidation();
72
+ return this._validation;
73
+ }
74
+ getValidatedData() {
75
+ return this._validatedData;
76
+ }
77
+ isValidated() {
78
+ return this._validatedData !== void 0;
79
+ }
80
+ includes() {
81
+ return this.getArrayParameterData("apiquery.parameters.include", "include", ApiQueryBuilderRequest.getIncludesArrayValueDelimiter());
82
+ }
83
+ appends() {
84
+ return this.getArrayParameterData("apiquery.parameters.append", "append", ApiQueryBuilderRequest.getAppendsArrayValueDelimiter());
85
+ }
86
+ sorts() {
87
+ return this.getArrayParameterData("apiquery.parameters.sort", "sort", ApiQueryBuilderRequest.getSortsArrayValueDelimiter());
88
+ }
89
+ fields() {
90
+ const fieldsParameterName = app.config.get("apiquery.parameters.fields", "fields");
91
+ const fieldsDelimiter = ApiQueryBuilderRequest.getFieldsArrayValueDelimiter();
92
+ const fieldsData = this.getRequestData(fieldsParameterName, {});
93
+ const fieldsPerTable = typeof fieldsData === "string" ? fieldsData.split(fieldsDelimiter) : fieldsData;
94
+ if ((Array.isArray(fieldsPerTable) ? fieldsPerTable : Object.keys(fieldsPerTable)).length === 0) return {};
95
+ const fields = {};
96
+ const entries = Array.isArray(fieldsPerTable) ? fieldsPerTable.map((item, index) => [String(index), item]) : Object.entries(fieldsPerTable);
97
+ for (const [rawModel, tableFields] of entries) {
98
+ const model = Number.isNaN(Number(rawModel)) ? rawModel : typeof tableFields === "string" && tableFields.includes(".") ? strBeforeLast(tableFields, ".") : "_";
99
+ if (UNSAFE_OBJECT_KEYS.has(model)) continue;
100
+ if (!Object.hasOwn(fields, model)) fields[model] = [];
101
+ const currentFields = typeof tableFields === "string" ? tableFields.split(fieldsDelimiter).map((field) => strAfterLast(field, ".")) : tableFields;
102
+ fields[model].push(...currentFields);
103
+ }
104
+ return fields;
105
+ }
106
+ filters() {
107
+ const filterParameterName = app.config.get("apiquery.parameters.filter", "filter");
108
+ const filterParts = this._validatedData ? this._validatedData[filterParameterName] ?? {} : this.getRequestData(filterParameterName, {});
109
+ if (!isPlainObject(filterParts)) return {};
110
+ const filters = {};
111
+ for (const [key, value] of Object.entries(filterParts)) {
112
+ if (UNSAFE_OBJECT_KEYS.has(key)) continue;
113
+ filters[key] = this.getFilterValue(value);
114
+ }
115
+ return filters;
116
+ }
117
+ getArrayParameterData(configKey, defaultParam, delimiter) {
118
+ const parameterName = app.config.get(configKey, defaultParam);
119
+ const rawParts = this.getRequestData(parameterName, []);
120
+ return (typeof rawParts === "string" ? rawParts.split(delimiter) : Array.isArray(rawParts) ? rawParts : [rawParts]).filter(Boolean);
121
+ }
122
+ getFilterValue(value) {
123
+ if (value === void 0 || value === null) return null;
124
+ if (value === "" || Array.isArray(value) && value.length === 0 || isPlainObject(value) && Object.keys(value).length === 0) return typeof value === "object" ? [] : value;
125
+ if (Array.isArray(value)) return value.map((item) => this.getFilterValue(item));
126
+ if (isPlainObject(value)) {
127
+ const result = {};
128
+ for (const [key, val] of Object.entries(value)) {
129
+ if (UNSAFE_OBJECT_KEYS.has(key)) continue;
130
+ result[key] = this.getFilterValue(val);
131
+ }
132
+ return result;
133
+ }
134
+ if (typeof value === "string") {
135
+ const delimiter = ApiQueryBuilderRequest.getFilterArrayValueDelimiter();
136
+ if (value.includes(delimiter)) return value.split(delimiter);
137
+ if (value === "true") return true;
138
+ if (value === "false") return false;
139
+ }
140
+ return value;
141
+ }
142
+ getRequestData(key, defaultValue) {
143
+ return this._request.input(key, defaultValue);
144
+ }
145
+ async runValidation() {
146
+ const validator = await this.toValidator();
147
+ const request = this._request;
148
+ const data = typeof request.validateUsing === "function" ? await request.validateUsing(validator) : await validator.validate(this.all());
149
+ this._validatedData = data;
150
+ return this._validatedData;
151
+ }
152
+ static setArrayValueDelimiter(delimiter) {
153
+ this.includesArrayValueDelimiter = delimiter;
154
+ this.appendsArrayValueDelimiter = delimiter;
155
+ this.fieldsArrayValueDelimiter = delimiter;
156
+ this.sortsArrayValueDelimiter = delimiter;
157
+ this.filterArrayValueDelimiter = delimiter;
158
+ }
159
+ static setIncludesArrayValueDelimiter(includesArrayValueDelimiter) {
160
+ this.includesArrayValueDelimiter = includesArrayValueDelimiter;
161
+ }
162
+ static setAppendsArrayValueDelimiter(appendsArrayValueDelimiter) {
163
+ this.appendsArrayValueDelimiter = appendsArrayValueDelimiter;
164
+ }
165
+ static setFieldsArrayValueDelimiter(fieldsArrayValueDelimiter) {
166
+ this.fieldsArrayValueDelimiter = fieldsArrayValueDelimiter;
167
+ }
168
+ static setSortsArrayValueDelimiter(sortsArrayValueDelimiter) {
169
+ this.sortsArrayValueDelimiter = sortsArrayValueDelimiter;
170
+ }
171
+ static setFilterArrayValueDelimiter(filterArrayValueDelimiter) {
172
+ this.filterArrayValueDelimiter = filterArrayValueDelimiter;
173
+ }
174
+ static getIncludesArrayValueDelimiter() {
175
+ return this.includesArrayValueDelimiter;
176
+ }
177
+ static getAppendsArrayValueDelimiter() {
178
+ return this.appendsArrayValueDelimiter;
179
+ }
180
+ static getFieldsArrayValueDelimiter() {
181
+ return this.fieldsArrayValueDelimiter;
182
+ }
183
+ static getSortsArrayValueDelimiter() {
184
+ return this.sortsArrayValueDelimiter;
185
+ }
186
+ static getFilterArrayValueDelimiter() {
187
+ return this.filterArrayValueDelimiter;
188
+ }
189
+ static resetDelimiters() {
190
+ this.includesArrayValueDelimiter = ",";
191
+ this.appendsArrayValueDelimiter = ",";
192
+ this.fieldsArrayValueDelimiter = ",";
193
+ this.sortsArrayValueDelimiter = ",";
194
+ this.filterArrayValueDelimiter = ",";
195
+ }
196
+ static fromRequest(request) {
197
+ return new ApiQueryBuilderRequest(request);
198
+ }
199
+ };
200
+ export { ApiQueryBuilderRequest };
@@ -0,0 +1,45 @@
1
+ import { ApiQueryBuilderRequest } from "./api_query_builder_request.js";
2
+ import { FilterItem, IncludeItem, QueryValidator, SortItem, ValidationRules } from "../types.js";
3
+ import { HttpRequest } from "@adonisjs/core/http";
4
+ import { LucidModel, ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
5
+ export declare abstract class BaseFilterable<Model extends LucidModel = LucidModel> {
6
+ static model?: LucidModel | (() => LucidModel);
7
+ request?: HttpRequest | ApiQueryBuilderRequest;
8
+ filters?: FilterItem<Model>[] | (() => FilterItem<Model>[]);
9
+ sorts?: SortItem<Model>[] | (() => SortItem<Model>[]);
10
+ defaultSorts?: SortItem<Model> | SortItem<Model>[] | (() => SortItem<Model> | SortItem<Model>[]);
11
+ includes?: IncludeItem<Model>[] | (() => IncludeItem<Model>[]);
12
+ rules?: ValidationRules | (() => ValidationRules);
13
+ autoValidate?: boolean;
14
+ constructor(request?: HttpRequest | ApiQueryBuilderRequest);
15
+ setup?(query: ModelQueryBuilderContract<Model>, request?: HttpRequest | ApiQueryBuilderRequest): void;
16
+ protected apply(query: ModelQueryBuilderContract<Model>, request?: HttpRequest | ApiQueryBuilderRequest): void;
17
+ resolveRules(): ValidationRules | undefined;
18
+ toApiQueryBuilderRequest(request?: HttpRequest | ApiQueryBuilderRequest): ApiQueryBuilderRequest;
19
+ toValidator(): Promise<QueryValidator>;
20
+ validate(request?: HttpRequest | ApiQueryBuilderRequest): Promise<Record<string, unknown>>;
21
+ protected resolvePropertyOrMethod(propertyNames: string[]): unknown;
22
+ protected resolveFilters(): FilterItem<Model>[];
23
+ protected resolveSorts(): SortItem<Model>[];
24
+ protected resolveDefaultSorts(): SortItem<Model> | SortItem<Model>[] | undefined;
25
+ protected resolveIncludes(): IncludeItem<Model>[];
26
+ static for<M extends LucidModel>(model: M | (() => M)): {
27
+ new (request?: HttpRequest | ApiQueryBuilderRequest): BaseFilterable<M>;
28
+ prototype: BaseFilterable<M>;
29
+ model: () => M;
30
+ $query: <R = InstanceType<M>>(request?: HttpRequest | ApiQueryBuilderRequest) => ModelQueryBuilderContract<M, R>;
31
+ $validateAndQuery: <R = InstanceType<M>>(request?: HttpRequest | ApiQueryBuilderRequest) => Promise<ModelQueryBuilderContract<M, R>>;
32
+ validator: () => Promise<QueryValidator>;
33
+ toValidator: () => Promise<QueryValidator>;
34
+ validate: (request?: HttpRequest | ApiQueryBuilderRequest) => Promise<Record<string, unknown>>;
35
+ };
36
+ static toValidator(): Promise<QueryValidator>;
37
+ static validator(): Promise<QueryValidator>;
38
+ static validate(request?: HttpRequest | ApiQueryBuilderRequest): Promise<Record<string, unknown>>;
39
+ static $validateAndQuery<This extends {
40
+ model?: unknown;
41
+ }, M extends LucidModel = This['model'] extends (() => infer Model) ? (Model extends LucidModel ? Model : LucidModel) : This['model'] extends LucidModel ? This['model'] : LucidModel, R = InstanceType<M>>(this: This, request?: HttpRequest | ApiQueryBuilderRequest): Promise<ModelQueryBuilderContract<M, R>>;
42
+ static $query<This extends {
43
+ model?: unknown;
44
+ }, M extends LucidModel = This['model'] extends (() => infer Model) ? (Model extends LucidModel ? Model : LucidModel) : This['model'] extends LucidModel ? This['model'] : LucidModel, R = InstanceType<M>>(this: This, request?: HttpRequest | ApiQueryBuilderRequest): ModelQueryBuilderContract<M, R>;
45
+ }
@@ -0,0 +1,172 @@
1
+ import { ApiQueryBuilderRequest } from "./api_query_builder_request.js";
2
+ import { AllowedFilter } from "./allowed_filter.js";
3
+ import { AllowedInclude } from "./allowed_include.js";
4
+ import { AllowedSort } from "./allowed_sort.js";
5
+ import { RuntimeException } from "@adonisjs/core/exceptions";
6
+ // src/base_filterable.ts
7
+ const toPascalCase = (str) => {
8
+ return str.replaceAll(/(^|[-_])(\w)/g, (_, __, c) => c.toUpperCase());
9
+ };
10
+ const RESERVED_PROPERTY_NAMES = /* @__PURE__ */ new Set([
11
+ "constructor",
12
+ "setup",
13
+ "apply",
14
+ "resolveFilters",
15
+ "resolveSorts",
16
+ "resolveDefaultSorts",
17
+ "resolveIncludes",
18
+ "resolvePropertyOrMethod",
19
+ "filters",
20
+ "sorts",
21
+ "defaultSorts",
22
+ "includes",
23
+ "request",
24
+ "model",
25
+ "rules",
26
+ "autoValidate"
27
+ ]);
28
+ var BaseFilterable = class {
29
+ static model;
30
+ request;
31
+ filters;
32
+ sorts;
33
+ defaultSorts;
34
+ includes;
35
+ rules;
36
+ autoValidate;
37
+ constructor(request) {
38
+ this.request = request;
39
+ }
40
+ apply(query, request) {
41
+ const req = request ?? this.request;
42
+ if (req) {
43
+ if (req instanceof ApiQueryBuilderRequest) query.$queryBuilderRequest = req;
44
+ else query.withRequest(req);
45
+ }
46
+ if (typeof this.setup === "function") this.setup(query, req);
47
+ const filters = this.resolveFilters();
48
+ if (filters.length > 0) query.allowedFilters(filters);
49
+ const sorts = this.resolveSorts();
50
+ if (sorts.length > 0) query.allowedSorts(sorts);
51
+ const defaultSorts = this.resolveDefaultSorts();
52
+ if (defaultSorts !== void 0) query.defaultSort(defaultSorts);
53
+ const includes = this.resolveIncludes();
54
+ if (includes.length > 0) query.allowedIncludes(includes);
55
+ const rules = this.resolveRules();
56
+ const apiRequest = query.$apiQueryBuilderRequest;
57
+ if (rules !== void 0) apiRequest.setRules(rules);
58
+ if (this.autoValidate !== void 0) apiRequest.setAutoValidate(this.autoValidate);
59
+ }
60
+ resolveRules() {
61
+ return this.resolvePropertyOrMethod(["rules"]);
62
+ }
63
+ toApiQueryBuilderRequest(request) {
64
+ const req = request ?? this.request;
65
+ const apiRequest = req instanceof ApiQueryBuilderRequest ? req : new ApiQueryBuilderRequest(req);
66
+ const rules = this.resolveRules();
67
+ if (rules !== void 0) apiRequest.setRules(rules);
68
+ if (this.autoValidate !== void 0) apiRequest.setAutoValidate(this.autoValidate);
69
+ const filterRules = AllowedFilter.extractRules(this.resolveFilters());
70
+ if (Object.keys(filterRules).length > 0) apiRequest.setFilterRules(filterRules);
71
+ return apiRequest;
72
+ }
73
+ async toValidator() {
74
+ return this.toApiQueryBuilderRequest().toValidator();
75
+ }
76
+ async validate(request) {
77
+ return this.toApiQueryBuilderRequest(request).validate();
78
+ }
79
+ resolvePropertyOrMethod(propertyNames) {
80
+ const target = this;
81
+ for (const name of propertyNames) {
82
+ if (typeof target[name] === "function") return target[name].call(this);
83
+ if (target[name] !== void 0) return target[name];
84
+ }
85
+ }
86
+ resolveFilters() {
87
+ const rawFilters = this.resolvePropertyOrMethod(["filters"]);
88
+ if (!rawFilters || !Array.isArray(rawFilters)) return [];
89
+ const target = this;
90
+ return rawFilters.map((filter) => {
91
+ if (filter instanceof AllowedFilter) return filter;
92
+ if (typeof filter === "string") {
93
+ const filterMethodName = `filter${toPascalCase(filter)}`;
94
+ if (typeof target[filterMethodName] === "function") return AllowedFilter.callback(filter, (query, value) => {
95
+ target[filterMethodName].call(this, query, value);
96
+ });
97
+ if (!RESERVED_PROPERTY_NAMES.has(filter) && typeof target[filter] === "function") return AllowedFilter.callback(filter, (query, value) => {
98
+ target[filter].call(this, query, value);
99
+ });
100
+ }
101
+ return filter;
102
+ });
103
+ }
104
+ resolveSorts() {
105
+ const rawSorts = this.resolvePropertyOrMethod(["sorts"]);
106
+ if (!rawSorts || !Array.isArray(rawSorts)) return [];
107
+ const target = this;
108
+ return rawSorts.map((sort) => {
109
+ if (sort instanceof AllowedSort) return sort;
110
+ if (typeof sort === "string") {
111
+ const cleanName = sort.replaceAll(/^-+/g, "");
112
+ const sortMethodName = `sort${toPascalCase(cleanName)}`;
113
+ if (typeof target[sortMethodName] === "function") return AllowedSort.callback(cleanName, (query, isDescending) => {
114
+ target[sortMethodName].call(this, query, isDescending);
115
+ });
116
+ if (!RESERVED_PROPERTY_NAMES.has(cleanName) && typeof target[cleanName] === "function") return AllowedSort.callback(cleanName, (query, isDescending) => {
117
+ target[cleanName].call(this, query, isDescending);
118
+ });
119
+ }
120
+ return sort;
121
+ });
122
+ }
123
+ resolveDefaultSorts() {
124
+ return this.resolvePropertyOrMethod(["defaultSorts"]);
125
+ }
126
+ resolveIncludes() {
127
+ const rawIncludes = this.resolvePropertyOrMethod(["includes"]);
128
+ if (!rawIncludes || !Array.isArray(rawIncludes)) return [];
129
+ const target = this;
130
+ return rawIncludes.flatMap((include) => {
131
+ if (include instanceof AllowedInclude || Array.isArray(include)) return include;
132
+ if (typeof include === "string") {
133
+ const includeMethodName = `include${toPascalCase(include)}`;
134
+ if (typeof target[includeMethodName] === "function") return AllowedInclude.callback(include, (query) => {
135
+ target[includeMethodName].call(this, query);
136
+ });
137
+ if (!RESERVED_PROPERTY_NAMES.has(include) && typeof target[include] === "function") return AllowedInclude.callback(include, (query) => {
138
+ target[include].call(this, query);
139
+ });
140
+ }
141
+ return include;
142
+ });
143
+ }
144
+ static for(model) {
145
+ const modelThunk = typeof model === "function" && !("query" in model) ? model : () => model;
146
+ return class extends this {
147
+ static model = modelThunk;
148
+ };
149
+ }
150
+ static async toValidator() {
151
+ return new this().toValidator();
152
+ }
153
+ static async validator() {
154
+ return this.toValidator();
155
+ }
156
+ static async validate(request) {
157
+ return new this(request).validate(request);
158
+ }
159
+ static async $validateAndQuery(request) {
160
+ await this.validate(request);
161
+ return this.$query(request);
162
+ }
163
+ static $query(request) {
164
+ const rawModel = this.model;
165
+ if (!rawModel) {
166
+ const name = this.name ?? "Filterable";
167
+ throw new RuntimeException(`No model is configured for filterable class "${name}". Define "static model = () => User" on "${name}".`);
168
+ }
169
+ return (typeof rawModel.query === "function" ? rawModel : typeof rawModel === "function" ? rawModel() : rawModel).$filter(this, request);
170
+ }
171
+ };
172
+ export { BaseFilterable };
@@ -0,0 +1,13 @@
1
+ import { ApiQueryBuilderRequest } from "../api_query_builder_request.js";
2
+ // src/bindings/api_query_request.ts
3
+ const extendModelQueryBuilderWithRequest = function(builder) {
4
+ builder.macro("withRequest", function(request) {
5
+ this.$queryBuilderRequest = ApiQueryBuilderRequest.fromRequest(request);
6
+ return this;
7
+ });
8
+ builder.getter("$apiQueryBuilderRequest", function() {
9
+ this.$queryBuilderRequest ??= new ApiQueryBuilderRequest();
10
+ return this.$queryBuilderRequest;
11
+ });
12
+ };
13
+ export { extendModelQueryBuilderWithRequest };
@@ -0,0 +1,51 @@
1
+ import { BaseFilterable } from "../base_filterable.js";
2
+ import { FilterableNotConfigured } from "../exceptions/filterable_not_configured.js";
3
+ // src/bindings/filterable_query.ts
4
+ const isFilterableClassOrInstance = (value) => {
5
+ if (value instanceof BaseFilterable) return true;
6
+ if (typeof value === "function") {
7
+ const fn = value;
8
+ if (fn.prototype instanceof BaseFilterable) return true;
9
+ if (fn.prototype && typeof fn.prototype.apply === "function" && typeof fn.prototype.setup === "function") return true;
10
+ }
11
+ return false;
12
+ };
13
+ const resolveFilterable = (raw) => {
14
+ if (!raw) return;
15
+ if (raw instanceof BaseFilterable) return raw;
16
+ if (typeof raw === "function") {
17
+ const fn = raw;
18
+ if (fn.prototype instanceof BaseFilterable) return raw;
19
+ if (fn.prototype && typeof fn.prototype.apply === "function") return raw;
20
+ try {
21
+ const resolved = raw();
22
+ return resolveFilterable(resolved);
23
+ } catch {
24
+ return raw;
25
+ }
26
+ }
27
+ };
28
+ const extendLucidWithFilterable = function(baseModel, builder) {
29
+ const modelWithFilter = baseModel;
30
+ modelWithFilter.$filter ??= function(filterableOrRequest, maybeRequest) {
31
+ return this.query().filter(filterableOrRequest, maybeRequest);
32
+ };
33
+ const filterMacro = function(filterableOrRequest, maybeRequest) {
34
+ let filterable;
35
+ let request;
36
+ if (isFilterableClassOrInstance(filterableOrRequest)) {
37
+ filterable = filterableOrRequest;
38
+ request = maybeRequest;
39
+ } else {
40
+ const model = this.model;
41
+ filterable = resolveFilterable(model.$filterable);
42
+ request = filterableOrRequest;
43
+ }
44
+ if (!filterable) throw new FilterableNotConfigured(this.model.name);
45
+ const FilterableClass = filterable;
46
+ (filterable instanceof BaseFilterable ? filterable : new FilterableClass(request)).apply(this, request);
47
+ return this;
48
+ };
49
+ builder.macro("filter", filterMacro);
50
+ };
51
+ export { extendLucidWithFilterable };
@@ -0,0 +1,38 @@
1
+ import { AllowedFilter } from "../allowed_filter.js";
2
+ import { InvalidFilterQuery } from "../exceptions/invalid_filter_query.js";
3
+ // src/bindings/filters_query.ts
4
+ const extendModelQueryBuilderWithFiltersQuery = function(builder, config) {
5
+ const ensureAllFiltersExist = (self, requestedFilters) => {
6
+ if (config.disableInvalidFilterQueryException) return;
7
+ const filterNames = Object.keys(requestedFilters);
8
+ if (filterNames.length === 0) return;
9
+ const allowedFilterNames = self._allowedFilters.map((allowedFilter) => allowedFilter.getName());
10
+ const allowedSet = new Set(allowedFilterNames);
11
+ const diff = filterNames.filter((filterName) => !allowedSet.has(filterName));
12
+ if (diff.length > 0) throw InvalidFilterQuery.filtersNotAllowed(diff, allowedFilterNames);
13
+ };
14
+ const addRequestedFiltersToQuery = (query, requestedFilters) => {
15
+ for (const allowedFilter of query._allowedFilters) {
16
+ const name = allowedFilter.getName();
17
+ if (Object.hasOwn(requestedFilters, name)) {
18
+ allowedFilter.filter(query, requestedFilters[name]);
19
+ continue;
20
+ }
21
+ if (allowedFilter.hasDefault()) allowedFilter.filter(query, allowedFilter.getDefault());
22
+ }
23
+ };
24
+ builder.macro("allowedFilters", function(...filters) {
25
+ const arrayFilters = Array.isArray(filters.at(0)) ? filters.at(0) : filters;
26
+ this._allowedFilters = arrayFilters.map((filter) => {
27
+ if (filter instanceof AllowedFilter) return filter;
28
+ return AllowedFilter.partial(filter);
29
+ });
30
+ const filterRules = AllowedFilter.extractRules(this._allowedFilters);
31
+ if (Object.keys(filterRules).length > 0) this.$apiQueryBuilderRequest.setFilterRules(filterRules);
32
+ const requestedFilters = this.$apiQueryBuilderRequest.filters();
33
+ ensureAllFiltersExist(this, requestedFilters);
34
+ addRequestedFiltersToQuery(this, requestedFilters);
35
+ return this;
36
+ });
37
+ };
38
+ export { extendModelQueryBuilderWithFiltersQuery };
@@ -0,0 +1,50 @@
1
+ import { AllowedInclude } from "../allowed_include.js";
2
+ import { InvalidIncludeQuery } from "../exceptions/invalid_include_query.js";
3
+ // src/bindings/includes_query.ts
4
+ const extendModelQueryBuilderWithIncludesQuery = function(builder, config) {
5
+ const ensureAllIncludesExist = (self, requestedIncludes) => {
6
+ if (config.disableInvalidIncludesQueryException || requestedIncludes.length === 0) return;
7
+ const allowedIncludeNames = self._allowedIncludes.map((allowedInclude) => allowedInclude.getName());
8
+ const allowedSet = new Set(allowedIncludeNames);
9
+ const diff = requestedIncludes.filter((include) => !allowedSet.has(include));
10
+ if (diff.length > 0) throw InvalidIncludeQuery.includesNotAllowed(diff, allowedIncludeNames);
11
+ };
12
+ const findInclude = (self, include) => {
13
+ return self._allowedIncludes.find((included) => included.isForInclude(include));
14
+ };
15
+ const addIncludesToQuery = (self, includes) => {
16
+ for (const include of includes) findInclude(self, include)?.include(self);
17
+ };
18
+ builder.macro("allowedIncludes", function(...includes) {
19
+ const arrayIncludes = Array.isArray(includes.at(0)) ? includes.at(0) : includes;
20
+ const reduced = [];
21
+ for (const include of arrayIncludes) {
22
+ if (typeof include === "string" && include.length === 0) continue;
23
+ if (Array.isArray(include)) {
24
+ reduced.push(...include);
25
+ continue;
26
+ }
27
+ if (include instanceof AllowedInclude) {
28
+ reduced.push(include);
29
+ continue;
30
+ }
31
+ if (include.endsWith(config.countSuffix)) {
32
+ reduced.push(...AllowedInclude.count(include));
33
+ continue;
34
+ }
35
+ reduced.push(...AllowedInclude.relationship(include));
36
+ }
37
+ const seen = /* @__PURE__ */ new Set();
38
+ this._allowedIncludes = reduced.filter((allowedInclude) => {
39
+ const name = allowedInclude.getName();
40
+ if (seen.has(name)) return false;
41
+ seen.add(name);
42
+ return true;
43
+ });
44
+ const requestedIncludes = this.$apiQueryBuilderRequest.includes();
45
+ ensureAllIncludesExist(this, requestedIncludes);
46
+ addIncludesToQuery(this, requestedIncludes);
47
+ return this;
48
+ });
49
+ };
50
+ export { extendModelQueryBuilderWithIncludesQuery };
@@ -0,0 +1,42 @@
1
+ import { AllowedSort } from "../allowed_sort.js";
2
+ import { InvalidSortQuery } from "../exceptions/invalid_sort_query.js";
3
+ // src/bindings/sorts_query.ts
4
+ const DESCENDING_ORDER_PREFIX = /^-+/;
5
+ const extendModelQueryBuilderWithSortsQuery = function(builder, config) {
6
+ const ensureAllSortsExist = (self, requestedSorts) => {
7
+ if (config.disableInvalidSortQueryException || requestedSorts.length === 0) return;
8
+ const requestedSortNames = requestedSorts.map((sort) => sort.replace(DESCENDING_ORDER_PREFIX, ""));
9
+ const allowedSortNames = self._allowedSorts.map((sort) => sort.getName());
10
+ const allowedSet = new Set(allowedSortNames);
11
+ const unknownSort = requestedSortNames.filter((sort) => !allowedSet.has(sort));
12
+ if (unknownSort.length > 0) throw InvalidSortQuery.sortsNotAllowed(unknownSort, allowedSortNames);
13
+ };
14
+ const findSort = (self, property) => {
15
+ return self._allowedSorts.find((sort) => sort.isSort(property));
16
+ };
17
+ const addRequestedSortsToQuery = (self, requestedSorts) => {
18
+ for (const property of requestedSorts) {
19
+ const isDescending = property.startsWith("-");
20
+ const key = property.replace(DESCENDING_ORDER_PREFIX, "");
21
+ findSort(self, key)?.sort(self, isDescending);
22
+ }
23
+ };
24
+ builder.macro("allowedSorts", function(...sorts) {
25
+ const arraySorts = Array.isArray(sorts.at(0)) ? sorts.at(0) : sorts;
26
+ this._allowedSorts = arraySorts.map((sort) => {
27
+ if (sort instanceof AllowedSort) return sort;
28
+ return AllowedSort.field(sort.replace(DESCENDING_ORDER_PREFIX, ""));
29
+ });
30
+ const requestedSorts = this.$apiQueryBuilderRequest.sorts();
31
+ ensureAllSortsExist(this, requestedSorts);
32
+ addRequestedSortsToQuery(this, requestedSorts);
33
+ return this;
34
+ });
35
+ builder.macro("defaultSort", function(...sorts) {
36
+ if (this.$apiQueryBuilderRequest.sorts().length > 0) return this;
37
+ const arraySorts = Array.isArray(sorts.at(0)) ? sorts.at(0) : sorts;
38
+ for (const rawSort of arraySorts) (rawSort instanceof AllowedSort ? rawSort : AllowedSort.field(rawSort)).sort(this);
39
+ return this;
40
+ });
41
+ };
42
+ export { extendModelQueryBuilderWithSortsQuery };
@@ -0,0 +1,27 @@
1
+ // src/bindings/validation_query.ts
2
+ let isPatched = false;
3
+ const extendModelQueryBuilderWithValidation = function(builder) {
4
+ builder.macro("validate", async function() {
5
+ return this.$apiQueryBuilderRequest.validate();
6
+ });
7
+ if (isPatched) return;
8
+ const proto = builder.prototype;
9
+ const originalExecQuery = proto.execQuery;
10
+ proto.execQuery = async function(...args) {
11
+ const query = this;
12
+ if (query.$queryBuilderRequest) {
13
+ const apiRequest = query.$queryBuilderRequest;
14
+ if (apiRequest.shouldAutoValidate() && apiRequest.hasValidation()) await apiRequest.validate();
15
+ }
16
+ return originalExecQuery.apply(this, args);
17
+ };
18
+ const originalClone = proto.clone;
19
+ proto.clone = function() {
20
+ const cloned = originalClone.call(this);
21
+ const query = this;
22
+ if (query.$queryBuilderRequest) cloned.$queryBuilderRequest = query.$queryBuilderRequest;
23
+ return cloned;
24
+ };
25
+ isPatched = true;
26
+ };
27
+ export { extendModelQueryBuilderWithValidation };
@@ -0,0 +1,20 @@
1
+ import { ApiQueryBuilderRequest } from "../api_query_builder_request.js";
2
+ import { BaseFilterable } from "../base_filterable.js";
3
+ import { FilterableConstructor, FilterableTarget } from "../../types.js";
4
+ import { HttpRequest } from "@adonisjs/core/http";
5
+ import { BaseModel } from "@adonisjs/lucid/orm";
6
+ import { LucidModel, ModelQueryBuilderContract } from "@adonisjs/lucid/types/model";
7
+ import { NormalizeConstructor } from "@adonisjs/core/types/helpers";
8
+ export type FilterableModel<T extends NormalizeConstructor<typeof BaseModel>> = (new (...args: unknown[]) => InstanceType<T>) & { [K in keyof T]: T[K]; } & {
9
+ $filterable?: FilterableTarget;
10
+ $filter<Model extends LucidModel>(this: Model, request?: HttpRequest | ApiQueryBuilderRequest): ModelQueryBuilderContract<Model, InstanceType<Model>>;
11
+ $filter<Model extends LucidModel>(this: Model, filterable: FilterableConstructor<Model> | BaseFilterable<Model>, request?: HttpRequest | ApiQueryBuilderRequest): ModelQueryBuilderContract<Model, InstanceType<Model>>;
12
+ };
13
+ /**
14
+ * Decorator to assign a filterable class to a Lucid Model
15
+ */
16
+ export declare function filterable<Model extends LucidModel = LucidModel>(target: FilterableTarget<Model>): (model: unknown) => void;
17
+ /**
18
+ * Mixin that adds filterable capabilities to a Lucid Model
19
+ */
20
+ export declare function Filterable<T extends NormalizeConstructor<typeof BaseModel>, Model extends LucidModel = LucidModel>(filterableTarget?: FilterableTarget<Model>): (superclass: T) => FilterableModel<T>;