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/README.md +3 -2
- package/dist/index.cjs +117 -32
- package/dist/index.d.cts +13 -8
- package/dist/index.d.mts +13 -8
- package/dist/index.d.ts +13 -8
- package/dist/index.mjs +115 -29
- package/package.json +18 -20
- package/src/filters/object.ts +2 -2
- package/src/hooks/checkMulti.ts +151 -0
- package/src/hooks/setData.ts +509 -0
- package/src/mixins/debounce-mixin/DebouncedStore.ts +1 -9
- package/src/mixins/debounce-mixin/debounceMixin.ts +2 -1
- package/src/mixins/debounce-mixin/utils.ts +10 -0
- package/src/types.ts +1 -0
- package/src/typesInternal.ts +2 -0
- package/src/utils/_utils.internal.ts +16 -0
- package/src/utils/deflattenQuery.ts +109 -0
- package/src/utils/filterQuery.ts +112 -40
- package/src/utils/flattenQuery.ts +198 -0
- package/src/utils/getItemsIsArray.ts +279 -0
- package/src/utils/getPaginate.ts +74 -1
- package/src/utils/index.ts +2 -0
- package/src/utils/isMulti.ts +51 -0
- package/src/utils/isPaginated.ts +72 -0
- package/src/utils/markHookForSkip.ts +411 -0
- package/src/utils/mergeQuery/mergeArrays.ts +68 -0
- package/src/utils/mergeQuery/mergeQuery.ts +464 -3
- package/src/utils/mergeQuery/types.ts +0 -1
- package/src/utils/mergeQuery/utils.ts +93 -5
- package/src/utils/optimizeBatchPatch.ts +54 -22
- package/src/utils/pushSet.ts +67 -1
- package/src/utils/setQueryKeySafely.ts +169 -4
- package/src/utils/setResultEmpty.ts +260 -0
- package/src/utils/shouldSkip.ts +121 -0
- package/src/utils/validateQueryProperty.ts +3 -6
- package/src/utils/internal.utils.ts +0 -9
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import _merge from "lodash/merge.js";
|
|
2
|
-
import _isEmpty from "lodash/isEmpty.js";
|
|
3
2
|
import type { Query } from "@feathersjs/feathers";
|
|
4
3
|
import {
|
|
5
4
|
areQueriesOverlapping,
|
|
@@ -10,7 +9,7 @@ import {
|
|
|
10
9
|
} from "./utils";
|
|
11
10
|
import type { MergeQueryOptions } from "./types";
|
|
12
11
|
import { filterQuery } from "../filterQuery";
|
|
13
|
-
import { hasOwnProperty } from "../internal
|
|
12
|
+
import { hasOwnProperty, isEmpty } from "../_utils.internal";
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Merges two queries into one.
|
|
@@ -72,7 +71,7 @@ export function mergeQuery(
|
|
|
72
71
|
options.useLogicalConjunction &&
|
|
73
72
|
(options.defaultHandle === "combine" ||
|
|
74
73
|
options.defaultHandle === "intersect") &&
|
|
75
|
-
!
|
|
74
|
+
!isEmpty(targetQuery)
|
|
76
75
|
) {
|
|
77
76
|
const logicalOp = options.defaultHandle === "combine" ? "$or" : "$and";
|
|
78
77
|
if (hasOwnProperty(sourceQuery, logicalOp)) {
|
|
@@ -99,3 +98,465 @@ export function mergeQuery(
|
|
|
99
98
|
...targetQuery,
|
|
100
99
|
};
|
|
101
100
|
}
|
|
101
|
+
|
|
102
|
+
if (import.meta.vitest) {
|
|
103
|
+
const { it, describe, expect } = import.meta.vitest;
|
|
104
|
+
const { feathers } = await import("@feathersjs/feathers");
|
|
105
|
+
const { MemoryService } = await import("@feathersjs/memory");
|
|
106
|
+
const { filterArray } = await import("../../filters/array");
|
|
107
|
+
const { Forbidden } = await import("@feathersjs/errors");
|
|
108
|
+
|
|
109
|
+
describe("general", function () {
|
|
110
|
+
it("$limit: -1", function () {
|
|
111
|
+
expect(mergeQuery({ $limit: -1 }, { id: 1 })).toEqual({
|
|
112
|
+
$limit: -1,
|
|
113
|
+
id: 1,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("simple objects passing", function () {
|
|
119
|
+
const app = feathers();
|
|
120
|
+
app.use(
|
|
121
|
+
"/service",
|
|
122
|
+
new MemoryService({
|
|
123
|
+
paginate: { default: 10, max: 100 },
|
|
124
|
+
operators: ["$and"],
|
|
125
|
+
filters: {
|
|
126
|
+
...filterArray("$and"),
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
const service = app.service("/service");
|
|
131
|
+
const passingPairs = {
|
|
132
|
+
empty: {
|
|
133
|
+
target: {},
|
|
134
|
+
source: {},
|
|
135
|
+
options: { useLogicalConjunction: false },
|
|
136
|
+
expected: {},
|
|
137
|
+
},
|
|
138
|
+
target: {
|
|
139
|
+
target: { id: 1, test1: true },
|
|
140
|
+
source: { id: 2, test2: false },
|
|
141
|
+
options: { defaultHandle: "target", useLogicalConjunction: false },
|
|
142
|
+
expected: { id: 1, test1: true, test2: false },
|
|
143
|
+
},
|
|
144
|
+
source: {
|
|
145
|
+
target: { id: 1, test1: true },
|
|
146
|
+
source: { id: 2, test2: false },
|
|
147
|
+
options: { defaultHandle: "source", useLogicalConjunction: false },
|
|
148
|
+
expected: { id: 2, test1: true, test2: false },
|
|
149
|
+
},
|
|
150
|
+
"native booleans": {
|
|
151
|
+
target: { test: true },
|
|
152
|
+
source: { test: false },
|
|
153
|
+
options: { useLogicalConjunction: false },
|
|
154
|
+
expected: { test: false },
|
|
155
|
+
},
|
|
156
|
+
"native nested to boolean": {
|
|
157
|
+
target: { test: { nested: [{ deep: true }] } },
|
|
158
|
+
source: { test: false },
|
|
159
|
+
options: { useLogicalConjunction: false },
|
|
160
|
+
expected: { test: false },
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
for (const key in passingPairs) {
|
|
164
|
+
const { target, source, options, expected } = passingPairs[key];
|
|
165
|
+
it(`'${key}'`, function () {
|
|
166
|
+
expect(
|
|
167
|
+
mergeQuery(target, source, Object.assign({ service }, options)),
|
|
168
|
+
).toEqual(expected);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("simple objects passing with handle combine", function () {
|
|
174
|
+
const app = feathers();
|
|
175
|
+
app.use(
|
|
176
|
+
"/service",
|
|
177
|
+
new MemoryService({
|
|
178
|
+
paginate: { default: 10, max: 100 },
|
|
179
|
+
operators: ["$and"],
|
|
180
|
+
filters: {
|
|
181
|
+
...filterArray("$and"),
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
const service = app.service("/service");
|
|
186
|
+
const passingPairs = {
|
|
187
|
+
"combine two numbers": {
|
|
188
|
+
target: { id: 1 },
|
|
189
|
+
source: { id: 2 },
|
|
190
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
191
|
+
expected: { id: { $in: [1, 2] } },
|
|
192
|
+
},
|
|
193
|
+
"combine $in different types": {
|
|
194
|
+
target: { id: "1" },
|
|
195
|
+
source: { id: 2 },
|
|
196
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
197
|
+
expected: { id: { $in: ["1", 2] } },
|
|
198
|
+
},
|
|
199
|
+
"combine number to $in with overlapping": {
|
|
200
|
+
target: { id: 1 },
|
|
201
|
+
source: { id: { $in: [1, 2] } },
|
|
202
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
203
|
+
expected: { id: { $in: [1, 2] } },
|
|
204
|
+
},
|
|
205
|
+
"combine numbers in $in": {
|
|
206
|
+
target: { id: 1 },
|
|
207
|
+
source: { id: { $in: [2, 3] } },
|
|
208
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
209
|
+
expected: { id: { $in: [2, 3, 1] } },
|
|
210
|
+
},
|
|
211
|
+
"combine two $in": {
|
|
212
|
+
target: { id: { $in: [2] } },
|
|
213
|
+
source: { id: { $in: [3, 4] } },
|
|
214
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
215
|
+
expected: { id: { $in: [2, 3, 4] } },
|
|
216
|
+
},
|
|
217
|
+
"combine two $or queries": {
|
|
218
|
+
target: { $or: [{ id: 1 }, { id: 2 }] },
|
|
219
|
+
source: { $or: [{ id: 3 }] },
|
|
220
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
221
|
+
expected: { $or: [{ id: 1 }, { id: 2 }, { id: 3 }] },
|
|
222
|
+
},
|
|
223
|
+
"combine two $and queries": {
|
|
224
|
+
target: { $and: [{ id: 1 }, { id: 2 }] },
|
|
225
|
+
source: { $and: [{ id: 3 }] },
|
|
226
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
227
|
+
expected: {
|
|
228
|
+
$or: [{ $and: [{ id: 1 }, { id: 2 }] }, { $and: [{ id: 3 }] }],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
"combine $or and $and queries": {
|
|
232
|
+
target: { $or: [{ id: 1 }, { id: 2 }], $and: [{ id: 4 }] },
|
|
233
|
+
source: { $or: [{ id: 3 }], $and: [{ id: 5 }] },
|
|
234
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
235
|
+
expected: {
|
|
236
|
+
$or: [
|
|
237
|
+
{ id: 1 },
|
|
238
|
+
{ id: 2 },
|
|
239
|
+
{ id: 3 },
|
|
240
|
+
{ $and: [{ id: 4 }] },
|
|
241
|
+
{ $and: [{ id: 5 }] },
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
"removes empty $or for both": {
|
|
246
|
+
target: { $or: [{}] },
|
|
247
|
+
source: { $or: [{}] },
|
|
248
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
249
|
+
expected: {},
|
|
250
|
+
},
|
|
251
|
+
"removes empty $and for both": {
|
|
252
|
+
target: { $and: [{}] },
|
|
253
|
+
source: { $and: [{}] },
|
|
254
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
255
|
+
expected: {},
|
|
256
|
+
},
|
|
257
|
+
"removes duplicate entries in $or": {
|
|
258
|
+
target: { $or: [{ id: 1 }, { id: 1 }, { id: 2 }] },
|
|
259
|
+
source: { $or: [{ id: 2 }] },
|
|
260
|
+
options: { defaultHandle: "combine", useLogicalConjunction: false },
|
|
261
|
+
expected: { $or: [{ id: 1 }, { id: 2 }] },
|
|
262
|
+
},
|
|
263
|
+
"$in and other props": {
|
|
264
|
+
target: { id: { $in: [1, 2, 3] }, status: "complete" },
|
|
265
|
+
source: {
|
|
266
|
+
id: { $in: [2, 3, 4] },
|
|
267
|
+
status: { $in: ["complete", "pending", "draft"] },
|
|
268
|
+
},
|
|
269
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
270
|
+
expected: { id: { $in: [2, 3] }, status: "complete" },
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
for (const key in passingPairs) {
|
|
274
|
+
const { target, source, options, expected } = passingPairs[key];
|
|
275
|
+
it(`'${key}'`, function () {
|
|
276
|
+
expect(
|
|
277
|
+
mergeQuery(target, source, Object.assign({ service }, options)),
|
|
278
|
+
).toEqual(expected);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
describe("with useLogicalConjunction: true", function () {
|
|
283
|
+
const app = feathers();
|
|
284
|
+
app.use(
|
|
285
|
+
"/service",
|
|
286
|
+
new MemoryService({
|
|
287
|
+
paginate: { default: 10, max: 100 },
|
|
288
|
+
operators: ["$and"],
|
|
289
|
+
filters: {
|
|
290
|
+
...filterArray("$and"),
|
|
291
|
+
},
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
const service = app.service("/service");
|
|
295
|
+
const passingPairs = {
|
|
296
|
+
"merge empty source": {
|
|
297
|
+
target: { id: 1 },
|
|
298
|
+
source: {},
|
|
299
|
+
options: { defaultHandle: "combine", useLogicalConjunction: true },
|
|
300
|
+
expected: { id: 1 },
|
|
301
|
+
},
|
|
302
|
+
"merge empty target": {
|
|
303
|
+
target: {},
|
|
304
|
+
source: { id: 1 },
|
|
305
|
+
options: { defaultHandle: "combine", useLogicalConjunction: true },
|
|
306
|
+
expected: { id: 1 },
|
|
307
|
+
},
|
|
308
|
+
"merge queries with $or": {
|
|
309
|
+
target: { id: 1 },
|
|
310
|
+
source: { $or: [{ id: { $in: [1, 3] } }], id: 2 },
|
|
311
|
+
options: { defaultHandle: "combine", useLogicalConjunction: true },
|
|
312
|
+
expected: { id: 1, $or: [{ id: { $in: [1, 3] } }, { id: 2 }] },
|
|
313
|
+
},
|
|
314
|
+
"merge queries with existing $or": {
|
|
315
|
+
target: { id: 1 },
|
|
316
|
+
source: { $or: [{ id: { $in: [1, 3] } }], id: 2 },
|
|
317
|
+
options: { defaultHandle: "combine", useLogicalConjunction: true },
|
|
318
|
+
expected: { id: 1, $or: [{ id: { $in: [1, 3] } }, { id: 2 }] },
|
|
319
|
+
},
|
|
320
|
+
"merge queries with existing $and": {
|
|
321
|
+
target: { id: 1 },
|
|
322
|
+
source: { $and: [{ id: 2 }] },
|
|
323
|
+
options: { defaultHandle: "combine", useLogicalConjunction: true },
|
|
324
|
+
expected: { id: 1, $or: [{ $and: [{ id: 2 }] }] },
|
|
325
|
+
},
|
|
326
|
+
"merge queries with existing $or in target": {
|
|
327
|
+
target: { $or: [{ id: 1 }], id: 3 },
|
|
328
|
+
source: { id: 2 },
|
|
329
|
+
options: { defaultHandle: "combine", useLogicalConjunction: true },
|
|
330
|
+
expected: { $or: [{ id: 1 }, { id: 2 }], id: 3 },
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
for (const key in passingPairs) {
|
|
334
|
+
const { target, source, options, expected } = passingPairs[key];
|
|
335
|
+
it(`'${key}'`, function () {
|
|
336
|
+
expect(
|
|
337
|
+
mergeQuery(target, source, Object.assign({ service }, options)),
|
|
338
|
+
).toEqual(expected);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe("simple objects passing with handle intersect", function () {
|
|
345
|
+
const app = feathers();
|
|
346
|
+
app.use(
|
|
347
|
+
"/service",
|
|
348
|
+
new MemoryService({
|
|
349
|
+
paginate: { default: 10, max: 100 },
|
|
350
|
+
whitelist: ["$and"],
|
|
351
|
+
filters: {
|
|
352
|
+
...filterArray("$and"),
|
|
353
|
+
},
|
|
354
|
+
}),
|
|
355
|
+
);
|
|
356
|
+
const service = app.service("/service");
|
|
357
|
+
const passingPairs = {
|
|
358
|
+
"intersect number and overlapping $in": {
|
|
359
|
+
target: { id: 1 },
|
|
360
|
+
source: { id: { $in: [1, 3] } },
|
|
361
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
362
|
+
expected: { id: 1 },
|
|
363
|
+
},
|
|
364
|
+
"intersect $in and $in with overlapping": {
|
|
365
|
+
target: { id: { $in: [1, 2] } },
|
|
366
|
+
source: { id: { $in: [1, 3] } },
|
|
367
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
368
|
+
expected: { id: 1 },
|
|
369
|
+
},
|
|
370
|
+
"$limit for target stays the same": {
|
|
371
|
+
target: { $limit: 50, $skip: 10, $sort: { id: 1 } },
|
|
372
|
+
source: { id: 1 },
|
|
373
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
374
|
+
expected: { id: 1, $limit: 50, $skip: 10, $sort: { id: 1 } },
|
|
375
|
+
},
|
|
376
|
+
"$limit gets overridden": {
|
|
377
|
+
target: { $limit: 50, $skip: 10, $sort: { id: 1 } },
|
|
378
|
+
source: { $limit: 10, id: 1 },
|
|
379
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
380
|
+
expected: { id: 1, $limit: 10, $skip: 10, $sort: { id: 1 } },
|
|
381
|
+
},
|
|
382
|
+
"intersects two $or queries": {
|
|
383
|
+
target: { $or: [{ id: 1 }, { id: 2 }] },
|
|
384
|
+
source: { $or: [{ id: 3 }] },
|
|
385
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
386
|
+
expected: {
|
|
387
|
+
$and: [{ $or: [{ id: 1 }, { id: 2 }] }, { $or: [{ id: 3 }] }],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
"intersects two $and queries": {
|
|
391
|
+
target: { $and: [{ id: 1 }, { id: 2 }] },
|
|
392
|
+
source: { $and: [{ id: 3 }] },
|
|
393
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
394
|
+
expected: { $and: [{ id: 1 }, { id: 2 }, { id: 3 }] },
|
|
395
|
+
},
|
|
396
|
+
"intersect $or and $and queries": {
|
|
397
|
+
target: { $or: [{ id: 1 }, { id: 2 }], $and: [{ id: 4 }] },
|
|
398
|
+
source: { $or: [{ id: 3 }], $and: [{ id: 5 }] },
|
|
399
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
400
|
+
expected: {
|
|
401
|
+
$and: [
|
|
402
|
+
{ id: 4 },
|
|
403
|
+
{ $or: [{ id: 1 }, { id: 2 }] },
|
|
404
|
+
{ $or: [{ id: 3 }] },
|
|
405
|
+
{ id: 5 },
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
"cleans up $and with empty entries": {
|
|
410
|
+
target: { $and: [{}, { id: 1 }, { id: 1 }, { id: 2 }] },
|
|
411
|
+
source: { $and: [{}, { id: 2 }] },
|
|
412
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
413
|
+
expected: { $and: [{ id: 1 }, { id: 2 }] },
|
|
414
|
+
},
|
|
415
|
+
"removes duplicate entries in $and": {
|
|
416
|
+
target: { $and: [{ id: 1 }, { id: 1 }, { id: 2 }] },
|
|
417
|
+
source: { $and: [{ id: 2 }] },
|
|
418
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
419
|
+
expected: { $and: [{ id: 1 }, { id: 2 }] },
|
|
420
|
+
},
|
|
421
|
+
"removes unnecessary $or in target with intersect": {
|
|
422
|
+
target: { $or: [{ id: 1 }, {}] },
|
|
423
|
+
source: { hi: "test" },
|
|
424
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
425
|
+
expected: { hi: "test" },
|
|
426
|
+
},
|
|
427
|
+
"removes unnecessary $or in source with intersect": {
|
|
428
|
+
target: { hi: "test" },
|
|
429
|
+
source: { $or: [{ id: 1 }, {}] },
|
|
430
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
431
|
+
expected: { hi: "test" },
|
|
432
|
+
},
|
|
433
|
+
"$in and other props": {
|
|
434
|
+
target: { id: { $in: [1, 2, 3] }, status: "complete" },
|
|
435
|
+
source: {
|
|
436
|
+
id: { $in: [2, 3, 4] },
|
|
437
|
+
status: { $in: ["complete", "pending", "draft"] },
|
|
438
|
+
},
|
|
439
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
440
|
+
expected: { id: { $in: [2, 3] }, status: "complete" },
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
for (const key in passingPairs) {
|
|
444
|
+
const { target, source, options, expected } = passingPairs[key];
|
|
445
|
+
it(`'${key}'`, function () {
|
|
446
|
+
expect(
|
|
447
|
+
mergeQuery(target, source, Object.assign({ service }, options)),
|
|
448
|
+
).toEqual(expected);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
describe("with useLogicalConjunction: true", function () {
|
|
453
|
+
const app = feathers();
|
|
454
|
+
app.use(
|
|
455
|
+
"/service",
|
|
456
|
+
new MemoryService({
|
|
457
|
+
paginate: { default: 10, max: 100 },
|
|
458
|
+
operators: ["$and"],
|
|
459
|
+
filters: {
|
|
460
|
+
...filterArray("$and"),
|
|
461
|
+
},
|
|
462
|
+
}),
|
|
463
|
+
);
|
|
464
|
+
const service = app.service("/service");
|
|
465
|
+
const passingPairs = {
|
|
466
|
+
"merge empty source": {
|
|
467
|
+
target: { id: 1 },
|
|
468
|
+
source: {},
|
|
469
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
470
|
+
expected: { id: 1 },
|
|
471
|
+
},
|
|
472
|
+
"merge empty target": {
|
|
473
|
+
target: {},
|
|
474
|
+
source: { id: 1 },
|
|
475
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
476
|
+
expected: { id: 1 },
|
|
477
|
+
},
|
|
478
|
+
"merge queries with $and": {
|
|
479
|
+
target: { id: 1 },
|
|
480
|
+
source: { id: { $in: [1, 3] } },
|
|
481
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
482
|
+
expected: { id: 1, $and: [{ id: { $in: [1, 3] } }] },
|
|
483
|
+
},
|
|
484
|
+
"merge queries with existing $and": {
|
|
485
|
+
target: { id: 1 },
|
|
486
|
+
source: { $and: [{ id: { $in: [1, 3] } }], id: 2 },
|
|
487
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
488
|
+
expected: { id: 1, $and: [{ id: { $in: [1, 3] } }, { id: 2 }] },
|
|
489
|
+
},
|
|
490
|
+
"merge queries with existing $or": {
|
|
491
|
+
target: { id: 1 },
|
|
492
|
+
source: { $or: [{ id: 2 }] },
|
|
493
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
494
|
+
expected: { id: 1, $and: [{ $or: [{ id: 2 }] }] },
|
|
495
|
+
},
|
|
496
|
+
"merge queries with existing $and in target": {
|
|
497
|
+
target: { $and: [{ id: 1 }], id: 3 },
|
|
498
|
+
source: { id: 2 },
|
|
499
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
500
|
+
expected: { $and: [{ id: 1 }, { id: 2 }], id: 3 },
|
|
501
|
+
},
|
|
502
|
+
"merge queries without $and": {
|
|
503
|
+
target: { id: 1 },
|
|
504
|
+
source: { userId: 2 },
|
|
505
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
506
|
+
expected: { id: 1, userId: 2 },
|
|
507
|
+
},
|
|
508
|
+
"skip merge if source is in target": {
|
|
509
|
+
target: { id: 1, userId: 2 },
|
|
510
|
+
source: { userId: 2 },
|
|
511
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
512
|
+
expected: { id: 1, userId: 2 },
|
|
513
|
+
},
|
|
514
|
+
"skip merge if target is in source": {
|
|
515
|
+
target: { userId: 2 },
|
|
516
|
+
source: { id: 1, userId: 2 },
|
|
517
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: true },
|
|
518
|
+
expected: { id: 1, userId: 2 },
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
for (const key in passingPairs) {
|
|
522
|
+
const { target, source, options, expected } = passingPairs[key];
|
|
523
|
+
it(`'${key}'`, function () {
|
|
524
|
+
expect(
|
|
525
|
+
mergeQuery(target, source, Object.assign({ service }, options)),
|
|
526
|
+
).toEqual(expected);
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
describe("simple objects failing", function () {
|
|
533
|
+
const failingPairs = {
|
|
534
|
+
"intersect two numbers": {
|
|
535
|
+
target: { id: 1 },
|
|
536
|
+
source: { id: 2 },
|
|
537
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
538
|
+
},
|
|
539
|
+
"intersect booleans": {
|
|
540
|
+
target: { test: true },
|
|
541
|
+
source: { test: false },
|
|
542
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
543
|
+
},
|
|
544
|
+
"intersect number and $in": {
|
|
545
|
+
target: { id: 1 },
|
|
546
|
+
source: { id: { $in: [2, 3] } },
|
|
547
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
548
|
+
},
|
|
549
|
+
"intersect two $in": {
|
|
550
|
+
target: { id: { $in: [1, 3] } },
|
|
551
|
+
source: { id: { $in: [2, 4] } },
|
|
552
|
+
options: { defaultHandle: "intersect", useLogicalConjunction: false },
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
for (const key in failingPairs) {
|
|
556
|
+
const { target, source, options } = failingPairs[key];
|
|
557
|
+
it(`'${key}'`, function () {
|
|
558
|
+
expect(() => mergeQuery(target, source, options)).toThrow(Forbidden);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Forbidden } from "@feathersjs/errors";
|
|
2
2
|
import _get from "lodash/get.js";
|
|
3
3
|
import _has from "lodash/has.js";
|
|
4
|
-
import _isEmpty from "lodash/isEmpty.js";
|
|
5
4
|
|
|
6
5
|
import _set from "lodash/set.js";
|
|
7
6
|
import _uniqWith from "lodash/uniqWith.js";
|
|
@@ -10,7 +9,7 @@ import { mergeArrays } from "./mergeArrays";
|
|
|
10
9
|
import type { Handle, MergeQueryOptions } from "./types";
|
|
11
10
|
import { deepEqual as _isEqual } from "fast-equals";
|
|
12
11
|
import type { Query } from "@feathersjs/feathers";
|
|
13
|
-
import { hasOwnProperty } from "../internal
|
|
12
|
+
import { hasOwnProperty, isEmpty } from "../_utils.internal";
|
|
14
13
|
|
|
15
14
|
export function handleArray(
|
|
16
15
|
target: Record<string, unknown>,
|
|
@@ -302,7 +301,7 @@ export function cleanOr(
|
|
|
302
301
|
return target;
|
|
303
302
|
}
|
|
304
303
|
|
|
305
|
-
if (target.some((x) =>
|
|
304
|
+
if (target.some((x) => isEmpty(x))) {
|
|
306
305
|
return undefined;
|
|
307
306
|
} else {
|
|
308
307
|
return arrayWithoutDuplicates(target);
|
|
@@ -316,10 +315,10 @@ export function cleanAnd(
|
|
|
316
315
|
return target;
|
|
317
316
|
}
|
|
318
317
|
|
|
319
|
-
if (target.every((x) =>
|
|
318
|
+
if (target.every((x) => isEmpty(x))) {
|
|
320
319
|
return undefined;
|
|
321
320
|
} else {
|
|
322
|
-
target = target.filter((x) => !
|
|
321
|
+
target = target.filter((x) => !isEmpty(x));
|
|
323
322
|
return arrayWithoutDuplicates(target);
|
|
324
323
|
}
|
|
325
324
|
}
|
|
@@ -393,3 +392,92 @@ export function areQueriesOverlapping(target: Query, source: Query): boolean {
|
|
|
393
392
|
|
|
394
393
|
return false;
|
|
395
394
|
}
|
|
395
|
+
|
|
396
|
+
if (import.meta.vitest) {
|
|
397
|
+
const { describe, it, expect } = import.meta.vitest;
|
|
398
|
+
|
|
399
|
+
describe("areQueriesOverlapping", function () {
|
|
400
|
+
it("empty", function () {
|
|
401
|
+
const query = areQueriesOverlapping({}, {});
|
|
402
|
+
|
|
403
|
+
expect(query).toBe(false);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("share same properties", function () {
|
|
407
|
+
const query = areQueriesOverlapping({ id: 1 }, { id: 1 });
|
|
408
|
+
|
|
409
|
+
expect(query).toBe(true);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("share same properties with different values", function () {
|
|
413
|
+
const query = areQueriesOverlapping({ id: 1 }, { id: 2 });
|
|
414
|
+
|
|
415
|
+
expect(query).toBe(true);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it("share some properties", function () {
|
|
419
|
+
const query = areQueriesOverlapping(
|
|
420
|
+
{ id: 1, test1: true, test2: true },
|
|
421
|
+
{ id: 2, test3: true, test4: true },
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
expect(query).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("do not share properties", function () {
|
|
428
|
+
const query = areQueriesOverlapping({ id: 1 }, { test: true });
|
|
429
|
+
|
|
430
|
+
expect(query).toBe(false);
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
describe("isQueryMoreExplicitThanQuery", function () {
|
|
435
|
+
it("empty", function () {
|
|
436
|
+
const query = isQueryMoreExplicitThanQuery({}, {});
|
|
437
|
+
|
|
438
|
+
expect(query).toStrictEqual({});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("query1 is empty", function () {
|
|
442
|
+
const query = isQueryMoreExplicitThanQuery({}, { id: 1 });
|
|
443
|
+
|
|
444
|
+
expect(query).toStrictEqual({ id: 1 });
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("query2 is empty", function () {
|
|
448
|
+
const query = isQueryMoreExplicitThanQuery({ id: 1 }, {});
|
|
449
|
+
|
|
450
|
+
expect(query).toStrictEqual({ id: 1 });
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it("query1 is superset of query2", function () {
|
|
454
|
+
const query = isQueryMoreExplicitThanQuery(
|
|
455
|
+
{ id: 1, test: true },
|
|
456
|
+
{ id: 1 },
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
expect(query).toStrictEqual({ id: 1, test: true });
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it("query2 is superset of query1", function () {
|
|
463
|
+
const query = isQueryMoreExplicitThanQuery(
|
|
464
|
+
{ id: 1 },
|
|
465
|
+
{ id: 1, test: true },
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(query).toStrictEqual({ id: 1, test: true });
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("queries do not overlap", function () {
|
|
472
|
+
const query = isQueryMoreExplicitThanQuery({ id: 1 }, { test: true });
|
|
473
|
+
|
|
474
|
+
expect(query).toBeUndefined();
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("queries overlap but differ", function () {
|
|
478
|
+
const query = isQueryMoreExplicitThanQuery({ id: 1 }, { id: 2 });
|
|
479
|
+
|
|
480
|
+
expect(query).toBeUndefined();
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
}
|