@eienjs/adonisjs-api-query 2.0.0 → 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 (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 +9 -3
  5. package/build/index.js +8 -2
  6. package/build/providers/api_query_provider.d.ts +20 -13
  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 +32 -10
  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 +2 -6
  65. package/build/src/filters/filters_operator.js +9 -10
  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 +26 -24
@@ -1,22 +1,45 @@
1
- import { Collection } from "collect.js";
1
+ import { QueryValidator, ValidationRules } from "../types.js";
2
2
  import { HttpRequest } from "@adonisjs/core/http";
3
-
4
- //#region src/api_query_builder_request.d.ts
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
- includes(): Collection<string>;
14
- appends(): Collection<unknown>;
15
- fields(): Collection<unknown>;
16
- sorts(): Collection<string>;
17
- filters(): Collection<unknown>;
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 { Collection } from "collect.js";
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
- //#region src/api_query_builder_request.ts
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
- if (!request) request = HttpContext.getOrFail().request;
15
- this._request = 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;
16
79
  }
17
80
  includes() {
18
- const includeParameterName = app.config.get("apiquery.parameters.include", "include");
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
- const appendParameterName = app.config.get("apiquery.parameters.append", "append");
25
- let appendParts = this.getRequestData(appendParameterName, {});
26
- if (typeof appendParts === "string") appendParts = appendParts.split(ApiQueryBuilderRequest.getAppendsArrayValueDelimiter());
27
- return new Collection(appendParts).filter();
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 = new Collection(typeof fieldsData === "string" ? fieldsData.split(ApiQueryBuilderRequest.getFieldsArrayValueDelimiter()) : fieldsData);
33
- if (fieldsPerTable.isEmpty()) return new Collection();
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.each((tableFields, model) => {
36
- if (!model || typeof model === "number") model = typeof tableFields === "string" && tableFields.includes(".") ? strBeforeLast(tableFields, ".") : "_";
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
- if (typeof tableFields === "string") tableFields = tableFields.split(ApiQueryBuilderRequest.getFieldsArrayValueDelimiter()).map((field) => {
39
- return strAfterLast(field, ".");
40
- });
41
- fields[model] = [...fields[model], ...tableFields];
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 (typeof filterParts === "string") return new Collection();
55
- return new Collection(filterParts).map((value) => {
56
- return this.getFilterValue(value);
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 || typeof value === "object" && Object.keys(value).length === 0) return typeof value === "object" ? [] : value;
62
- if (Array.isArray(value) || typeof value === "object") return new Collection(value).map((valueValue) => {
63
- return this.getFilterValue(valueValue);
64
- }).all();
65
- if (typeof value === "string" && value.includes(ApiQueryBuilderRequest.getFilterArrayValueDelimiter())) return value.split(ApiQueryBuilderRequest.getFilterArrayValueDelimiter());
66
- if (value === "true") return true;
67
- if (value === "false") return false;
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 { 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 };
@@ -1,13 +1,13 @@
1
1
  import { ApiQueryBuilderRequest } from "../api_query_builder_request.js";
2
- //#region src/bindings/api_query_request.ts
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
- return this.$queryBuilderRequest ?? new ApiQueryBuilderRequest();
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
- import collect$1 from "collect.js";
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 = self.$apiQueryBuilderRequest.filters().keys();
9
- const allowedFilterNames = self._allowedFilters.map((allowedFilter) => {
10
- return allowedFilter.getName();
11
- });
12
- const diff = filterNames.diff(allowedFilterNames);
13
- if (diff.isNotEmpty()) throw InvalidFilterQuery.filtersNotAllowed(diff.all(), allowedFilterNames.all());
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.each((allowedFilter) => {
20
- if (isFilterRequested(query, allowedFilter)) {
21
- const value = query.$apiQueryBuilderRequest.filters().get(allowedFilter.getName());
22
- allowedFilter.filter(query, value);
23
- return;
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 = collect$1(arrayFilters).map((filter) => {
26
+ this._allowedFilters = arrayFilters.map((filter) => {
31
27
  if (filter instanceof AllowedFilter) return filter;
32
28
  return AllowedFilter.partial(filter);
33
29
  });
34
- ensureAllFiltersExist(this);
35
- addRequestedFiltersToQuery(this);
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 };