feathers-utils 1.6.0 → 1.7.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 (68) 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 +25 -0
  5. package/dist/esm/hooks/setData.d.ts +4 -0
  6. package/dist/esm/hooks/setData.js +33 -0
  7. package/dist/esm/index.d.ts +30 -0
  8. package/dist/esm/index.js +31 -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 +5 -0
  12. package/dist/esm/mixins/debounce-mixin/index.js +20 -0
  13. package/dist/esm/types.d.ts +59 -0
  14. package/dist/esm/types.js +2 -0
  15. package/dist/esm/utils/addHook.d.ts +3 -0
  16. package/dist/esm/utils/addHook.js +36 -0
  17. package/dist/esm/utils/filterQuery.d.ts +3 -0
  18. package/dist/esm/utils/filterQuery.js +28 -0
  19. package/dist/esm/utils/getPaginate.d.ts +2 -0
  20. package/dist/esm/utils/getPaginate.js +14 -0
  21. package/dist/esm/utils/isMulti.d.ts +2 -0
  22. package/dist/esm/utils/isMulti.js +17 -0
  23. package/dist/esm/utils/isPaginated.d.ts +2 -0
  24. package/dist/esm/utils/isPaginated.js +8 -0
  25. package/dist/esm/utils/markHookForSkip.d.ts +3 -0
  26. package/dist/esm/utils/markHookForSkip.js +14 -0
  27. package/dist/esm/utils/mergeQuery/index.d.ts +3 -0
  28. package/dist/esm/utils/mergeQuery/index.js +327 -0
  29. package/dist/esm/utils/mergeQuery/mergeArrays.d.ts +2 -0
  30. package/dist/esm/utils/mergeQuery/mergeArrays.js +37 -0
  31. package/dist/esm/utils/pushSet.d.ts +2 -0
  32. package/dist/esm/utils/pushSet.js +19 -0
  33. package/dist/esm/utils/setResultEmpty.d.ts +2 -0
  34. package/dist/esm/utils/setResultEmpty.js +25 -0
  35. package/dist/esm/utils/shouldSkip.d.ts +2 -0
  36. package/dist/esm/utils/shouldSkip.js +29 -0
  37. package/dist/hooks/runPerItem.d.ts +2 -1
  38. package/dist/index.d.ts +4 -1
  39. package/dist/index.js +7 -1
  40. package/dist/mixins/debounce-mixin/DebouncedStore.d.ts +2 -2
  41. package/dist/utils/getPaginate.d.ts +2 -0
  42. package/dist/utils/getPaginate.js +15 -0
  43. package/dist/utils/isMulti.js +2 -3
  44. package/dist/utils/isPaginated.d.ts +2 -0
  45. package/dist/utils/isPaginated.js +12 -0
  46. package/dist/utils/setResultEmpty.d.ts +2 -0
  47. package/dist/utils/setResultEmpty.js +29 -0
  48. package/package.json +15 -9
  49. package/src/hooks/runPerItem.ts +2 -1
  50. package/src/index.ts +3 -0
  51. package/src/mixins/debounce-mixin/DebouncedStore.ts +2 -2
  52. package/src/utils/getPaginate.ts +18 -0
  53. package/src/utils/isMulti.ts +2 -3
  54. package/src/utils/isPaginated.ts +12 -0
  55. package/src/utils/setResultEmpty.ts +28 -0
  56. package/tsconfig-esm.json +9 -0
  57. package/.github/workflows/node.js.yml +0 -45
  58. package/.vscode/launch.json +0 -29
  59. package/.vscode/settings.json +0 -14
  60. package/test/hooks/checkMulti.test.ts +0 -121
  61. package/test/hooks/setData.test.ts +0 -333
  62. package/test/mixins/debounce-mixin.test.ts +0 -174
  63. package/test/utils/addHook.test.ts +0 -307
  64. package/test/utils/isMulti.test.ts +0 -53
  65. package/test/utils/markHookForSkip.test.ts +0 -292
  66. package/test/utils/mergeQuery.test.ts +0 -407
  67. package/test/utils/pushSet.test.ts +0 -66
  68. 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,25 @@
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
+ let items = getItems(context);
16
+ items = (Array.isArray(items)) ? items : [items];
17
+ const promises = items.map(async (item) => {
18
+ await actionPerItem(item, context);
19
+ });
20
+ if (options.wait) {
21
+ await Promise.all(promises);
22
+ }
23
+ return context;
24
+ };
25
+ };
@@ -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,33 @@
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
+ let items = getItems(context);
14
+ items = (Array.isArray(items)) ? items : [items];
15
+ if (!_has(context, from)) {
16
+ if (!context.params?.provider || options.allowUndefined === true) {
17
+ return context;
18
+ }
19
+ if (!options.overwrite && items.every((item) => _has(item, to))) {
20
+ return context;
21
+ }
22
+ throw new Forbidden(`Expected field ${from.toString()} not available`);
23
+ }
24
+ const val = _get(context, from);
25
+ items.forEach((item) => {
26
+ if (!options.overwrite && _has(item, to)) {
27
+ return;
28
+ }
29
+ _set(item, to, val);
30
+ });
31
+ return context;
32
+ };
33
+ }
@@ -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").HookContext<any, import("@feathersjs/feathers").Service<any>>) => any, options: import("./types").HookRunPerItemOptions) => (context: import("@feathersjs/feathers").HookContext<any, import("@feathersjs/feathers").Service<any>>) => Promise<import("@feathersjs/feathers").HookContext<any, import("@feathersjs/feathers").Service<any>>>;
8
+ };
9
+ export { checkMulti };
10
+ export { setData };
11
+ export { runPerItem };
12
+ import { debounceMixin, DebouncedStore } from "./mixins/debounce-mixin";
13
+ export declare const mixins: {
14
+ debounceMixin: typeof debounceMixin;
15
+ DebouncedStore: typeof DebouncedStore;
16
+ };
17
+ export { debounceMixin };
18
+ export { DebouncedStore };
19
+ export { addHook } from "./utils/addHook";
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,31 @@
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 { addHook } from "./utils/addHook";
21
+ export { getPaginate } from "./utils/getPaginate";
22
+ export { isMulti } from "./utils/isMulti";
23
+ export { isPaginated } from "./utils/isPaginated";
24
+ export { mergeQuery } from "./utils/mergeQuery/index";
25
+ export { mergeArrays } from "./utils/mergeQuery/mergeArrays";
26
+ export { pushSet } from "./utils/pushSet";
27
+ export { setResultEmpty } from "./utils/setResultEmpty";
28
+ export { markHookForSkip } from "./utils/markHookForSkip";
29
+ export { shouldSkip } from "./utils/shouldSkip";
30
+ export { filterQuery } from "./utils/filterQuery";
31
+ 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<{}> | 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,5 @@
1
+ import { DebouncedStore } from "./DebouncedStore";
2
+ import type { Application } from "@feathersjs/feathers";
3
+ import type { InitDebounceMixinOptions } from "../../types";
4
+ export declare function debounceMixin(options?: Partial<InitDebounceMixinOptions>): ((app: Application) => void);
5
+ 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,59 @@
1
+ import type { Application, Service } from "@feathersjs/feathers";
2
+ export declare type Path = Array<string | number>;
3
+ export declare type HookType = "before" | "after" | "error";
4
+ export declare type ServiceMethodName = "find" | "get" | "create" | "update" | "patch" | "remove";
5
+ export declare type Handle = "target" | "source" | "combine" | "intersect" | "intersectOrFull";
6
+ export declare type FirstLast = "first" | "last";
7
+ export interface HookSetDataOptions {
8
+ allowUndefined?: boolean;
9
+ overwrite?: boolean;
10
+ }
11
+ export interface AddHookOptions {
12
+ types: HookType[];
13
+ methods: ServiceMethodName[];
14
+ orderByType: Record<HookType, FirstLast>;
15
+ whitelist?: string[];
16
+ blacklist?: string[];
17
+ }
18
+ export interface HookRunPerItemOptions {
19
+ wait?: boolean;
20
+ }
21
+ export interface InitDebounceMixinOptions {
22
+ default: Partial<DebouncedStoreOptions>;
23
+ blacklist: string[];
24
+ [key: string]: unknown;
25
+ }
26
+ export declare type DebouncedFunctionApp = (app?: Application) => void | Promise<void>;
27
+ export interface DebouncedStoreOptions {
28
+ leading: boolean;
29
+ maxWait: number | undefined;
30
+ trailing: boolean;
31
+ wait: number;
32
+ }
33
+ export interface PushSetOptions {
34
+ unique?: boolean;
35
+ }
36
+ export declare type ActionOnEmptyIntersect = (target: unknown, source: unknown, prependKey: Path) => void;
37
+ export interface MergeQueryOptions<T> extends FilterQueryOptions<T> {
38
+ defaultHandle: Handle;
39
+ actionOnEmptyIntersect: ActionOnEmptyIntersect;
40
+ useLogicalConjunction: boolean;
41
+ handle?: {
42
+ [key: string]: Handle;
43
+ };
44
+ }
45
+ export interface FilterQueryOptions<T> {
46
+ service?: Service<T>;
47
+ operators?: string[];
48
+ filters?: string[];
49
+ }
50
+ export interface PlainFilterQueryOptions {
51
+ operators?: string[];
52
+ filters?: string[];
53
+ }
54
+ export interface FilterQueryResult {
55
+ filters: Record<string, unknown>;
56
+ query: Record<string, unknown>;
57
+ paginate?: unknown;
58
+ [key: string]: unknown;
59
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //#endregion
@@ -0,0 +1,3 @@
1
+ import type { Application } from "@feathersjs/feathers";
2
+ import type { AddHookOptions } from "../types";
3
+ export declare const addHook: (app: Application, hook: unknown, options: AddHookOptions) => void;
@@ -0,0 +1,36 @@
1
+ const defaultOptions = () => {
2
+ return {
3
+ whitelist: ["*"],
4
+ blacklist: []
5
+ };
6
+ };
7
+ export const addHook = (app, hook, options) => {
8
+ options = Object.assign(defaultOptions(), options);
9
+ const { types, methods, whitelist, blacklist, orderByType } = options;
10
+ if (types.some(x => !(Object.keys(orderByType).includes(x)))) {
11
+ throw new Error("'feathers-utils/addHook': some types in 'orderByType' are undefined");
12
+ }
13
+ if (Object.keys(orderByType).some(type => !((["first", "last"]).includes(orderByType[type])))) {
14
+ throw new Error("'feathers-utils/addHook': only allowed values for 'orderByType' are 'first' or 'last'");
15
+ }
16
+ const allServices = app.services;
17
+ for (const servicePath in allServices) {
18
+ if (!Object.prototype.hasOwnProperty.call(allServices, servicePath)) {
19
+ continue;
20
+ }
21
+ const service = app.service(servicePath);
22
+ if (blacklist && blacklist.includes(servicePath)) {
23
+ continue;
24
+ }
25
+ if (whitelist && !whitelist.includes("*") && !whitelist.includes(servicePath)) {
26
+ continue;
27
+ }
28
+ types.forEach(type => {
29
+ const order = orderByType[type];
30
+ const unshiftOrPush = (order === "first") ? "unshift" : "push";
31
+ methods.forEach(method => {
32
+ service.__hooks[type][method][unshiftOrPush](hook);
33
+ });
34
+ });
35
+ }
36
+ };
@@ -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,2 @@
1
+ import { HookContext, PaginationOptions } from "@feathersjs/feathers";
2
+ export declare const getPaginate: (context: HookContext) => PaginationOptions | undefined;
@@ -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;