feathers-utils 2.0.0-0 → 2.0.0-2

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 (97) hide show
  1. package/README.md +16 -9
  2. package/dist/esm/filters/array.d.ts +2 -0
  3. package/dist/esm/filters/array.js +10 -0
  4. package/dist/esm/hooks/checkMulti.d.ts +2 -2
  5. package/dist/esm/hooks/createRelated.d.ts +3 -0
  6. package/dist/esm/hooks/createRelated.js +26 -0
  7. package/dist/esm/hooks/onDelete.d.ts +3 -0
  8. package/dist/esm/hooks/onDelete.js +40 -0
  9. package/dist/esm/hooks/removeRelated.d.ts +3 -0
  10. package/dist/esm/hooks/removeRelated.js +30 -0
  11. package/dist/esm/hooks/runPerItem.d.ts +2 -3
  12. package/dist/esm/hooks/runPerItem.js +6 -6
  13. package/dist/esm/hooks/setData.d.ts +2 -3
  14. package/dist/esm/hooks/setData.js +13 -8
  15. package/dist/esm/index.d.ts +8 -2
  16. package/dist/esm/index.js +8 -1
  17. package/dist/esm/mixins/debounce-mixin/DebouncedStore.d.ts +1 -1
  18. package/dist/esm/mixins/debounce-mixin/DebouncedStore.js +1 -1
  19. package/dist/esm/mixins/debounce-mixin/index.d.ts +1 -1
  20. package/dist/esm/types.d.ts +35 -16
  21. package/dist/esm/utils/filterQuery.d.ts +2 -2
  22. package/dist/esm/utils/filterQuery.js +9 -7
  23. package/dist/esm/utils/getItemsIsArray.d.ts +3 -0
  24. package/dist/esm/utils/getItemsIsArray.js +18 -0
  25. package/dist/esm/utils/getPaginate.d.ts +1 -1
  26. package/dist/esm/utils/isPaginated.d.ts +1 -1
  27. package/dist/esm/utils/markHookForSkip.d.ts +2 -2
  28. package/dist/esm/utils/mergeQuery/index.js +18 -7
  29. package/dist/esm/utils/pushSet.d.ts +1 -1
  30. package/dist/esm/utils/pushSet.js +3 -3
  31. package/dist/esm/utils/setResultEmpty.d.ts +1 -1
  32. package/dist/esm/utils/validateQueryProperty.d.ts +2 -0
  33. package/dist/esm/utils/validateQueryProperty.js +20 -0
  34. package/dist/filters/array.d.ts +2 -0
  35. package/dist/filters/array.js +14 -0
  36. package/dist/hooks/checkMulti.d.ts +2 -2
  37. package/dist/hooks/createRelated.d.ts +3 -0
  38. package/dist/hooks/createRelated.js +39 -0
  39. package/dist/hooks/onDelete.d.ts +3 -0
  40. package/dist/hooks/onDelete.js +53 -0
  41. package/dist/hooks/removeRelated.d.ts +3 -0
  42. package/dist/hooks/removeRelated.js +43 -0
  43. package/dist/hooks/runPerItem.d.ts +2 -3
  44. package/dist/hooks/runPerItem.js +6 -6
  45. package/dist/hooks/setData.d.ts +2 -3
  46. package/dist/hooks/setData.js +17 -12
  47. package/dist/index.d.ts +8 -2
  48. package/dist/index.js +21 -4
  49. package/dist/mixins/debounce-mixin/DebouncedStore.d.ts +1 -1
  50. package/dist/mixins/debounce-mixin/DebouncedStore.js +2 -2
  51. package/dist/mixins/debounce-mixin/index.d.ts +1 -1
  52. package/dist/types.d.ts +35 -16
  53. package/dist/utils/filterQuery.d.ts +2 -2
  54. package/dist/utils/filterQuery.js +19 -6
  55. package/dist/utils/getItemsIsArray.d.ts +3 -0
  56. package/dist/utils/getItemsIsArray.js +22 -0
  57. package/dist/utils/getPaginate.d.ts +1 -1
  58. package/dist/utils/isPaginated.d.ts +1 -1
  59. package/dist/utils/markHookForSkip.d.ts +2 -2
  60. package/dist/utils/mergeQuery/index.js +53 -42
  61. package/dist/utils/pushSet.d.ts +1 -1
  62. package/dist/utils/pushSet.js +6 -6
  63. package/dist/utils/setResultEmpty.d.ts +1 -1
  64. package/dist/utils/validateQueryProperty.d.ts +2 -0
  65. package/dist/utils/validateQueryProperty.js +22 -0
  66. package/package.json +33 -19
  67. package/src/filters/array.ts +14 -0
  68. package/src/hooks/checkMulti.ts +3 -1
  69. package/src/hooks/createRelated.ts +45 -0
  70. package/src/hooks/onDelete.ts +56 -0
  71. package/src/hooks/removeRelated.ts +42 -0
  72. package/src/hooks/runPerItem.ts +9 -12
  73. package/src/hooks/setData.ts +17 -12
  74. package/src/index.ts +11 -1
  75. package/src/mixins/debounce-mixin/DebouncedStore.ts +49 -49
  76. package/src/mixins/debounce-mixin/index.ts +6 -3
  77. package/src/types.ts +46 -16
  78. package/src/utils/filterQuery.ts +15 -14
  79. package/src/utils/getItemsIsArray.ts +23 -0
  80. package/src/utils/getPaginate.ts +1 -2
  81. package/src/utils/isMulti.ts +3 -1
  82. package/src/utils/isPaginated.ts +1 -1
  83. package/src/utils/markHookForSkip.ts +2 -2
  84. package/src/utils/mergeQuery/index.ts +20 -8
  85. package/src/utils/pushSet.ts +3 -3
  86. package/src/utils/setResultEmpty.ts +1 -1
  87. package/src/utils/shouldSkip.ts +4 -1
  88. package/src/utils/validateQueryProperty.ts +27 -0
  89. package/.eslintignore +0 -3
  90. package/.eslintrc.js +0 -44
  91. package/.gitlab-ci.yml +0 -11
  92. package/.mocharc.js +0 -11
  93. package/.nycrc.json +0 -22
  94. package/index.js +0 -9
  95. package/tsconfig-esm.json +0 -9
  96. package/tsconfig.json +0 -16
  97. package/tsconfig.test.json +0 -13
@@ -1,17 +1,17 @@
1
- import _get from "lodash/get";
2
- import _set from "lodash/set";
3
- import _has from "lodash/has";
4
-
5
- import { getItems } from "feathers-hooks-common";
1
+ import _get from "lodash/get.js";
2
+ import _set from "lodash/set.js";
3
+ import _has from "lodash/has.js";
6
4
 
7
5
  import { Forbidden } from "@feathersjs/errors";
6
+ import { getItemsIsArray } from "../utils/getItemsIsArray";
8
7
 
9
8
  import type { HookContext } from "@feathersjs/feathers";
9
+ import type { PropertyPath } from "lodash";
10
10
 
11
11
  import type {
12
- HookSetDataOptions
12
+ HookSetDataOptions,
13
+ ReturnSyncHook
13
14
  } from "../types";
14
- import type { PropertyPath } from "lodash";
15
15
 
16
16
  const defaultOptions: Required<HookSetDataOptions> = {
17
17
  allowUndefined: false,
@@ -22,13 +22,11 @@ export function setData(
22
22
  from: PropertyPath,
23
23
  to: PropertyPath,
24
24
  _options?: HookSetDataOptions
25
- ): ((context: HookContext) => HookContext) {
25
+ ): ReturnSyncHook {
26
26
  const options: Required<HookSetDataOptions> = Object.assign({}, defaultOptions, _options);
27
27
  return (context: HookContext): HookContext => {
28
28
 
29
- //@ts-expect-error type error because feathers-hooks-common is feathers@4
30
- let items = getItems(context);
31
- items = (Array.isArray(items)) ? items : [items];
29
+ const { items } = getItemsIsArray(context);
32
30
 
33
31
  if (!_has(context, from)) {
34
32
  if (!context.params?.provider || options.allowUndefined === true) {
@@ -45,7 +43,14 @@ export function setData(
45
43
  const val = _get(context, from);
46
44
 
47
45
  items.forEach((item: Record<string, unknown>) => {
48
- if (!options.overwrite && _has(item, to)) { return; }
46
+ let overwrite: boolean;
47
+ if (typeof options.overwrite === "function") {
48
+ overwrite = options.overwrite(item, context);
49
+ } else {
50
+ overwrite = options.overwrite;
51
+ }
52
+
53
+ if (!overwrite && _has(item, to)) { return; }
49
54
 
50
55
  _set(item, to, val);
51
56
  });
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // hooks
2
2
  import { checkMulti } from "./hooks/checkMulti";
3
3
  import { setData } from "./hooks/setData";
4
+
4
5
  import { runPerItem } from "./hooks/runPerItem";
5
6
 
6
7
  export const hooks = {
@@ -10,7 +11,9 @@ export const hooks = {
10
11
  };
11
12
 
12
13
  export { checkMulti };
14
+ export { createRelated } from "./hooks/createRelated";
13
15
  export { setData };
16
+ export { removeRelated } from "./hooks/removeRelated";
14
17
  export { runPerItem };
15
18
 
16
19
  import { debounceMixin, DebouncedService, DebouncedStore } from "./mixins/debounce-mixin";
@@ -33,8 +36,15 @@ export { pushSet } from "./utils/pushSet";
33
36
  export { setResultEmpty } from "./utils/setResultEmpty";
34
37
 
35
38
  export { markHookForSkip } from "./utils/markHookForSkip";
39
+ export { filterQuery } from "./utils/filterQuery";
40
+ export { getItemsIsArray } from "./utils/getItemsIsArray";
41
+ export { onDelete } from "./hooks/onDelete";
36
42
  export { shouldSkip } from "./utils/shouldSkip";
37
43
 
38
- export { filterQuery } from "./utils/filterQuery";
44
+ export { validateQueryProperty } from "./utils/validateQueryProperty";
45
+
46
+
47
+ // query filters
48
+ export { filterArray } from "./filters/array";
39
49
 
40
50
  export * from "./types";
@@ -1,4 +1,4 @@
1
- import _debounce from "lodash/debounce";
1
+ import _debounce from "lodash/debounce.js";
2
2
 
3
3
  import type { DebouncedFunc } from "lodash";
4
4
  import type { Application, Id } from "@feathersjs/feathers";
@@ -18,58 +18,58 @@ export const makeDefaultOptions = (): DebouncedStoreOptions => {
18
18
  };
19
19
 
20
20
  export class DebouncedStore {
21
- private _app: Application;
22
- private _options: DebouncedStoreOptions;
23
- private _isRunningById: Record<string, unknown>;
24
- _queueById: Record<string, DebouncedFunc<((id: Id, action: DebouncedFunctionApp) => void | Promise<void>)>>;
25
- //_waitingById: Record<string, WaitingObject>;
26
- add;
27
- constructor(app: Application, options?: Partial<DebouncedStoreOptions>) {
28
- this._app = app;
29
- this._options = Object.assign(makeDefaultOptions(), options);
30
- this._queueById = {};
31
- this._isRunningById = {};
32
- //this._waitingById = {};
21
+ private _app: Application;
22
+ private _options: DebouncedStoreOptions;
23
+ private _isRunningById: Record<string, unknown>;
24
+ _queueById: Record<string, DebouncedFunc<((id: Id, action: DebouncedFunctionApp) => void | Promise<void>)>>;
25
+ //_waitingById: Record<string, WaitingObject>;
26
+ add;
27
+ constructor(app: Application, options?: Partial<DebouncedStoreOptions>) {
28
+ this._app = app;
29
+ this._options = Object.assign(makeDefaultOptions(), options);
30
+ this._queueById = {};
31
+ this._isRunningById = {};
32
+ //this._waitingById = {};
33
33
 
34
- this.add = this.debounceById(
35
- this.unbounced,
36
- this._options.wait,
37
- {
38
- leading: this._options.leading,
39
- maxWait: this._options.maxWait,
40
- trailing: this._options.trailing
41
- }
42
- );
43
- }
44
-
45
- private async unbounced(id: Id, action: DebouncedFunctionApp): Promise<void> {
46
- if (this._queueById[id] === undefined) {
47
- return;
34
+ this.add = this.debounceById(
35
+ this.unbounced,
36
+ this._options.wait,
37
+ {
38
+ leading: this._options.leading,
39
+ maxWait: this._options.maxWait,
40
+ trailing: this._options.trailing
48
41
  }
49
- delete this._queueById[id];
50
- this._isRunningById[id] = true;
51
- await action(this._app);
52
- delete this._isRunningById[id];
42
+ );
43
+ }
44
+
45
+ private async unbounced(id: Id, action: DebouncedFunctionApp): Promise<void> {
46
+ if (this._queueById[id] === undefined) {
47
+ return;
53
48
  }
49
+ delete this._queueById[id];
50
+ this._isRunningById[id] = true;
51
+ await action(this._app);
52
+ delete this._isRunningById[id];
53
+ }
54
54
 
55
- private debounceById(
56
- func: ((id: Id, action: DebouncedFunctionApp) => Promise<void>),
57
- wait: number,
58
- options?: Partial<DebouncedStoreOptions>
59
- ) {
60
- return (id: Id, action: ((app?: Application) => void | Promise<void>)) => {
61
- if (typeof this._queueById[id] === "function") {
62
- return this._queueById[id](id, action);
63
- }
64
-
65
- this._queueById[id] = _debounce((id, action) => {
66
- this.unbounced(id, action);
67
- }, wait, { ...options, leading: false }); // leading required for return promise
55
+ private debounceById(
56
+ func: ((id: Id, action: DebouncedFunctionApp) => Promise<void>),
57
+ wait: number,
58
+ options?: Partial<DebouncedStoreOptions>
59
+ ) {
60
+ return (id: Id, action: ((app?: Application) => void | Promise<void>)) => {
61
+ if (typeof this._queueById[id] === "function") {
68
62
  return this._queueById[id](id, action);
69
- };
70
- }
63
+ }
64
+
65
+ this._queueById[id] = _debounce((id, action) => {
66
+ this.unbounced(id, action);
67
+ }, wait, { ...options, leading: false }); // leading required for return promise
68
+ return this._queueById[id](id, action);
69
+ };
70
+ }
71
71
 
72
- cancel(id: Id): void {
73
- delete this._queueById[id];
74
- }
72
+ cancel(id: Id): void {
73
+ delete this._queueById[id];
74
+ }
75
75
  }
@@ -11,14 +11,17 @@ import type {
11
11
  } from "../../types";
12
12
 
13
13
  export type DebouncedService = FeathersService & {
14
- debouncedStore?: DebouncedStore;
14
+ debouncedStore: DebouncedStore;
15
15
  };
16
16
 
17
- export function debounceMixin(options?: Partial<InitDebounceMixinOptions>): ((app: Application) => void) {
17
+ export function debounceMixin(
18
+ options?: Partial<InitDebounceMixinOptions>
19
+ ): ((app: Application) => void) {
18
20
  return (app: Application): void => {
19
21
  options = options || {};
20
22
  const defaultOptions = Object.assign(makeDefaultOptions(), options?.default);
21
- app.mixins.push((service: DebouncedService, path) => {
23
+
24
+ app.mixins.push((service: any, path) => {
22
25
  // if path is on blacklist, don't add debouncedStore to service
23
26
  if (options?.blacklist && options.blacklist.includes(path)) return;
24
27
  // if service already has registered something on `debouncedStore`
package/src/types.ts CHANGED
@@ -1,18 +1,31 @@
1
- import type { Application } from "@feathersjs/feathers";
2
- import type { AdapterService } from "@feathersjs/adapter-commons";
1
+ import type { Application, HookContext } from "@feathersjs/feathers";
2
+ import type {
3
+ AdapterBase,
4
+ FilterQueryOptions as PlainFilterQueryOptions
5
+ } from "@feathersjs/adapter-commons";
3
6
 
4
7
  export type Path = Array<string|number>;
8
+ export type MaybeArray<T> = T | T[]
9
+ export type Promisable<T> = T | Promise<T>
10
+
5
11
  export type HookType = "before" | "after" | "error";
6
12
  export type ServiceMethodName = "find" | "get" | "create" | "update" | "patch" | "remove";
13
+ export type ReturnSyncHook = (context: HookContext) => HookContext
14
+ export type ReturnAsyncHook = (context: HookContext) => Promise<HookContext>
7
15
 
8
16
  export type Handle = "target" | "source" | "combine" | "intersect"| "intersectOrFull";
9
17
  export type FirstLast = "first" | "last";
10
18
 
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ export type Predicate<T = any> = (item: T) => boolean
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ export type PredicateWithContext<T = any> = (item: T, context: HookContext) => boolean
23
+
11
24
  //#region hooks
12
25
 
13
26
  export interface HookSetDataOptions {
14
27
  allowUndefined?: boolean
15
- overwrite?: boolean
28
+ overwrite?: boolean | PredicateWithContext
16
29
  }
17
30
 
18
31
  export interface AddHookOptions {
@@ -27,6 +40,29 @@ export interface HookRunPerItemOptions {
27
40
  wait?: boolean
28
41
  }
29
42
 
43
+ export interface RemoveRelatedOptions<S = Record<string, any>> {
44
+ service: keyof S
45
+ keyThere: string
46
+ keyHere: string
47
+ blocking?: boolean
48
+ }
49
+
50
+ export interface CreateRelatedOptions<S = Record<string, any>> {
51
+ service: keyof S
52
+ multi?: boolean
53
+ data: (item: any, context: HookContext) => Promisable<Record<string, any>>
54
+ createItemsInDataArraySeparately?: boolean
55
+ }
56
+
57
+ export type OnDeleteAction = "cascade" | "set null";
58
+
59
+ export interface OnDeleteOptions {
60
+ keyThere: string
61
+ keyHere: string
62
+ onDelete: OnDeleteAction
63
+ blocking?: boolean
64
+ }
65
+
30
66
  //#endregion
31
67
 
32
68
  //#region mixins
@@ -70,21 +106,15 @@ export interface MergeQueryOptions<T> extends FilterQueryOptions<T> {
70
106
  }
71
107
 
72
108
  export interface FilterQueryOptions<T> {
73
- service?: AdapterService<T>
74
- operators?: string[],
75
- filters?: string[],
109
+ service?: AdapterBase<T>
110
+ operators?: PlainFilterQueryOptions["operators"],
111
+ filters?: PlainFilterQueryOptions["filters"],
76
112
  }
77
113
 
78
- export interface PlainFilterQueryOptions {
79
- operators?: string[],
80
- filters?: string[],
81
- }
82
-
83
- export interface FilterQueryResult {
84
- filters: Record<string, unknown>
85
- query: Record<string, unknown>
86
- paginate?: unknown
87
- [key: string]: unknown
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ export interface GetItemsIsArrayOptions<T = any> {
116
+ items: T[]
117
+ isArray: boolean
88
118
  }
89
119
 
90
120
  //#endregion
@@ -1,34 +1,35 @@
1
1
  import {
2
- filterQuery as plainFilterQuery
2
+ filterQuery as plainFilterQuery,
3
3
  } from "@feathersjs/adapter-commons";
4
4
 
5
5
  import type {
6
- FilterQueryOptions,
7
- FilterQueryResult,
8
- PlainFilterQueryOptions
9
- } from "../types";
6
+ FilterQueryOptions as PlainFilterQueryOptions,
7
+ } from "@feathersjs/adapter-commons";
10
8
 
11
9
  import type { Query } from "@feathersjs/feathers";
10
+ import type { FilterQueryOptions } from "../types";
12
11
 
13
- export function filterQuery<T>(query: Query, options?: FilterQueryOptions<T>): FilterQueryResult {
12
+ export function filterQuery<T>(query: Query, _options?: FilterQueryOptions<T>) {
14
13
  query = query || {};
15
- options = options || {};
16
- if (options?.service) {
17
- const { service } = options;
14
+ _options = _options || {};
15
+ const { service, ...options } = _options;
16
+ if (service) {
18
17
  const operators = options.operators
19
18
  ? options.operators
20
- : service.options?.whitelist;
19
+ : service.options?.operators;
21
20
  const filters = options.filters
22
21
  ? options.filters
23
22
  : service.options?.filters;
24
23
  const optionsForFilterQuery: PlainFilterQueryOptions = {};
25
24
  if (operators) { optionsForFilterQuery.operators = operators; }
26
25
  if (filters) { optionsForFilterQuery.filters = filters; }
27
- if (typeof service?.filterQuery === "function") {
28
- return service.filterQuery({ query }, optionsForFilterQuery) as FilterQueryResult;
26
+ // @ts-expect-error service has no filterQuery method
27
+ if (service && "filterQuery" in service && typeof service.filterQuery === "function") {
28
+ // @ts-expect-error service has no filterQuery method
29
+ return service.filterQuery({ query }, optionsForFilterQuery);
29
30
  } else {
30
- return plainFilterQuery(query, optionsForFilterQuery) as FilterQueryResult;
31
+ return plainFilterQuery(query, optionsForFilterQuery);
31
32
  }
32
33
  }
33
- return plainFilterQuery(query, options) as FilterQueryResult;
34
+ return plainFilterQuery(query, options);
34
35
  }
@@ -0,0 +1,23 @@
1
+ import type { HookContext } from "@feathersjs/feathers";
2
+ import type { GetItemsIsArrayOptions } from "..";
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ export const getItemsIsArray = <T = any>(
6
+ context: HookContext
7
+ ): GetItemsIsArrayOptions<T> => {
8
+ let itemOrItems = context.type === "before"
9
+ ? context.data
10
+ : context.result;
11
+ itemOrItems = itemOrItems && context.method === "find"
12
+ ? (itemOrItems.data || itemOrItems)
13
+ : itemOrItems;
14
+ const isArray = Array.isArray(itemOrItems);
15
+ return {
16
+ items: (isArray)
17
+ ? itemOrItems
18
+ : (itemOrItems != null)
19
+ ? [itemOrItems]
20
+ : [],
21
+ isArray
22
+ };
23
+ };
@@ -1,4 +1,4 @@
1
- import { HookContext } from "@feathersjs/feathers";
1
+ import type { HookContext } from "@feathersjs/feathers";
2
2
 
3
3
  // TODO: seems like this does no longer exist in feathers
4
4
  interface PaginationOptions {
@@ -6,7 +6,6 @@ interface PaginationOptions {
6
6
  max: number;
7
7
  }
8
8
 
9
-
10
9
  export const getPaginate = (
11
10
  context: HookContext
12
11
  ): PaginationOptions | undefined => {
@@ -1,6 +1,8 @@
1
1
  import type { HookContext } from "@feathersjs/feathers";
2
2
 
3
- export const isMulti = (context: HookContext): boolean => {
3
+ export const isMulti = (
4
+ context: HookContext
5
+ ): boolean => {
4
6
  const { method } = context;
5
7
  if (method === "find") {
6
8
  return true;
@@ -1,4 +1,4 @@
1
- import { HookContext } from "@feathersjs/feathers";
1
+ import type { HookContext } from "@feathersjs/feathers";
2
2
  import { getPaginate } from "./getPaginate";
3
3
 
4
4
  export const isPaginated = (
@@ -1,11 +1,11 @@
1
1
  import { pushSet } from "./pushSet";
2
2
 
3
3
  import type { HookContext } from "@feathersjs/feathers";
4
- import type { HookType } from "feathers-hooks-common/types";
4
+ import type { HookType, MaybeArray } from "../types";
5
5
 
6
6
  export function markHookForSkip<T>(
7
7
  hookName: string,
8
- type: "all" | HookType | HookType[],
8
+ type: "all" | MaybeArray<HookType>,
9
9
  context?: Partial<HookContext<T>>
10
10
  ): Partial<HookContext<T>> {
11
11
  context = context || {};
@@ -1,10 +1,10 @@
1
- import _get from "lodash/get";
2
- import _has from "lodash/has";
3
- import _isEmpty from "lodash/isEmpty";
4
- import _isEqual from "lodash/isEqual";
5
- import _merge from "lodash/merge";
6
- import _set from "lodash/set";
7
- import _uniqWith from "lodash/uniqWith";
1
+ import _get from "lodash/get.js";
2
+ import _has from "lodash/has.js";
3
+ import _isEmpty from "lodash/isEmpty.js";
4
+ import _isEqual from "lodash/isEqual.js";
5
+ import _merge from "lodash/merge.js";
6
+ import _set from "lodash/set.js";
7
+ import _uniqWith from "lodash/uniqWith.js";
8
8
 
9
9
  import { mergeArrays } from "./mergeArrays";
10
10
  import { filterQuery } from "../filterQuery";
@@ -249,6 +249,12 @@ function makeDefaultOptions<T>(options?: Partial<MergeQueryOptions<T>>): MergeQu
249
249
  return options as MergeQueryOptions<T>;
250
250
  }
251
251
 
252
+ function moveProperty(source: Record<string, any>, target: Record<string, any>, key: string): void {
253
+ if (!Object.prototype.hasOwnProperty.call(source, key)) { return; }
254
+ target[key] = source[key];
255
+ delete source[key];
256
+ }
257
+
252
258
  export function mergeQuery<T>(target: Query, source: Query, options?: Partial<MergeQueryOptions<T>>): Query {
253
259
  const fullOptions = makeDefaultOptions(options);
254
260
  const {
@@ -259,9 +265,12 @@ export function mergeQuery<T>(target: Query, source: Query, options?: Partial<Me
259
265
  service: fullOptions.service
260
266
  });
261
267
 
268
+ moveProperty(targetFilters, targetQuery, "$or");
269
+ moveProperty(targetFilters, targetQuery, "$and");
270
+
262
271
  if (target.$limit) { targetFilters.$limit = target.$limit; }
263
272
 
264
- let {
273
+ let {
265
274
  // eslint-disable-next-line prefer-const
266
275
  filters: sourceFilters,
267
276
  query: sourceQuery
@@ -270,6 +279,9 @@ export function mergeQuery<T>(target: Query, source: Query, options?: Partial<Me
270
279
  service: fullOptions.service
271
280
  });
272
281
 
282
+ moveProperty(sourceFilters, sourceQuery, "$or");
283
+ moveProperty(sourceFilters, sourceQuery, "$and");
284
+
273
285
  if (source.$limit) { sourceFilters.$limit = source.$limit; }
274
286
 
275
287
  //#region filters
@@ -1,6 +1,6 @@
1
- import _isEqual from "lodash/isEqual";
2
- import _get from "lodash/get";
3
- import _set from "lodash/set";
1
+ import _isEqual from "lodash/isEqual.js";
2
+ import _get from "lodash/get.js";
3
+ import _set from "lodash/set.js";
4
4
 
5
5
  import type {
6
6
  Path,
@@ -1,4 +1,4 @@
1
- import { HookContext } from "@feathersjs/feathers";
1
+ import type { HookContext } from "@feathersjs/feathers";
2
2
  import { isMulti } from "..";
3
3
  import { isPaginated } from "./isPaginated";
4
4
 
@@ -5,7 +5,10 @@ import { GeneralError } from "@feathersjs/errors";
5
5
 
6
6
  import type { HookContext } from "@feathersjs/feathers";
7
7
 
8
- export const shouldSkip = (hookName: string, context: HookContext): boolean => {
8
+ export const shouldSkip = (
9
+ hookName: string,
10
+ context: HookContext
11
+ ): boolean => {
9
12
  if (!context.params || !context.params.skipHooks) {
10
13
  return false;
11
14
  }
@@ -0,0 +1,27 @@
1
+ import { _ } from "@feathersjs/commons";
2
+ import { BadRequest } from "@feathersjs/errors";
3
+ import type { Query } from "@feathersjs/feathers";
4
+
5
+ const isPlainObject = (value: any) => _.isObject(value) && value.constructor === {}.constructor;
6
+
7
+ export const validateQueryProperty = (query: any, operators: string[] = []): Query => {
8
+ if (!isPlainObject(query)) {
9
+ return query;
10
+ }
11
+
12
+ for (const key of Object.keys(query)) {
13
+ if (key.startsWith("$") && !operators.includes(key)) {
14
+ throw new BadRequest(`Invalid query parameter ${key}`, query);
15
+ }
16
+
17
+ const value = query[key];
18
+
19
+ if (isPlainObject(value)) {
20
+ query[key] = validateQueryProperty(value, operators);
21
+ }
22
+ }
23
+
24
+ return {
25
+ ...query
26
+ };
27
+ };
package/.eslintignore DELETED
@@ -1,3 +0,0 @@
1
- node_modules
2
- dist
3
- coverage
package/.eslintrc.js DELETED
@@ -1,44 +0,0 @@
1
- module.exports = {
2
- root: true,
3
- env: {
4
- node: true,
5
- mocha: true
6
- },
7
- parser: "@typescript-eslint/parser",
8
- plugins: [
9
- "security",
10
- "@typescript-eslint"
11
- ],
12
- extends: [
13
- "eslint:recommended",
14
- "plugin:security/recommended",
15
- "plugin:@typescript-eslint/recommended"
16
- ],
17
- rules: {
18
- "quotes": ["warn", "double", "avoid-escape"],
19
- "indent": ["warn", 2, { "SwitchCase": 1 }],
20
- "semi": ["warn", "always"],
21
- "@typescript-eslint/no-unused-vars": "warn",
22
- "no-console": "off",
23
- "camelcase": "warn",
24
- "require-atomic-updates": "off",
25
- "prefer-destructuring": ["warn", {
26
- "array": false,
27
- "object": true
28
- }, {
29
- "enforceForRenamedProperties": false
30
- }],
31
- "security/detect-object-injection": "off",
32
- "object-curly-spacing": ["warn", "always"],
33
- "prefer-const": ["warn"]
34
- },
35
- overrides: [
36
- {
37
- "files": ["test/**/*.ts"],
38
- "rules": {
39
- "@typescript-eslint/ban-ts-comment": ["off"]
40
- }
41
- }
42
- ]
43
- };
44
-
package/.gitlab-ci.yml DELETED
@@ -1,11 +0,0 @@
1
- .feathers-utils:
2
- only:
3
- changes:
4
- - packages/feathers-utils/**/*
5
-
6
- test:feathers-utils:
7
- variables:
8
- DIR: packages/feathers-utils
9
- extends:
10
- - .feathers-utils
11
- - .test
package/.mocharc.js DELETED
@@ -1,11 +0,0 @@
1
- 'use strict';
2
- const path = require("path");
3
-
4
- module.exports = {
5
- extension: ["ts"],
6
- package: path.join(__dirname, "./package.json"),
7
- ui: "bdd",
8
- spec: [
9
- "./test/**/*.test.ts",
10
- ]
11
- };
package/.nycrc.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "extends": "@istanbuljs/nyc-config-typescript",
3
- "cache": false,
4
- "check-coverage": false,
5
- "include": [
6
- "src/**/*.js",
7
- "src/**/*.ts"
8
- ],
9
- "exclude": [
10
- "src/types.ts",
11
- "src/index.ts"
12
- ],
13
- "reporter": [
14
- "html",
15
- "text",
16
- "text-summary",
17
- "lcov"
18
- ],
19
- "sourceMap": true,
20
- "all": true,
21
- "instrument": true
22
- }
package/index.js DELETED
@@ -1,9 +0,0 @@
1
- exports.mergeQuery = require("./src/mergeQuery");
2
- exports.mergeArrays = require("./src/mergeQuery/mergeArrays");
3
- exports.shouldSkip = require("./src/shouldSkip");
4
- exports.pushSet = require("./src/pushSet");
5
- exports.isMulti = require("./src/isMulti");
6
-
7
- exports.hooks = {
8
- checkMulti: require("./src/hooks/checkMulti")
9
- };