feathers-utils 1.6.1 → 2.0.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 (84) hide show
  1. package/dist/esm/hooks/checkMulti.d.ts +2 -0
  2. package/dist/esm/hooks/checkMulti.js +18 -0
  3. package/dist/esm/hooks/runPerItem.d.ts +4 -0
  4. package/dist/esm/hooks/runPerItem.js +26 -0
  5. package/dist/esm/hooks/setData.d.ts +4 -0
  6. package/dist/esm/hooks/setData.js +34 -0
  7. package/dist/esm/index.d.ts +30 -0
  8. package/dist/esm/index.js +30 -0
  9. package/dist/esm/mixins/debounce-mixin/DebouncedStore.d.ts +15 -0
  10. package/dist/esm/mixins/debounce-mixin/DebouncedStore.js +46 -0
  11. package/dist/esm/mixins/debounce-mixin/index.d.ts +8 -0
  12. package/dist/esm/mixins/debounce-mixin/index.js +20 -0
  13. package/dist/esm/types.d.ts +60 -0
  14. package/dist/esm/types.js +2 -0
  15. package/dist/esm/utils/filterQuery.d.ts +3 -0
  16. package/dist/esm/utils/filterQuery.js +28 -0
  17. package/dist/esm/utils/getPaginate.d.ts +7 -0
  18. package/dist/esm/utils/getPaginate.js +14 -0
  19. package/dist/esm/utils/isMulti.d.ts +2 -0
  20. package/dist/esm/utils/isMulti.js +17 -0
  21. package/dist/esm/utils/isPaginated.d.ts +2 -0
  22. package/dist/esm/utils/isPaginated.js +8 -0
  23. package/dist/esm/utils/markHookForSkip.d.ts +3 -0
  24. package/dist/esm/utils/markHookForSkip.js +14 -0
  25. package/dist/esm/utils/mergeQuery/index.d.ts +3 -0
  26. package/dist/esm/utils/mergeQuery/index.js +327 -0
  27. package/dist/esm/utils/mergeQuery/mergeArrays.d.ts +2 -0
  28. package/dist/esm/utils/mergeQuery/mergeArrays.js +37 -0
  29. package/dist/esm/utils/pushSet.d.ts +2 -0
  30. package/dist/esm/utils/pushSet.js +19 -0
  31. package/dist/esm/utils/setResultEmpty.d.ts +2 -0
  32. package/dist/esm/utils/setResultEmpty.js +25 -0
  33. package/dist/esm/utils/shouldSkip.d.ts +2 -0
  34. package/dist/esm/utils/shouldSkip.js +29 -0
  35. package/dist/hooks/checkMulti.js +2 -2
  36. package/dist/hooks/runPerItem.d.ts +2 -1
  37. package/dist/hooks/runPerItem.js +3 -2
  38. package/dist/hooks/setData.js +7 -6
  39. package/dist/index.d.ts +6 -3
  40. package/dist/index.js +7 -3
  41. package/dist/mixins/debounce-mixin/DebouncedStore.d.ts +2 -2
  42. package/dist/mixins/debounce-mixin/DebouncedStore.js +2 -2
  43. package/dist/mixins/debounce-mixin/index.d.ts +4 -1
  44. package/dist/mixins/debounce-mixin/index.js +1 -1
  45. package/dist/types.d.ts +3 -2
  46. package/dist/utils/filterQuery.js +2 -2
  47. package/dist/utils/getPaginate.d.ts +7 -0
  48. package/dist/utils/getPaginate.js +15 -0
  49. package/dist/utils/isMulti.js +2 -3
  50. package/dist/utils/isPaginated.d.ts +2 -0
  51. package/dist/utils/isPaginated.js +12 -0
  52. package/dist/utils/markHookForSkip.js +1 -1
  53. package/dist/utils/mergeQuery/index.js +38 -38
  54. package/dist/utils/pushSet.js +3 -3
  55. package/dist/utils/setResultEmpty.d.ts +2 -0
  56. package/dist/utils/setResultEmpty.js +29 -0
  57. package/package.json +19 -13
  58. package/src/hooks/runPerItem.ts +4 -1
  59. package/src/hooks/setData.ts +1 -0
  60. package/src/index.ts +6 -3
  61. package/src/mixins/debounce-mixin/DebouncedStore.ts +1 -1
  62. package/src/mixins/debounce-mixin/index.ts +7 -3
  63. package/src/types.ts +4 -3
  64. package/src/utils/filterQuery.ts +2 -2
  65. package/src/utils/getPaginate.ts +26 -0
  66. package/src/utils/isMulti.ts +2 -3
  67. package/src/utils/isPaginated.ts +12 -0
  68. package/src/utils/setResultEmpty.ts +28 -0
  69. package/tsconfig-esm.json +9 -0
  70. package/.github/workflows/node.js.yml +0 -45
  71. package/.vscode/launch.json +0 -28
  72. package/.vscode/settings.json +0 -14
  73. package/dist/utils/addHook.d.ts +0 -3
  74. package/dist/utils/addHook.js +0 -40
  75. package/src/utils/addHook.ts +0 -49
  76. package/test/hooks/checkMulti.test.ts +0 -121
  77. package/test/hooks/setData.test.ts +0 -333
  78. package/test/mixins/debounce-mixin.test.ts +0 -174
  79. package/test/utils/addHook.test.ts +0 -307
  80. package/test/utils/isMulti.test.ts +0 -53
  81. package/test/utils/markHookForSkip.test.ts +0 -292
  82. package/test/utils/mergeQuery.test.ts +0 -407
  83. package/test/utils/pushSet.test.ts +0 -66
  84. package/test/utils/shouldSkip.test.ts +0 -67
@@ -0,0 +1,2 @@
1
+ import type { HookContext } from "@feathersjs/feathers";
2
+ export declare function checkMulti(): ((context: HookContext) => HookContext);
@@ -0,0 +1,18 @@
1
+ import { MethodNotAllowed } from "@feathersjs/errors";
2
+ import { shouldSkip } from "../utils/shouldSkip";
3
+ import { isMulti } from "../utils/isMulti";
4
+ export function checkMulti() {
5
+ return (context) => {
6
+ if (shouldSkip("checkMulti", context)) {
7
+ return context;
8
+ }
9
+ const { service, method } = context;
10
+ if (!service.allowsMulti || !isMulti(context) || method === "find") {
11
+ return context;
12
+ }
13
+ if (!service.allowsMulti(method)) {
14
+ throw new MethodNotAllowed(`Can not ${method} multiple entries`);
15
+ }
16
+ return context;
17
+ };
18
+ }
@@ -0,0 +1,4 @@
1
+ import type { HookRunPerItemOptions } from "../types";
2
+ import type { HookContext } from "@feathersjs/feathers";
3
+ import type { Promisable } from "type-fest";
4
+ export declare const runPerItem: (actionPerItem: (item: any, context: HookContext) => Promisable<any>, options: HookRunPerItemOptions) => (context: HookContext) => Promise<HookContext>;
@@ -0,0 +1,26 @@
1
+ import { getItems } from "feathers-hooks-common";
2
+ import { shouldSkip } from "../utils/shouldSkip";
3
+ const makeOptions = (options) => {
4
+ options = options || {};
5
+ return Object.assign({
6
+ wait: true
7
+ }, options);
8
+ };
9
+ export const runPerItem = (actionPerItem, options) => {
10
+ options = makeOptions(options);
11
+ return async (context) => {
12
+ if (shouldSkip("runForItems", context)) {
13
+ return context;
14
+ }
15
+ //@ts-expect-error type error because feathers-hooks-common is feathers@4
16
+ let items = getItems(context);
17
+ items = (Array.isArray(items)) ? items : [items];
18
+ const promises = items.map(async (item) => {
19
+ await actionPerItem(item, context);
20
+ });
21
+ if (options.wait) {
22
+ await Promise.all(promises);
23
+ }
24
+ return context;
25
+ };
26
+ };
@@ -0,0 +1,4 @@
1
+ import type { HookContext } from "@feathersjs/feathers";
2
+ import type { HookSetDataOptions } from "../types";
3
+ import type { PropertyPath } from "lodash";
4
+ export declare function setData(from: PropertyPath, to: PropertyPath, _options?: HookSetDataOptions): ((context: HookContext) => HookContext);
@@ -0,0 +1,34 @@
1
+ import _get from "lodash/get";
2
+ import _set from "lodash/set";
3
+ import _has from "lodash/has";
4
+ import { getItems } from "feathers-hooks-common";
5
+ import { Forbidden } from "@feathersjs/errors";
6
+ const defaultOptions = {
7
+ allowUndefined: false,
8
+ overwrite: true
9
+ };
10
+ export function setData(from, to, _options) {
11
+ const options = Object.assign({}, defaultOptions, _options);
12
+ return (context) => {
13
+ //@ts-expect-error type error because feathers-hooks-common is feathers@4
14
+ let items = getItems(context);
15
+ items = (Array.isArray(items)) ? items : [items];
16
+ if (!_has(context, from)) {
17
+ if (!context.params?.provider || options.allowUndefined === true) {
18
+ return context;
19
+ }
20
+ if (!options.overwrite && items.every((item) => _has(item, to))) {
21
+ return context;
22
+ }
23
+ throw new Forbidden(`Expected field ${from.toString()} not available`);
24
+ }
25
+ const val = _get(context, from);
26
+ items.forEach((item) => {
27
+ if (!options.overwrite && _has(item, to)) {
28
+ return;
29
+ }
30
+ _set(item, to, val);
31
+ });
32
+ return context;
33
+ };
34
+ }
@@ -0,0 +1,30 @@
1
+ import { checkMulti } from "./hooks/checkMulti";
2
+ import { setData } from "./hooks/setData";
3
+ import { runPerItem } from "./hooks/runPerItem";
4
+ export declare const hooks: {
5
+ checkMulti: typeof checkMulti;
6
+ setData: typeof setData;
7
+ runPerItem: (actionPerItem: (item: any, context: import("@feathersjs/feathers/lib").HookContext<import("@feathersjs/feathers/lib").Application<any, any>, any>) => any, options: import("./types").HookRunPerItemOptions) => (context: import("@feathersjs/feathers/lib").HookContext<import("@feathersjs/feathers/lib").Application<any, any>, any>) => Promise<import("@feathersjs/feathers/lib").HookContext<import("@feathersjs/feathers/lib").Application<any, any>, any>>;
8
+ };
9
+ export { checkMulti };
10
+ export { setData };
11
+ export { runPerItem };
12
+ import { debounceMixin, DebouncedService, DebouncedStore } from "./mixins/debounce-mixin";
13
+ export declare const mixins: {
14
+ debounceMixin: typeof debounceMixin;
15
+ DebouncedStore: typeof DebouncedStore;
16
+ };
17
+ export { debounceMixin };
18
+ export { DebouncedService };
19
+ export { DebouncedStore };
20
+ export { getPaginate } from "./utils/getPaginate";
21
+ export { isMulti } from "./utils/isMulti";
22
+ export { isPaginated } from "./utils/isPaginated";
23
+ export { mergeQuery } from "./utils/mergeQuery/index";
24
+ export { mergeArrays } from "./utils/mergeQuery/mergeArrays";
25
+ export { pushSet } from "./utils/pushSet";
26
+ export { setResultEmpty } from "./utils/setResultEmpty";
27
+ export { markHookForSkip } from "./utils/markHookForSkip";
28
+ export { shouldSkip } from "./utils/shouldSkip";
29
+ export { filterQuery } from "./utils/filterQuery";
30
+ export * from "./types";
@@ -0,0 +1,30 @@
1
+ // hooks
2
+ import { checkMulti } from "./hooks/checkMulti";
3
+ import { setData } from "./hooks/setData";
4
+ import { runPerItem } from "./hooks/runPerItem";
5
+ export const hooks = {
6
+ checkMulti,
7
+ setData,
8
+ runPerItem
9
+ };
10
+ export { checkMulti };
11
+ export { setData };
12
+ export { runPerItem };
13
+ import { debounceMixin, DebouncedStore } from "./mixins/debounce-mixin";
14
+ export const mixins = {
15
+ debounceMixin,
16
+ DebouncedStore
17
+ };
18
+ export { debounceMixin };
19
+ export { DebouncedStore };
20
+ export { getPaginate } from "./utils/getPaginate";
21
+ export { isMulti } from "./utils/isMulti";
22
+ export { isPaginated } from "./utils/isPaginated";
23
+ export { mergeQuery } from "./utils/mergeQuery/index";
24
+ export { mergeArrays } from "./utils/mergeQuery/mergeArrays";
25
+ export { pushSet } from "./utils/pushSet";
26
+ export { setResultEmpty } from "./utils/setResultEmpty";
27
+ export { markHookForSkip } from "./utils/markHookForSkip";
28
+ export { shouldSkip } from "./utils/shouldSkip";
29
+ export { filterQuery } from "./utils/filterQuery";
30
+ export * from "./types";
@@ -0,0 +1,15 @@
1
+ import type { DebouncedFunc } from "lodash";
2
+ import type { Application, Id } from "@feathersjs/feathers";
3
+ import type { DebouncedFunctionApp, DebouncedStoreOptions } from "../../types";
4
+ export declare const makeDefaultOptions: () => DebouncedStoreOptions;
5
+ export declare class DebouncedStore {
6
+ private _app;
7
+ private _options;
8
+ private _isRunningById;
9
+ _queueById: Record<string, DebouncedFunc<((id: Id, action: DebouncedFunctionApp) => void | Promise<void>)>>;
10
+ add: (id: Id, action: (app?: Application<any, any> | undefined) => void | Promise<void>) => void | Promise<void> | undefined;
11
+ constructor(app: Application, options?: Partial<DebouncedStoreOptions>);
12
+ private unbounced;
13
+ private debounceById;
14
+ cancel(id: Id): void;
15
+ }
@@ -0,0 +1,46 @@
1
+ import _debounce from "lodash/debounce";
2
+ export const makeDefaultOptions = () => {
3
+ return {
4
+ leading: false,
5
+ maxWait: undefined,
6
+ trailing: true,
7
+ wait: 100
8
+ };
9
+ };
10
+ export class DebouncedStore {
11
+ constructor(app, options) {
12
+ this._app = app;
13
+ this._options = Object.assign(makeDefaultOptions(), options);
14
+ this._queueById = {};
15
+ this._isRunningById = {};
16
+ //this._waitingById = {};
17
+ this.add = this.debounceById(this.unbounced, this._options.wait, {
18
+ leading: this._options.leading,
19
+ maxWait: this._options.maxWait,
20
+ trailing: this._options.trailing
21
+ });
22
+ }
23
+ async unbounced(id, action) {
24
+ if (this._queueById[id] === undefined) {
25
+ return;
26
+ }
27
+ delete this._queueById[id];
28
+ this._isRunningById[id] = true;
29
+ await action(this._app);
30
+ delete this._isRunningById[id];
31
+ }
32
+ debounceById(func, wait, options) {
33
+ return (id, action) => {
34
+ if (typeof this._queueById[id] === "function") {
35
+ return this._queueById[id](id, action);
36
+ }
37
+ this._queueById[id] = _debounce((id, action) => {
38
+ this.unbounced(id, action);
39
+ }, wait, { ...options, leading: false }); // leading required for return promise
40
+ return this._queueById[id](id, action);
41
+ };
42
+ }
43
+ cancel(id) {
44
+ delete this._queueById[id];
45
+ }
46
+ }
@@ -0,0 +1,8 @@
1
+ import { DebouncedStore } from "./DebouncedStore";
2
+ import type { Application, FeathersService } from "@feathersjs/feathers";
3
+ import type { InitDebounceMixinOptions } from "../../types";
4
+ export declare type DebouncedService = FeathersService & {
5
+ debouncedStore?: DebouncedStore;
6
+ };
7
+ export declare function debounceMixin(options?: Partial<InitDebounceMixinOptions>): ((app: Application) => void);
8
+ export { DebouncedStore };
@@ -0,0 +1,20 @@
1
+ import { DebouncedStore, makeDefaultOptions } from "./DebouncedStore";
2
+ export function debounceMixin(options) {
3
+ return (app) => {
4
+ options = options || {};
5
+ const defaultOptions = Object.assign(makeDefaultOptions(), options?.default);
6
+ app.mixins.push((service, path) => {
7
+ // if path is on blacklist, don't add debouncedStore to service
8
+ if (options?.blacklist && options.blacklist.includes(path))
9
+ return;
10
+ // if service already has registered something on `debouncedStore`
11
+ if (service.debouncedStore) {
12
+ console.warn(`[feathers-utils] service: '${path}' already has a property 'debouncedStore'. Mixin will skip creating a new debouncedStore`);
13
+ return;
14
+ }
15
+ const serviceOptions = Object.assign({}, defaultOptions, options?.[path]);
16
+ service.debouncedStore = new DebouncedStore(app, serviceOptions);
17
+ });
18
+ };
19
+ }
20
+ export { DebouncedStore };
@@ -0,0 +1,60 @@
1
+ import type { Application } from "@feathersjs/feathers";
2
+ import type { AdapterService } from "@feathersjs/adapter-commons";
3
+ export declare type Path = Array<string | number>;
4
+ export declare type HookType = "before" | "after" | "error";
5
+ export declare type ServiceMethodName = "find" | "get" | "create" | "update" | "patch" | "remove";
6
+ export declare type Handle = "target" | "source" | "combine" | "intersect" | "intersectOrFull";
7
+ export declare type FirstLast = "first" | "last";
8
+ export interface HookSetDataOptions {
9
+ allowUndefined?: boolean;
10
+ overwrite?: boolean;
11
+ }
12
+ export interface AddHookOptions {
13
+ types: HookType[];
14
+ methods: ServiceMethodName[];
15
+ orderByType: Record<HookType, FirstLast>;
16
+ whitelist?: string[];
17
+ blacklist?: string[];
18
+ }
19
+ export interface HookRunPerItemOptions {
20
+ wait?: boolean;
21
+ }
22
+ export interface InitDebounceMixinOptions {
23
+ default: Partial<DebouncedStoreOptions>;
24
+ blacklist: string[];
25
+ [key: string]: unknown;
26
+ }
27
+ export declare type DebouncedFunctionApp = (app?: Application) => void | Promise<void>;
28
+ export interface DebouncedStoreOptions {
29
+ leading: boolean;
30
+ maxWait: number | undefined;
31
+ trailing: boolean;
32
+ wait: number;
33
+ }
34
+ export interface PushSetOptions {
35
+ unique?: boolean;
36
+ }
37
+ export declare type ActionOnEmptyIntersect = (target: unknown, source: unknown, prependKey: Path) => void;
38
+ export interface MergeQueryOptions<T> extends FilterQueryOptions<T> {
39
+ defaultHandle: Handle;
40
+ actionOnEmptyIntersect: ActionOnEmptyIntersect;
41
+ useLogicalConjunction: boolean;
42
+ handle?: {
43
+ [key: string]: Handle;
44
+ };
45
+ }
46
+ export interface FilterQueryOptions<T> {
47
+ service?: AdapterService<T>;
48
+ operators?: string[];
49
+ filters?: string[];
50
+ }
51
+ export interface PlainFilterQueryOptions {
52
+ operators?: string[];
53
+ filters?: string[];
54
+ }
55
+ export interface FilterQueryResult {
56
+ filters: Record<string, unknown>;
57
+ query: Record<string, unknown>;
58
+ paginate?: unknown;
59
+ [key: string]: unknown;
60
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //#endregion
@@ -0,0 +1,3 @@
1
+ import type { FilterQueryOptions, FilterQueryResult } from "../types";
2
+ import type { Query } from "@feathersjs/feathers";
3
+ export declare function filterQuery<T>(query: Query, options?: FilterQueryOptions<T>): FilterQueryResult;
@@ -0,0 +1,28 @@
1
+ import { filterQuery as plainFilterQuery } from "@feathersjs/adapter-commons";
2
+ export function filterQuery(query, options) {
3
+ query = query || {};
4
+ options = options || {};
5
+ if (options?.service) {
6
+ const { service } = options;
7
+ const operators = options.operators
8
+ ? options.operators
9
+ : service.options?.whitelist;
10
+ const filters = options.filters
11
+ ? options.filters
12
+ : service.options?.filters;
13
+ const optionsForFilterQuery = {};
14
+ if (operators) {
15
+ optionsForFilterQuery.operators = operators;
16
+ }
17
+ if (filters) {
18
+ optionsForFilterQuery.filters = filters;
19
+ }
20
+ if (typeof service?.filterQuery === "function") {
21
+ return service.filterQuery({ query }, optionsForFilterQuery);
22
+ }
23
+ else {
24
+ return plainFilterQuery(query, optionsForFilterQuery);
25
+ }
26
+ }
27
+ return plainFilterQuery(query, options);
28
+ }
@@ -0,0 +1,7 @@
1
+ import { HookContext } from "@feathersjs/feathers";
2
+ interface PaginationOptions {
3
+ default: number;
4
+ max: number;
5
+ }
6
+ export declare const getPaginate: (context: HookContext) => PaginationOptions | undefined;
7
+ export {};
@@ -0,0 +1,14 @@
1
+ export const getPaginate = (context) => {
2
+ if (Object.prototype.hasOwnProperty.call(context.params, "paginate")) {
3
+ return context.params.paginate || undefined;
4
+ }
5
+ if (context.params.paginate === false) {
6
+ return undefined;
7
+ }
8
+ let options = context.service.options || {};
9
+ options = {
10
+ ...options,
11
+ ...context.params.adapter
12
+ };
13
+ return options.paginate || undefined;
14
+ };
@@ -0,0 +1,2 @@
1
+ import type { HookContext } from "@feathersjs/feathers";
2
+ export declare const isMulti: (context: HookContext) => boolean;
@@ -0,0 +1,17 @@
1
+ export const isMulti = (context) => {
2
+ const { method } = context;
3
+ if (method === "find") {
4
+ return true;
5
+ }
6
+ else if (["patch", "remove"].includes(method)) {
7
+ return context.id == null;
8
+ }
9
+ else if (method === "create") {
10
+ const items = context.type === "before" ? context.data : context.result;
11
+ return Array.isArray(items);
12
+ }
13
+ else if (["get", "update"].includes(method)) {
14
+ return false;
15
+ }
16
+ return false;
17
+ };
@@ -0,0 +1,2 @@
1
+ import { HookContext } from "@feathersjs/feathers";
2
+ export declare const isPaginated: (context: HookContext) => boolean;
@@ -0,0 +1,8 @@
1
+ import { getPaginate } from "./getPaginate";
2
+ export const isPaginated = (context) => {
3
+ if (context.params.paginate === false) {
4
+ return false;
5
+ }
6
+ const paginate = getPaginate(context);
7
+ return !!paginate;
8
+ };
@@ -0,0 +1,3 @@
1
+ import type { HookContext } from "@feathersjs/feathers";
2
+ import type { HookType } from "feathers-hooks-common/types";
3
+ export declare function markHookForSkip<T>(hookName: string, type: "all" | HookType | HookType[], context?: Partial<HookContext<T>>): Partial<HookContext<T>>;
@@ -0,0 +1,14 @@
1
+ import { pushSet } from "./pushSet";
2
+ export function markHookForSkip(hookName, type, context) {
3
+ context = context || {};
4
+ const params = context.params || {};
5
+ const types = (Array.isArray(type)) ? type : [type];
6
+ types.forEach(t => {
7
+ const combinedName = (t === "all")
8
+ ? hookName
9
+ : `${type}:${hookName}`;
10
+ pushSet(params, ["skipHooks"], combinedName, { unique: true });
11
+ });
12
+ context.params = params;
13
+ return context;
14
+ }
@@ -0,0 +1,3 @@
1
+ import type { Query } from "@feathersjs/feathers";
2
+ import type { MergeQueryOptions } from "../../types";
3
+ export declare function mergeQuery<T>(target: Query, source: Query, options?: Partial<MergeQueryOptions<T>>): Query;