@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.
- package/README.md +138 -0
- package/build/configure.d.ts +1 -5
- package/build/configure.js +1 -2
- package/build/index.d.ts +12 -6
- package/build/index.js +26 -20
- package/build/providers/api_query_provider.d.ts +19 -12
- package/build/providers/api_query_provider.js +7 -3
- package/build/src/allowed_filter.d.ts +14 -10
- package/build/src/allowed_filter.js +34 -12
- package/build/src/allowed_include.d.ts +6 -11
- package/build/src/allowed_include.js +14 -15
- package/build/src/allowed_sort.d.ts +2 -6
- package/build/src/allowed_sort.js +1 -2
- package/build/src/api_query_builder_request.d.ts +33 -12
- package/build/src/api_query_builder_request.js +119 -41
- package/build/src/base_filterable.d.ts +45 -0
- package/build/src/base_filterable.js +172 -0
- package/build/src/bindings/api_query_request.js +3 -3
- package/build/src/bindings/filterable_query.js +51 -0
- package/build/src/bindings/filters_query.js +21 -23
- package/build/src/bindings/includes_query.js +32 -33
- package/build/src/bindings/sorts_query.js +20 -23
- package/build/src/bindings/validation_query.js +27 -0
- package/build/src/decorators/filterable.d.ts +20 -0
- package/build/src/decorators/filterable.js +24 -0
- package/build/src/define_config.d.ts +1 -5
- package/build/src/define_config.js +3 -3
- package/build/src/enums/filter_operator.d.ts +2 -5
- package/build/src/enums/filter_operator.js +1 -2
- package/build/src/enums/sort_direction.d.ts +2 -5
- package/build/src/enums/sort_direction.js +1 -2
- package/build/src/exceptions/allowed_fields_must_be_called_before_allowed_includes.d.ts +4 -8
- package/build/src/exceptions/allowed_fields_must_be_called_before_allowed_includes.js +3 -4
- package/build/src/exceptions/filterable_not_configured.d.ts +5 -0
- package/build/src/exceptions/filterable_not_configured.js +12 -0
- package/build/src/exceptions/invalid_append_query.d.ts +4 -6
- package/build/src/exceptions/invalid_append_query.js +5 -2
- package/build/src/exceptions/invalid_direction.d.ts +2 -6
- package/build/src/exceptions/invalid_direction.js +1 -2
- package/build/src/exceptions/invalid_field_query.d.ts +7 -0
- package/build/src/exceptions/invalid_field_query.js +17 -0
- package/build/src/exceptions/invalid_filter_query.d.ts +2 -6
- package/build/src/exceptions/invalid_filter_query.js +1 -2
- package/build/src/exceptions/invalid_filter_value.d.ts +2 -6
- package/build/src/exceptions/invalid_filter_value.js +1 -2
- package/build/src/exceptions/invalid_include_query.d.ts +2 -6
- package/build/src/exceptions/invalid_include_query.js +1 -2
- package/build/src/exceptions/invalid_query.d.ts +2 -6
- package/build/src/exceptions/invalid_query.js +1 -2
- package/build/src/exceptions/invalid_sort_query.d.ts +2 -6
- package/build/src/exceptions/invalid_sort_query.js +1 -2
- package/build/src/exceptions/unknown_included_fields_query.d.ts +2 -6
- package/build/src/exceptions/unknown_included_fields_query.js +1 -2
- package/build/src/exceptions/vine_not_installed.d.ts +5 -0
- package/build/src/exceptions/vine_not_installed.js +12 -0
- package/build/src/filters/filters_begin_with_strict.d.ts +2 -6
- package/build/src/filters/filters_begin_with_strict.js +2 -3
- package/build/src/filters/filters_callback.d.ts +2 -6
- package/build/src/filters/filters_callback.js +1 -2
- package/build/src/filters/filters_ends_with_strict.d.ts +2 -6
- package/build/src/filters/filters_ends_with_strict.js +2 -3
- package/build/src/filters/filters_exact.d.ts +2 -6
- package/build/src/filters/filters_exact.js +15 -12
- package/build/src/filters/filters_operator.d.ts +3 -7
- package/build/src/filters/filters_operator.js +10 -11
- package/build/src/filters/filters_partial.d.ts +2 -8
- package/build/src/filters/filters_partial.js +7 -9
- package/build/src/filters/filters_scope.d.ts +12 -0
- package/build/src/filters/filters_scope.js +98 -0
- package/build/src/filters/filters_trashed.d.ts +2 -6
- package/build/src/filters/filters_trashed.js +1 -2
- package/build/src/includes/included_callback.d.ts +2 -6
- package/build/src/includes/included_callback.js +1 -2
- package/build/src/includes/included_count.d.ts +2 -6
- package/build/src/includes/included_count.js +2 -2
- package/build/src/includes/included_relationship.d.ts +4 -8
- package/build/src/includes/included_relationship.js +19 -22
- package/build/src/sorts/sorts_callback.d.ts +2 -6
- package/build/src/sorts/sorts_callback.js +1 -2
- package/build/src/sorts/sorts_field.d.ts +2 -6
- package/build/src/sorts/sorts_field.js +1 -2
- package/build/src/utils/helpers.js +2 -14
- package/build/src/validation/schema_builder.d.ts +3 -0
- package/build/src/validation/schema_builder.js +38 -0
- package/build/src/validation/vine_resolver.js +13 -0
- package/build/stubs/main.d.ts +1 -4
- package/build/stubs/main.js +1 -2
- package/build/types.d.ts +44 -19
- package/package.json +31 -24
|
@@ -1,22 +1,45 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { QueryValidator, ValidationRules } from "../types.js";
|
|
2
2
|
import { HttpRequest } from "@adonisjs/core/http";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
declare class ApiQueryBuilderRequest {
|
|
3
|
+
import { SchemaTypes } from "@vinejs/vine/types";
|
|
4
|
+
export declare class ApiQueryBuilderRequest {
|
|
6
5
|
protected static includesArrayValueDelimiter: string;
|
|
7
6
|
protected static appendsArrayValueDelimiter: string;
|
|
8
7
|
protected static fieldsArrayValueDelimiter: string;
|
|
9
8
|
protected static sortsArrayValueDelimiter: string;
|
|
10
9
|
protected static filterArrayValueDelimiter: string;
|
|
11
10
|
private readonly _request;
|
|
11
|
+
private _rules?;
|
|
12
|
+
private _filterRules;
|
|
13
|
+
private _autoValidate?;
|
|
14
|
+
private _validation?;
|
|
15
|
+
private _validatedData?;
|
|
12
16
|
constructor(request?: HttpRequest);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
getRequest(): HttpRequest;
|
|
18
|
+
all(): Record<string, unknown>;
|
|
19
|
+
setRules(rules: ValidationRules): this;
|
|
20
|
+
getRules(): ValidationRules | undefined;
|
|
21
|
+
setFilterRules(rules: Record<string, SchemaTypes>): this;
|
|
22
|
+
getFilterRules(): Record<string, SchemaTypes>;
|
|
23
|
+
setAutoValidate(autoValidate: boolean): this;
|
|
24
|
+
shouldAutoValidate(): boolean;
|
|
25
|
+
hasValidation(): boolean;
|
|
26
|
+
toValidator(): Promise<QueryValidator>;
|
|
27
|
+
/**
|
|
28
|
+
* Validates the request once (the result is memoized) and returns the validated data.
|
|
29
|
+
* Returns the raw request data when no rules are configured.
|
|
30
|
+
*/
|
|
31
|
+
validate<T = Record<string, unknown>>(rules?: ValidationRules): Promise<T>;
|
|
32
|
+
getValidatedData(): Record<string, unknown> | undefined;
|
|
33
|
+
isValidated(): boolean;
|
|
34
|
+
includes(): string[];
|
|
35
|
+
appends(): string[];
|
|
36
|
+
sorts(): string[];
|
|
37
|
+
fields(): Record<string, string[]>;
|
|
38
|
+
filters(): Record<string, unknown>;
|
|
39
|
+
protected getArrayParameterData(configKey: string, defaultParam: string, delimiter: string): string[];
|
|
18
40
|
protected getFilterValue(value: unknown): unknown;
|
|
19
41
|
protected getRequestData<T = unknown>(key: string, defaultValue?: T): T;
|
|
42
|
+
protected runValidation(): Promise<Record<string, unknown>>;
|
|
20
43
|
static setArrayValueDelimiter(delimiter: string): void;
|
|
21
44
|
static setIncludesArrayValueDelimiter(includesArrayValueDelimiter: string): void;
|
|
22
45
|
static setAppendsArrayValueDelimiter(appendsArrayValueDelimiter: string): void;
|
|
@@ -30,6 +53,4 @@ declare class ApiQueryBuilderRequest {
|
|
|
30
53
|
static getFilterArrayValueDelimiter(): string;
|
|
31
54
|
static resetDelimiters(): void;
|
|
32
55
|
static fromRequest(request: HttpRequest): ApiQueryBuilderRequest;
|
|
33
|
-
}
|
|
34
|
-
//#endregion
|
|
35
|
-
export { ApiQueryBuilderRequest };
|
|
56
|
+
}
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { strAfterLast, strBeforeLast } from "./utils/helpers.js";
|
|
2
|
-
import {
|
|
2
|
+
import { buildQueryValidator } from "./validation/schema_builder.js";
|
|
3
3
|
import { HttpContext } from "@adonisjs/core/http";
|
|
4
4
|
import app from "@adonisjs/core/services/app";
|
|
5
|
-
|
|
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
|
+
};
|
|
6
16
|
var ApiQueryBuilderRequest = class ApiQueryBuilderRequest {
|
|
7
17
|
static includesArrayValueDelimiter = ",";
|
|
8
18
|
static appendsArrayValueDelimiter = ",";
|
|
@@ -10,66 +20,135 @@ var ApiQueryBuilderRequest = class ApiQueryBuilderRequest {
|
|
|
10
20
|
static sortsArrayValueDelimiter = ",";
|
|
11
21
|
static filterArrayValueDelimiter = ",";
|
|
12
22
|
_request;
|
|
23
|
+
_rules;
|
|
24
|
+
_filterRules = {};
|
|
25
|
+
_autoValidate;
|
|
26
|
+
_validation;
|
|
27
|
+
_validatedData;
|
|
13
28
|
constructor(request) {
|
|
14
|
-
|
|
15
|
-
|
|
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;
|
|
16
79
|
}
|
|
17
80
|
includes() {
|
|
18
|
-
|
|
19
|
-
let includeParts = this.getRequestData(includeParameterName, []);
|
|
20
|
-
if (typeof includeParts === "string") includeParts = includeParts.split(ApiQueryBuilderRequest.getIncludesArrayValueDelimiter());
|
|
21
|
-
return new Collection(includeParts).filter();
|
|
81
|
+
return this.getArrayParameterData("apiquery.parameters.include", "include", ApiQueryBuilderRequest.getIncludesArrayValueDelimiter());
|
|
22
82
|
}
|
|
23
83
|
appends() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return
|
|
84
|
+
return this.getArrayParameterData("apiquery.parameters.append", "append", ApiQueryBuilderRequest.getAppendsArrayValueDelimiter());
|
|
85
|
+
}
|
|
86
|
+
sorts() {
|
|
87
|
+
return this.getArrayParameterData("apiquery.parameters.sort", "sort", ApiQueryBuilderRequest.getSortsArrayValueDelimiter());
|
|
28
88
|
}
|
|
29
89
|
fields() {
|
|
30
90
|
const fieldsParameterName = app.config.get("apiquery.parameters.fields", "fields");
|
|
91
|
+
const fieldsDelimiter = ApiQueryBuilderRequest.getFieldsArrayValueDelimiter();
|
|
31
92
|
const fieldsData = this.getRequestData(fieldsParameterName, {});
|
|
32
|
-
const fieldsPerTable =
|
|
33
|
-
if (fieldsPerTable.
|
|
93
|
+
const fieldsPerTable = typeof fieldsData === "string" ? fieldsData.split(fieldsDelimiter) : fieldsData;
|
|
94
|
+
if ((Array.isArray(fieldsPerTable) ? fieldsPerTable : Object.keys(fieldsPerTable)).length === 0) return {};
|
|
34
95
|
const fields = {};
|
|
35
|
-
fieldsPerTable.
|
|
36
|
-
|
|
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;
|
|
37
100
|
if (!Object.hasOwn(fields, model)) fields[model] = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
return new Collection(fields);
|
|
44
|
-
}
|
|
45
|
-
sorts() {
|
|
46
|
-
const sortParameterName = app.config.get("apiquery.parameters.sort", "sort");
|
|
47
|
-
let sortParts = this.getRequestData(sortParameterName, []);
|
|
48
|
-
if (typeof sortParts === "string") sortParts = sortParts.split(ApiQueryBuilderRequest.getSortsArrayValueDelimiter());
|
|
49
|
-
return new Collection(sortParts).filter();
|
|
101
|
+
const currentFields = typeof tableFields === "string" ? tableFields.split(fieldsDelimiter).map((field) => strAfterLast(field, ".")) : tableFields;
|
|
102
|
+
fields[model].push(...currentFields);
|
|
103
|
+
}
|
|
104
|
+
return fields;
|
|
50
105
|
}
|
|
51
106
|
filters() {
|
|
52
107
|
const filterParameterName = app.config.get("apiquery.parameters.filter", "filter");
|
|
53
|
-
const filterParts = this.getRequestData(filterParameterName,
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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);
|
|
58
121
|
}
|
|
59
122
|
getFilterValue(value) {
|
|
60
123
|
if (value === void 0 || value === null) return null;
|
|
61
|
-
if (value === "" || Array.isArray(value) && value.length === 0 ||
|
|
62
|
-
if (Array.isArray(value)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
}
|
|
68
140
|
return value;
|
|
69
141
|
}
|
|
70
142
|
getRequestData(key, defaultValue) {
|
|
71
143
|
return this._request.input(key, defaultValue);
|
|
72
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
|
+
}
|
|
73
152
|
static setArrayValueDelimiter(delimiter) {
|
|
74
153
|
this.includesArrayValueDelimiter = delimiter;
|
|
75
154
|
this.appendsArrayValueDelimiter = delimiter;
|
|
@@ -118,5 +197,4 @@ var ApiQueryBuilderRequest = class ApiQueryBuilderRequest {
|
|
|
118
197
|
return new ApiQueryBuilderRequest(request);
|
|
119
198
|
}
|
|
120
199
|
};
|
|
121
|
-
//#endregion
|
|
122
200
|
export { ApiQueryBuilderRequest };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { FilterItem, IncludeItem, QueryValidator, SortItem, ValidationRules } from "../types.js";
|
|
2
|
+
import { ApiQueryBuilderRequest } from "./api_query_builder_request.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 { AllowedFilter } from "./allowed_filter.js";
|
|
2
|
+
import { AllowedInclude } from "./allowed_include.js";
|
|
3
|
+
import { AllowedSort } from "./allowed_sort.js";
|
|
4
|
+
import { ApiQueryBuilderRequest } from "./api_query_builder_request.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 };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ApiQueryBuilderRequest } from "../api_query_builder_request.js";
|
|
2
|
-
|
|
2
|
+
// src/bindings/api_query_request.ts
|
|
3
3
|
const extendModelQueryBuilderWithRequest = function(builder) {
|
|
4
4
|
builder.macro("withRequest", function(request) {
|
|
5
5
|
this.$queryBuilderRequest = ApiQueryBuilderRequest.fromRequest(request);
|
|
6
6
|
return this;
|
|
7
7
|
});
|
|
8
8
|
builder.getter("$apiQueryBuilderRequest", function() {
|
|
9
|
-
|
|
9
|
+
this.$queryBuilderRequest ??= new ApiQueryBuilderRequest();
|
|
10
|
+
return this.$queryBuilderRequest;
|
|
10
11
|
});
|
|
11
12
|
};
|
|
12
|
-
//#endregion
|
|
13
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 };
|
|
@@ -1,40 +1,38 @@
|
|
|
1
1
|
import { AllowedFilter } from "../allowed_filter.js";
|
|
2
2
|
import { InvalidFilterQuery } from "../exceptions/invalid_filter_query.js";
|
|
3
|
-
|
|
4
|
-
//#region src/bindings/filters_query.ts
|
|
3
|
+
// src/bindings/filters_query.ts
|
|
5
4
|
const extendModelQueryBuilderWithFiltersQuery = function(builder, config) {
|
|
6
|
-
const ensureAllFiltersExist = (self) => {
|
|
5
|
+
const ensureAllFiltersExist = (self, requestedFilters) => {
|
|
7
6
|
if (config.disableInvalidFilterQueryException) return;
|
|
8
|
-
const filterNames =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const diff = filterNames.
|
|
13
|
-
if (diff.
|
|
14
|
-
};
|
|
15
|
-
const isFilterRequested = (self, allowedFilter) => {
|
|
16
|
-
return self.$apiQueryBuilderRequest.filters().has(allowedFilter.getName());
|
|
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);
|
|
17
13
|
};
|
|
18
|
-
const addRequestedFiltersToQuery = (query) => {
|
|
19
|
-
query._allowedFilters
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
allowedFilter.filter(query,
|
|
23
|
-
|
|
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;
|
|
24
20
|
}
|
|
25
21
|
if (allowedFilter.hasDefault()) allowedFilter.filter(query, allowedFilter.getDefault());
|
|
26
|
-
}
|
|
22
|
+
}
|
|
27
23
|
};
|
|
28
24
|
builder.macro("allowedFilters", function(...filters) {
|
|
29
25
|
const arrayFilters = Array.isArray(filters.at(0)) ? filters.at(0) : filters;
|
|
30
|
-
this._allowedFilters =
|
|
26
|
+
this._allowedFilters = arrayFilters.map((filter) => {
|
|
31
27
|
if (filter instanceof AllowedFilter) return filter;
|
|
32
28
|
return AllowedFilter.partial(filter);
|
|
33
29
|
});
|
|
34
|
-
|
|
35
|
-
|
|
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);
|
|
36
35
|
return this;
|
|
37
36
|
});
|
|
38
37
|
};
|
|
39
|
-
//#endregion
|
|
40
38
|
export { extendModelQueryBuilderWithFiltersQuery };
|