feathers-utils 4.1.0 → 5.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/dist/index.cjs CHANGED
@@ -607,6 +607,30 @@ const validateQueryProperty = (query, operators = []) => {
607
607
  };
608
608
  };
609
609
 
610
+ function optimizeBatchPatch(items, options) {
611
+ const map = [];
612
+ const id = options?.id ?? "id";
613
+ for (const [id2, data] of items) {
614
+ const index = map.findIndex((item) => fastEquals.deepEqual(item.data, data));
615
+ if (index === -1) {
616
+ map.push({ ids: [id2], data });
617
+ } else {
618
+ map[index].ids.push(id2);
619
+ }
620
+ }
621
+ return map.map(({ ids, data }) => {
622
+ return ids.length === 1 ? [ids[0], data, void 0] : [
623
+ null,
624
+ data,
625
+ {
626
+ query: {
627
+ [id]: { $in: ids }
628
+ }
629
+ }
630
+ ];
631
+ });
632
+ }
633
+
610
634
  function checkMulti() {
611
635
  return (context) => {
612
636
  if (shouldSkip("checkMulti", context)) {
@@ -658,27 +682,30 @@ function createRelated({
658
682
  };
659
683
  }
660
684
 
661
- const forEach = (actionPerItem, _options) => {
662
- const options = {
663
- wait: "parallel",
664
- items: "automatic",
665
- ..._options
666
- };
685
+ const forEach = (actionPerItem, options) => {
686
+ const { wait = "parallel", items: from = "automatic" } = options || {};
667
687
  return async (context) => {
668
- if (shouldSkip("runForItems", context)) {
688
+ if (shouldSkip("forEach", context)) {
669
689
  return context;
670
690
  }
671
- const { items } = getItemsIsArray(context, { from: options.items });
691
+ const { items } = getItemsIsArray(context, { from });
692
+ const forAll = options?.forAll ? await options.forAll(items, context) : {};
672
693
  const promises = [];
694
+ let i = 0;
673
695
  for (const item of items) {
674
- const promise = actionPerItem(item, context);
675
- if (options.wait === "sequential") {
696
+ const promise = actionPerItem(item, {
697
+ context,
698
+ i,
699
+ fromAll: forAll
700
+ });
701
+ if (wait === "sequential") {
676
702
  await promise;
677
703
  } else {
678
704
  promises.push(promise);
679
705
  }
706
+ i++;
680
707
  }
681
- if (options.wait === "parallel") {
708
+ if (wait === "parallel") {
682
709
  await Promise.all(promises);
683
710
  }
684
711
  return context;
@@ -986,6 +1013,7 @@ exports.markHookForSkip = markHookForSkip;
986
1013
  exports.mergeArrays = mergeArrays;
987
1014
  exports.mergeQuery = mergeQuery;
988
1015
  exports.onDelete = onDelete;
1016
+ exports.optimizeBatchPatch = optimizeBatchPatch;
989
1017
  exports.parseFields = parseFields;
990
1018
  exports.pushSet = pushSet;
991
1019
  exports.removeRelated = removeRelated;
package/dist/index.d.cts CHANGED
@@ -36,11 +36,17 @@ interface GetItemsIsArrayResult<T = any> {
36
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
38
 
39
- interface HookForEachOptions {
39
+ type HookForEachOptions<T = any, H = HookContext, R = never> = {
40
40
  wait?: "sequential" | "parallel" | false;
41
41
  items?: GetItemsIsArrayOptions["from"];
42
- }
43
- declare const forEach: <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?: HookForEachOptions) => ReturnAsyncHook<H>;
42
+ forAll?: (items: T[], context: H) => Promisable<R>;
43
+ };
44
+ type ActionPerItem<T, H, R> = (item: T, options: {
45
+ context: H;
46
+ i: number;
47
+ fromAll: R;
48
+ }) => Promisable<any>;
49
+ declare const forEach: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = never>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
44
50
 
45
51
  type OnDeleteAction = "cascade" | "set null";
46
52
  interface OnDeleteOptions {
@@ -234,6 +240,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
234
240
  */
235
241
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
236
242
 
243
+ type OptimizeBatchPatchOptions = {
244
+ id?: string;
245
+ };
246
+ type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
247
+ declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
248
+
237
249
  declare const filterArray: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
238
250
 
239
251
  declare const filterObject: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
@@ -284,4 +296,4 @@ type InferRemoveResultFromPath<App extends Application, Path extends string, IdO
284
296
  type InferDataFromPath<App extends Application, Path extends string, Method extends "create" | "update" | "patch"> = Method extends "create" ? InferCreateDataFromPath<App, Path> : Method extends "update" ? InferUpdateDataFromPath<App, Path> : Method extends "patch" ? InferPatchDataFromPath<App, Path> : never;
285
297
  type InferResultFromPath<App extends Application, Path extends string, Method extends "get" | "find" | "create" | "update" | "patch" | "remove"> = Method extends "get" ? InferGetResultFromPath<App, Path> : Method extends "find" ? InferFindResultFromPath<App, Path> : Method extends "create" ? InferCreateResultFromPath<App, Path> : Method extends "update" ? InferUpdateResultFromPath<App, Path> : Method extends "patch" ? InferPatchResultFromPath<App, Path> : Method extends "remove" ? InferRemoveResultFromPath<App, Path> : never;
286
298
 
287
- export { type ActionOnEmptyIntersect, type CreateRelatedOptions, type DebouncedFunctionApp, type DebouncedService, DebouncedStore, type DebouncedStoreOptions, type FirstLast, type GetItemsIsArrayFrom, type GetItemsIsArrayOptions, type GetItemsIsArrayResult, type GetService, type Handle, type HookForEachOptions, type HookRunPerItemOptions, type HookSetDataOptions, type InferCreateData, type InferCreateDataFromPath, type InferCreateDataSingle, type InferCreateDataSingleFromPath, type InferCreateResult, type InferCreateResultFromPath, type InferCreateResultSingle, type InferCreateResultSingleFromPath, type InferDataFromPath, type InferFindResult, type InferFindResultFromPath, type InferGetResult, type InferGetResultFromPath, type InferPatchData, type InferPatchDataFromPath, type InferPatchResult, type InferPatchResultFromPath, type InferRemoveResult, type InferRemoveResultFromPath, type InferResultFromPath, type InferUpdateData, type InferUpdateDataFromPath, type InferUpdateResult, type InferUpdateResultFromPath, type InitDebounceMixinOptions, type MergeQueryOptions, type OnDeleteAction, type OnDeleteOptions, type Predicate, type PredicateWithContext, type PushSetOptions, type RemoveRelatedOptions, type SetQueryKeySafelyOptions, type ShouldSkipOptions, checkMulti, createRelated, debounceMixin, defineHooks, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
299
+ export { type ActionOnEmptyIntersect, type CreateRelatedOptions, type DebouncedFunctionApp, type DebouncedService, DebouncedStore, type DebouncedStoreOptions, type FirstLast, type GetItemsIsArrayFrom, type GetItemsIsArrayOptions, type GetItemsIsArrayResult, type GetService, type Handle, type HookForEachOptions, type HookRunPerItemOptions, type HookSetDataOptions, type InferCreateData, type InferCreateDataFromPath, type InferCreateDataSingle, type InferCreateDataSingleFromPath, type InferCreateResult, type InferCreateResultFromPath, type InferCreateResultSingle, type InferCreateResultSingleFromPath, type InferDataFromPath, type InferFindResult, type InferFindResultFromPath, type InferGetResult, type InferGetResultFromPath, type InferPatchData, type InferPatchDataFromPath, type InferPatchResult, type InferPatchResultFromPath, type InferRemoveResult, type InferRemoveResultFromPath, type InferResultFromPath, type InferUpdateData, type InferUpdateDataFromPath, type InferUpdateResult, type InferUpdateResultFromPath, type InitDebounceMixinOptions, type MergeQueryOptions, type OnDeleteAction, type OnDeleteOptions, type OptimizeBatchPatchOptions, type OptimizeBatchPatchResultItem, type Predicate, type PredicateWithContext, type PushSetOptions, type RemoveRelatedOptions, type SetQueryKeySafelyOptions, type ShouldSkipOptions, checkMulti, createRelated, debounceMixin, defineHooks, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, optimizeBatchPatch, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
package/dist/index.d.mts CHANGED
@@ -36,11 +36,17 @@ interface GetItemsIsArrayResult<T = any> {
36
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
38
 
39
- interface HookForEachOptions {
39
+ type HookForEachOptions<T = any, H = HookContext, R = never> = {
40
40
  wait?: "sequential" | "parallel" | false;
41
41
  items?: GetItemsIsArrayOptions["from"];
42
- }
43
- declare const forEach: <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?: HookForEachOptions) => ReturnAsyncHook<H>;
42
+ forAll?: (items: T[], context: H) => Promisable<R>;
43
+ };
44
+ type ActionPerItem<T, H, R> = (item: T, options: {
45
+ context: H;
46
+ i: number;
47
+ fromAll: R;
48
+ }) => Promisable<any>;
49
+ declare const forEach: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = never>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
44
50
 
45
51
  type OnDeleteAction = "cascade" | "set null";
46
52
  interface OnDeleteOptions {
@@ -234,6 +240,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
234
240
  */
235
241
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
236
242
 
243
+ type OptimizeBatchPatchOptions = {
244
+ id?: string;
245
+ };
246
+ type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
247
+ declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
248
+
237
249
  declare const filterArray: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
238
250
 
239
251
  declare const filterObject: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
@@ -284,4 +296,4 @@ type InferRemoveResultFromPath<App extends Application, Path extends string, IdO
284
296
  type InferDataFromPath<App extends Application, Path extends string, Method extends "create" | "update" | "patch"> = Method extends "create" ? InferCreateDataFromPath<App, Path> : Method extends "update" ? InferUpdateDataFromPath<App, Path> : Method extends "patch" ? InferPatchDataFromPath<App, Path> : never;
285
297
  type InferResultFromPath<App extends Application, Path extends string, Method extends "get" | "find" | "create" | "update" | "patch" | "remove"> = Method extends "get" ? InferGetResultFromPath<App, Path> : Method extends "find" ? InferFindResultFromPath<App, Path> : Method extends "create" ? InferCreateResultFromPath<App, Path> : Method extends "update" ? InferUpdateResultFromPath<App, Path> : Method extends "patch" ? InferPatchResultFromPath<App, Path> : Method extends "remove" ? InferRemoveResultFromPath<App, Path> : never;
286
298
 
287
- export { type ActionOnEmptyIntersect, type CreateRelatedOptions, type DebouncedFunctionApp, type DebouncedService, DebouncedStore, type DebouncedStoreOptions, type FirstLast, type GetItemsIsArrayFrom, type GetItemsIsArrayOptions, type GetItemsIsArrayResult, type GetService, type Handle, type HookForEachOptions, type HookRunPerItemOptions, type HookSetDataOptions, type InferCreateData, type InferCreateDataFromPath, type InferCreateDataSingle, type InferCreateDataSingleFromPath, type InferCreateResult, type InferCreateResultFromPath, type InferCreateResultSingle, type InferCreateResultSingleFromPath, type InferDataFromPath, type InferFindResult, type InferFindResultFromPath, type InferGetResult, type InferGetResultFromPath, type InferPatchData, type InferPatchDataFromPath, type InferPatchResult, type InferPatchResultFromPath, type InferRemoveResult, type InferRemoveResultFromPath, type InferResultFromPath, type InferUpdateData, type InferUpdateDataFromPath, type InferUpdateResult, type InferUpdateResultFromPath, type InitDebounceMixinOptions, type MergeQueryOptions, type OnDeleteAction, type OnDeleteOptions, type Predicate, type PredicateWithContext, type PushSetOptions, type RemoveRelatedOptions, type SetQueryKeySafelyOptions, type ShouldSkipOptions, checkMulti, createRelated, debounceMixin, defineHooks, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
299
+ export { type ActionOnEmptyIntersect, type CreateRelatedOptions, type DebouncedFunctionApp, type DebouncedService, DebouncedStore, type DebouncedStoreOptions, type FirstLast, type GetItemsIsArrayFrom, type GetItemsIsArrayOptions, type GetItemsIsArrayResult, type GetService, type Handle, type HookForEachOptions, type HookRunPerItemOptions, type HookSetDataOptions, type InferCreateData, type InferCreateDataFromPath, type InferCreateDataSingle, type InferCreateDataSingleFromPath, type InferCreateResult, type InferCreateResultFromPath, type InferCreateResultSingle, type InferCreateResultSingleFromPath, type InferDataFromPath, type InferFindResult, type InferFindResultFromPath, type InferGetResult, type InferGetResultFromPath, type InferPatchData, type InferPatchDataFromPath, type InferPatchResult, type InferPatchResultFromPath, type InferRemoveResult, type InferRemoveResultFromPath, type InferResultFromPath, type InferUpdateData, type InferUpdateDataFromPath, type InferUpdateResult, type InferUpdateResultFromPath, type InitDebounceMixinOptions, type MergeQueryOptions, type OnDeleteAction, type OnDeleteOptions, type OptimizeBatchPatchOptions, type OptimizeBatchPatchResultItem, type Predicate, type PredicateWithContext, type PushSetOptions, type RemoveRelatedOptions, type SetQueryKeySafelyOptions, type ShouldSkipOptions, checkMulti, createRelated, debounceMixin, defineHooks, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, optimizeBatchPatch, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
package/dist/index.d.ts CHANGED
@@ -36,11 +36,17 @@ interface GetItemsIsArrayResult<T = any> {
36
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
38
 
39
- interface HookForEachOptions {
39
+ type HookForEachOptions<T = any, H = HookContext, R = never> = {
40
40
  wait?: "sequential" | "parallel" | false;
41
41
  items?: GetItemsIsArrayOptions["from"];
42
- }
43
- declare const forEach: <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?: HookForEachOptions) => ReturnAsyncHook<H>;
42
+ forAll?: (items: T[], context: H) => Promisable<R>;
43
+ };
44
+ type ActionPerItem<T, H, R> = (item: T, options: {
45
+ context: H;
46
+ i: number;
47
+ fromAll: R;
48
+ }) => Promisable<any>;
49
+ declare const forEach: <H extends HookContext<_feathersjs_feathers.Application<any, any>, any> = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = never>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
44
50
 
45
51
  type OnDeleteAction = "cascade" | "set null";
46
52
  interface OnDeleteOptions {
@@ -234,6 +240,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
234
240
  */
235
241
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
236
242
 
243
+ type OptimizeBatchPatchOptions = {
244
+ id?: string;
245
+ };
246
+ type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
247
+ declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
248
+
237
249
  declare const filterArray: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
238
250
 
239
251
  declare const filterObject: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
@@ -284,4 +296,4 @@ type InferRemoveResultFromPath<App extends Application, Path extends string, IdO
284
296
  type InferDataFromPath<App extends Application, Path extends string, Method extends "create" | "update" | "patch"> = Method extends "create" ? InferCreateDataFromPath<App, Path> : Method extends "update" ? InferUpdateDataFromPath<App, Path> : Method extends "patch" ? InferPatchDataFromPath<App, Path> : never;
285
297
  type InferResultFromPath<App extends Application, Path extends string, Method extends "get" | "find" | "create" | "update" | "patch" | "remove"> = Method extends "get" ? InferGetResultFromPath<App, Path> : Method extends "find" ? InferFindResultFromPath<App, Path> : Method extends "create" ? InferCreateResultFromPath<App, Path> : Method extends "update" ? InferUpdateResultFromPath<App, Path> : Method extends "patch" ? InferPatchResultFromPath<App, Path> : Method extends "remove" ? InferRemoveResultFromPath<App, Path> : never;
286
298
 
287
- export { type ActionOnEmptyIntersect, type CreateRelatedOptions, type DebouncedFunctionApp, type DebouncedService, DebouncedStore, type DebouncedStoreOptions, type FirstLast, type GetItemsIsArrayFrom, type GetItemsIsArrayOptions, type GetItemsIsArrayResult, type GetService, type Handle, type HookForEachOptions, type HookRunPerItemOptions, type HookSetDataOptions, type InferCreateData, type InferCreateDataFromPath, type InferCreateDataSingle, type InferCreateDataSingleFromPath, type InferCreateResult, type InferCreateResultFromPath, type InferCreateResultSingle, type InferCreateResultSingleFromPath, type InferDataFromPath, type InferFindResult, type InferFindResultFromPath, type InferGetResult, type InferGetResultFromPath, type InferPatchData, type InferPatchDataFromPath, type InferPatchResult, type InferPatchResultFromPath, type InferRemoveResult, type InferRemoveResultFromPath, type InferResultFromPath, type InferUpdateData, type InferUpdateDataFromPath, type InferUpdateResult, type InferUpdateResultFromPath, type InitDebounceMixinOptions, type MergeQueryOptions, type OnDeleteAction, type OnDeleteOptions, type Predicate, type PredicateWithContext, type PushSetOptions, type RemoveRelatedOptions, type SetQueryKeySafelyOptions, type ShouldSkipOptions, checkMulti, createRelated, debounceMixin, defineHooks, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
299
+ export { type ActionOnEmptyIntersect, type CreateRelatedOptions, type DebouncedFunctionApp, type DebouncedService, DebouncedStore, type DebouncedStoreOptions, type FirstLast, type GetItemsIsArrayFrom, type GetItemsIsArrayOptions, type GetItemsIsArrayResult, type GetService, type Handle, type HookForEachOptions, type HookRunPerItemOptions, type HookSetDataOptions, type InferCreateData, type InferCreateDataFromPath, type InferCreateDataSingle, type InferCreateDataSingleFromPath, type InferCreateResult, type InferCreateResultFromPath, type InferCreateResultSingle, type InferCreateResultSingleFromPath, type InferDataFromPath, type InferFindResult, type InferFindResultFromPath, type InferGetResult, type InferGetResultFromPath, type InferPatchData, type InferPatchDataFromPath, type InferPatchResult, type InferPatchResultFromPath, type InferRemoveResult, type InferRemoveResultFromPath, type InferResultFromPath, type InferUpdateData, type InferUpdateDataFromPath, type InferUpdateResult, type InferUpdateResultFromPath, type InitDebounceMixinOptions, type MergeQueryOptions, type OnDeleteAction, type OnDeleteOptions, type OptimizeBatchPatchOptions, type OptimizeBatchPatchResultItem, type Predicate, type PredicateWithContext, type PushSetOptions, type RemoveRelatedOptions, type SetQueryKeySafelyOptions, type ShouldSkipOptions, checkMulti, createRelated, debounceMixin, defineHooks, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, optimizeBatchPatch, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
package/dist/index.mjs CHANGED
@@ -593,6 +593,30 @@ const validateQueryProperty = (query, operators = []) => {
593
593
  };
594
594
  };
595
595
 
596
+ function optimizeBatchPatch(items, options) {
597
+ const map = [];
598
+ const id = options?.id ?? "id";
599
+ for (const [id2, data] of items) {
600
+ const index = map.findIndex((item) => deepEqual(item.data, data));
601
+ if (index === -1) {
602
+ map.push({ ids: [id2], data });
603
+ } else {
604
+ map[index].ids.push(id2);
605
+ }
606
+ }
607
+ return map.map(({ ids, data }) => {
608
+ return ids.length === 1 ? [ids[0], data, void 0] : [
609
+ null,
610
+ data,
611
+ {
612
+ query: {
613
+ [id]: { $in: ids }
614
+ }
615
+ }
616
+ ];
617
+ });
618
+ }
619
+
596
620
  function checkMulti() {
597
621
  return (context) => {
598
622
  if (shouldSkip("checkMulti", context)) {
@@ -644,27 +668,30 @@ function createRelated({
644
668
  };
645
669
  }
646
670
 
647
- const forEach = (actionPerItem, _options) => {
648
- const options = {
649
- wait: "parallel",
650
- items: "automatic",
651
- ..._options
652
- };
671
+ const forEach = (actionPerItem, options) => {
672
+ const { wait = "parallel", items: from = "automatic" } = options || {};
653
673
  return async (context) => {
654
- if (shouldSkip("runForItems", context)) {
674
+ if (shouldSkip("forEach", context)) {
655
675
  return context;
656
676
  }
657
- const { items } = getItemsIsArray(context, { from: options.items });
677
+ const { items } = getItemsIsArray(context, { from });
678
+ const forAll = options?.forAll ? await options.forAll(items, context) : {};
658
679
  const promises = [];
680
+ let i = 0;
659
681
  for (const item of items) {
660
- const promise = actionPerItem(item, context);
661
- if (options.wait === "sequential") {
682
+ const promise = actionPerItem(item, {
683
+ context,
684
+ i,
685
+ fromAll: forAll
686
+ });
687
+ if (wait === "sequential") {
662
688
  await promise;
663
689
  } else {
664
690
  promises.push(promise);
665
691
  }
692
+ i++;
666
693
  }
667
- if (options.wait === "parallel") {
694
+ if (wait === "parallel") {
668
695
  await Promise.all(promises);
669
696
  }
670
697
  return context;
@@ -954,4 +981,4 @@ const filterObject = (...keys) => {
954
981
  return result;
955
982
  };
956
983
 
957
- export { DebouncedStore, checkMulti, createRelated, debounceMixin, defineHooks, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
984
+ export { DebouncedStore, checkMulti, createRelated, debounceMixin, defineHooks, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, optimizeBatchPatch, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feathers-utils",
3
- "version": "4.1.0",
3
+ "version": "5.0.0",
4
4
  "description": "Some utils for projects using '@feathersjs/feathers'",
5
5
  "author": "fratzinger",
6
6
  "repository": {
@@ -5,42 +5,63 @@ import type { HookContext } from "@feathersjs/feathers";
5
5
  import type { GetItemsIsArrayOptions } from "../utils/getItemsIsArray";
6
6
  import { getItemsIsArray } from "../utils/getItemsIsArray";
7
7
 
8
- export interface HookForEachOptions {
8
+ export type HookForEachOptions<T = any, H = HookContext, R = never> = {
9
9
  wait?: "sequential" | "parallel" | false;
10
10
  items?: GetItemsIsArrayOptions["from"];
11
- }
11
+ forAll?: (items: T[], context: H) => Promisable<R>;
12
+ };
13
+
14
+ type ActionPerItem<T, H, R> = (
15
+ item: T,
16
+ options: {
17
+ context: H;
18
+ i: number;
19
+ fromAll: R;
20
+ },
21
+ ) => Promisable<any>;
12
22
 
13
- export const forEach = <H extends HookContext = HookContext, T = any>(
14
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
- actionPerItem: (item: T, context: H) => Promisable<any>,
16
- _options?: HookForEachOptions,
23
+ export const forEach = <
24
+ H extends HookContext = HookContext,
25
+ T = any,
26
+ R = never,
27
+ >(
28
+ actionPerItem: ActionPerItem<T, H, R>,
29
+ options?: HookForEachOptions<T, H, R>,
17
30
  ): ReturnAsyncHook<H> => {
18
- const options: Required<HookForEachOptions> = {
19
- wait: "parallel",
20
- items: "automatic",
21
- ..._options,
22
- };
31
+ const { wait = "parallel", items: from = "automatic" } = options || {};
23
32
 
24
33
  return async (context: H): Promise<H> => {
25
- if (shouldSkip("runForItems", context)) {
34
+ if (shouldSkip("forEach", context)) {
26
35
  return context;
27
36
  }
28
37
 
29
- const { items } = getItemsIsArray(context, { from: options.items });
38
+ const { items } = getItemsIsArray(context, { from });
39
+
40
+ const forAll = options?.forAll
41
+ ? await options.forAll(items, context)
42
+ : ({} as R);
30
43
 
31
44
  const promises: Promise<any>[] = [];
32
45
 
46
+ let i = 0;
47
+
33
48
  for (const item of items) {
34
- const promise = actionPerItem(item, context);
49
+ const promise = actionPerItem(item, {
50
+ context,
51
+ i,
52
+ fromAll: forAll,
53
+ });
35
54
 
36
- if (options.wait === "sequential") {
55
+ if (wait === "sequential") {
37
56
  await promise;
38
57
  } else {
39
58
  promises.push(promise);
40
59
  }
60
+
61
+ i++;
41
62
  }
42
63
 
43
- if (options.wait === "parallel") {
64
+ if (wait === "parallel") {
44
65
  await Promise.all(promises);
45
66
  }
46
67
 
@@ -12,3 +12,4 @@ export * from "./setResultEmpty";
12
12
  export * from "./shouldSkip";
13
13
  export * from "./toJSON";
14
14
  export * from "./validateQueryProperty";
15
+ export * from "./optimizeBatchPatch";
@@ -0,0 +1,66 @@
1
+ import type { Id, Params } from "@feathersjs/feathers";
2
+ import { deepEqual } from "fast-equals";
3
+
4
+ export type OptimizeBatchPatchOptions = {
5
+ id?: string;
6
+ };
7
+
8
+ export type OptimizeBatchPatchResultItem<
9
+ T = Record<string, unknown>,
10
+ P = Params,
11
+ > = [Id, T, P | undefined];
12
+
13
+ export function optimizeBatchPatch<
14
+ T extends Record<string, unknown>,
15
+ P extends Params,
16
+ >(
17
+ items: Map<Id, T>,
18
+ options?: OptimizeBatchPatchOptions,
19
+ ): OptimizeBatchPatchResultItem<T, P>[] {
20
+ const map: { ids: Id[]; data: T }[] = [];
21
+
22
+ const id = options?.id ?? "id";
23
+
24
+ for (const [id, data] of items) {
25
+ const index = map.findIndex((item) => deepEqual(item.data, data));
26
+
27
+ if (index === -1) {
28
+ map.push({ ids: [id], data });
29
+ } else {
30
+ map[index].ids.push(id);
31
+ }
32
+ }
33
+
34
+ return map.map(({ ids, data }) => {
35
+ return ids.length === 1
36
+ ? ([ids[0], data, undefined] as OptimizeBatchPatchResultItem<T, P>)
37
+ : ([
38
+ null,
39
+ data,
40
+ {
41
+ query: {
42
+ [id]: { $in: ids },
43
+ },
44
+ },
45
+ ] as OptimizeBatchPatchResultItem<T, P>);
46
+ });
47
+ }
48
+
49
+ if (import.meta.vitest) {
50
+ const { it, expect } = import.meta.vitest;
51
+ 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([
61
+ [null, { name: "John" }, { query: { id: { $in: ["1", "3"] } } }],
62
+ [null, { name: "Jane" }, { query: { id: { $in: ["2", "4"] } } }],
63
+ [5, { name: "Jack" }, undefined],
64
+ ]);
65
+ });
66
+ }