feathers-utils 5.1.0 → 6.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.mjs CHANGED
@@ -1,16 +1,12 @@
1
1
  import { Forbidden, GeneralError, BadRequest, MethodNotAllowed } from '@feathersjs/errors';
2
- import _isEqual from 'lodash/isEqual.js';
2
+ import { deepEqual } from 'fast-equals';
3
3
  import _get from 'lodash/get.js';
4
4
  import _set from 'lodash/set.js';
5
5
  import _merge from 'lodash/merge.js';
6
- import _isEmpty from 'lodash/isEmpty.js';
7
6
  import _has from 'lodash/has.js';
8
7
  import _uniqWith from 'lodash/uniqWith.js';
9
- import { deepEqual } from 'fast-equals';
10
- import { _ } from '@feathersjs/commons';
11
8
  import { checkContext } from 'feathers-hooks-common';
12
9
  import _debounce from 'lodash/debounce.js';
13
- import _isObject from 'lodash/isObject.js';
14
10
 
15
11
  function defineHooks(hooks) {
16
12
  return hooks;
@@ -34,6 +30,23 @@ function filterQuery(providedQuery) {
34
30
  }
35
31
  return result;
36
32
  }
33
+ function reassembleQuery(query) {
34
+ const { $select, $limit, $skip, $sort, query: rest } = query;
35
+ const result = rest;
36
+ if ($select !== void 0) {
37
+ result.$select = $select;
38
+ }
39
+ if ($limit !== void 0) {
40
+ result.$limit = $limit;
41
+ }
42
+ if ($skip !== void 0) {
43
+ result.$skip = $skip;
44
+ }
45
+ if ($sort !== void 0) {
46
+ result.$sort = $sort;
47
+ }
48
+ return result;
49
+ }
37
50
 
38
51
  const getItemsIsArray = (context, options) => {
39
52
  const { from = "automatic" } = options || {};
@@ -58,7 +71,9 @@ const getItemsIsArray = (context, options) => {
58
71
  const hasOwnProperty = (obj, ...keys) => {
59
72
  return keys.some((x) => Object.prototype.hasOwnProperty.call(obj, x));
60
73
  };
61
- const isPlainObject$1 = (value) => value && [void 0, Object].includes(value.constructor);
74
+ const isObject = (item) => !!item && typeof item === "object" && !Array.isArray(item);
75
+ const isPlainObject = (value) => isObject(value) && value.constructor === {}.constructor;
76
+ const isEmpty = (obj) => [Object, Array].includes((obj || {}).constructor) && !Object.keys(obj || {}).length;
62
77
 
63
78
  const getPaginate = (context) => {
64
79
  if (hasOwnProperty(context.params, "paginate")) {
@@ -106,7 +121,7 @@ const pushSet = (obj, path, val, options) => {
106
121
  _set(obj, path, arr);
107
122
  return arr;
108
123
  } else {
109
- if (options.unique && arr.some((x) => _isEqual(x, val))) {
124
+ if (options.unique && arr.some((x) => deepEqual(x, val))) {
110
125
  return arr;
111
126
  }
112
127
  arr.push(val);
@@ -389,7 +404,7 @@ function cleanOr(target) {
389
404
  if (!target || !Array.isArray(target) || target.length <= 0) {
390
405
  return target;
391
406
  }
392
- if (target.some((x) => _isEmpty(x))) {
407
+ if (target.some((x) => isEmpty(x))) {
393
408
  return void 0;
394
409
  } else {
395
410
  return arrayWithoutDuplicates(target);
@@ -399,10 +414,10 @@ function cleanAnd(target) {
399
414
  if (!target || !Array.isArray(target) || target.length <= 0) {
400
415
  return target;
401
416
  }
402
- if (target.every((x) => _isEmpty(x))) {
417
+ if (target.every((x) => isEmpty(x))) {
403
418
  return void 0;
404
419
  } else {
405
- target = target.filter((x) => !_isEmpty(x));
420
+ target = target.filter((x) => !isEmpty(x));
406
421
  return arrayWithoutDuplicates(target);
407
422
  }
408
423
  }
@@ -468,7 +483,7 @@ function mergeQuery(target, source, _options) {
468
483
  ...sourceQuery
469
484
  };
470
485
  }
471
- if (options.useLogicalConjunction && (options.defaultHandle === "combine" || options.defaultHandle === "intersect") && !_isEmpty(targetQuery)) {
486
+ if (options.useLogicalConjunction && (options.defaultHandle === "combine" || options.defaultHandle === "intersect") && !isEmpty(targetQuery)) {
472
487
  const logicalOp = options.defaultHandle === "combine" ? "$or" : "$and";
473
488
  if (hasOwnProperty(sourceQuery, logicalOp)) {
474
489
  const andOr = sourceQuery[logicalOp];
@@ -509,7 +524,7 @@ const setQueryKeySafely = (params, key, value, operator = "$eq", options) => {
509
524
  }
510
525
  return params;
511
526
  }
512
- if (isPlainObject$1(params.query[key]) && !(operator in params.query[key])) {
527
+ if (isPlainObject(params.query[key]) && !(operator in params.query[key])) {
513
528
  params.query[key][operator] = value;
514
529
  } else {
515
530
  (_a = params.query).$and ?? (_a.$and = []);
@@ -574,9 +589,8 @@ const toJSON = (context) => {
574
589
  return context;
575
590
  };
576
591
 
577
- const isPlainObject = (value) => _.isObject(value) && value.constructor === {}.constructor;
578
592
  const validateQueryProperty = (query, operators = []) => {
579
- if (!isPlainObject(query)) {
593
+ if (!isObject(query)) {
580
594
  return query;
581
595
  }
582
596
  for (const key of Object.keys(query)) {
@@ -584,7 +598,7 @@ const validateQueryProperty = (query, operators = []) => {
584
598
  throw new BadRequest(`Invalid query parameter ${key}`, query);
585
599
  }
586
600
  const value = query[key];
587
- if (isPlainObject(value)) {
601
+ if (isObject(value)) {
588
602
  query[key] = validateQueryProperty(value, operators);
589
603
  }
590
604
  }
@@ -595,13 +609,18 @@ const validateQueryProperty = (query, operators = []) => {
595
609
 
596
610
  function optimizeBatchPatch(items, options) {
597
611
  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));
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
+ });
601
620
  if (index === -1) {
602
- map.push({ ids: [id2], data });
621
+ map.push({ ids: [id], data });
603
622
  } else {
604
- map[index].ids.push(id2);
623
+ map[index].ids.push(id);
605
624
  }
606
625
  }
607
626
  return map.map(({ ids, data }) => {
@@ -610,13 +629,79 @@ function optimizeBatchPatch(items, options) {
610
629
  data,
611
630
  {
612
631
  query: {
613
- [id]: { $in: ids }
632
+ [idKey]: { $in: ids }
614
633
  }
615
634
  }
616
635
  ];
617
636
  });
618
637
  }
619
638
 
639
+ function flattenQuery(q) {
640
+ if (Array.isArray(q)) {
641
+ return q.map(flattenQuery);
642
+ }
643
+ if (!isObject(q)) {
644
+ return q;
645
+ }
646
+ const { query, $limit, $select, $skip, $sort } = filterQuery(q);
647
+ const res = {};
648
+ function step(object, options) {
649
+ const { prev = [], result = res } = options ?? {};
650
+ Object.keys(object).forEach((key) => {
651
+ const value = object[key];
652
+ if (Array.isArray(value)) {
653
+ const newValues = value.map(
654
+ (v) => step(v, {
655
+ result: {}
656
+ })
657
+ );
658
+ _set(result, [...prev, key], newValues);
659
+ return;
660
+ }
661
+ if (key.startsWith("$")) {
662
+ _set(result, [...prev, key], value);
663
+ return;
664
+ }
665
+ const newKey = !prev.length ? [key] : [...prev.slice(0, -1), `${prev[prev.length - 1]}.${key}`];
666
+ if (!isObject(value)) {
667
+ _set(result, newKey, value);
668
+ return;
669
+ } else {
670
+ step(value, {
671
+ prev: newKey,
672
+ result
673
+ });
674
+ return;
675
+ }
676
+ });
677
+ return result;
678
+ }
679
+ return reassembleQuery({
680
+ $limit,
681
+ $select,
682
+ $skip,
683
+ $sort,
684
+ query: step(query)
685
+ });
686
+ }
687
+
688
+ function deflattenQuery(query) {
689
+ const result = {};
690
+ Object.keys(query).forEach((key) => {
691
+ const value = query[key];
692
+ if (Array.isArray(value)) {
693
+ _set(result, key, value.map(deflattenQuery));
694
+ return;
695
+ }
696
+ if (isObject(value)) {
697
+ _set(result, key, deflattenQuery(value));
698
+ return;
699
+ }
700
+ _set(result, key, query[key]);
701
+ });
702
+ return result;
703
+ }
704
+
620
705
  function checkMulti() {
621
706
  return (context) => {
622
707
  if (shouldSkip("checkMulti", context)) {
@@ -934,12 +1019,6 @@ const paramsFromClient = defineParamsFromClient(
934
1019
  FROM_CLIENT_FOR_SERVER_DEFAULT_KEY
935
1020
  );
936
1021
 
937
- var __defProp = Object.defineProperty;
938
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
939
- var __publicField = (obj, key, value) => {
940
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
941
- return value;
942
- };
943
1022
  const makeDefaultOptions = () => {
944
1023
  return {
945
1024
  leading: false,
@@ -948,6 +1027,13 @@ const makeDefaultOptions = () => {
948
1027
  wait: 100
949
1028
  };
950
1029
  };
1030
+
1031
+ var __defProp = Object.defineProperty;
1032
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
1033
+ var __publicField = (obj, key, value) => {
1034
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1035
+ return value;
1036
+ };
951
1037
  class DebouncedStore {
952
1038
  constructor(app, options) {
953
1039
  __publicField(this, "_app");
@@ -1040,7 +1126,7 @@ const filterArray = (...keys) => {
1040
1126
  };
1041
1127
 
1042
1128
  const filterQueryObject = (key) => (obj, { operators }) => {
1043
- if (obj && !_isObject(obj)) {
1129
+ if (obj && !isObject(obj)) {
1044
1130
  throw new Error(
1045
1131
  `Invalid query parameter: '${key}'. It has to be an object`
1046
1132
  );
@@ -1055,4 +1141,4 @@ const filterObject = (...keys) => {
1055
1141
  return result;
1056
1142
  };
1057
1143
 
1058
- export { DebouncedStore, checkMulti, createRelated, debounceMixin, defineHooks, defineParamsForServer, defineParamsFromClient, filterArray, filterObject, filterQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, makeDefaultOptions, markHookForSkip, mergeArrays, mergeQuery, onDelete, optimizeBatchPatch, paramsForServer, paramsFromClient, parseFields, pushSet, removeRelated, runPerItem, setData, setQueryKeySafely, setResultEmpty, shouldSkip, toJSON, validateQueryProperty };
1144
+ export { DebouncedStore, checkMulti, createRelated, debounceMixin, defineHooks, defineParamsForServer, defineParamsFromClient, deflattenQuery, filterArray, filterObject, filterQuery, flattenQuery, forEach, getItemsIsArray, getPaginate, isMulti, isPaginated, markHookForSkip, mergeArrays, mergeQuery, onDelete, optimizeBatchPatch, paramsForServer, paramsFromClient, parseFields, pushSet, reassembleQuery, 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": "5.1.0",
3
+ "version": "6.0.0",
4
4
  "description": "Some utils for projects using '@feathersjs/feathers'",
5
5
  "author": "fratzinger",
6
6
  "repository": {
@@ -40,33 +40,31 @@
40
40
  "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
41
41
  },
42
42
  "dependencies": {
43
- "@feathersjs/feathers": "^5.0.11",
44
- "@feathersjs/adapter-commons": "^5.0.11",
45
- "@feathersjs/commons": "^5.0.11",
46
- "@feathersjs/errors": "^5.0.11",
43
+ "@feathersjs/adapter-commons": "^5.0.13",
44
+ "@feathersjs/errors": "^5.0.13",
45
+ "@feathersjs/feathers": "^5.0.13",
47
46
  "fast-equals": "^5.0.1",
48
47
  "feathers-hooks-common": "^8.1.1",
49
48
  "lodash": "^4.17.21"
50
49
  },
51
50
  "devDependencies": {
52
- "@feathersjs/memory": "^5.0.11",
53
- "@types/lodash": "^4.14.201",
54
- "@types/node": "^20.9.0",
55
- "@typescript-eslint/eslint-plugin": "^6.10.0",
56
- "@typescript-eslint/parser": "^6.10.0",
57
- "@vitest/coverage-v8": "^0.34.6",
58
- "eslint": "^8.53.0",
59
- "eslint-config-prettier": "^9.0.0",
51
+ "@feathersjs/memory": "^5.0.13",
52
+ "@types/lodash": "^4.14.202",
53
+ "@types/node": "^20.10.6",
54
+ "@typescript-eslint/eslint-plugin": "^6.16.0",
55
+ "@typescript-eslint/parser": "^6.16.0",
56
+ "@vitest/coverage-v8": "^1.1.0",
57
+ "eslint": "^8.56.0",
58
+ "eslint-config-prettier": "^9.1.0",
60
59
  "eslint-import-resolver-typescript": "^3.6.1",
61
- "eslint-plugin-import": "^2.29.0",
62
- "eslint-plugin-prettier": "^5.0.1",
63
- "eslint-plugin-security": "^1.7.1",
64
- "np": "^8.0.4",
65
- "prettier": "^3.1.0",
60
+ "eslint-plugin-import": "^2.29.1",
61
+ "eslint-plugin-prettier": "^5.1.2",
62
+ "np": "^9.2.0",
63
+ "prettier": "^3.1.1",
66
64
  "shx": "^0.3.4",
67
- "typescript": "^5.2.2",
65
+ "typescript": "^5.3.3",
68
66
  "unbuild": "^2.0.0",
69
- "vitest": "^0.34.6"
67
+ "vitest": "^1.1.0"
70
68
  },
71
69
  "peerDependencies": {
72
70
  "@feathersjs/feathers": "^5.0.0"
@@ -1,11 +1,11 @@
1
1
  import type { FilterQueryOptions } from "@feathersjs/adapter-commons";
2
2
  import { validateQueryProperty } from "../utils/validateQueryProperty";
3
- import _isObject from "lodash/isObject.js";
3
+ import { isObject } from "../utils/_utils.internal";
4
4
 
5
5
  const filterQueryObject =
6
6
  (key: string) =>
7
7
  (obj: any, { operators }: FilterQueryOptions) => {
8
- if (obj && !_isObject(obj)) {
8
+ if (obj && !isObject(obj)) {
9
9
  throw new Error(
10
10
  `Invalid query parameter: '${key}'. It has to be an object`,
11
11
  );
@@ -24,3 +24,154 @@ export function checkMulti<H extends HookContext = HookContext>() {
24
24
  return context;
25
25
  };
26
26
  }
27
+
28
+ if (import.meta.vitest) {
29
+ const { it, assert } = import.meta.vitest;
30
+
31
+ it("passes if 'allowsMulti' not defined", function () {
32
+ const makeContext = (type: string, method: string) =>
33
+ ({
34
+ service: {},
35
+ type,
36
+ method,
37
+ }) as HookContext;
38
+ ["before", "after"].forEach((type) => {
39
+ ["find", "get", "create", "update", "patch", "remove"].forEach(
40
+ (method) => {
41
+ const context = makeContext(type, method);
42
+
43
+ assert.doesNotThrow(
44
+ () => checkMulti()(context),
45
+ `'${type}:${method}': does not throw`,
46
+ );
47
+ },
48
+ );
49
+ });
50
+ });
51
+
52
+ it("passes if 'allowsMulti' returns true", function () {
53
+ const makeContext = (type: string, method: string) => {
54
+ const context = {
55
+ service: {
56
+ allowsMulti: () => true,
57
+ },
58
+ method,
59
+ type,
60
+ } as HookContext;
61
+ if (method === "create") {
62
+ type === "before" ? (context.data = []) : (context.result = []);
63
+ }
64
+ return context as HookContext;
65
+ };
66
+ ["before", "after"].forEach((type) => {
67
+ ["find", "get", "create", "update", "patch", "remove"].forEach(
68
+ (method) => {
69
+ const context = makeContext(type, method);
70
+ assert.doesNotThrow(
71
+ () => checkMulti()(context),
72
+ `'${type}:${method}': does not throw`,
73
+ );
74
+ },
75
+ );
76
+ });
77
+ });
78
+
79
+ it("passes for 'find', 'get' and 'update'", function () {
80
+ const makeContext = (type: string, method: string) =>
81
+ ({
82
+ service: {
83
+ allowsMulti: () => false,
84
+ },
85
+ type,
86
+ method,
87
+ }) as HookContext;
88
+ ["before", "after"].forEach((type) => {
89
+ ["find", "get", "update"].forEach((method) => {
90
+ const context = makeContext(type, method);
91
+ assert.doesNotThrow(
92
+ () => checkMulti()(context),
93
+ `'${type}:${method}': does not throw`,
94
+ );
95
+ });
96
+ });
97
+ });
98
+
99
+ it("passes if 'allowsMulti' returns false and is no multi data", function () {
100
+ const makeContext = (type: string, method: string) => {
101
+ const context = {
102
+ service: {
103
+ allowsMulti: (method: string) => method !== "find",
104
+ },
105
+ method,
106
+ type,
107
+ } as HookContext;
108
+ if (method === "create") {
109
+ type === "before" ? (context.data = {}) : (context.result = {});
110
+ }
111
+ if (["patch", "remove"].includes(method)) {
112
+ context.id = 1;
113
+ }
114
+ return context as HookContext;
115
+ };
116
+ ["before", "after"].forEach((type) => {
117
+ ["create", "patch", "remove"].forEach((method) => {
118
+ const context = makeContext(type, method);
119
+ assert.doesNotThrow(
120
+ () => checkMulti()(context),
121
+ `'${type}:${method}': does not throw`,
122
+ );
123
+ });
124
+ });
125
+ });
126
+
127
+ it("throws if 'allowsMulti' returns false and multi data", function () {
128
+ const makeContext = (type: string, method: string) => {
129
+ const context = {
130
+ service: {
131
+ allowsMulti: () => false,
132
+ },
133
+ method,
134
+ type,
135
+ } as HookContext;
136
+ if (method === "create") {
137
+ type === "before" ? (context.data = []) : (context.result = []);
138
+ }
139
+ return context as HookContext;
140
+ };
141
+ ["before", "after"].forEach((type) => {
142
+ ["create", "patch", "remove"].forEach((method) => {
143
+ const context = makeContext(type, method);
144
+ assert.throws(() => checkMulti()(context));
145
+ });
146
+ });
147
+ });
148
+
149
+ it("can skip hook", function () {
150
+ const makeContext = (type: string, method: string) => {
151
+ const context = {
152
+ service: {
153
+ allowsMulti: () => false,
154
+ },
155
+ method,
156
+ type,
157
+ params: {},
158
+ } as HookContext;
159
+ if (method === "create") {
160
+ type === "before" ? (context.data = []) : (context.result = []);
161
+ }
162
+ return context as HookContext;
163
+ };
164
+ ["before", "after"].forEach((type) => {
165
+ ["create", "patch", "remove"].forEach((method) => {
166
+ const context = makeContext(type, method);
167
+ context.params = {
168
+ skipHooks: ["checkMulti"],
169
+ };
170
+ assert.doesNotThrow(
171
+ () => checkMulti()(context),
172
+ `'${type}:${method}': throws`,
173
+ );
174
+ });
175
+ });
176
+ });
177
+ }