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 { PaginationOptions } from "@feathersjs/adapter-commons";
2
2
  import type { HookContext } from "@feathersjs/feathers";
3
- import { hasOwnProperty } from "./internal.utils";
3
+ import { hasOwnProperty } from "./_utils.internal";
4
4
 
5
5
  /**
6
6
  * util to get paginate options from context
@@ -27,3 +27,76 @@ export const getPaginate = <H extends HookContext = HookContext>(
27
27
 
28
28
  return options.paginate || undefined;
29
29
  };
30
+
31
+ if (import.meta.vitest) {
32
+ const { it, assert } = import.meta.vitest;
33
+
34
+ it("returns service.options.paginate", function () {
35
+ const serviceOptions = {
36
+ paginate: {
37
+ default: 10,
38
+ max: 50,
39
+ },
40
+ };
41
+
42
+ const paginate = getPaginate({
43
+ params: {},
44
+ service: {
45
+ options: serviceOptions,
46
+ },
47
+ } as HookContext);
48
+
49
+ assert.deepStrictEqual(paginate, { default: 10, max: 50 });
50
+ });
51
+
52
+ it("returns undefined for params.paginate: false", function () {
53
+ const serviceOptions = {
54
+ paginate: {
55
+ default: 10,
56
+ max: 50,
57
+ },
58
+ };
59
+
60
+ const paginate = getPaginate({
61
+ params: { paginate: false },
62
+ service: {
63
+ options: serviceOptions,
64
+ },
65
+ } as HookContext);
66
+
67
+ assert.deepStrictEqual(paginate, undefined);
68
+ });
69
+
70
+ it("returns context.adapter.paginate over service.options.paginate", function () {
71
+ const serviceOptions = {
72
+ paginate: {
73
+ default: 10,
74
+ max: 50,
75
+ },
76
+ };
77
+
78
+ const paginate = getPaginate({
79
+ params: { adapter: { paginate: { default: 20, max: 100 } } },
80
+ service: {
81
+ options: serviceOptions,
82
+ },
83
+ } as HookContext);
84
+
85
+ assert.deepStrictEqual(paginate, { default: 20, max: 100 });
86
+ });
87
+
88
+ it("returns undefined for no paginate", function () {
89
+ const serviceOptions = {
90
+ paginate: false,
91
+ };
92
+
93
+ const paginate = getPaginate({
94
+ params: {},
95
+ service: {
96
+ options: serviceOptions,
97
+ },
98
+ } as HookContext);
99
+
100
+ assert.deepStrictEqual(paginate, undefined);
101
+ });
102
+ }
@@ -13,3 +13,5 @@ export * from "./shouldSkip";
13
13
  export * from "./toJSON";
14
14
  export * from "./validateQueryProperty";
15
15
  export * from "./optimizeBatchPatch";
16
+ export * from "./flattenQuery";
17
+ export * from "./deflattenQuery";
@@ -25,3 +25,54 @@ export const isMulti = <H extends HookContext = HookContext>(
25
25
  }
26
26
  return false;
27
27
  };
28
+
29
+ if (import.meta.vitest) {
30
+ const { it, assert } = import.meta.vitest;
31
+
32
+ it("returns true", function () {
33
+ const makeContext = (type: string, method: string) => {
34
+ const context = {
35
+ method,
36
+ type,
37
+ } as HookContext;
38
+ if (method === "create") {
39
+ type === "before" ? (context.data = []) : (context.result = []);
40
+ }
41
+ return context;
42
+ };
43
+ ["before", "after"].forEach((type) => {
44
+ ["find", "create", "patch", "remove"].forEach((method) => {
45
+ const context = makeContext(type, method);
46
+ assert.strictEqual(
47
+ isMulti(context),
48
+ true,
49
+ `'${type}:${method}': returns true`,
50
+ );
51
+ });
52
+ });
53
+ });
54
+
55
+ it("returns false", function () {
56
+ const makeContext = (type: string, method: string) => {
57
+ const context = {
58
+ method,
59
+ type,
60
+ id: 0,
61
+ } as HookContext;
62
+ if (method === "create") {
63
+ type === "before" ? (context.data = {}) : (context.result = {});
64
+ }
65
+ return context;
66
+ };
67
+ ["before", "after"].forEach((type) => {
68
+ ["get", "create", "update", "patch", "remove"].forEach((method) => {
69
+ const context = makeContext(type, method);
70
+ assert.strictEqual(
71
+ isMulti(context),
72
+ false,
73
+ `'${type}:${method}': returns false`,
74
+ );
75
+ });
76
+ });
77
+ });
78
+ }
@@ -15,3 +15,75 @@ export const isPaginated = <H extends HookContext = HookContext>(
15
15
 
16
16
  return !!paginate;
17
17
  };
18
+
19
+ if (import.meta.vitest) {
20
+ const { it, assert } = import.meta.vitest;
21
+
22
+ it("returns true for service.options.paginate", function () {
23
+ const serviceOptions = {
24
+ paginate: {
25
+ default: 10,
26
+ max: 50,
27
+ },
28
+ };
29
+
30
+ const paginate = isPaginated({
31
+ params: {},
32
+ service: {
33
+ options: serviceOptions,
34
+ },
35
+ method: "find",
36
+ } as HookContext);
37
+
38
+ assert.deepStrictEqual(paginate, true);
39
+ });
40
+
41
+ it("returns false for params.paginate: false", function () {
42
+ const serviceOptions = {
43
+ paginate: {
44
+ default: 10,
45
+ max: 50,
46
+ },
47
+ };
48
+
49
+ const paginate = isPaginated({
50
+ params: { paginate: false },
51
+ service: {
52
+ options: serviceOptions,
53
+ },
54
+ } as HookContext);
55
+
56
+ assert.deepStrictEqual(paginate, false);
57
+ });
58
+
59
+ it("returns true for context.adapter.paginate", function () {
60
+ const serviceOptions = {
61
+ paginate: false,
62
+ };
63
+
64
+ const paginate = isPaginated({
65
+ params: { adapter: { paginate: { default: 20, max: 100 } } },
66
+ service: {
67
+ options: serviceOptions,
68
+ },
69
+ method: "find",
70
+ } as HookContext);
71
+
72
+ assert.deepStrictEqual(paginate, true);
73
+ });
74
+
75
+ it("returns false for no paginate", function () {
76
+ const serviceOptions = {
77
+ paginate: false,
78
+ };
79
+
80
+ const paginate = isPaginated({
81
+ params: {},
82
+ service: {
83
+ options: serviceOptions,
84
+ },
85
+ } as HookContext);
86
+
87
+ assert.deepStrictEqual(paginate, false);
88
+ });
89
+ }
@@ -27,3 +27,414 @@ export function markHookForSkip<H extends HookContext = HookContext>(
27
27
  context!.params = params;
28
28
  return context;
29
29
  }
30
+
31
+ if (import.meta.vitest) {
32
+ const { it, assert } = import.meta.vitest;
33
+ const { feathers } = await import("@feathersjs/feathers");
34
+ const { MemoryService } = await import("@feathersjs/memory");
35
+ const { hasOwnProperty } = await import("./_utils.internal");
36
+ const { shouldSkip } = await import("./shouldSkip");
37
+
38
+ it("returns hook object", function () {
39
+ const context = markHookForSkip("test", "all");
40
+ assert.ok(context, "returned context");
41
+ assert.ok(context.params.skipHooks, "has skipHooks");
42
+ });
43
+
44
+ it("returns hook object for undefined context", function () {
45
+ const context = markHookForSkip("test", "all");
46
+ assert.ok(context, "returned context");
47
+ assert.ok(context.params.skipHooks, "has skipHooks");
48
+ });
49
+
50
+ it("skips explicitly before hook", async function () {
51
+ const app = feathers();
52
+ app.use("service", new MemoryService());
53
+ const service = app.service("service");
54
+ const ranInto = {};
55
+ service.hooks({
56
+ before: {
57
+ all: [
58
+ (context) => {
59
+ markHookForSkip("test", "before", context);
60
+ },
61
+ ],
62
+ find: [
63
+ (context) => {
64
+ if (shouldSkip("test", context)) {
65
+ return context;
66
+ }
67
+ ranInto["find"] = true;
68
+ },
69
+ (context) => {
70
+ context.result = null;
71
+ },
72
+ ],
73
+ get: [
74
+ (context) => {
75
+ if (shouldSkip("test", context)) {
76
+ return context;
77
+ }
78
+ ranInto["get"] = true;
79
+ },
80
+ (context) => {
81
+ context.result = null;
82
+ },
83
+ ],
84
+ create: [
85
+ (context) => {
86
+ if (shouldSkip("test", context)) {
87
+ return context;
88
+ }
89
+ ranInto["create"] = true;
90
+ },
91
+ (context) => {
92
+ context.result = null;
93
+ },
94
+ ],
95
+ update: [
96
+ (context) => {
97
+ if (shouldSkip("test", context)) {
98
+ return context;
99
+ }
100
+ ranInto["update"] = true;
101
+ },
102
+ (context) => {
103
+ context.result = null;
104
+ },
105
+ ],
106
+ patch: [
107
+ (context) => {
108
+ if (shouldSkip("test", context)) {
109
+ return context;
110
+ }
111
+ ranInto["patch"] = true;
112
+ },
113
+ (context) => {
114
+ context.result = null;
115
+ },
116
+ ],
117
+ remove: [
118
+ (context) => {
119
+ if (shouldSkip("test", context)) {
120
+ return context;
121
+ }
122
+ ranInto["remove"] = true;
123
+ },
124
+ (context) => {
125
+ context.result = null;
126
+ },
127
+ ],
128
+ },
129
+ });
130
+ const methods = {
131
+ find: [],
132
+ get: [1],
133
+ create: [{}],
134
+ update: [1, {}],
135
+ patch: [1, {}],
136
+ remove: [1],
137
+ };
138
+ const promises = Object.keys(methods).map(async (method) => {
139
+ await service[method](...methods[method]);
140
+ assert.ok(
141
+ !hasOwnProperty(ranInto, method),
142
+ `'${method}': did not run into hook`,
143
+ );
144
+ return true;
145
+ });
146
+
147
+ const results = await Promise.all(promises);
148
+ assert.ok(
149
+ results.every((x) => x === true),
150
+ "all ok",
151
+ );
152
+ });
153
+
154
+ it("skips explicitly after hook", async function () {
155
+ const app = feathers();
156
+ app.use("service", new MemoryService());
157
+ const service = app.service("service");
158
+ const ranInto = {};
159
+ service.hooks({
160
+ before: {
161
+ all: [
162
+ (context) => {
163
+ markHookForSkip("test", "after", context);
164
+ },
165
+ (context) => {
166
+ context.result = null;
167
+ return context;
168
+ },
169
+ ],
170
+ },
171
+ after: {
172
+ find: [
173
+ (context) => {
174
+ if (shouldSkip("test", context)) {
175
+ return context;
176
+ }
177
+ ranInto["find"] = true;
178
+ },
179
+ (context) => {
180
+ context.result = null;
181
+ },
182
+ ],
183
+ get: [
184
+ (context) => {
185
+ if (shouldSkip("test", context)) {
186
+ return context;
187
+ }
188
+ ranInto["get"] = true;
189
+ },
190
+ (context) => {
191
+ context.result = null;
192
+ },
193
+ ],
194
+ create: [
195
+ (context) => {
196
+ if (shouldSkip("test", context)) {
197
+ return context;
198
+ }
199
+ ranInto["create"] = true;
200
+ },
201
+ (context) => {
202
+ context.result = null;
203
+ },
204
+ ],
205
+ update: [
206
+ (context) => {
207
+ if (shouldSkip("test", context)) {
208
+ return context;
209
+ }
210
+ ranInto["update"] = true;
211
+ },
212
+ (context) => {
213
+ context.result = null;
214
+ },
215
+ ],
216
+ patch: [
217
+ (context) => {
218
+ if (shouldSkip("test", context)) {
219
+ return context;
220
+ }
221
+ ranInto["patch"] = true;
222
+ },
223
+ (context) => {
224
+ context.result = null;
225
+ },
226
+ ],
227
+ remove: [
228
+ (context) => {
229
+ if (shouldSkip("test", context)) {
230
+ return context;
231
+ }
232
+ ranInto["remove"] = true;
233
+ },
234
+ (context) => {
235
+ context.result = null;
236
+ },
237
+ ],
238
+ },
239
+ });
240
+ const methods = {
241
+ find: [],
242
+ get: [1],
243
+ create: [{}],
244
+ update: [1, {}],
245
+ patch: [1, {}],
246
+ remove: [1],
247
+ };
248
+ const promises = Object.keys(methods).map(async (method) => {
249
+ await service[method](...methods[method]);
250
+ assert.ok(
251
+ !hasOwnProperty(ranInto, method),
252
+ `'${method}': did not run into hook`,
253
+ );
254
+ return true;
255
+ });
256
+
257
+ const results = await Promise.all(promises);
258
+ assert.ok(
259
+ results.every((x) => x === true),
260
+ "all ok",
261
+ );
262
+ });
263
+
264
+ it("skips all hooks", async function () {
265
+ const app = feathers();
266
+ app.use("service", new MemoryService());
267
+ const service = app.service("service");
268
+ const ranIntoBefore = {};
269
+ const ranIntoAfter = {};
270
+ service.hooks({
271
+ before: {
272
+ all: [
273
+ (context) => {
274
+ markHookForSkip("test", "all", context);
275
+ },
276
+ ],
277
+ find: [
278
+ (context) => {
279
+ if (shouldSkip("test", context)) {
280
+ return context;
281
+ }
282
+ ranIntoBefore["find"] = true;
283
+ },
284
+ (context) => {
285
+ context.result = null;
286
+ },
287
+ ],
288
+ get: [
289
+ (context) => {
290
+ if (shouldSkip("test", context)) {
291
+ return context;
292
+ }
293
+ ranIntoBefore["get"] = true;
294
+ },
295
+ (context) => {
296
+ context.result = null;
297
+ },
298
+ ],
299
+ create: [
300
+ (context) => {
301
+ if (shouldSkip("test", context)) {
302
+ return context;
303
+ }
304
+ ranIntoBefore["create"] = true;
305
+ },
306
+ (context) => {
307
+ context.result = null;
308
+ },
309
+ ],
310
+ update: [
311
+ (context) => {
312
+ if (shouldSkip("test", context)) {
313
+ return context;
314
+ }
315
+ ranIntoBefore["update"] = true;
316
+ },
317
+ (context) => {
318
+ context.result = null;
319
+ },
320
+ ],
321
+ patch: [
322
+ (context) => {
323
+ if (shouldSkip("test", context)) {
324
+ return context;
325
+ }
326
+ ranIntoBefore["patch"] = true;
327
+ },
328
+ (context) => {
329
+ context.result = null;
330
+ },
331
+ ],
332
+ remove: [
333
+ (context) => {
334
+ if (shouldSkip("test", context)) {
335
+ return context;
336
+ }
337
+ ranIntoBefore["remove"] = true;
338
+ },
339
+ (context) => {
340
+ context.result = null;
341
+ },
342
+ ],
343
+ },
344
+ after: {
345
+ find: [
346
+ (context) => {
347
+ if (shouldSkip("test", context)) {
348
+ return context;
349
+ }
350
+ ranIntoAfter["find"] = true;
351
+ },
352
+ (context) => {
353
+ context.result = null;
354
+ },
355
+ ],
356
+ get: [
357
+ (context) => {
358
+ if (shouldSkip("test", context)) {
359
+ return context;
360
+ }
361
+ ranIntoAfter["get"] = true;
362
+ },
363
+ (context) => {
364
+ context.result = null;
365
+ },
366
+ ],
367
+ create: [
368
+ (context) => {
369
+ if (shouldSkip("test", context)) {
370
+ return context;
371
+ }
372
+ ranIntoAfter["create"] = true;
373
+ },
374
+ (context) => {
375
+ context.result = null;
376
+ },
377
+ ],
378
+ update: [
379
+ (context) => {
380
+ if (shouldSkip("test", context)) {
381
+ return context;
382
+ }
383
+ ranIntoAfter["update"] = true;
384
+ },
385
+ (context) => {
386
+ context.result = null;
387
+ },
388
+ ],
389
+ patch: [
390
+ (context) => {
391
+ if (shouldSkip("test", context)) {
392
+ return context;
393
+ }
394
+ ranIntoAfter["patch"] = true;
395
+ },
396
+ (context) => {
397
+ context.result = null;
398
+ },
399
+ ],
400
+ remove: [
401
+ (context) => {
402
+ if (shouldSkip("test", context)) {
403
+ return context;
404
+ }
405
+ ranIntoAfter["remove"] = true;
406
+ },
407
+ (context) => {
408
+ context.result = null;
409
+ },
410
+ ],
411
+ },
412
+ });
413
+ const methods = {
414
+ find: [],
415
+ get: [1],
416
+ create: [{}],
417
+ update: [1, {}],
418
+ patch: [1, {}],
419
+ remove: [1],
420
+ };
421
+ const promises = Object.keys(methods).map(async (method) => {
422
+ await service[method](...methods[method]);
423
+ assert.ok(
424
+ !hasOwnProperty(ranIntoBefore, method),
425
+ `'${method}': did not run into before hook`,
426
+ );
427
+ assert.ok(
428
+ !hasOwnProperty(ranIntoAfter, method),
429
+ `'${method}': did not run into after hook`,
430
+ );
431
+ return true;
432
+ });
433
+
434
+ const results = await Promise.all(promises);
435
+ assert.ok(
436
+ results.every((x) => x === true),
437
+ "all ok",
438
+ );
439
+ });
440
+ }
@@ -48,3 +48,71 @@ export function mergeArrays<T>(
48
48
  }
49
49
  return undefined;
50
50
  }
51
+
52
+ if (import.meta.vitest) {
53
+ const { it, expect } = import.meta.vitest;
54
+
55
+ describe("target", function () {
56
+ it("returns target", function () {
57
+ const arr = mergeArrays([1], [2], "target");
58
+
59
+ expect(arr).toStrictEqual([1]);
60
+ });
61
+
62
+ it("returns target even if undefined", function () {
63
+ const arr = mergeArrays(undefined, [1], "target");
64
+
65
+ expect(arr).toBeUndefined();
66
+ });
67
+ });
68
+
69
+ describe("source", function () {
70
+ it("returns source", function () {
71
+ const arr = mergeArrays([1], [2], "source");
72
+
73
+ expect(arr).toStrictEqual([2]);
74
+ });
75
+
76
+ it("returns source even if undefined", function () {
77
+ const arr = mergeArrays([1], undefined, "source");
78
+
79
+ expect(arr).toBeUndefined();
80
+ });
81
+ });
82
+
83
+ describe("combine", function () {
84
+ it("returns combined array", function () {
85
+ const arr = mergeArrays([1, 2], [2, 3], "combine");
86
+
87
+ expect(arr).toStrictEqual([1, 2, 3]);
88
+ });
89
+
90
+ it("returns one array if combine", function () {
91
+ const arr1 = mergeArrays([1, 2], undefined, "combine");
92
+
93
+ expect(arr1).toStrictEqual([1, 2]);
94
+
95
+ const arr2 = mergeArrays(undefined, [1, 2], "combine");
96
+
97
+ expect(arr2).toStrictEqual([1, 2]);
98
+ });
99
+ });
100
+
101
+ describe("intersect", function () {
102
+ it("returns intersected array", function () {
103
+ const arr = mergeArrays([1, 2], [2, 3], "intersect");
104
+
105
+ expect(arr).toStrictEqual([2]);
106
+ });
107
+
108
+ it("returns undefined if one is undefined", function () {
109
+ const arr1 = mergeArrays([1, 2], undefined, "intersect");
110
+
111
+ expect(arr1).toBeUndefined();
112
+
113
+ const arr2 = mergeArrays(undefined, [1, 2], "intersect");
114
+
115
+ expect(arr2).toBeUndefined();
116
+ });
117
+ });
118
+ }