feathers-utils 5.2.0 → 7.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.
package/README.md CHANGED
@@ -1,10 +1,9 @@
1
1
  # feathers-utils
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/feathers-utils)](https://www.npmjs.com/package/feathers-utils)
4
- [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/fratzinger/feathers-utils/Node.js%20CI)](https://github.com/fratzinger/feathers-utils/actions/workflows/node.js.yml?query=branch%3Amain)
4
+ [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/fratzinger/feathers-utils/node.js.yml?branch=main)](https://github.com/fratzinger/feathers-utils/actions/workflows/node.js.yml?query=branch%3Amain)
5
5
  [![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/fratzinger/feathers-utils)](https://codeclimate.com/github/fratzinger/feathers-utils)
6
6
  [![Code Climate coverage](https://img.shields.io/codeclimate/coverage/fratzinger/feathers-utils)](https://codeclimate.com/github/fratzinger/feathers-utils)
7
- [![libraries.io](https://img.shields.io/librariesio/release/npm/feathers-utils)](https://libraries.io/npm/feathers-utils)
8
7
  [![npm](https://img.shields.io/npm/dm/feathers-utils)](https://www.npmjs.com/package/feathers-utils)
9
8
  [![GitHub license](https://img.shields.io/github/license/fratzinger/feathers-utils)](https://github.com/fratzinger/feathers-utils/blob/main/LICENSE.md)
10
9
 
package/dist/index.cjs CHANGED
@@ -620,13 +620,18 @@ const validateQueryProperty = (query, operators = []) => {
620
620
 
621
621
  function optimizeBatchPatch(items, options) {
622
622
  const map = [];
623
- const id = options?.id ?? "id";
624
- for (const [id2, data] of items) {
625
- const index = map.findIndex((item) => fastEquals.deepEqual(item.data, data));
623
+ const idKey = options?.id ?? "id";
624
+ for (const _data of items) {
625
+ const data = _data;
626
+ const id = _data[idKey];
627
+ delete data[idKey];
628
+ const index = map.findIndex((item) => {
629
+ return fastEquals.deepEqual(item.data, data);
630
+ });
626
631
  if (index === -1) {
627
- map.push({ ids: [id2], data });
632
+ map.push({ ids: [id], data });
628
633
  } else {
629
- map[index].ids.push(id2);
634
+ map[index].ids.push(id);
630
635
  }
631
636
  }
632
637
  return map.map(({ ids, data }) => {
@@ -635,7 +640,7 @@ function optimizeBatchPatch(items, options) {
635
640
  data,
636
641
  {
637
642
  query: {
638
- [id]: { $in: ids }
643
+ [idKey]: { $in: ids }
639
644
  }
640
645
  }
641
646
  ];
@@ -724,37 +729,35 @@ function checkMulti() {
724
729
  };
725
730
  }
726
731
 
727
- function createRelated({
728
- service,
729
- multi = true,
730
- data,
731
- createItemsInDataArraySeparately = true
732
- }) {
733
- if (!service || !data) {
734
- throw "initialize hook 'createRelated' completely!";
735
- }
732
+ function createRelated(options) {
736
733
  return async (context) => {
737
734
  if (shouldSkip("createRelated", context)) {
738
735
  return context;
739
736
  }
740
737
  feathersHooksCommon.checkContext(context, "after", void 0, "createRelated");
741
738
  const { items } = getItemsIsArray(context);
742
- let dataToCreate = (await Promise.all(items.map(async (item) => data(item, context)))).filter((x) => !!x);
743
- if (createItemsInDataArraySeparately) {
744
- dataToCreate = dataToCreate.flat();
745
- }
746
- if (!dataToCreate || dataToCreate.length <= 0) {
747
- return context;
748
- }
749
- if (multi) {
750
- await context.app.service(service).create(dataToCreate);
751
- } else {
752
- await Promise.all(
753
- dataToCreate.map(
754
- async (item) => context.app.service(service).create(item)
755
- )
756
- );
757
- }
739
+ const entries = Array.isArray(options) ? options : [options];
740
+ await Promise.all(
741
+ entries.map(async (entry) => {
742
+ const { data, service, createItemsInDataArraySeparately, multi } = entry;
743
+ let dataToCreate = (await Promise.all(items.map(async (item) => data(item, context)))).filter((x) => !!x);
744
+ if (createItemsInDataArraySeparately) {
745
+ dataToCreate = dataToCreate.flat();
746
+ }
747
+ if (!dataToCreate || dataToCreate.length <= 0) {
748
+ return context;
749
+ }
750
+ if (multi) {
751
+ await context.app.service(service).create(dataToCreate);
752
+ } else {
753
+ await Promise.all(
754
+ dataToCreate.map(
755
+ async (item) => context.app.service(service).create(item)
756
+ )
757
+ );
758
+ }
759
+ })
760
+ );
758
761
  return context;
759
762
  };
760
763
  }
@@ -792,46 +795,44 @@ const forEach = (actionPerItem, options) => {
792
795
  };
793
796
  };
794
797
 
795
- function onDelete(service, {
796
- keyThere,
797
- keyHere = "id",
798
- onDelete: onDelete2 = "cascade",
799
- blocking = true
800
- }) {
801
- if (!service || !keyThere) {
802
- throw "initialize hook 'removeRelated' completely!";
803
- }
804
- if (!["cascade", "set null"].includes(onDelete2)) {
805
- throw "onDelete must be 'cascade' or 'set null'";
806
- }
798
+ function onDelete(options) {
807
799
  return async (context) => {
808
800
  if (shouldSkip("onDelete", context)) {
809
801
  return context;
810
802
  }
811
803
  feathersHooksCommon.checkContext(context, "after", "remove", "onDelete");
812
804
  const { items } = getItemsIsArray(context);
813
- let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
814
- ids = [...new Set(ids)];
815
- if (!ids || ids.length <= 0) {
816
- return context;
817
- }
818
- const params = {
819
- query: {
820
- [keyThere]: {
821
- $in: ids
805
+ const entries = Array.isArray(options) ? options : [options];
806
+ const promises = [];
807
+ entries.forEach(
808
+ async ({ keyHere, keyThere, onDelete: onDelete2, service, blocking }) => {
809
+ let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
810
+ ids = [...new Set(ids)];
811
+ if (!ids || ids.length <= 0) {
812
+ return context;
822
813
  }
823
- },
824
- paginate: false
825
- };
826
- let promise;
827
- if (onDelete2 === "cascade") {
828
- promise = context.app.service(service).remove(null, params);
829
- } else if (onDelete2 === "set null") {
830
- const data = { [keyThere]: null };
831
- promise = context.app.service(service).patch(null, data, params);
832
- }
833
- if (blocking) {
834
- await promise;
814
+ const params = {
815
+ query: {
816
+ [keyThere]: {
817
+ $in: ids
818
+ }
819
+ },
820
+ paginate: false
821
+ };
822
+ let promise = void 0;
823
+ if (onDelete2 === "cascade") {
824
+ promise = context.app.service(service).remove(null, params);
825
+ } else if (onDelete2 === "set null") {
826
+ const data = { [keyThere]: null };
827
+ promise = context.app.service(service).patch(null, data, params);
828
+ }
829
+ if (blocking) {
830
+ promises.push(promise);
831
+ }
832
+ }
833
+ );
834
+ if (promises.length) {
835
+ await Promise.all(promises);
835
836
  }
836
837
  return context;
837
838
  };
package/dist/index.d.cts CHANGED
@@ -14,6 +14,7 @@ type MaybeArray<T> = T | T[];
14
14
  type Promisable<T> = T | Promise<T>;
15
15
  type Path = Array<string | number>;
16
16
  type ReturnAsyncHook<H extends HookContext$1 = HookContext$1> = (context: H) => Promise<H>;
17
+ type KeyOf<T> = Extract<keyof T, string>;
17
18
 
18
19
  interface CreateRelatedOptions<S = Record<string, any>> {
19
20
  service: keyof S;
@@ -24,7 +25,7 @@ interface CreateRelatedOptions<S = Record<string, any>> {
24
25
  /**
25
26
  * hook to create related items
26
27
  */
27
- declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, multi, data, createItemsInDataArraySeparately, }: CreateRelatedOptions<S>): (context: H) => Promise<H>;
28
+ declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<CreateRelatedOptions<S>>): (context: H) => Promise<H>;
28
29
 
29
30
  type GetItemsIsArrayFrom = "data" | "result" | "automatic";
30
31
  type GetItemsIsArrayOptions = {
@@ -34,7 +35,7 @@ interface GetItemsIsArrayResult<T = any> {
34
35
  items: T[];
35
36
  isArray: boolean;
36
37
  }
37
- declare const getItemsIsArray: <T = any, H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
38
+ declare const getItemsIsArray: <T = any, H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
38
39
 
39
40
  type HookForEachOptions<T = any, H = HookContext, R = any> = {
40
41
  wait?: "sequential" | "parallel" | false;
@@ -48,10 +49,11 @@ type ActionPerItem<T, H, R> = (item: T, options: {
48
49
  } & (undefined extends R ? {} : {
49
50
  fromAll: R;
50
51
  })) => Promisable<any>;
51
- declare const forEach: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
52
+ declare const forEach: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
52
53
 
53
54
  type OnDeleteAction = "cascade" | "set null";
54
- interface OnDeleteOptions {
55
+ interface OnDeleteOptions<Path extends string = string> {
56
+ service: Path;
55
57
  keyThere: string;
56
58
  keyHere: string;
57
59
  onDelete: OnDeleteAction;
@@ -60,7 +62,7 @@ interface OnDeleteOptions {
60
62
  /**
61
63
  * hook to manipulate related items on delete
62
64
  */
63
- declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(service: keyof S, { keyThere, keyHere, onDelete, blocking, }: OnDeleteOptions): (context: H) => Promise<H>;
65
+ declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<OnDeleteOptions<KeyOf<S>>>): (context: H) => Promise<H>;
64
66
 
65
67
  /**
66
68
  * Parse fields to date or number
@@ -78,6 +80,8 @@ interface RemoveRelatedOptions<S = Record<string, any>> {
78
80
  }
79
81
  /**
80
82
  * hook to remove related items
83
+ *
84
+ * @deprecated use 'onDelete' instead
81
85
  */
82
86
  declare function removeRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, keyThere, keyHere, blocking, }: RemoveRelatedOptions<S>): (context: H) => Promise<H>;
83
87
 
@@ -88,7 +92,7 @@ interface HookRunPerItemOptions {
88
92
  * hook to run a hook for each item in the context
89
93
  * uses `context.result` if it is existent. otherwise uses context.data
90
94
  */
91
- declare const runPerItem: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
95
+ declare const runPerItem: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
92
96
 
93
97
  type Predicate<T = any> = (item: T) => boolean;
94
98
  type PredicateWithContext<T = any> = (item: T, context: HookContext) => boolean;
@@ -102,14 +106,14 @@ interface HookSetDataOptions {
102
106
  */
103
107
  declare function setData<H extends HookContext = HookContext>(from: PropertyPath, to: PropertyPath, _options?: HookSetDataOptions): (context: H) => H;
104
108
 
105
- declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
109
+ declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
106
110
  /**
107
111
  * a hook to move params to query._$client
108
112
  * the server only receives 'query' from params. All other params are ignored.
109
113
  * So, to use `$populateParams` on the server, we need to move the params to query._$client
110
114
  * the server will move them back to params
111
115
  */
112
- declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
116
+ declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
113
117
 
114
118
  declare function defineParamsFromClient(keyToHide: string): (...whitelist: string[]) => (context: HookContext) => HookContext;
115
119
  declare const paramsFromClient: (...whitelist: string[]) => (context: HookContext) => HookContext;
@@ -175,7 +179,7 @@ declare function reassembleQuery(query: FilterQueryResult): Query;
175
179
  * 2. it uses `service.options.paginate` if it exists
176
180
  * 3. it uses `context.params.adapter` if it exists
177
181
  */
178
- declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
182
+ declare const getPaginate: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
179
183
 
180
184
  /**
181
185
  * util to check if a hook is a multi hook:
@@ -186,12 +190,12 @@ declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Applicati
186
190
  * - patch: `context.id == null`
187
191
  * - remove: `context.id == null`
188
192
  */
189
- declare const isMulti: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
193
+ declare const isMulti: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
190
194
 
191
195
  /**
192
196
  * util to check if a hook is a paginated hook using `getPaginate`
193
197
  */
194
- declare const isPaginated: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
198
+ declare const isPaginated: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
195
199
 
196
200
  /**
197
201
  * util to mark a hook for skip, has to be used with `shouldSkip`
@@ -236,7 +240,7 @@ declare const setQueryKeySafely: (params: Params, key: string, value: any, opera
236
240
  /**
237
241
  * util to set `context.result` to an empty array or object, depending on the hook type
238
242
  */
239
- declare const setResultEmpty: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
243
+ declare const setResultEmpty: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
240
244
 
241
245
  type ShouldSkipOptions = {
242
246
  notSkippable?: boolean;
@@ -244,7 +248,7 @@ type ShouldSkipOptions = {
244
248
  /**
245
249
  * util to detect if a hook should be skipped
246
250
  */
247
- declare const shouldSkip: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
251
+ declare const shouldSkip: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
248
252
 
249
253
  declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
250
254
 
@@ -253,11 +257,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
253
257
  */
254
258
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
255
259
 
256
- type OptimizeBatchPatchOptions = {
257
- id?: string;
260
+ type OptimizeBatchPatchOptions<IdKey extends string> = {
261
+ /** the key of the id property */
262
+ id?: IdKey;
258
263
  };
259
264
  type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
260
- declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
265
+ declare function optimizeBatchPatch<T extends Record<string, any>, IdKey extends KeyOf<T>, P extends Params, R extends Omit<T, IdKey> = Omit<T, IdKey>>(items: T[], options?: OptimizeBatchPatchOptions<IdKey>): OptimizeBatchPatchResultItem<R, P>[];
261
266
 
262
267
  declare function flattenQuery(q: Query): any;
263
268
 
package/dist/index.d.mts CHANGED
@@ -14,6 +14,7 @@ type MaybeArray<T> = T | T[];
14
14
  type Promisable<T> = T | Promise<T>;
15
15
  type Path = Array<string | number>;
16
16
  type ReturnAsyncHook<H extends HookContext$1 = HookContext$1> = (context: H) => Promise<H>;
17
+ type KeyOf<T> = Extract<keyof T, string>;
17
18
 
18
19
  interface CreateRelatedOptions<S = Record<string, any>> {
19
20
  service: keyof S;
@@ -24,7 +25,7 @@ interface CreateRelatedOptions<S = Record<string, any>> {
24
25
  /**
25
26
  * hook to create related items
26
27
  */
27
- declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, multi, data, createItemsInDataArraySeparately, }: CreateRelatedOptions<S>): (context: H) => Promise<H>;
28
+ declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<CreateRelatedOptions<S>>): (context: H) => Promise<H>;
28
29
 
29
30
  type GetItemsIsArrayFrom = "data" | "result" | "automatic";
30
31
  type GetItemsIsArrayOptions = {
@@ -34,7 +35,7 @@ interface GetItemsIsArrayResult<T = any> {
34
35
  items: T[];
35
36
  isArray: boolean;
36
37
  }
37
- declare const getItemsIsArray: <T = any, H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
38
+ declare const getItemsIsArray: <T = any, H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
38
39
 
39
40
  type HookForEachOptions<T = any, H = HookContext, R = any> = {
40
41
  wait?: "sequential" | "parallel" | false;
@@ -48,10 +49,11 @@ type ActionPerItem<T, H, R> = (item: T, options: {
48
49
  } & (undefined extends R ? {} : {
49
50
  fromAll: R;
50
51
  })) => Promisable<any>;
51
- declare const forEach: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
52
+ declare const forEach: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
52
53
 
53
54
  type OnDeleteAction = "cascade" | "set null";
54
- interface OnDeleteOptions {
55
+ interface OnDeleteOptions<Path extends string = string> {
56
+ service: Path;
55
57
  keyThere: string;
56
58
  keyHere: string;
57
59
  onDelete: OnDeleteAction;
@@ -60,7 +62,7 @@ interface OnDeleteOptions {
60
62
  /**
61
63
  * hook to manipulate related items on delete
62
64
  */
63
- declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(service: keyof S, { keyThere, keyHere, onDelete, blocking, }: OnDeleteOptions): (context: H) => Promise<H>;
65
+ declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<OnDeleteOptions<KeyOf<S>>>): (context: H) => Promise<H>;
64
66
 
65
67
  /**
66
68
  * Parse fields to date or number
@@ -78,6 +80,8 @@ interface RemoveRelatedOptions<S = Record<string, any>> {
78
80
  }
79
81
  /**
80
82
  * hook to remove related items
83
+ *
84
+ * @deprecated use 'onDelete' instead
81
85
  */
82
86
  declare function removeRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, keyThere, keyHere, blocking, }: RemoveRelatedOptions<S>): (context: H) => Promise<H>;
83
87
 
@@ -88,7 +92,7 @@ interface HookRunPerItemOptions {
88
92
  * hook to run a hook for each item in the context
89
93
  * uses `context.result` if it is existent. otherwise uses context.data
90
94
  */
91
- declare const runPerItem: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
95
+ declare const runPerItem: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
92
96
 
93
97
  type Predicate<T = any> = (item: T) => boolean;
94
98
  type PredicateWithContext<T = any> = (item: T, context: HookContext) => boolean;
@@ -102,14 +106,14 @@ interface HookSetDataOptions {
102
106
  */
103
107
  declare function setData<H extends HookContext = HookContext>(from: PropertyPath, to: PropertyPath, _options?: HookSetDataOptions): (context: H) => H;
104
108
 
105
- declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
109
+ declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
106
110
  /**
107
111
  * a hook to move params to query._$client
108
112
  * the server only receives 'query' from params. All other params are ignored.
109
113
  * So, to use `$populateParams` on the server, we need to move the params to query._$client
110
114
  * the server will move them back to params
111
115
  */
112
- declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
116
+ declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
113
117
 
114
118
  declare function defineParamsFromClient(keyToHide: string): (...whitelist: string[]) => (context: HookContext) => HookContext;
115
119
  declare const paramsFromClient: (...whitelist: string[]) => (context: HookContext) => HookContext;
@@ -175,7 +179,7 @@ declare function reassembleQuery(query: FilterQueryResult): Query;
175
179
  * 2. it uses `service.options.paginate` if it exists
176
180
  * 3. it uses `context.params.adapter` if it exists
177
181
  */
178
- declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
182
+ declare const getPaginate: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
179
183
 
180
184
  /**
181
185
  * util to check if a hook is a multi hook:
@@ -186,12 +190,12 @@ declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Applicati
186
190
  * - patch: `context.id == null`
187
191
  * - remove: `context.id == null`
188
192
  */
189
- declare const isMulti: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
193
+ declare const isMulti: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
190
194
 
191
195
  /**
192
196
  * util to check if a hook is a paginated hook using `getPaginate`
193
197
  */
194
- declare const isPaginated: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
198
+ declare const isPaginated: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
195
199
 
196
200
  /**
197
201
  * util to mark a hook for skip, has to be used with `shouldSkip`
@@ -236,7 +240,7 @@ declare const setQueryKeySafely: (params: Params, key: string, value: any, opera
236
240
  /**
237
241
  * util to set `context.result` to an empty array or object, depending on the hook type
238
242
  */
239
- declare const setResultEmpty: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
243
+ declare const setResultEmpty: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
240
244
 
241
245
  type ShouldSkipOptions = {
242
246
  notSkippable?: boolean;
@@ -244,7 +248,7 @@ type ShouldSkipOptions = {
244
248
  /**
245
249
  * util to detect if a hook should be skipped
246
250
  */
247
- declare const shouldSkip: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
251
+ declare const shouldSkip: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
248
252
 
249
253
  declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
250
254
 
@@ -253,11 +257,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
253
257
  */
254
258
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
255
259
 
256
- type OptimizeBatchPatchOptions = {
257
- id?: string;
260
+ type OptimizeBatchPatchOptions<IdKey extends string> = {
261
+ /** the key of the id property */
262
+ id?: IdKey;
258
263
  };
259
264
  type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
260
- declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
265
+ declare function optimizeBatchPatch<T extends Record<string, any>, IdKey extends KeyOf<T>, P extends Params, R extends Omit<T, IdKey> = Omit<T, IdKey>>(items: T[], options?: OptimizeBatchPatchOptions<IdKey>): OptimizeBatchPatchResultItem<R, P>[];
261
266
 
262
267
  declare function flattenQuery(q: Query): any;
263
268
 
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ type MaybeArray<T> = T | T[];
14
14
  type Promisable<T> = T | Promise<T>;
15
15
  type Path = Array<string | number>;
16
16
  type ReturnAsyncHook<H extends HookContext$1 = HookContext$1> = (context: H) => Promise<H>;
17
+ type KeyOf<T> = Extract<keyof T, string>;
17
18
 
18
19
  interface CreateRelatedOptions<S = Record<string, any>> {
19
20
  service: keyof S;
@@ -24,7 +25,7 @@ interface CreateRelatedOptions<S = Record<string, any>> {
24
25
  /**
25
26
  * hook to create related items
26
27
  */
27
- declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, multi, data, createItemsInDataArraySeparately, }: CreateRelatedOptions<S>): (context: H) => Promise<H>;
28
+ declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<CreateRelatedOptions<S>>): (context: H) => Promise<H>;
28
29
 
29
30
  type GetItemsIsArrayFrom = "data" | "result" | "automatic";
30
31
  type GetItemsIsArrayOptions = {
@@ -34,7 +35,7 @@ interface GetItemsIsArrayResult<T = any> {
34
35
  items: T[];
35
36
  isArray: boolean;
36
37
  }
37
- declare const getItemsIsArray: <T = any, H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
38
+ declare const getItemsIsArray: <T = any, H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
38
39
 
39
40
  type HookForEachOptions<T = any, H = HookContext, R = any> = {
40
41
  wait?: "sequential" | "parallel" | false;
@@ -48,10 +49,11 @@ type ActionPerItem<T, H, R> = (item: T, options: {
48
49
  } & (undefined extends R ? {} : {
49
50
  fromAll: R;
50
51
  })) => Promisable<any>;
51
- declare const forEach: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
52
+ declare const forEach: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
52
53
 
53
54
  type OnDeleteAction = "cascade" | "set null";
54
- interface OnDeleteOptions {
55
+ interface OnDeleteOptions<Path extends string = string> {
56
+ service: Path;
55
57
  keyThere: string;
56
58
  keyHere: string;
57
59
  onDelete: OnDeleteAction;
@@ -60,7 +62,7 @@ interface OnDeleteOptions {
60
62
  /**
61
63
  * hook to manipulate related items on delete
62
64
  */
63
- declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(service: keyof S, { keyThere, keyHere, onDelete, blocking, }: OnDeleteOptions): (context: H) => Promise<H>;
65
+ declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<OnDeleteOptions<KeyOf<S>>>): (context: H) => Promise<H>;
64
66
 
65
67
  /**
66
68
  * Parse fields to date or number
@@ -78,6 +80,8 @@ interface RemoveRelatedOptions<S = Record<string, any>> {
78
80
  }
79
81
  /**
80
82
  * hook to remove related items
83
+ *
84
+ * @deprecated use 'onDelete' instead
81
85
  */
82
86
  declare function removeRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, keyThere, keyHere, blocking, }: RemoveRelatedOptions<S>): (context: H) => Promise<H>;
83
87
 
@@ -88,7 +92,7 @@ interface HookRunPerItemOptions {
88
92
  * hook to run a hook for each item in the context
89
93
  * uses `context.result` if it is existent. otherwise uses context.data
90
94
  */
91
- declare const runPerItem: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
95
+ declare const runPerItem: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
92
96
 
93
97
  type Predicate<T = any> = (item: T) => boolean;
94
98
  type PredicateWithContext<T = any> = (item: T, context: HookContext) => boolean;
@@ -102,14 +106,14 @@ interface HookSetDataOptions {
102
106
  */
103
107
  declare function setData<H extends HookContext = HookContext>(from: PropertyPath, to: PropertyPath, _options?: HookSetDataOptions): (context: H) => H;
104
108
 
105
- declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
109
+ declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
106
110
  /**
107
111
  * a hook to move params to query._$client
108
112
  * the server only receives 'query' from params. All other params are ignored.
109
113
  * So, to use `$populateParams` on the server, we need to move the params to query._$client
110
114
  * the server will move them back to params
111
115
  */
112
- declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
116
+ declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
113
117
 
114
118
  declare function defineParamsFromClient(keyToHide: string): (...whitelist: string[]) => (context: HookContext) => HookContext;
115
119
  declare const paramsFromClient: (...whitelist: string[]) => (context: HookContext) => HookContext;
@@ -175,7 +179,7 @@ declare function reassembleQuery(query: FilterQueryResult): Query;
175
179
  * 2. it uses `service.options.paginate` if it exists
176
180
  * 3. it uses `context.params.adapter` if it exists
177
181
  */
178
- declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
182
+ declare const getPaginate: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
179
183
 
180
184
  /**
181
185
  * util to check if a hook is a multi hook:
@@ -186,12 +190,12 @@ declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Applicati
186
190
  * - patch: `context.id == null`
187
191
  * - remove: `context.id == null`
188
192
  */
189
- declare const isMulti: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
193
+ declare const isMulti: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
190
194
 
191
195
  /**
192
196
  * util to check if a hook is a paginated hook using `getPaginate`
193
197
  */
194
- declare const isPaginated: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
198
+ declare const isPaginated: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
195
199
 
196
200
  /**
197
201
  * util to mark a hook for skip, has to be used with `shouldSkip`
@@ -236,7 +240,7 @@ declare const setQueryKeySafely: (params: Params, key: string, value: any, opera
236
240
  /**
237
241
  * util to set `context.result` to an empty array or object, depending on the hook type
238
242
  */
239
- declare const setResultEmpty: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
243
+ declare const setResultEmpty: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
240
244
 
241
245
  type ShouldSkipOptions = {
242
246
  notSkippable?: boolean;
@@ -244,7 +248,7 @@ type ShouldSkipOptions = {
244
248
  /**
245
249
  * util to detect if a hook should be skipped
246
250
  */
247
- declare const shouldSkip: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
251
+ declare const shouldSkip: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
248
252
 
249
253
  declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
250
254
 
@@ -253,11 +257,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
253
257
  */
254
258
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
255
259
 
256
- type OptimizeBatchPatchOptions = {
257
- id?: string;
260
+ type OptimizeBatchPatchOptions<IdKey extends string> = {
261
+ /** the key of the id property */
262
+ id?: IdKey;
258
263
  };
259
264
  type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
260
- declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
265
+ declare function optimizeBatchPatch<T extends Record<string, any>, IdKey extends KeyOf<T>, P extends Params, R extends Omit<T, IdKey> = Omit<T, IdKey>>(items: T[], options?: OptimizeBatchPatchOptions<IdKey>): OptimizeBatchPatchResultItem<R, P>[];
261
266
 
262
267
  declare function flattenQuery(q: Query): any;
263
268
 
package/dist/index.mjs CHANGED
@@ -609,13 +609,18 @@ const validateQueryProperty = (query, operators = []) => {
609
609
 
610
610
  function optimizeBatchPatch(items, options) {
611
611
  const map = [];
612
- const id = options?.id ?? "id";
613
- for (const [id2, data] of items) {
614
- const index = map.findIndex((item) => deepEqual(item.data, data));
612
+ const idKey = options?.id ?? "id";
613
+ for (const _data of items) {
614
+ const data = _data;
615
+ const id = _data[idKey];
616
+ delete data[idKey];
617
+ const index = map.findIndex((item) => {
618
+ return deepEqual(item.data, data);
619
+ });
615
620
  if (index === -1) {
616
- map.push({ ids: [id2], data });
621
+ map.push({ ids: [id], data });
617
622
  } else {
618
- map[index].ids.push(id2);
623
+ map[index].ids.push(id);
619
624
  }
620
625
  }
621
626
  return map.map(({ ids, data }) => {
@@ -624,7 +629,7 @@ function optimizeBatchPatch(items, options) {
624
629
  data,
625
630
  {
626
631
  query: {
627
- [id]: { $in: ids }
632
+ [idKey]: { $in: ids }
628
633
  }
629
634
  }
630
635
  ];
@@ -713,37 +718,35 @@ function checkMulti() {
713
718
  };
714
719
  }
715
720
 
716
- function createRelated({
717
- service,
718
- multi = true,
719
- data,
720
- createItemsInDataArraySeparately = true
721
- }) {
722
- if (!service || !data) {
723
- throw "initialize hook 'createRelated' completely!";
724
- }
721
+ function createRelated(options) {
725
722
  return async (context) => {
726
723
  if (shouldSkip("createRelated", context)) {
727
724
  return context;
728
725
  }
729
726
  checkContext(context, "after", void 0, "createRelated");
730
727
  const { items } = getItemsIsArray(context);
731
- let dataToCreate = (await Promise.all(items.map(async (item) => data(item, context)))).filter((x) => !!x);
732
- if (createItemsInDataArraySeparately) {
733
- dataToCreate = dataToCreate.flat();
734
- }
735
- if (!dataToCreate || dataToCreate.length <= 0) {
736
- return context;
737
- }
738
- if (multi) {
739
- await context.app.service(service).create(dataToCreate);
740
- } else {
741
- await Promise.all(
742
- dataToCreate.map(
743
- async (item) => context.app.service(service).create(item)
744
- )
745
- );
746
- }
728
+ const entries = Array.isArray(options) ? options : [options];
729
+ await Promise.all(
730
+ entries.map(async (entry) => {
731
+ const { data, service, createItemsInDataArraySeparately, multi } = entry;
732
+ let dataToCreate = (await Promise.all(items.map(async (item) => data(item, context)))).filter((x) => !!x);
733
+ if (createItemsInDataArraySeparately) {
734
+ dataToCreate = dataToCreate.flat();
735
+ }
736
+ if (!dataToCreate || dataToCreate.length <= 0) {
737
+ return context;
738
+ }
739
+ if (multi) {
740
+ await context.app.service(service).create(dataToCreate);
741
+ } else {
742
+ await Promise.all(
743
+ dataToCreate.map(
744
+ async (item) => context.app.service(service).create(item)
745
+ )
746
+ );
747
+ }
748
+ })
749
+ );
747
750
  return context;
748
751
  };
749
752
  }
@@ -781,46 +784,44 @@ const forEach = (actionPerItem, options) => {
781
784
  };
782
785
  };
783
786
 
784
- function onDelete(service, {
785
- keyThere,
786
- keyHere = "id",
787
- onDelete: onDelete2 = "cascade",
788
- blocking = true
789
- }) {
790
- if (!service || !keyThere) {
791
- throw "initialize hook 'removeRelated' completely!";
792
- }
793
- if (!["cascade", "set null"].includes(onDelete2)) {
794
- throw "onDelete must be 'cascade' or 'set null'";
795
- }
787
+ function onDelete(options) {
796
788
  return async (context) => {
797
789
  if (shouldSkip("onDelete", context)) {
798
790
  return context;
799
791
  }
800
792
  checkContext(context, "after", "remove", "onDelete");
801
793
  const { items } = getItemsIsArray(context);
802
- let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
803
- ids = [...new Set(ids)];
804
- if (!ids || ids.length <= 0) {
805
- return context;
806
- }
807
- const params = {
808
- query: {
809
- [keyThere]: {
810
- $in: ids
794
+ const entries = Array.isArray(options) ? options : [options];
795
+ const promises = [];
796
+ entries.forEach(
797
+ async ({ keyHere, keyThere, onDelete: onDelete2, service, blocking }) => {
798
+ let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
799
+ ids = [...new Set(ids)];
800
+ if (!ids || ids.length <= 0) {
801
+ return context;
811
802
  }
812
- },
813
- paginate: false
814
- };
815
- let promise;
816
- if (onDelete2 === "cascade") {
817
- promise = context.app.service(service).remove(null, params);
818
- } else if (onDelete2 === "set null") {
819
- const data = { [keyThere]: null };
820
- promise = context.app.service(service).patch(null, data, params);
821
- }
822
- if (blocking) {
823
- await promise;
803
+ const params = {
804
+ query: {
805
+ [keyThere]: {
806
+ $in: ids
807
+ }
808
+ },
809
+ paginate: false
810
+ };
811
+ let promise = void 0;
812
+ if (onDelete2 === "cascade") {
813
+ promise = context.app.service(service).remove(null, params);
814
+ } else if (onDelete2 === "set null") {
815
+ const data = { [keyThere]: null };
816
+ promise = context.app.service(service).patch(null, data, params);
817
+ }
818
+ if (blocking) {
819
+ promises.push(promise);
820
+ }
821
+ }
822
+ );
823
+ if (promises.length) {
824
+ await Promise.all(promises);
824
825
  }
825
826
  return context;
826
827
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feathers-utils",
3
- "version": "5.2.0",
3
+ "version": "7.0.0",
4
4
  "description": "Some utils for projects using '@feathersjs/feathers'",
5
5
  "author": "fratzinger",
6
6
  "repository": {
@@ -30,43 +30,43 @@
30
30
  "lib/**",
31
31
  "dist/**"
32
32
  ],
33
- "scripts": {
34
- "build": "unbuild",
35
- "version": "npm run build",
36
- "release": "np",
37
- "test": "vitest run",
38
- "vitest": "vitest",
39
- "coverage": "vitest run --coverage",
40
- "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
41
- },
42
33
  "dependencies": {
43
- "@feathersjs/adapter-commons": "^5.0.13",
44
- "@feathersjs/errors": "^5.0.13",
45
- "@feathersjs/feathers": "^5.0.13",
34
+ "@feathersjs/adapter-commons": "^5.0.31",
35
+ "@feathersjs/errors": "^5.0.31",
36
+ "@feathersjs/feathers": "^5.0.31",
46
37
  "fast-equals": "^5.0.1",
47
- "feathers-hooks-common": "^8.1.1",
38
+ "feathers-hooks-common": "^8.2.1",
48
39
  "lodash": "^4.17.21"
49
40
  },
50
41
  "devDependencies": {
51
- "@feathersjs/memory": "^5.0.13",
52
- "@types/lodash": "^4.14.202",
53
- "@types/node": "^20.10.6",
42
+ "@feathersjs/memory": "^5.0.31",
43
+ "@types/lodash": "^4.17.13",
44
+ "@types/node": "^22.10.1",
54
45
  "@typescript-eslint/eslint-plugin": "^6.16.0",
55
46
  "@typescript-eslint/parser": "^6.16.0",
56
- "@vitest/coverage-v8": "^1.1.0",
47
+ "@vitest/coverage-v8": "^2.1.8",
57
48
  "eslint": "^8.56.0",
58
49
  "eslint-config-prettier": "^9.1.0",
59
- "eslint-import-resolver-typescript": "^3.6.1",
50
+ "eslint-import-resolver-typescript": "^3.7.0",
60
51
  "eslint-plugin-import": "^2.29.1",
61
52
  "eslint-plugin-prettier": "^5.1.2",
62
- "np": "^9.2.0",
63
- "prettier": "^3.1.1",
53
+ "np": "^10.1.0",
54
+ "prettier": "^3.4.1",
64
55
  "shx": "^0.3.4",
65
- "typescript": "^5.3.3",
56
+ "typescript": "^5.7.2",
66
57
  "unbuild": "^2.0.0",
67
- "vitest": "^1.1.0"
58
+ "vitest": "^2.1.8"
68
59
  },
69
60
  "peerDependencies": {
70
61
  "@feathersjs/feathers": "^5.0.0"
62
+ },
63
+ "scripts": {
64
+ "build": "unbuild",
65
+ "version": "npm run build",
66
+ "release": "np",
67
+ "test": "vitest run",
68
+ "vitest": "vitest",
69
+ "coverage": "vitest run --coverage",
70
+ "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
71
71
  }
72
- }
72
+ }
package/src/.DS_Store ADDED
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  import type { HookContext } from "@feathersjs/feathers";
2
2
  import { checkContext } from "feathers-hooks-common";
3
- import type { Promisable } from "../typesInternal";
3
+ import type { MaybeArray, Promisable } from "../typesInternal";
4
4
  import { getItemsIsArray, shouldSkip } from "../utils";
5
5
 
6
6
  export interface CreateRelatedOptions<S = Record<string, any>> {
@@ -16,15 +16,7 @@ export interface CreateRelatedOptions<S = Record<string, any>> {
16
16
  export function createRelated<
17
17
  S = Record<string, any>,
18
18
  H extends HookContext = HookContext,
19
- >({
20
- service,
21
- multi = true,
22
- data,
23
- createItemsInDataArraySeparately = true,
24
- }: CreateRelatedOptions<S>) {
25
- if (!service || !data) {
26
- throw "initialize hook 'createRelated' completely!";
27
- }
19
+ >(options: MaybeArray<CreateRelatedOptions<S>>) {
28
20
  return async (context: H) => {
29
21
  if (shouldSkip("createRelated", context)) {
30
22
  return context;
@@ -34,27 +26,36 @@ export function createRelated<
34
26
 
35
27
  const { items } = getItemsIsArray(context);
36
28
 
37
- let dataToCreate = (
38
- await Promise.all(items.map(async (item) => data(item, context)))
39
- ).filter((x) => !!x);
40
-
41
- if (createItemsInDataArraySeparately) {
42
- dataToCreate = dataToCreate.flat();
43
- }
44
-
45
- if (!dataToCreate || dataToCreate.length <= 0) {
46
- return context;
47
- }
48
-
49
- if (multi) {
50
- await context.app.service(service as string).create(dataToCreate);
51
- } else {
52
- await Promise.all(
53
- dataToCreate.map(async (item) =>
54
- context.app.service(service as string).create(item),
55
- ),
56
- );
57
- }
29
+ const entries = Array.isArray(options) ? options : [options];
30
+
31
+ await Promise.all(
32
+ entries.map(async (entry) => {
33
+ const { data, service, createItemsInDataArraySeparately, multi } =
34
+ entry;
35
+
36
+ let dataToCreate = (
37
+ await Promise.all(items.map(async (item) => data(item, context)))
38
+ ).filter((x) => !!x);
39
+
40
+ if (createItemsInDataArraySeparately) {
41
+ dataToCreate = dataToCreate.flat();
42
+ }
43
+
44
+ if (!dataToCreate || dataToCreate.length <= 0) {
45
+ return context;
46
+ }
47
+
48
+ if (multi) {
49
+ await context.app.service(service as string).create(dataToCreate);
50
+ } else {
51
+ await Promise.all(
52
+ dataToCreate.map(async (item) =>
53
+ context.app.service(service as string).create(item),
54
+ ),
55
+ );
56
+ }
57
+ }),
58
+ );
58
59
 
59
60
  return context;
60
61
  };
@@ -1,10 +1,12 @@
1
1
  import type { HookContext } from "@feathersjs/feathers";
2
2
  import { checkContext } from "feathers-hooks-common";
3
3
  import { getItemsIsArray, shouldSkip } from "../utils";
4
+ import type { KeyOf, MaybeArray } from "../typesInternal";
4
5
 
5
6
  export type OnDeleteAction = "cascade" | "set null";
6
7
 
7
- export interface OnDeleteOptions {
8
+ export interface OnDeleteOptions<Path extends string = string> {
9
+ service: Path;
8
10
  keyThere: string;
9
11
  keyHere: string;
10
12
  onDelete: OnDeleteAction;
@@ -17,22 +19,7 @@ export interface OnDeleteOptions {
17
19
  export function onDelete<
18
20
  S = Record<string, any>,
19
21
  H extends HookContext = HookContext,
20
- >(
21
- service: keyof S,
22
- {
23
- keyThere,
24
- keyHere = "id",
25
- onDelete = "cascade",
26
- blocking = true,
27
- }: OnDeleteOptions,
28
- ) {
29
- if (!service || !keyThere) {
30
- throw "initialize hook 'removeRelated' completely!";
31
- }
32
- if (!["cascade", "set null"].includes(onDelete)) {
33
- throw "onDelete must be 'cascade' or 'set null'";
34
- }
35
-
22
+ >(options: MaybeArray<OnDeleteOptions<KeyOf<S>>>) {
36
23
  return async (context: H) => {
37
24
  if (shouldSkip("onDelete", context)) {
38
25
  return context;
@@ -42,35 +29,47 @@ export function onDelete<
42
29
 
43
30
  const { items } = getItemsIsArray(context);
44
31
 
45
- let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
46
- ids = [...new Set(ids)];
32
+ const entries = Array.isArray(options) ? options : [options];
47
33
 
48
- if (!ids || ids.length <= 0) {
49
- return context;
50
- }
34
+ const promises: Promise<any>[] = [];
51
35
 
52
- const params = {
53
- query: {
54
- [keyThere]: {
55
- $in: ids,
56
- },
57
- },
58
- paginate: false,
59
- };
36
+ entries.forEach(
37
+ async ({ keyHere, keyThere, onDelete, service, blocking }) => {
38
+ let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
39
+ ids = [...new Set(ids)];
60
40
 
61
- let promise;
41
+ if (!ids || ids.length <= 0) {
42
+ return context;
43
+ }
62
44
 
63
- if (onDelete === "cascade") {
64
- promise = context.app.service(service as string).remove(null, params);
65
- } else if (onDelete === "set null") {
66
- const data = { [keyThere]: null };
67
- promise = context.app
68
- .service(service as string)
69
- .patch(null, data, params);
70
- }
45
+ const params = {
46
+ query: {
47
+ [keyThere]: {
48
+ $in: ids,
49
+ },
50
+ },
51
+ paginate: false,
52
+ };
53
+
54
+ let promise: Promise<any> | undefined = undefined;
55
+
56
+ if (onDelete === "cascade") {
57
+ promise = context.app.service(service as string).remove(null, params);
58
+ } else if (onDelete === "set null") {
59
+ const data = { [keyThere]: null };
60
+ promise = context.app
61
+ .service(service as string)
62
+ .patch(null, data, params);
63
+ }
64
+
65
+ if (blocking) {
66
+ promises.push(promise);
67
+ }
68
+ },
69
+ );
71
70
 
72
- if (blocking) {
73
- await promise;
71
+ if (promises.length) {
72
+ await Promise.all(promises);
74
73
  }
75
74
 
76
75
  return context;
@@ -11,6 +11,8 @@ export interface RemoveRelatedOptions<S = Record<string, any>> {
11
11
 
12
12
  /**
13
13
  * hook to remove related items
14
+ *
15
+ * @deprecated use 'onDelete' instead
14
16
  */
15
17
  export function removeRelated<
16
18
  S = Record<string, any>,
@@ -22,3 +22,5 @@ export type ReturnSyncHook<H extends HookContext = HookContext> = (
22
22
  export type ReturnAsyncHook<H extends HookContext = HookContext> = (
23
23
  context: H,
24
24
  ) => Promise<H>;
25
+
26
+ export type KeyOf<T> = Extract<keyof T, string>;
@@ -1,8 +1,10 @@
1
1
  import type { Id, Params } from "@feathersjs/feathers";
2
2
  import { deepEqual } from "fast-equals";
3
+ import type { KeyOf } from "../typesInternal";
3
4
 
4
- export type OptimizeBatchPatchOptions = {
5
- id?: string;
5
+ export type OptimizeBatchPatchOptions<IdKey extends string> = {
6
+ /** the key of the id property */
7
+ id?: IdKey;
6
8
  };
7
9
 
8
10
  export type OptimizeBatchPatchResultItem<
@@ -11,18 +13,26 @@ export type OptimizeBatchPatchResultItem<
11
13
  > = [Id, T, P | undefined];
12
14
 
13
15
  export function optimizeBatchPatch<
14
- T extends Record<string, unknown>,
16
+ T extends Record<string, any>,
17
+ IdKey extends KeyOf<T>,
15
18
  P extends Params,
19
+ R extends Omit<T, IdKey> = Omit<T, IdKey>,
16
20
  >(
17
- items: Map<Id, T>,
18
- options?: OptimizeBatchPatchOptions,
19
- ): OptimizeBatchPatchResultItem<T, P>[] {
20
- const map: { ids: Id[]; data: T }[] = [];
21
+ items: T[],
22
+ options?: OptimizeBatchPatchOptions<IdKey>,
23
+ ): OptimizeBatchPatchResultItem<R, P>[] {
24
+ const map: { ids: Id[]; data: R }[] = [];
21
25
 
22
- const id = options?.id ?? "id";
26
+ const idKey = options?.id ?? "id";
23
27
 
24
- for (const [id, data] of items) {
25
- const index = map.findIndex((item) => deepEqual(item.data, data));
28
+ for (const _data of items) {
29
+ const data = _data as unknown as R;
30
+ const id = _data[idKey];
31
+ delete data[idKey as any];
32
+
33
+ const index = map.findIndex((item) => {
34
+ return deepEqual(item.data, data);
35
+ });
26
36
 
27
37
  if (index === -1) {
28
38
  map.push({ ids: [id], data });
@@ -33,34 +43,56 @@ export function optimizeBatchPatch<
33
43
 
34
44
  return map.map(({ ids, data }) => {
35
45
  return ids.length === 1
36
- ? ([ids[0], data, undefined] as OptimizeBatchPatchResultItem<T, P>)
46
+ ? ([ids[0], data, undefined] as OptimizeBatchPatchResultItem<R, P>)
37
47
  : ([
38
48
  null,
39
49
  data,
40
50
  {
41
51
  query: {
42
- [id]: { $in: ids },
52
+ [idKey]: { $in: ids },
43
53
  },
44
54
  },
45
- ] as OptimizeBatchPatchResultItem<T, P>);
55
+ ] as OptimizeBatchPatchResultItem<R, P>);
46
56
  });
47
57
  }
48
58
 
49
59
  if (import.meta.vitest) {
50
60
  const { it, expect } = import.meta.vitest;
51
61
  it("optimizeBatchPatch", () => {
52
- const items = new Map<Id, Record<string, unknown>>([
53
- ["1", { name: "John" }],
54
- ["2", { name: "Jane" }],
55
- ["3", { name: "John" }],
56
- ["4", { name: "Jane" }],
57
- [5, { name: "Jack" }],
58
- ]);
59
-
60
- expect(optimizeBatchPatch(items)).toEqual([
62
+ expect(
63
+ optimizeBatchPatch(
64
+ [
65
+ { id: "1", name: "John" },
66
+ { id: "2", name: "Jane" },
67
+ { id: "3", name: "John" },
68
+ { id: "4", name: "Jane" },
69
+ { id: 5, name: "Jack" },
70
+ ],
71
+ { id: "id" },
72
+ ),
73
+ ).toEqual([
61
74
  [null, { name: "John" }, { query: { id: { $in: ["1", "3"] } } }],
62
75
  [null, { name: "Jane" }, { query: { id: { $in: ["2", "4"] } } }],
63
76
  [5, { name: "Jack" }, undefined],
64
77
  ]);
65
78
  });
79
+
80
+ it("optimizeBatchPatch with _id", () => {
81
+ expect(
82
+ optimizeBatchPatch(
83
+ [
84
+ { _id: "1", name: "John" },
85
+ { _id: "2", name: "Jane" },
86
+ { _id: "3", name: "John" },
87
+ { _id: "4", name: "Jane" },
88
+ { _id: 5, name: "Jack" },
89
+ ],
90
+ { id: "_id" },
91
+ ),
92
+ ).toEqual([
93
+ [null, { name: "John" }, { query: { _id: { $in: ["1", "3"] } } }],
94
+ [null, { name: "Jane" }, { query: { _id: { $in: ["2", "4"] } } }],
95
+ [5, { name: "Jack" }, undefined],
96
+ ]);
97
+ });
66
98
  }