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.
@@ -1,6 +1,6 @@
1
1
  import type { Query } from "@feathersjs/feathers";
2
2
 
3
- type FilterQueryResult<Q extends Query> = {
3
+ type FilterQueryResult<Q extends Query = Query> = {
4
4
  $select: Q["$select"] extends any ? Q["$select"] : never;
5
5
  $limit: Q["$limit"] extends any ? Q["$limit"] : never;
6
6
  $skip: Q["$skip"] extends any ? Q["$skip"] : never;
@@ -41,58 +41,130 @@ export function filterQuery<Q extends Query>(
41
41
  return result;
42
42
  }
43
43
 
44
+ export function reassembleQuery(query: FilterQueryResult): Query {
45
+ const { $select, $limit, $skip, $sort, query: rest } = query;
46
+
47
+ const result: Query = rest;
48
+
49
+ if ($select !== undefined) {
50
+ result.$select = $select;
51
+ }
52
+
53
+ if ($limit !== undefined) {
54
+ result.$limit = $limit;
55
+ }
56
+
57
+ if ($skip !== undefined) {
58
+ result.$skip = $skip;
59
+ }
60
+
61
+ if ($sort !== undefined) {
62
+ result.$sort = $sort;
63
+ }
64
+
65
+ return result;
66
+ }
67
+
44
68
  if (import.meta.vitest) {
45
- const { it, expect } = import.meta.vitest;
46
-
47
- it("should filter query", () => {
48
- const query = {
49
- $select: ["a"],
50
- $limit: 10,
51
- $skip: 10,
52
- $sort: {
53
- a: 1,
54
- },
55
- a: 1,
56
- b: 2,
57
- };
58
-
59
- expect(filterQuery(query)).toEqual({
60
- $select: ["a"],
61
- $limit: 10,
62
- $skip: 10,
63
- $sort: {
69
+ const { describe, it, expect } = import.meta.vitest;
70
+
71
+ describe("filterQuery", () => {
72
+ it("should filter query", () => {
73
+ const query = {
74
+ $select: ["a"],
75
+ $limit: 10,
76
+ $skip: 10,
77
+ $sort: {
78
+ a: 1,
79
+ },
64
80
  a: 1,
65
- },
66
- query: {
81
+ b: 2,
82
+ };
83
+
84
+ expect(filterQuery(query)).toEqual({
85
+ $select: ["a"],
86
+ $limit: 10,
87
+ $skip: 10,
88
+ $sort: {
89
+ a: 1,
90
+ },
91
+ query: {
92
+ a: 1,
93
+ b: 2,
94
+ },
95
+ });
96
+ });
97
+
98
+ it("should not include filters if not provided", () => {
99
+ const query = {
67
100
  a: 1,
68
101
  b: 2,
69
- },
102
+ };
103
+
104
+ expect(filterQuery(query)).toEqual({
105
+ query: {
106
+ a: 1,
107
+ b: 2,
108
+ },
109
+ });
70
110
  });
71
- });
72
111
 
73
- it("should not include filters if not provided", () => {
74
- const query = {
75
- a: 1,
76
- b: 2,
77
- };
112
+ it("sets empty query object if empty object is provided", () => {
113
+ expect(filterQuery({})).toEqual({
114
+ query: {},
115
+ });
116
+ });
117
+
118
+ it("sets empty query object if undefined is provided", () => {
119
+ expect(filterQuery(undefined)).toEqual({
120
+ query: {},
121
+ });
122
+ });
123
+ });
78
124
 
79
- expect(filterQuery(query)).toEqual({
80
- query: {
125
+ describe("reassembleQuery", () => {
126
+ it("should filter query", () => {
127
+ expect(
128
+ reassembleQuery(
129
+ filterQuery({
130
+ $select: ["a"],
131
+ $limit: 10,
132
+ $skip: 10,
133
+ $sort: {
134
+ a: 1,
135
+ },
136
+ a: 1,
137
+ b: 2,
138
+ }),
139
+ ),
140
+ ).toEqual({
141
+ $select: ["a"],
142
+ $limit: 10,
143
+ $skip: 10,
144
+ $sort: {
145
+ a: 1,
146
+ },
81
147
  a: 1,
82
148
  b: 2,
83
- },
149
+ });
84
150
  });
85
- });
86
151
 
87
- it("sets empty query object if empty object is provided", () => {
88
- expect(filterQuery({})).toEqual({
89
- query: {},
152
+ it("should not include filters if not provided", () => {
153
+ expect(
154
+ reassembleQuery(
155
+ filterQuery({
156
+ a: 1,
157
+ b: 2,
158
+ }),
159
+ ),
160
+ ).toEqual({
161
+ a: 1,
162
+ b: 2,
163
+ });
90
164
  });
91
- });
92
165
 
93
- it("sets empty query object if undefined is provided", () => {
94
- expect(filterQuery(undefined)).toEqual({
95
- query: {},
166
+ it("sets empty query object if empty object is provided", () => {
167
+ expect(reassembleQuery(filterQuery({}))).toEqual({});
96
168
  });
97
169
  });
98
170
  }
@@ -0,0 +1,198 @@
1
+ import type { Query } from "@feathersjs/feathers";
2
+ import { filterQuery, reassembleQuery } from "./filterQuery";
3
+ import _set from "lodash/set.js";
4
+ import { isObject } from "./_utils.internal";
5
+
6
+ export function flattenQuery(q: Query) {
7
+ if (Array.isArray(q)) {
8
+ return q.map(flattenQuery);
9
+ }
10
+
11
+ if (!isObject(q)) {
12
+ return q;
13
+ }
14
+
15
+ const { query, $limit, $select, $skip, $sort } = filterQuery(q);
16
+
17
+ type StepOptions = {
18
+ prev?: string[];
19
+ result?: Query;
20
+ };
21
+
22
+ const res = {};
23
+
24
+ function step(object: Query, options?: StepOptions) {
25
+ const { prev = [], result = res } = options ?? {};
26
+
27
+ Object.keys(object).forEach((key) => {
28
+ const value = object[key];
29
+ if (Array.isArray(value)) {
30
+ const newValues = value.map((v) =>
31
+ step(v, {
32
+ result: {},
33
+ }),
34
+ );
35
+ _set(result, [...prev, key], newValues);
36
+ return;
37
+ }
38
+
39
+ if (key.startsWith("$")) {
40
+ _set(result, [...prev, key], value);
41
+ return;
42
+ }
43
+
44
+ const newKey = !prev.length
45
+ ? [key]
46
+ : [...prev.slice(0, -1), `${prev[prev.length - 1]}.${key}`];
47
+
48
+ if (!isObject(value)) {
49
+ _set(result, newKey, value);
50
+ return;
51
+ } else {
52
+ step(value, {
53
+ prev: newKey,
54
+ result,
55
+ });
56
+ return;
57
+ }
58
+ });
59
+
60
+ return result;
61
+ }
62
+
63
+ return reassembleQuery({
64
+ $limit,
65
+ $select,
66
+ $skip,
67
+ $sort,
68
+ query: step(query),
69
+ });
70
+ }
71
+
72
+ if (import.meta.vitest) {
73
+ const { describe, it, expect } = import.meta.vitest;
74
+
75
+ describe("flattenQuery", () => {
76
+ it("should flatten a query", () => {
77
+ expect(
78
+ flattenQuery({
79
+ a: 1,
80
+ b: {
81
+ c: 1,
82
+ d: 1,
83
+ },
84
+ e: {
85
+ f: {
86
+ g: 1,
87
+ },
88
+ },
89
+ }),
90
+ ).toEqual({
91
+ a: 1,
92
+ "b.c": 1,
93
+ "b.d": 1,
94
+ "e.f.g": 1,
95
+ });
96
+ });
97
+ });
98
+
99
+ it("should handle operators", () => {
100
+ expect(
101
+ flattenQuery({
102
+ a: {
103
+ b: 1,
104
+ },
105
+ c: {
106
+ $gt: 1,
107
+ $lte: 1,
108
+ },
109
+ }),
110
+ ).toEqual({
111
+ "a.b": 1,
112
+ c: {
113
+ $gt: 1,
114
+ $lte: 1,
115
+ },
116
+ });
117
+ });
118
+
119
+ it("should handle $or / $and", () => {
120
+ expect(
121
+ flattenQuery({
122
+ a: {
123
+ b: 1,
124
+ },
125
+ $and: [
126
+ {
127
+ c: {
128
+ d: 1,
129
+ },
130
+ },
131
+ ],
132
+ $or: [
133
+ {
134
+ e: {
135
+ f: 1,
136
+ },
137
+ },
138
+ ],
139
+ }),
140
+ ).toEqual({
141
+ "a.b": 1,
142
+ $and: [
143
+ {
144
+ "c.d": 1,
145
+ },
146
+ ],
147
+ $or: [
148
+ {
149
+ "e.f": 1,
150
+ },
151
+ ],
152
+ });
153
+ });
154
+
155
+ it("should handle nested $or / $and with operators", () => {
156
+ expect(
157
+ flattenQuery({
158
+ a: {
159
+ b: 1,
160
+ },
161
+ $and: [
162
+ {
163
+ $or: [
164
+ {
165
+ c1: {
166
+ d1: 1,
167
+ },
168
+ },
169
+ {
170
+ c2: {
171
+ d2: {
172
+ $gt: 1,
173
+ },
174
+ },
175
+ },
176
+ ],
177
+ },
178
+ ],
179
+ }),
180
+ ).toEqual({
181
+ "a.b": 1,
182
+ $and: [
183
+ {
184
+ $or: [
185
+ {
186
+ "c1.d1": 1,
187
+ },
188
+ {
189
+ "c2.d2": {
190
+ $gt: 1,
191
+ },
192
+ },
193
+ ],
194
+ },
195
+ ],
196
+ });
197
+ });
198
+ }
@@ -41,3 +41,282 @@ export const getItemsIsArray = <T = any, H extends HookContext = HookContext>(
41
41
  isArray,
42
42
  };
43
43
  };
44
+
45
+ if (import.meta.vitest) {
46
+ const { describe, it, assert } = import.meta.vitest;
47
+
48
+ const assertBefore = (context, items, isArray) => {
49
+ const arrays: (GetItemsIsArrayFrom | undefined)[] = [
50
+ undefined,
51
+ "data",
52
+ "automatic",
53
+ ];
54
+
55
+ for (const from of arrays) {
56
+ const { items: items2, isArray: isArray2 } = getItemsIsArray(context, {
57
+ from,
58
+ });
59
+ assert.deepStrictEqual(items2, items, `from: ${from}`);
60
+ assert.deepStrictEqual(isArray2, isArray, `from: ${from}`);
61
+ }
62
+ };
63
+
64
+ const assertAfter = (context, items, isArray) => {
65
+ const arrays: (GetItemsIsArrayFrom | undefined)[] = [
66
+ undefined,
67
+ "result",
68
+ "automatic",
69
+ ];
70
+
71
+ for (const from of arrays) {
72
+ const { items: items2, isArray: isArray2 } = getItemsIsArray(context, {
73
+ from,
74
+ });
75
+ assert.deepStrictEqual(items2, items, `from: ${from}`);
76
+ assert.deepStrictEqual(isArray2, isArray, `from: ${from}`);
77
+ }
78
+ };
79
+
80
+ describe("before", function () {
81
+ it("find:before", async function () {
82
+ const context = {
83
+ type: "before",
84
+ method: "find",
85
+ params: {
86
+ query: {},
87
+ },
88
+ } as any as HookContext;
89
+
90
+ assertBefore(context, [], false);
91
+ });
92
+
93
+ it("get:before", async function () {
94
+ const context = {
95
+ type: "before",
96
+ method: "get",
97
+ id: 1,
98
+ params: {
99
+ query: {},
100
+ },
101
+ } as any as HookContext;
102
+
103
+ assertBefore(context, [], false);
104
+ });
105
+
106
+ it("create:before single", async function () {
107
+ const context = {
108
+ type: "before",
109
+ method: "create",
110
+ data: { test: true },
111
+ params: {
112
+ query: {},
113
+ },
114
+ } as any as HookContext;
115
+
116
+ assertBefore(context, [{ test: true }], false);
117
+ });
118
+
119
+ it("create:before multi", async function () {
120
+ const context = {
121
+ type: "before",
122
+ method: "create",
123
+ data: [{ test: true }, { test: true }],
124
+ params: {
125
+ query: {},
126
+ },
127
+ } as any as HookContext;
128
+
129
+ assertBefore(context, [{ test: true }, { test: true }], true);
130
+ });
131
+
132
+ it("update:before single", async function () {
133
+ const context = {
134
+ type: "before",
135
+ method: "update",
136
+ id: 1,
137
+ data: { test: true },
138
+ params: {
139
+ query: {},
140
+ },
141
+ } as any as HookContext;
142
+
143
+ assertBefore(context, [{ test: true }], false);
144
+ });
145
+
146
+ it("patch:before single", async function () {
147
+ const context = {
148
+ type: "before",
149
+ method: "patch",
150
+ id: 1,
151
+ data: { test: true },
152
+ params: {
153
+ query: {},
154
+ },
155
+ } as any as HookContext;
156
+
157
+ assertBefore(context, [{ test: true }], false);
158
+ });
159
+
160
+ it("patch:before multi", async function () {
161
+ const context = {
162
+ type: "before",
163
+ method: "patch",
164
+ id: null,
165
+ data: { test: true },
166
+ params: {
167
+ query: {},
168
+ },
169
+ } as any as HookContext;
170
+
171
+ assertBefore(context, [{ test: true }], false);
172
+ });
173
+
174
+ it("remove:before single", async function () {
175
+ const context = {
176
+ type: "before",
177
+ method: "remove",
178
+ id: 1,
179
+ params: {
180
+ query: {},
181
+ },
182
+ } as any as HookContext;
183
+
184
+ assertBefore(context, [], false);
185
+ });
186
+
187
+ it("remove:before multi", async function () {
188
+ const context = {
189
+ type: "before",
190
+ method: "remove",
191
+ id: null,
192
+ params: {
193
+ query: {},
194
+ },
195
+ } as any as HookContext;
196
+
197
+ assertBefore(context, [], false);
198
+ });
199
+ });
200
+
201
+ describe("after", function () {
202
+ it("find:after paginate:true", function () {
203
+ const context = {
204
+ type: "after",
205
+ method: "find",
206
+ result: {
207
+ total: 1,
208
+ skip: 0,
209
+ limit: 10,
210
+ data: [{ test: true }],
211
+ },
212
+ params: {
213
+ query: {},
214
+ },
215
+ } as any as HookContext;
216
+
217
+ assertAfter(context, [{ test: true }], true);
218
+ });
219
+
220
+ it("find:after paginate:false", function () {
221
+ const context = {
222
+ type: "after",
223
+ method: "find",
224
+ result: [{ test: true }],
225
+ params: {
226
+ query: {},
227
+ },
228
+ } as any as HookContext;
229
+
230
+ assertAfter(context, [{ test: true }], true);
231
+ });
232
+
233
+ it("get:after", function () {
234
+ const context = {
235
+ type: "after",
236
+ method: "get",
237
+ id: 1,
238
+ result: { test: true },
239
+ params: {
240
+ query: {},
241
+ },
242
+ } as any as HookContext;
243
+
244
+ assertAfter(context, [{ test: true }], false);
245
+ });
246
+
247
+ it("update:after", function () {
248
+ const context = {
249
+ type: "after",
250
+ method: "update",
251
+ id: 1,
252
+ data: { test: "yes" },
253
+ result: { test: true },
254
+ params: {
255
+ query: {},
256
+ },
257
+ } as any as HookContext;
258
+
259
+ assertAfter(context, [{ test: true }], false);
260
+ });
261
+
262
+ it("patch:after single", function () {
263
+ const context = {
264
+ type: "after",
265
+ method: "patch",
266
+ id: 1,
267
+ data: { test: "yes" },
268
+ result: { test: true },
269
+ params: {
270
+ query: {},
271
+ },
272
+ } as any as HookContext;
273
+
274
+ assertAfter(context, [{ test: true }], false);
275
+ });
276
+
277
+ it("patch:after multi", function () {
278
+ const context = {
279
+ type: "after",
280
+ method: "patch",
281
+ id: null,
282
+ data: { test: "yes" },
283
+ result: [{ test: true }, { test: true }],
284
+ params: {
285
+ query: {},
286
+ },
287
+ } as any as HookContext;
288
+
289
+ assertAfter(context, [{ test: true }, { test: true }], true);
290
+ });
291
+
292
+ it("remove:after single", function () {
293
+ const context = {
294
+ type: "after",
295
+ method: "remove",
296
+ id: 1,
297
+ data: { test: "yes" },
298
+ result: { test: true },
299
+ params: {
300
+ query: {},
301
+ },
302
+ } as any as HookContext;
303
+
304
+ assertAfter(context, [{ test: true }], false);
305
+ });
306
+
307
+ it("remove:after multi", function () {
308
+ const context = {
309
+ type: "after",
310
+ method: "remove",
311
+ id: null,
312
+ data: { test: "yes" },
313
+ result: [{ test: true }, { test: true }],
314
+ params: {
315
+ query: {},
316
+ },
317
+ } as any as HookContext;
318
+
319
+ assertAfter(context, [{ test: true }, { test: true }], true);
320
+ });
321
+ });
322
+ }