feathers-utils 4.0.0 → 4.2.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
@@ -36,6 +36,7 @@ npm i feathers-utils
36
36
 
37
37
  ### Utils
38
38
 
39
+ - `defineHooks`
39
40
  - `filterQuery`
40
41
  - `getItemsIsArray(context)`: returns `{ items: any[], isArray: boolean }`
41
42
  - `getPaginate`
package/dist/index.cjs CHANGED
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  const errors = require('@feathersjs/errors');
4
+ const _isEqual = require('lodash/isEqual.js');
5
+ const _get = require('lodash/get.js');
6
+ const _set = require('lodash/set.js');
4
7
  const _merge = require('lodash/merge.js');
5
8
  const _isEmpty = require('lodash/isEmpty.js');
6
- const _get = require('lodash/get.js');
7
9
  const _has = require('lodash/has.js');
8
- const _set = require('lodash/set.js');
9
10
  const _uniqWith = require('lodash/uniqWith.js');
10
11
  const fastEquals = require('fast-equals');
11
- const _isEqual = require('lodash/isEqual.js');
12
12
  const commons = require('@feathersjs/commons');
13
13
  const feathersHooksCommon = require('feathers-hooks-common');
14
14
  const _debounce = require('lodash/debounce.js');
@@ -16,16 +16,130 @@ const _isObject = require('lodash/isObject.js');
16
16
 
17
17
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
18
18
 
19
+ const _isEqual__default = /*#__PURE__*/_interopDefaultCompat(_isEqual);
20
+ const _get__default = /*#__PURE__*/_interopDefaultCompat(_get);
21
+ const _set__default = /*#__PURE__*/_interopDefaultCompat(_set);
19
22
  const _merge__default = /*#__PURE__*/_interopDefaultCompat(_merge);
20
23
  const _isEmpty__default = /*#__PURE__*/_interopDefaultCompat(_isEmpty);
21
- const _get__default = /*#__PURE__*/_interopDefaultCompat(_get);
22
24
  const _has__default = /*#__PURE__*/_interopDefaultCompat(_has);
23
- const _set__default = /*#__PURE__*/_interopDefaultCompat(_set);
24
25
  const _uniqWith__default = /*#__PURE__*/_interopDefaultCompat(_uniqWith);
25
- const _isEqual__default = /*#__PURE__*/_interopDefaultCompat(_isEqual);
26
26
  const _debounce__default = /*#__PURE__*/_interopDefaultCompat(_debounce);
27
27
  const _isObject__default = /*#__PURE__*/_interopDefaultCompat(_isObject);
28
28
 
29
+ function defineHooks(hooks) {
30
+ return hooks;
31
+ }
32
+
33
+ function filterQuery(providedQuery) {
34
+ providedQuery ?? (providedQuery = {});
35
+ const { $select, $limit, $skip, $sort, ...query } = providedQuery;
36
+ const result = { query };
37
+ if ("$select" in providedQuery) {
38
+ result.$select = $select;
39
+ }
40
+ if ("$limit" in providedQuery) {
41
+ result.$limit = $limit;
42
+ }
43
+ if ("$skip" in providedQuery) {
44
+ result.$skip = $skip;
45
+ }
46
+ if ("$sort" in providedQuery) {
47
+ result.$sort = $sort;
48
+ }
49
+ return result;
50
+ }
51
+
52
+ const getItemsIsArray = (context, options) => {
53
+ const { from = "automatic" } = options || {};
54
+ let itemOrItems;
55
+ if (from === "automatic") {
56
+ itemOrItems = context.type === "before" ? context.data : context.result;
57
+ } else if (from === "data") {
58
+ itemOrItems = context.data;
59
+ } else if (from === "result") {
60
+ itemOrItems = context.result;
61
+ }
62
+ if ((from === "automatic" || from === "result") && context.type === "after") {
63
+ itemOrItems = itemOrItems && context.method === "find" ? itemOrItems.data || itemOrItems : itemOrItems;
64
+ }
65
+ const isArray = Array.isArray(itemOrItems);
66
+ return {
67
+ items: isArray ? itemOrItems : itemOrItems != null ? [itemOrItems] : [],
68
+ isArray
69
+ };
70
+ };
71
+
72
+ const hasOwnProperty = (obj, ...keys) => {
73
+ return keys.some((x) => Object.prototype.hasOwnProperty.call(obj, x));
74
+ };
75
+ const isPlainObject$1 = (value) => value && [void 0, Object].includes(value.constructor);
76
+
77
+ const getPaginate = (context) => {
78
+ if (hasOwnProperty(context.params, "paginate")) {
79
+ return context.params.paginate || void 0;
80
+ }
81
+ if (context.params.paginate === false) {
82
+ return void 0;
83
+ }
84
+ let options = context.service?.options || {};
85
+ options = {
86
+ ...options,
87
+ ...context.params.adapter
88
+ };
89
+ return options.paginate || void 0;
90
+ };
91
+
92
+ const isMulti = (context) => {
93
+ const { method } = context;
94
+ if (method === "find") {
95
+ return true;
96
+ } else if (["patch", "remove"].includes(method)) {
97
+ return context.id == null;
98
+ } else if (method === "create") {
99
+ const items = context.type === "before" ? context.data : context.result;
100
+ return Array.isArray(items);
101
+ } else if (["get", "update"].includes(method)) {
102
+ return false;
103
+ }
104
+ return false;
105
+ };
106
+
107
+ const isPaginated = (context) => {
108
+ if (context.params.paginate === false || context.method !== "find") {
109
+ return false;
110
+ }
111
+ const paginate = getPaginate(context);
112
+ return !!paginate;
113
+ };
114
+
115
+ const pushSet = (obj, path, val, options) => {
116
+ options = options || {};
117
+ let arr = _get__default(obj, path);
118
+ if (!arr || !Array.isArray(arr)) {
119
+ arr = [val];
120
+ _set__default(obj, path, arr);
121
+ return arr;
122
+ } else {
123
+ if (options.unique && arr.some((x) => _isEqual__default(x, val))) {
124
+ return arr;
125
+ }
126
+ arr.push(val);
127
+ return arr;
128
+ }
129
+ };
130
+
131
+ function markHookForSkip(hookName, type, context) {
132
+ context = context || {};
133
+ const params = context.params || {};
134
+ const types = Array.isArray(type) ? type : [type];
135
+ types.forEach((t) => {
136
+ const combinedName = t === "all" ? hookName : `${type}:${hookName}`;
137
+ pushSet(params, ["skipHooks"], combinedName, { unique: true });
138
+ });
139
+ context.params = params;
140
+ return context;
141
+ }
142
+
29
143
  function mergeArrays(targetArr, sourceArr, handle, prependKey, actionOnEmptyIntersect) {
30
144
  if (!sourceArr && !targetArr) {
31
145
  return;
@@ -65,11 +179,6 @@ function mergeArrays(targetArr, sourceArr, handle, prependKey, actionOnEmptyInte
65
179
  return void 0;
66
180
  }
67
181
 
68
- const hasOwnProperty = (obj, ...keys) => {
69
- return keys.some((x) => Object.prototype.hasOwnProperty.call(obj, x));
70
- };
71
- const isPlainObject$1 = (value) => value && [void 0, Object].includes(value.constructor);
72
-
73
182
  function handleArray(target, source, key, options) {
74
183
  const targetVal = _get__default(target, key);
75
184
  const sourceVal = _get__default(source, key);
@@ -352,25 +461,6 @@ function areQueriesOverlapping(target, source) {
352
461
  return false;
353
462
  }
354
463
 
355
- function filterQuery(providedQuery) {
356
- providedQuery ?? (providedQuery = {});
357
- const { $select, $limit, $skip, $sort, ...query } = providedQuery;
358
- const result = { query };
359
- if ("$select" in providedQuery) {
360
- result.$select = $select;
361
- }
362
- if ("$limit" in providedQuery) {
363
- result.$limit = $limit;
364
- }
365
- if ("$skip" in providedQuery) {
366
- result.$skip = $skip;
367
- }
368
- if ("$sort" in providedQuery) {
369
- result.$sort = $sort;
370
- }
371
- return result;
372
- }
373
-
374
464
  function mergeQuery(target, source, _options) {
375
465
  const options = makeDefaultOptions$1(_options);
376
466
  const { query: targetQuery, ...targetFilters } = filterQuery(target);
@@ -414,92 +504,40 @@ function mergeQuery(target, source, _options) {
414
504
  };
415
505
  }
416
506
 
417
- const getItemsIsArray = (context, options) => {
418
- const { from = "automatic" } = options || {};
419
- let itemOrItems;
420
- if (from === "automatic") {
421
- itemOrItems = context.type === "before" ? context.data : context.result;
422
- } else if (from === "data") {
423
- itemOrItems = context.data;
424
- } else if (from === "result") {
425
- itemOrItems = context.result;
426
- }
427
- if ((from === "automatic" || from === "result") && context.type === "after") {
428
- itemOrItems = itemOrItems && context.method === "find" ? itemOrItems.data || itemOrItems : itemOrItems;
429
- }
430
- const isArray = Array.isArray(itemOrItems);
431
- return {
432
- items: isArray ? itemOrItems : itemOrItems != null ? [itemOrItems] : [],
433
- isArray
434
- };
435
- };
436
-
437
- const getPaginate = (context) => {
438
- if (hasOwnProperty(context.params, "paginate")) {
439
- return context.params.paginate || void 0;
440
- }
441
- if (context.params.paginate === false) {
442
- return void 0;
507
+ const setQueryKeySafely = (params, key, value, operator = "$eq", options) => {
508
+ var _a;
509
+ const { mutate = false } = options || {};
510
+ if (!mutate) {
511
+ params = structuredClone(params);
443
512
  }
444
- let options = context.service?.options || {};
445
- options = {
446
- ...options,
447
- ...context.params.adapter
448
- };
449
- return options.paginate || void 0;
450
- };
451
-
452
- const isMulti = (context) => {
453
- const { method } = context;
454
- if (method === "find") {
455
- return true;
456
- } else if (["patch", "remove"].includes(method)) {
457
- return context.id == null;
458
- } else if (method === "create") {
459
- const items = context.type === "before" ? context.data : context.result;
460
- return Array.isArray(items);
461
- } else if (["get", "update"].includes(method)) {
462
- return false;
513
+ if (!params.query) {
514
+ params.query = {};
463
515
  }
464
- return false;
465
- };
466
-
467
- const isPaginated = (context) => {
468
- if (context.params.paginate === false || context.method !== "find") {
469
- return false;
516
+ if (!(key in params.query)) {
517
+ if (operator === "$eq") {
518
+ params.query[key] = value;
519
+ } else {
520
+ params.query[key] = {
521
+ [operator]: value
522
+ };
523
+ }
524
+ return params;
470
525
  }
471
- const paginate = getPaginate(context);
472
- return !!paginate;
473
- };
474
-
475
- const pushSet = (obj, path, val, options) => {
476
- options = options || {};
477
- let arr = _get__default(obj, path);
478
- if (!arr || !Array.isArray(arr)) {
479
- arr = [val];
480
- _set__default(obj, path, arr);
481
- return arr;
526
+ if (isPlainObject$1(params.query[key]) && !(operator in params.query[key])) {
527
+ params.query[key][operator] = value;
482
528
  } else {
483
- if (options.unique && arr.some((x) => _isEqual__default(x, val))) {
484
- return arr;
485
- }
486
- arr.push(val);
487
- return arr;
529
+ (_a = params.query).$and ?? (_a.$and = []);
530
+ params.query.$and.push(
531
+ operator === "$eq" ? { [key]: value } : {
532
+ [key]: {
533
+ [operator]: value
534
+ }
535
+ }
536
+ );
488
537
  }
538
+ return params;
489
539
  };
490
540
 
491
- function markHookForSkip(hookName, type, context) {
492
- context = context || {};
493
- const params = context.params || {};
494
- const types = Array.isArray(type) ? type : [type];
495
- types.forEach((t) => {
496
- const combinedName = t === "all" ? hookName : `${type}:${hookName}`;
497
- pushSet(params, ["skipHooks"], combinedName, { unique: true });
498
- });
499
- context.params = params;
500
- return context;
501
- }
502
-
503
541
  const setResultEmpty = (context) => {
504
542
  if (context.result) {
505
543
  return context;
@@ -543,6 +581,13 @@ const shouldSkip = (hookName, context, options) => {
543
581
  return false;
544
582
  };
545
583
 
584
+ const toJSON = (context) => {
585
+ if (context.toJSON) {
586
+ return context.toJSON();
587
+ }
588
+ return context;
589
+ };
590
+
546
591
  const isPlainObject = (value) => commons._.isObject(value) && value.constructor === {}.constructor;
547
592
  const validateQueryProperty = (query, operators = []) => {
548
593
  if (!isPlainObject(query)) {
@@ -562,46 +607,29 @@ const validateQueryProperty = (query, operators = []) => {
562
607
  };
563
608
  };
564
609
 
565
- const toJSON = (context) => {
566
- if (context.toJSON) {
567
- return context.toJSON();
568
- }
569
- return context;
570
- };
571
-
572
- const setQueryKeySafely = (params, key, value, operator = "$eq", options) => {
573
- var _a;
574
- const { mutate = false } = options || {};
575
- if (!mutate) {
576
- params = structuredClone(params);
577
- }
578
- if (!params.query) {
579
- params.query = {};
580
- }
581
- if (!(key in params.query)) {
582
- if (operator === "$eq") {
583
- params.query[key] = value;
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 });
584
617
  } else {
585
- params.query[key] = {
586
- [operator]: value
587
- };
618
+ map[index].ids.push(id2);
588
619
  }
589
- return params;
590
620
  }
591
- if (isPlainObject$1(params.query[key]) && !(operator in params.query[key])) {
592
- params.query[key][operator] = value;
593
- } else {
594
- (_a = params.query).$and ?? (_a.$and = []);
595
- params.query.$and.push(
596
- operator === "$eq" ? { [key]: value } : {
597
- [key]: {
598
- [operator]: value
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 }
599
628
  }
600
629
  }
601
- );
602
- }
603
- return params;
604
- };
630
+ ];
631
+ });
632
+ }
605
633
 
606
634
  function checkMulti() {
607
635
  return (context) => {
@@ -968,6 +996,7 @@ exports.DebouncedStore = DebouncedStore;
968
996
  exports.checkMulti = checkMulti;
969
997
  exports.createRelated = createRelated;
970
998
  exports.debounceMixin = debounceMixin;
999
+ exports.defineHooks = defineHooks;
971
1000
  exports.filterArray = filterArray;
972
1001
  exports.filterObject = filterObject;
973
1002
  exports.filterQuery = filterQuery;
@@ -981,6 +1010,7 @@ exports.markHookForSkip = markHookForSkip;
981
1010
  exports.mergeArrays = mergeArrays;
982
1011
  exports.mergeQuery = mergeQuery;
983
1012
  exports.onDelete = onDelete;
1013
+ exports.optimizeBatchPatch = optimizeBatchPatch;
984
1014
  exports.parseFields = parseFields;
985
1015
  exports.pushSet = pushSet;
986
1016
  exports.removeRelated = removeRelated;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _feathersjs_feathers from '@feathersjs/feathers';
2
- import { HookContext, Application, Id, Query, Params } from '@feathersjs/feathers';
2
+ import { HookContext, Application, Id, HookOptions, Query, Params } from '@feathersjs/feathers';
3
3
  import { HookContext as HookContext$1, Application as Application$1 } from '@feathersjs/feathers/lib';
4
4
  import { PropertyPath, DebouncedFunc } from 'lodash';
5
5
  import { PaginationOptions, FilterQueryOptions } from '@feathersjs/adapter-commons';
@@ -125,28 +125,14 @@ declare class DebouncedStore {
125
125
 
126
126
  declare function debounceMixin(options?: Partial<InitDebounceMixinOptions>): (app: Application$1) => void;
127
127
 
128
- type Handle = "target" | "source" | "combine" | "intersect" | "intersectOrFull";
129
- type FirstLast = "first" | "last";
130
- type ActionOnEmptyIntersect = (target: unknown, source: unknown, prependKey: Path) => void;
131
- interface MergeQueryOptions {
132
- defaultHandle: Handle;
133
- actionOnEmptyIntersect: ActionOnEmptyIntersect;
134
- useLogicalConjunction: boolean;
135
- handle?: {
136
- [key: string]: Handle;
137
- };
138
- }
139
-
140
- declare function mergeArrays<T>(targetArr: T[] | undefined, sourceArr: T[] | undefined, handle: Handle, prependKey?: Path, actionOnEmptyIntersect?: ActionOnEmptyIntersect): T[] | undefined;
141
-
142
- /**
143
- * Merges two queries into one.
144
- * @param target Query to be merged into
145
- * @param source Query to be merged from
146
- * @param _options
147
- * @returns Query
148
- */
149
- declare function mergeQuery(target: Query, source: Query, _options?: Partial<MergeQueryOptions>): Query;
128
+ declare function defineHooks<A extends Application = Application, S = {
129
+ find: any;
130
+ get: any;
131
+ create: any;
132
+ update: any;
133
+ patch: any;
134
+ remove: any;
135
+ }, Options = HookOptions<A, S>>(hooks: Options): Options;
150
136
 
151
137
  type FilterQueryResult<Q extends Query> = {
152
138
  $select: Q["$select"] extends any ? Q["$select"] : never;
@@ -192,6 +178,29 @@ declare const isPaginated: <H extends HookContext<_feathersjs_feathers.Applicati
192
178
  */
193
179
  declare function markHookForSkip<H extends HookContext = HookContext>(hookName: string, type: "all" | MaybeArray<HookType>, context?: H): H;
194
180
 
181
+ type Handle = "target" | "source" | "combine" | "intersect" | "intersectOrFull";
182
+ type FirstLast = "first" | "last";
183
+ type ActionOnEmptyIntersect = (target: unknown, source: unknown, prependKey: Path) => void;
184
+ interface MergeQueryOptions {
185
+ defaultHandle: Handle;
186
+ actionOnEmptyIntersect: ActionOnEmptyIntersect;
187
+ useLogicalConjunction: boolean;
188
+ handle?: {
189
+ [key: string]: Handle;
190
+ };
191
+ }
192
+
193
+ declare function mergeArrays<T>(targetArr: T[] | undefined, sourceArr: T[] | undefined, handle: Handle, prependKey?: Path, actionOnEmptyIntersect?: ActionOnEmptyIntersect): T[] | undefined;
194
+
195
+ /**
196
+ * Merges two queries into one.
197
+ * @param target Query to be merged into
198
+ * @param source Query to be merged from
199
+ * @param _options
200
+ * @returns Query
201
+ */
202
+ declare function mergeQuery(target: Query, source: Query, _options?: Partial<MergeQueryOptions>): Query;
203
+
195
204
  interface PushSetOptions {
196
205
  unique?: boolean;
197
206
  }
@@ -200,6 +209,11 @@ interface PushSetOptions {
200
209
  */
201
210
  declare const pushSet: (obj: Record<string, unknown>, path: string | Path, val: unknown, options?: PushSetOptions) => unknown[];
202
211
 
212
+ type SetQueryKeySafelyOptions = {
213
+ mutate?: boolean;
214
+ };
215
+ declare const setQueryKeySafely: <P extends Params<_feathersjs_feathers.Query> = Params<_feathersjs_feathers.Query>>(params: P, key: string, value: any, operator?: string, options?: SetQueryKeySafelyOptions) => P;
216
+
203
217
  /**
204
218
  * util to set `context.result` to an empty array or object, depending on the hook type
205
219
  */
@@ -213,17 +227,18 @@ type ShouldSkipOptions = {
213
227
  */
214
228
  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;
215
229
 
230
+ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
231
+
216
232
  /**
217
233
  * util to validate a query for operators
218
234
  */
219
235
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
220
236
 
221
- declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
222
-
223
- type SetQueryKeySafelyOptions = {
224
- mutate?: boolean;
237
+ type OptimizeBatchPatchOptions = {
238
+ id?: string;
225
239
  };
226
- declare const setQueryKeySafely: <P extends Params<_feathersjs_feathers.Query> = Params<_feathersjs_feathers.Query>>(params: P, key: string, value: any, operator?: string, options?: SetQueryKeySafelyOptions) => P;
240
+ type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
241
+ declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
227
242
 
228
243
  declare const filterArray: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
229
244
 
@@ -275,4 +290,4 @@ type InferRemoveResultFromPath<App extends Application, Path extends string, IdO
275
290
  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;
276
291
  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;
277
292
 
278
- 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, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
293
+ 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
@@ -1,5 +1,5 @@
1
1
  import * as _feathersjs_feathers from '@feathersjs/feathers';
2
- import { HookContext, Application, Id, Query, Params } from '@feathersjs/feathers';
2
+ import { HookContext, Application, Id, HookOptions, Query, Params } from '@feathersjs/feathers';
3
3
  import { HookContext as HookContext$1, Application as Application$1 } from '@feathersjs/feathers/lib';
4
4
  import { PropertyPath, DebouncedFunc } from 'lodash';
5
5
  import { PaginationOptions, FilterQueryOptions } from '@feathersjs/adapter-commons';
@@ -125,28 +125,14 @@ declare class DebouncedStore {
125
125
 
126
126
  declare function debounceMixin(options?: Partial<InitDebounceMixinOptions>): (app: Application$1) => void;
127
127
 
128
- type Handle = "target" | "source" | "combine" | "intersect" | "intersectOrFull";
129
- type FirstLast = "first" | "last";
130
- type ActionOnEmptyIntersect = (target: unknown, source: unknown, prependKey: Path) => void;
131
- interface MergeQueryOptions {
132
- defaultHandle: Handle;
133
- actionOnEmptyIntersect: ActionOnEmptyIntersect;
134
- useLogicalConjunction: boolean;
135
- handle?: {
136
- [key: string]: Handle;
137
- };
138
- }
139
-
140
- declare function mergeArrays<T>(targetArr: T[] | undefined, sourceArr: T[] | undefined, handle: Handle, prependKey?: Path, actionOnEmptyIntersect?: ActionOnEmptyIntersect): T[] | undefined;
141
-
142
- /**
143
- * Merges two queries into one.
144
- * @param target Query to be merged into
145
- * @param source Query to be merged from
146
- * @param _options
147
- * @returns Query
148
- */
149
- declare function mergeQuery(target: Query, source: Query, _options?: Partial<MergeQueryOptions>): Query;
128
+ declare function defineHooks<A extends Application = Application, S = {
129
+ find: any;
130
+ get: any;
131
+ create: any;
132
+ update: any;
133
+ patch: any;
134
+ remove: any;
135
+ }, Options = HookOptions<A, S>>(hooks: Options): Options;
150
136
 
151
137
  type FilterQueryResult<Q extends Query> = {
152
138
  $select: Q["$select"] extends any ? Q["$select"] : never;
@@ -192,6 +178,29 @@ declare const isPaginated: <H extends HookContext<_feathersjs_feathers.Applicati
192
178
  */
193
179
  declare function markHookForSkip<H extends HookContext = HookContext>(hookName: string, type: "all" | MaybeArray<HookType>, context?: H): H;
194
180
 
181
+ type Handle = "target" | "source" | "combine" | "intersect" | "intersectOrFull";
182
+ type FirstLast = "first" | "last";
183
+ type ActionOnEmptyIntersect = (target: unknown, source: unknown, prependKey: Path) => void;
184
+ interface MergeQueryOptions {
185
+ defaultHandle: Handle;
186
+ actionOnEmptyIntersect: ActionOnEmptyIntersect;
187
+ useLogicalConjunction: boolean;
188
+ handle?: {
189
+ [key: string]: Handle;
190
+ };
191
+ }
192
+
193
+ declare function mergeArrays<T>(targetArr: T[] | undefined, sourceArr: T[] | undefined, handle: Handle, prependKey?: Path, actionOnEmptyIntersect?: ActionOnEmptyIntersect): T[] | undefined;
194
+
195
+ /**
196
+ * Merges two queries into one.
197
+ * @param target Query to be merged into
198
+ * @param source Query to be merged from
199
+ * @param _options
200
+ * @returns Query
201
+ */
202
+ declare function mergeQuery(target: Query, source: Query, _options?: Partial<MergeQueryOptions>): Query;
203
+
195
204
  interface PushSetOptions {
196
205
  unique?: boolean;
197
206
  }
@@ -200,6 +209,11 @@ interface PushSetOptions {
200
209
  */
201
210
  declare const pushSet: (obj: Record<string, unknown>, path: string | Path, val: unknown, options?: PushSetOptions) => unknown[];
202
211
 
212
+ type SetQueryKeySafelyOptions = {
213
+ mutate?: boolean;
214
+ };
215
+ declare const setQueryKeySafely: <P extends Params<_feathersjs_feathers.Query> = Params<_feathersjs_feathers.Query>>(params: P, key: string, value: any, operator?: string, options?: SetQueryKeySafelyOptions) => P;
216
+
203
217
  /**
204
218
  * util to set `context.result` to an empty array or object, depending on the hook type
205
219
  */
@@ -213,17 +227,18 @@ type ShouldSkipOptions = {
213
227
  */
214
228
  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;
215
229
 
230
+ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
231
+
216
232
  /**
217
233
  * util to validate a query for operators
218
234
  */
219
235
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
220
236
 
221
- declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
222
-
223
- type SetQueryKeySafelyOptions = {
224
- mutate?: boolean;
237
+ type OptimizeBatchPatchOptions = {
238
+ id?: string;
225
239
  };
226
- declare const setQueryKeySafely: <P extends Params<_feathersjs_feathers.Query> = Params<_feathersjs_feathers.Query>>(params: P, key: string, value: any, operator?: string, options?: SetQueryKeySafelyOptions) => P;
240
+ type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
241
+ declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
227
242
 
228
243
  declare const filterArray: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
229
244
 
@@ -275,4 +290,4 @@ type InferRemoveResultFromPath<App extends Application, Path extends string, IdO
275
290
  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;
276
291
  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;
277
292
 
278
- 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, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
293
+ 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
@@ -1,5 +1,5 @@
1
1
  import * as _feathersjs_feathers from '@feathersjs/feathers';
2
- import { HookContext, Application, Id, Query, Params } from '@feathersjs/feathers';
2
+ import { HookContext, Application, Id, HookOptions, Query, Params } from '@feathersjs/feathers';
3
3
  import { HookContext as HookContext$1, Application as Application$1 } from '@feathersjs/feathers/lib';
4
4
  import { PropertyPath, DebouncedFunc } from 'lodash';
5
5
  import { PaginationOptions, FilterQueryOptions } from '@feathersjs/adapter-commons';
@@ -125,28 +125,14 @@ declare class DebouncedStore {
125
125
 
126
126
  declare function debounceMixin(options?: Partial<InitDebounceMixinOptions>): (app: Application$1) => void;
127
127
 
128
- type Handle = "target" | "source" | "combine" | "intersect" | "intersectOrFull";
129
- type FirstLast = "first" | "last";
130
- type ActionOnEmptyIntersect = (target: unknown, source: unknown, prependKey: Path) => void;
131
- interface MergeQueryOptions {
132
- defaultHandle: Handle;
133
- actionOnEmptyIntersect: ActionOnEmptyIntersect;
134
- useLogicalConjunction: boolean;
135
- handle?: {
136
- [key: string]: Handle;
137
- };
138
- }
139
-
140
- declare function mergeArrays<T>(targetArr: T[] | undefined, sourceArr: T[] | undefined, handle: Handle, prependKey?: Path, actionOnEmptyIntersect?: ActionOnEmptyIntersect): T[] | undefined;
141
-
142
- /**
143
- * Merges two queries into one.
144
- * @param target Query to be merged into
145
- * @param source Query to be merged from
146
- * @param _options
147
- * @returns Query
148
- */
149
- declare function mergeQuery(target: Query, source: Query, _options?: Partial<MergeQueryOptions>): Query;
128
+ declare function defineHooks<A extends Application = Application, S = {
129
+ find: any;
130
+ get: any;
131
+ create: any;
132
+ update: any;
133
+ patch: any;
134
+ remove: any;
135
+ }, Options = HookOptions<A, S>>(hooks: Options): Options;
150
136
 
151
137
  type FilterQueryResult<Q extends Query> = {
152
138
  $select: Q["$select"] extends any ? Q["$select"] : never;
@@ -192,6 +178,29 @@ declare const isPaginated: <H extends HookContext<_feathersjs_feathers.Applicati
192
178
  */
193
179
  declare function markHookForSkip<H extends HookContext = HookContext>(hookName: string, type: "all" | MaybeArray<HookType>, context?: H): H;
194
180
 
181
+ type Handle = "target" | "source" | "combine" | "intersect" | "intersectOrFull";
182
+ type FirstLast = "first" | "last";
183
+ type ActionOnEmptyIntersect = (target: unknown, source: unknown, prependKey: Path) => void;
184
+ interface MergeQueryOptions {
185
+ defaultHandle: Handle;
186
+ actionOnEmptyIntersect: ActionOnEmptyIntersect;
187
+ useLogicalConjunction: boolean;
188
+ handle?: {
189
+ [key: string]: Handle;
190
+ };
191
+ }
192
+
193
+ declare function mergeArrays<T>(targetArr: T[] | undefined, sourceArr: T[] | undefined, handle: Handle, prependKey?: Path, actionOnEmptyIntersect?: ActionOnEmptyIntersect): T[] | undefined;
194
+
195
+ /**
196
+ * Merges two queries into one.
197
+ * @param target Query to be merged into
198
+ * @param source Query to be merged from
199
+ * @param _options
200
+ * @returns Query
201
+ */
202
+ declare function mergeQuery(target: Query, source: Query, _options?: Partial<MergeQueryOptions>): Query;
203
+
195
204
  interface PushSetOptions {
196
205
  unique?: boolean;
197
206
  }
@@ -200,6 +209,11 @@ interface PushSetOptions {
200
209
  */
201
210
  declare const pushSet: (obj: Record<string, unknown>, path: string | Path, val: unknown, options?: PushSetOptions) => unknown[];
202
211
 
212
+ type SetQueryKeySafelyOptions = {
213
+ mutate?: boolean;
214
+ };
215
+ declare const setQueryKeySafely: <P extends Params<_feathersjs_feathers.Query> = Params<_feathersjs_feathers.Query>>(params: P, key: string, value: any, operator?: string, options?: SetQueryKeySafelyOptions) => P;
216
+
203
217
  /**
204
218
  * util to set `context.result` to an empty array or object, depending on the hook type
205
219
  */
@@ -213,17 +227,18 @@ type ShouldSkipOptions = {
213
227
  */
214
228
  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;
215
229
 
230
+ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
231
+
216
232
  /**
217
233
  * util to validate a query for operators
218
234
  */
219
235
  declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
220
236
 
221
- declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
222
-
223
- type SetQueryKeySafelyOptions = {
224
- mutate?: boolean;
237
+ type OptimizeBatchPatchOptions = {
238
+ id?: string;
225
239
  };
226
- declare const setQueryKeySafely: <P extends Params<_feathersjs_feathers.Query> = Params<_feathersjs_feathers.Query>>(params: P, key: string, value: any, operator?: string, options?: SetQueryKeySafelyOptions) => P;
240
+ type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
241
+ declare function optimizeBatchPatch<T extends Record<string, unknown>, P extends Params>(items: Map<Id, T>, options?: OptimizeBatchPatchOptions): OptimizeBatchPatchResultItem<T, P>[];
227
242
 
228
243
  declare const filterArray: <T extends string[]>(...keys: T) => { [key in T[number]]: (value: any, options: FilterQueryOptions) => any; };
229
244
 
@@ -275,4 +290,4 @@ type InferRemoveResultFromPath<App extends Application, Path extends string, IdO
275
290
  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;
276
291
  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;
277
292
 
278
- 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, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
293
+ 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
@@ -1,17 +1,131 @@
1
1
  import { Forbidden, GeneralError, BadRequest, MethodNotAllowed } from '@feathersjs/errors';
2
+ import _isEqual from 'lodash/isEqual.js';
3
+ import _get from 'lodash/get.js';
4
+ import _set from 'lodash/set.js';
2
5
  import _merge from 'lodash/merge.js';
3
6
  import _isEmpty from 'lodash/isEmpty.js';
4
- import _get from 'lodash/get.js';
5
7
  import _has from 'lodash/has.js';
6
- import _set from 'lodash/set.js';
7
8
  import _uniqWith from 'lodash/uniqWith.js';
8
9
  import { deepEqual } from 'fast-equals';
9
- import _isEqual from 'lodash/isEqual.js';
10
10
  import { _ } from '@feathersjs/commons';
11
11
  import { checkContext } from 'feathers-hooks-common';
12
12
  import _debounce from 'lodash/debounce.js';
13
13
  import _isObject from 'lodash/isObject.js';
14
14
 
15
+ function defineHooks(hooks) {
16
+ return hooks;
17
+ }
18
+
19
+ function filterQuery(providedQuery) {
20
+ providedQuery ?? (providedQuery = {});
21
+ const { $select, $limit, $skip, $sort, ...query } = providedQuery;
22
+ const result = { query };
23
+ if ("$select" in providedQuery) {
24
+ result.$select = $select;
25
+ }
26
+ if ("$limit" in providedQuery) {
27
+ result.$limit = $limit;
28
+ }
29
+ if ("$skip" in providedQuery) {
30
+ result.$skip = $skip;
31
+ }
32
+ if ("$sort" in providedQuery) {
33
+ result.$sort = $sort;
34
+ }
35
+ return result;
36
+ }
37
+
38
+ const getItemsIsArray = (context, options) => {
39
+ const { from = "automatic" } = options || {};
40
+ let itemOrItems;
41
+ if (from === "automatic") {
42
+ itemOrItems = context.type === "before" ? context.data : context.result;
43
+ } else if (from === "data") {
44
+ itemOrItems = context.data;
45
+ } else if (from === "result") {
46
+ itemOrItems = context.result;
47
+ }
48
+ if ((from === "automatic" || from === "result") && context.type === "after") {
49
+ itemOrItems = itemOrItems && context.method === "find" ? itemOrItems.data || itemOrItems : itemOrItems;
50
+ }
51
+ const isArray = Array.isArray(itemOrItems);
52
+ return {
53
+ items: isArray ? itemOrItems : itemOrItems != null ? [itemOrItems] : [],
54
+ isArray
55
+ };
56
+ };
57
+
58
+ const hasOwnProperty = (obj, ...keys) => {
59
+ return keys.some((x) => Object.prototype.hasOwnProperty.call(obj, x));
60
+ };
61
+ const isPlainObject$1 = (value) => value && [void 0, Object].includes(value.constructor);
62
+
63
+ const getPaginate = (context) => {
64
+ if (hasOwnProperty(context.params, "paginate")) {
65
+ return context.params.paginate || void 0;
66
+ }
67
+ if (context.params.paginate === false) {
68
+ return void 0;
69
+ }
70
+ let options = context.service?.options || {};
71
+ options = {
72
+ ...options,
73
+ ...context.params.adapter
74
+ };
75
+ return options.paginate || void 0;
76
+ };
77
+
78
+ const isMulti = (context) => {
79
+ const { method } = context;
80
+ if (method === "find") {
81
+ return true;
82
+ } else if (["patch", "remove"].includes(method)) {
83
+ return context.id == null;
84
+ } else if (method === "create") {
85
+ const items = context.type === "before" ? context.data : context.result;
86
+ return Array.isArray(items);
87
+ } else if (["get", "update"].includes(method)) {
88
+ return false;
89
+ }
90
+ return false;
91
+ };
92
+
93
+ const isPaginated = (context) => {
94
+ if (context.params.paginate === false || context.method !== "find") {
95
+ return false;
96
+ }
97
+ const paginate = getPaginate(context);
98
+ return !!paginate;
99
+ };
100
+
101
+ const pushSet = (obj, path, val, options) => {
102
+ options = options || {};
103
+ let arr = _get(obj, path);
104
+ if (!arr || !Array.isArray(arr)) {
105
+ arr = [val];
106
+ _set(obj, path, arr);
107
+ return arr;
108
+ } else {
109
+ if (options.unique && arr.some((x) => _isEqual(x, val))) {
110
+ return arr;
111
+ }
112
+ arr.push(val);
113
+ return arr;
114
+ }
115
+ };
116
+
117
+ function markHookForSkip(hookName, type, context) {
118
+ context = context || {};
119
+ const params = context.params || {};
120
+ const types = Array.isArray(type) ? type : [type];
121
+ types.forEach((t) => {
122
+ const combinedName = t === "all" ? hookName : `${type}:${hookName}`;
123
+ pushSet(params, ["skipHooks"], combinedName, { unique: true });
124
+ });
125
+ context.params = params;
126
+ return context;
127
+ }
128
+
15
129
  function mergeArrays(targetArr, sourceArr, handle, prependKey, actionOnEmptyIntersect) {
16
130
  if (!sourceArr && !targetArr) {
17
131
  return;
@@ -51,11 +165,6 @@ function mergeArrays(targetArr, sourceArr, handle, prependKey, actionOnEmptyInte
51
165
  return void 0;
52
166
  }
53
167
 
54
- const hasOwnProperty = (obj, ...keys) => {
55
- return keys.some((x) => Object.prototype.hasOwnProperty.call(obj, x));
56
- };
57
- const isPlainObject$1 = (value) => value && [void 0, Object].includes(value.constructor);
58
-
59
168
  function handleArray(target, source, key, options) {
60
169
  const targetVal = _get(target, key);
61
170
  const sourceVal = _get(source, key);
@@ -338,25 +447,6 @@ function areQueriesOverlapping(target, source) {
338
447
  return false;
339
448
  }
340
449
 
341
- function filterQuery(providedQuery) {
342
- providedQuery ?? (providedQuery = {});
343
- const { $select, $limit, $skip, $sort, ...query } = providedQuery;
344
- const result = { query };
345
- if ("$select" in providedQuery) {
346
- result.$select = $select;
347
- }
348
- if ("$limit" in providedQuery) {
349
- result.$limit = $limit;
350
- }
351
- if ("$skip" in providedQuery) {
352
- result.$skip = $skip;
353
- }
354
- if ("$sort" in providedQuery) {
355
- result.$sort = $sort;
356
- }
357
- return result;
358
- }
359
-
360
450
  function mergeQuery(target, source, _options) {
361
451
  const options = makeDefaultOptions$1(_options);
362
452
  const { query: targetQuery, ...targetFilters } = filterQuery(target);
@@ -400,92 +490,40 @@ function mergeQuery(target, source, _options) {
400
490
  };
401
491
  }
402
492
 
403
- const getItemsIsArray = (context, options) => {
404
- const { from = "automatic" } = options || {};
405
- let itemOrItems;
406
- if (from === "automatic") {
407
- itemOrItems = context.type === "before" ? context.data : context.result;
408
- } else if (from === "data") {
409
- itemOrItems = context.data;
410
- } else if (from === "result") {
411
- itemOrItems = context.result;
412
- }
413
- if ((from === "automatic" || from === "result") && context.type === "after") {
414
- itemOrItems = itemOrItems && context.method === "find" ? itemOrItems.data || itemOrItems : itemOrItems;
415
- }
416
- const isArray = Array.isArray(itemOrItems);
417
- return {
418
- items: isArray ? itemOrItems : itemOrItems != null ? [itemOrItems] : [],
419
- isArray
420
- };
421
- };
422
-
423
- const getPaginate = (context) => {
424
- if (hasOwnProperty(context.params, "paginate")) {
425
- return context.params.paginate || void 0;
426
- }
427
- if (context.params.paginate === false) {
428
- return void 0;
493
+ const setQueryKeySafely = (params, key, value, operator = "$eq", options) => {
494
+ var _a;
495
+ const { mutate = false } = options || {};
496
+ if (!mutate) {
497
+ params = structuredClone(params);
429
498
  }
430
- let options = context.service?.options || {};
431
- options = {
432
- ...options,
433
- ...context.params.adapter
434
- };
435
- return options.paginate || void 0;
436
- };
437
-
438
- const isMulti = (context) => {
439
- const { method } = context;
440
- if (method === "find") {
441
- return true;
442
- } else if (["patch", "remove"].includes(method)) {
443
- return context.id == null;
444
- } else if (method === "create") {
445
- const items = context.type === "before" ? context.data : context.result;
446
- return Array.isArray(items);
447
- } else if (["get", "update"].includes(method)) {
448
- return false;
499
+ if (!params.query) {
500
+ params.query = {};
449
501
  }
450
- return false;
451
- };
452
-
453
- const isPaginated = (context) => {
454
- if (context.params.paginate === false || context.method !== "find") {
455
- return false;
502
+ if (!(key in params.query)) {
503
+ if (operator === "$eq") {
504
+ params.query[key] = value;
505
+ } else {
506
+ params.query[key] = {
507
+ [operator]: value
508
+ };
509
+ }
510
+ return params;
456
511
  }
457
- const paginate = getPaginate(context);
458
- return !!paginate;
459
- };
460
-
461
- const pushSet = (obj, path, val, options) => {
462
- options = options || {};
463
- let arr = _get(obj, path);
464
- if (!arr || !Array.isArray(arr)) {
465
- arr = [val];
466
- _set(obj, path, arr);
467
- return arr;
512
+ if (isPlainObject$1(params.query[key]) && !(operator in params.query[key])) {
513
+ params.query[key][operator] = value;
468
514
  } else {
469
- if (options.unique && arr.some((x) => _isEqual(x, val))) {
470
- return arr;
471
- }
472
- arr.push(val);
473
- return arr;
515
+ (_a = params.query).$and ?? (_a.$and = []);
516
+ params.query.$and.push(
517
+ operator === "$eq" ? { [key]: value } : {
518
+ [key]: {
519
+ [operator]: value
520
+ }
521
+ }
522
+ );
474
523
  }
524
+ return params;
475
525
  };
476
526
 
477
- function markHookForSkip(hookName, type, context) {
478
- context = context || {};
479
- const params = context.params || {};
480
- const types = Array.isArray(type) ? type : [type];
481
- types.forEach((t) => {
482
- const combinedName = t === "all" ? hookName : `${type}:${hookName}`;
483
- pushSet(params, ["skipHooks"], combinedName, { unique: true });
484
- });
485
- context.params = params;
486
- return context;
487
- }
488
-
489
527
  const setResultEmpty = (context) => {
490
528
  if (context.result) {
491
529
  return context;
@@ -529,6 +567,13 @@ const shouldSkip = (hookName, context, options) => {
529
567
  return false;
530
568
  };
531
569
 
570
+ const toJSON = (context) => {
571
+ if (context.toJSON) {
572
+ return context.toJSON();
573
+ }
574
+ return context;
575
+ };
576
+
532
577
  const isPlainObject = (value) => _.isObject(value) && value.constructor === {}.constructor;
533
578
  const validateQueryProperty = (query, operators = []) => {
534
579
  if (!isPlainObject(query)) {
@@ -548,46 +593,29 @@ const validateQueryProperty = (query, operators = []) => {
548
593
  };
549
594
  };
550
595
 
551
- const toJSON = (context) => {
552
- if (context.toJSON) {
553
- return context.toJSON();
554
- }
555
- return context;
556
- };
557
-
558
- const setQueryKeySafely = (params, key, value, operator = "$eq", options) => {
559
- var _a;
560
- const { mutate = false } = options || {};
561
- if (!mutate) {
562
- params = structuredClone(params);
563
- }
564
- if (!params.query) {
565
- params.query = {};
566
- }
567
- if (!(key in params.query)) {
568
- if (operator === "$eq") {
569
- params.query[key] = value;
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 });
570
603
  } else {
571
- params.query[key] = {
572
- [operator]: value
573
- };
604
+ map[index].ids.push(id2);
574
605
  }
575
- return params;
576
606
  }
577
- if (isPlainObject$1(params.query[key]) && !(operator in params.query[key])) {
578
- params.query[key][operator] = value;
579
- } else {
580
- (_a = params.query).$and ?? (_a.$and = []);
581
- params.query.$and.push(
582
- operator === "$eq" ? { [key]: value } : {
583
- [key]: {
584
- [operator]: value
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 }
585
614
  }
586
615
  }
587
- );
588
- }
589
- return params;
590
- };
616
+ ];
617
+ });
618
+ }
591
619
 
592
620
  function checkMulti() {
593
621
  return (context) => {
@@ -950,4 +978,4 @@ const filterObject = (...keys) => {
950
978
  return result;
951
979
  };
952
980
 
953
- export { DebouncedStore, checkMulti, createRelated, debounceMixin, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
981
+ 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.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "Some utils for projects using '@feathersjs/feathers'",
5
5
  "author": "fratzinger",
6
6
  "repository": {
@@ -40,6 +40,7 @@
40
40
  "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
41
41
  },
42
42
  "dependencies": {
43
+ "@feathersjs/feathers": "^5.0.11",
43
44
  "@feathersjs/adapter-commons": "^5.0.11",
44
45
  "@feathersjs/commons": "^5.0.11",
45
46
  "@feathersjs/errors": "^5.0.11",
@@ -48,7 +49,6 @@
48
49
  "lodash": "^4.17.21"
49
50
  },
50
51
  "devDependencies": {
51
- "@feathersjs/feathers": "^5.0.11",
52
52
  "@feathersjs/memory": "^5.0.11",
53
53
  "@types/lodash": "^4.14.201",
54
54
  "@types/node": "^20.9.0",
@@ -0,0 +1,16 @@
1
+ import type { Application, HookOptions } from "@feathersjs/feathers";
2
+
3
+ export function defineHooks<
4
+ A extends Application = Application,
5
+ S = {
6
+ find: any;
7
+ get: any;
8
+ create: any;
9
+ update: any;
10
+ patch: any;
11
+ remove: any;
12
+ },
13
+ Options = HookOptions<A, S>,
14
+ >(hooks: Options): Options {
15
+ return hooks;
16
+ }
@@ -1,13 +1,15 @@
1
- export * from "./mergeQuery";
1
+ export * from "./defineHooks";
2
2
  export * from "./filterQuery";
3
3
  export * from "./getItemsIsArray";
4
4
  export * from "./getPaginate";
5
5
  export * from "./isMulti";
6
6
  export * from "./isPaginated";
7
7
  export * from "./markHookForSkip";
8
+ export * from "./mergeQuery";
8
9
  export * from "./pushSet";
10
+ export * from "./setQueryKeySafely";
9
11
  export * from "./setResultEmpty";
10
12
  export * from "./shouldSkip";
11
- export * from "./validateQueryProperty";
12
13
  export * from "./toJSON";
13
- export * from "./setQueryKeySafely";
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
+ }