feathers-utils 10.1.0 → 10.3.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/{hooks-Bg5XWcV7.mjs → hooks-DpFQfcFa.mjs} +122 -59
- package/dist/hooks-DpFQfcFa.mjs.map +1 -0
- package/dist/hooks.d.mts +110 -36
- package/dist/hooks.mjs +5 -5
- package/dist/{index-DKA0E_ad.d.mts → index-C6MN6wag.d.mts} +181 -64
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +7 -7
- package/dist/{internal.utils-BMzV_-xp.mjs → internal.utils-BBB-b6Ud.mjs} +16 -2
- package/dist/internal.utils-BBB-b6Ud.mjs.map +1 -0
- package/dist/{mutate-result.util-Dqzepn1M.mjs → mutate-result.util-C0nY6L7i.mjs} +2 -2
- package/dist/{mutate-result.util-Dqzepn1M.mjs.map → mutate-result.util-C0nY6L7i.mjs.map} +1 -1
- package/dist/{predicates-puYa4nkf.mjs → predicates-NOnUyMic.mjs} +53 -53
- package/dist/{predicates-puYa4nkf.mjs.map → predicates-NOnUyMic.mjs.map} +1 -1
- package/dist/predicates.d.mts +18 -18
- package/dist/predicates.mjs +1 -1
- package/dist/{resolve-B9hRleHY.mjs → resolve-BflgIVD8.mjs} +2 -2
- package/dist/{resolve-B9hRleHY.mjs.map → resolve-BflgIVD8.mjs.map} +1 -1
- package/dist/resolvers.mjs +1 -1
- package/dist/{transform-result.hook-B65pTRJO.mjs → transform-result.hook-V2QYN2K0.mjs} +2 -2
- package/dist/{transform-result.hook-B65pTRJO.mjs.map → transform-result.hook-V2QYN2K0.mjs.map} +1 -1
- package/dist/transformers.mjs +3 -3
- package/dist/{utils-Br6DNQ1B.mjs → utils-DByCpAsf.mjs} +407 -146
- package/dist/utils-DByCpAsf.mjs.map +1 -0
- package/dist/utils.d.mts +2 -2
- package/dist/utils.mjs +4 -4
- package/package.json +1 -1
- package/src/common/index.ts +1 -0
- package/src/common/is-empty-object.ts +38 -0
- package/src/hooks/index.ts +3 -1
- package/src/hooks/mute-event/mute-event.hook.ts +56 -0
- package/src/hooks/set-query-defaults/set-query-defaults.hook.ts +37 -0
- package/src/hooks/soft-delete/soft-delete.hook.ts +17 -3
- package/src/predicates/index.ts +3 -3
- package/src/utils/add-to-query/add-to-query.util.ts +19 -1
- package/src/utils/index.ts +5 -2
- package/src/utils/merge-query/dedupe-branches.ts +42 -0
- package/src/utils/merge-query/extract-query-filters.ts +80 -0
- package/src/utils/merge-query/has-conflict.ts +39 -0
- package/src/utils/merge-query/logical-branches.ts +39 -0
- package/src/utils/merge-query/merge-query-bodies.ts +136 -0
- package/src/utils/merge-query/merge-query.util.ts +102 -0
- package/src/utils/merge-query/merge-select.ts +64 -0
- package/src/utils/query-defaults/query-defaults.util.ts +39 -0
- package/src/utils/query-has-property/query-has-property.util.ts +37 -0
- package/src/utils/walk-query/walk-query.util.ts +43 -3
- package/dist/hooks-Bg5XWcV7.mjs.map +0 -1
- package/dist/internal.utils-BMzV_-xp.mjs.map +0 -1
- package/dist/utils-Br6DNQ1B.mjs.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as isEmptyObject, r as toArray } from "./internal.utils-BBB-b6Ud.mjs";
|
|
2
|
+
import { d as isPaginated, m as isContext, p as isMulti } from "./predicates-NOnUyMic.mjs";
|
|
3
|
+
import { a as getResultIsArray, o as getDataIsArray } from "./mutate-result.util-C0nY6L7i.mjs";
|
|
3
4
|
import { dequal } from "dequal";
|
|
4
5
|
import * as feathers from "@feathersjs/feathers";
|
|
5
6
|
//#region src/utils/sort-query-properties/sort-query-properties.util.ts
|
|
@@ -88,58 +89,13 @@ const addSkip = (context, hooks) => {
|
|
|
88
89
|
}
|
|
89
90
|
};
|
|
90
91
|
//#endregion
|
|
91
|
-
//#region src/utils/chunk-find/chunk-find.util.ts
|
|
92
|
-
/**
|
|
93
|
-
* Use `for await` to iterate over chunks (pages) of results from a `find` method.
|
|
94
|
-
*
|
|
95
|
-
* This function is useful for processing large datasets in batches without loading everything into memory at once.
|
|
96
|
-
* It uses pagination to fetch results in chunks, yielding each page's data array.
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```ts
|
|
100
|
-
* import { chunkFind } from 'feathers-utils/utils'
|
|
101
|
-
*
|
|
102
|
-
* const app = feathers()
|
|
103
|
-
*
|
|
104
|
-
* // Assuming 'users' service has many records
|
|
105
|
-
* for await (const users of chunkFind(app, 'users', {
|
|
106
|
-
* params: { query: { active: true }, // Custom query parameters
|
|
107
|
-
* } })) {
|
|
108
|
-
* console.log(users) // Process each chunk of user records
|
|
109
|
-
* }
|
|
110
|
-
* ```
|
|
111
|
-
*
|
|
112
|
-
* @see https://utils.feathersjs.com/utils/chunk-find.html
|
|
113
|
-
*/
|
|
114
|
-
async function* chunkFind(app, servicePath, options) {
|
|
115
|
-
const service = app.service(servicePath);
|
|
116
|
-
if (!service || !("find" in service)) throw new Error(`Service '${servicePath}' does not have a 'find' method.`);
|
|
117
|
-
const params = {
|
|
118
|
-
...options?.params,
|
|
119
|
-
query: {
|
|
120
|
-
...options?.params?.query ?? {},
|
|
121
|
-
$limit: options?.params?.query?.$limit ?? 10,
|
|
122
|
-
$skip: options?.params?.query?.$skip ?? 0
|
|
123
|
-
},
|
|
124
|
-
paginate: {
|
|
125
|
-
default: options?.params?.paginate?.default ?? 10,
|
|
126
|
-
max: options?.params?.paginate?.max ?? 100
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
let result;
|
|
130
|
-
do {
|
|
131
|
-
result = await service.find(params);
|
|
132
|
-
if (!result.data.length) break;
|
|
133
|
-
yield result.data;
|
|
134
|
-
params.query.$skip = (params.query.$skip ?? 0) + result.data.length;
|
|
135
|
-
} while (result.total > params.query.$skip);
|
|
136
|
-
}
|
|
137
|
-
//#endregion
|
|
138
92
|
//#region src/utils/add-to-query/add-to-query.util.ts
|
|
139
93
|
/**
|
|
140
94
|
* Safely merges properties into a Feathers query object. If a property already exists
|
|
141
95
|
* with a different value, it wraps both in a `$and` array to preserve both conditions.
|
|
142
|
-
* If the exact same key-value pair already exists, no changes are made.
|
|
96
|
+
* If the exact same key-value pair already exists, no changes are made. When the added
|
|
97
|
+
* query is itself a pure `$and` (`{ $and: [...] }`), its branches are flattened into the
|
|
98
|
+
* target's `$and` rather than nested.
|
|
143
99
|
*
|
|
144
100
|
* @example
|
|
145
101
|
* ```ts
|
|
@@ -164,6 +120,15 @@ function addToQuery(targetQuery, query) {
|
|
|
164
120
|
return entries.every(([property, value]) => property in targetQuery && dequal(targetQuery[property], value));
|
|
165
121
|
}
|
|
166
122
|
if (isAlreadyInQuery(targetQuery, entries)) return targetQuery;
|
|
123
|
+
if (entries.length === 1 && Array.isArray(query.$and)) {
|
|
124
|
+
const existing = targetQuery.$and ?? [];
|
|
125
|
+
const newBranches = query.$and.filter((branch) => !existing.some((q) => dequal(q, branch)));
|
|
126
|
+
if (newBranches.length === 0) return targetQuery;
|
|
127
|
+
return {
|
|
128
|
+
...targetQuery,
|
|
129
|
+
$and: [...existing, ...newBranches]
|
|
130
|
+
};
|
|
131
|
+
}
|
|
167
132
|
if (!targetQuery.$and) return {
|
|
168
133
|
...targetQuery,
|
|
169
134
|
$and: [{ ...query }]
|
|
@@ -199,6 +164,53 @@ function checkContext(context, typeOrOptions, methods, label = "anonymous") {
|
|
|
199
164
|
}
|
|
200
165
|
}
|
|
201
166
|
//#endregion
|
|
167
|
+
//#region src/utils/chunk-find/chunk-find.util.ts
|
|
168
|
+
/**
|
|
169
|
+
* Use `for await` to iterate over chunks (pages) of results from a `find` method.
|
|
170
|
+
*
|
|
171
|
+
* This function is useful for processing large datasets in batches without loading everything into memory at once.
|
|
172
|
+
* It uses pagination to fetch results in chunks, yielding each page's data array.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* import { chunkFind } from 'feathers-utils/utils'
|
|
177
|
+
*
|
|
178
|
+
* const app = feathers()
|
|
179
|
+
*
|
|
180
|
+
* // Assuming 'users' service has many records
|
|
181
|
+
* for await (const users of chunkFind(app, 'users', {
|
|
182
|
+
* params: { query: { active: true }, // Custom query parameters
|
|
183
|
+
* } })) {
|
|
184
|
+
* console.log(users) // Process each chunk of user records
|
|
185
|
+
* }
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* @see https://utils.feathersjs.com/utils/chunk-find.html
|
|
189
|
+
*/
|
|
190
|
+
async function* chunkFind(app, servicePath, options) {
|
|
191
|
+
const service = app.service(servicePath);
|
|
192
|
+
if (!service || !("find" in service)) throw new Error(`Service '${servicePath}' does not have a 'find' method.`);
|
|
193
|
+
const params = {
|
|
194
|
+
...options?.params,
|
|
195
|
+
query: {
|
|
196
|
+
...options?.params?.query ?? {},
|
|
197
|
+
$limit: options?.params?.query?.$limit ?? 10,
|
|
198
|
+
$skip: options?.params?.query?.$skip ?? 0
|
|
199
|
+
},
|
|
200
|
+
paginate: {
|
|
201
|
+
default: options?.params?.paginate?.default ?? 10,
|
|
202
|
+
max: options?.params?.paginate?.max ?? 100
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
let result;
|
|
206
|
+
do {
|
|
207
|
+
result = await service.find(params);
|
|
208
|
+
if (!result.data.length) break;
|
|
209
|
+
yield result.data;
|
|
210
|
+
params.query.$skip = (params.query.$skip ?? 0) + result.data.length;
|
|
211
|
+
} while (result.total > params.query.$skip);
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
202
214
|
//#region src/utils/context-to-json/context-to-json.util.ts
|
|
203
215
|
/**
|
|
204
216
|
* Converts a FeathersJS HookContext to a plain JSON object by calling `toJSON()` if available.
|
|
@@ -314,6 +326,172 @@ async function* iterateFind(app, servicePath, options) {
|
|
|
314
326
|
} while (result.total > params.query.$skip);
|
|
315
327
|
}
|
|
316
328
|
//#endregion
|
|
329
|
+
//#region src/utils/merge-query/extract-query-filters.ts
|
|
330
|
+
/**
|
|
331
|
+
* Splits a query into its special filters ($select, $limit, $skip, $sort) and the
|
|
332
|
+
* remaining query body. Internal helper for {@link mergeQuery} — not part of the
|
|
333
|
+
* public API.
|
|
334
|
+
*/
|
|
335
|
+
function extractQueryFilters(providedQuery) {
|
|
336
|
+
providedQuery ??= {};
|
|
337
|
+
const { $select, $limit, $skip, $sort, ...query } = providedQuery;
|
|
338
|
+
const result = { query };
|
|
339
|
+
if ("$select" in providedQuery) result.$select = $select;
|
|
340
|
+
if ("$limit" in providedQuery) result.$limit = $limit;
|
|
341
|
+
if ("$skip" in providedQuery) result.$skip = $skip;
|
|
342
|
+
if ("$sort" in providedQuery) result.$sort = $sort;
|
|
343
|
+
return result;
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/utils/merge-query/logical-branches.ts
|
|
347
|
+
/**
|
|
348
|
+
* Returns the branches of a logical-only query (a query whose single key is `op`),
|
|
349
|
+
* or `null` when the query is not purely `{ [op]: [...] }`. Internal helper for
|
|
350
|
+
* {@link mergeQuery}.
|
|
351
|
+
*/
|
|
352
|
+
function logicalBranches(query, op) {
|
|
353
|
+
const keys = Object.keys(query);
|
|
354
|
+
if (keys.length === 1 && keys[0] === op && Array.isArray(query[op])) return query[op];
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
//#endregion
|
|
358
|
+
//#region src/utils/merge-query/dedupe-branches.ts
|
|
359
|
+
/**
|
|
360
|
+
* Removes empty (`{}`) and deep-equal duplicate branches, preserving order.
|
|
361
|
+
* Internal helper for {@link mergeQuery}.
|
|
362
|
+
*/
|
|
363
|
+
function dedupeBranches(branches) {
|
|
364
|
+
const result = [];
|
|
365
|
+
for (const branch of branches) {
|
|
366
|
+
if (isEmptyObject(branch)) continue;
|
|
367
|
+
if (!result.some((existing) => dequal(existing, branch))) result.push(branch);
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/utils/merge-query/has-conflict.ts
|
|
373
|
+
/**
|
|
374
|
+
* Two query bodies conflict when they share at least one key whose values are not
|
|
375
|
+
* deep-equal. Internal helper for {@link mergeQuery}.
|
|
376
|
+
*/
|
|
377
|
+
function hasConflict(target, source) {
|
|
378
|
+
for (const key of Object.keys(target)) if (key in source && !dequal(target[key], source[key])) return true;
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
//#region src/utils/merge-query/merge-query-bodies.ts
|
|
383
|
+
/**
|
|
384
|
+
* Merges two query bodies (filters already removed) according to the mode.
|
|
385
|
+
* Internal helper for {@link mergeQuery}.
|
|
386
|
+
*
|
|
387
|
+
* - `target` / `source`: precedence merge (that side wins on conflict).
|
|
388
|
+
* - `combine`: the two bodies always become branches of a single `$or`.
|
|
389
|
+
* - `intersect`: non-conflicting bodies merge flat; on conflict they become
|
|
390
|
+
* branches of a single `$and`.
|
|
391
|
+
*
|
|
392
|
+
* Logical-only bodies (`{ $or: [...] }` for combine, `{ $and: [...] }` for
|
|
393
|
+
* intersect) are flattened into the result and their branches de-duplicated.
|
|
394
|
+
*/
|
|
395
|
+
function mergeQueryBodies(target, source, mode) {
|
|
396
|
+
if (mode === "target") return {
|
|
397
|
+
...source,
|
|
398
|
+
...target
|
|
399
|
+
};
|
|
400
|
+
if (mode === "source") return {
|
|
401
|
+
...target,
|
|
402
|
+
...source
|
|
403
|
+
};
|
|
404
|
+
if (isEmptyObject(target)) return { ...source };
|
|
405
|
+
if (isEmptyObject(source)) return { ...target };
|
|
406
|
+
const op = mode === "combine" ? "$or" : "$and";
|
|
407
|
+
const targetBranches = logicalBranches(target, op);
|
|
408
|
+
const sourceBranches = logicalBranches(source, op);
|
|
409
|
+
if (op === "$and" && !targetBranches && !sourceBranches && !hasConflict(target, source)) return {
|
|
410
|
+
...target,
|
|
411
|
+
...source
|
|
412
|
+
};
|
|
413
|
+
const branches = dedupeBranches([...targetBranches ?? [target], ...sourceBranches ?? [source]]);
|
|
414
|
+
if (branches.length === 0) return {};
|
|
415
|
+
if (branches.length === 1) return { ...branches[0] };
|
|
416
|
+
return { [op]: branches };
|
|
417
|
+
}
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region src/utils/merge-query/merge-select.ts
|
|
420
|
+
/**
|
|
421
|
+
* Merges two `$select` filters according to the mode: `combine` → union,
|
|
422
|
+
* `intersect` → intersection, `target`/`source` → that side. When only one side
|
|
423
|
+
* provides a `$select`, that one is used. Internal helper for {@link mergeQuery}.
|
|
424
|
+
*/
|
|
425
|
+
function mergeSelect(target, source, mode) {
|
|
426
|
+
if (target === void 0) return source;
|
|
427
|
+
if (source === void 0) return target;
|
|
428
|
+
if (mode === "target") return target;
|
|
429
|
+
if (mode === "source") return source;
|
|
430
|
+
const targetArr = Array.isArray(target) ? target : [target];
|
|
431
|
+
const sourceArr = Array.isArray(source) ? source : [source];
|
|
432
|
+
if (mode === "combine") return [...new Set([...targetArr, ...sourceArr])];
|
|
433
|
+
return targetArr.filter((value) => sourceArr.includes(value));
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region src/utils/merge-query/merge-query.util.ts
|
|
437
|
+
/**
|
|
438
|
+
* Properties are combined with a logical operator rather than merged at the value
|
|
439
|
+
* level, so the result is always a valid query: `combine` always wraps the two
|
|
440
|
+
* queries in `$or` (broaden — OR has no flat form), while `intersect` merges
|
|
441
|
+
* non-conflicting properties flat and wraps conflicts in `$and` (narrow). The
|
|
442
|
+
* special filters `$select`, `$limit`, `$skip` and `$sort` are merged separately.
|
|
443
|
+
* Inputs are never mutated.
|
|
444
|
+
*
|
|
445
|
+
* This is well suited to merging a client-provided query with a server-side
|
|
446
|
+
* restriction inside a hook.
|
|
447
|
+
*
|
|
448
|
+
* @param target Query to be merged into
|
|
449
|
+
* @param source Query to be merged from
|
|
450
|
+
* @param options
|
|
451
|
+
* @returns the merged query
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* ```ts
|
|
455
|
+
* import { mergeQuery } from 'feathers-utils/utils'
|
|
456
|
+
*
|
|
457
|
+
* // combine (default): the two queries always become an $or
|
|
458
|
+
* mergeQuery({ id: 1 }, { id: 2 })
|
|
459
|
+
* // => { $or: [{ id: 1 }, { id: 2 }] }
|
|
460
|
+
*
|
|
461
|
+
* mergeQuery({ status: 'active' }, { authorId: 5 })
|
|
462
|
+
* // => { $or: [{ status: 'active' }, { authorId: 5 }] }
|
|
463
|
+
* ```
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* ```ts
|
|
467
|
+
* // intersect: non-conflicting properties merge flat, conflicts become an $and
|
|
468
|
+
* mergeQuery({ status: 'active' }, { authorId: 5 }, { mode: 'intersect' })
|
|
469
|
+
* // => { status: 'active', authorId: 5 }
|
|
470
|
+
*
|
|
471
|
+
* mergeQuery({ id: 1 }, { id: 2 }, { mode: 'intersect' })
|
|
472
|
+
* // => { $and: [{ id: 1 }, { id: 2 }] }
|
|
473
|
+
* ```
|
|
474
|
+
*
|
|
475
|
+
* @see https://utils.feathersjs.com/utils/merge-query.html
|
|
476
|
+
*/
|
|
477
|
+
function mergeQuery(target, source, options) {
|
|
478
|
+
const mode = options?.mode ?? "combine";
|
|
479
|
+
const targetFilters = extractQueryFilters(target);
|
|
480
|
+
const sourceFilters = extractQueryFilters(source);
|
|
481
|
+
const result = mergeQueryBodies(targetFilters.query, sourceFilters.query, mode);
|
|
482
|
+
const $select = mergeSelect(targetFilters.$select, sourceFilters.$select, mode);
|
|
483
|
+
if ($select !== void 0) result.$select = $select;
|
|
484
|
+
if ("$limit" in sourceFilters) result.$limit = sourceFilters.$limit;
|
|
485
|
+
else if ("$limit" in targetFilters) result.$limit = targetFilters.$limit;
|
|
486
|
+
if ("$skip" in sourceFilters) result.$skip = sourceFilters.$skip;
|
|
487
|
+
else if ("$skip" in targetFilters) result.$skip = targetFilters.$skip;
|
|
488
|
+
if ("$sort" in targetFilters || "$sort" in sourceFilters) result.$sort = {
|
|
489
|
+
...targetFilters.$sort,
|
|
490
|
+
...sourceFilters.$sort
|
|
491
|
+
};
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
//#endregion
|
|
317
495
|
//#region src/utils/patch-batch/patch-batch.util.ts
|
|
318
496
|
/**
|
|
319
497
|
* Deterministic, key-order-independent serialization used to group items with
|
|
@@ -377,6 +555,183 @@ function patchBatch(items, options) {
|
|
|
377
555
|
});
|
|
378
556
|
}
|
|
379
557
|
//#endregion
|
|
558
|
+
//#region src/utils/walk-query/walk-query.util.ts
|
|
559
|
+
const _walkQueryUtil = (query, walker, state, options) => {
|
|
560
|
+
const stop = () => {
|
|
561
|
+
state.stopped = true;
|
|
562
|
+
};
|
|
563
|
+
let cloned = false;
|
|
564
|
+
const clonedSecond = {};
|
|
565
|
+
function set(key, value, secondKey) {
|
|
566
|
+
if (!cloned) {
|
|
567
|
+
query = { ...query };
|
|
568
|
+
cloned = true;
|
|
569
|
+
}
|
|
570
|
+
if (secondKey !== void 0) {
|
|
571
|
+
if (!clonedSecond[key]) {
|
|
572
|
+
query[key] = { ...query[key] };
|
|
573
|
+
clonedSecond[key] = true;
|
|
574
|
+
}
|
|
575
|
+
query[key][secondKey] = value;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
query[key] = value;
|
|
579
|
+
}
|
|
580
|
+
for (const key in query) {
|
|
581
|
+
if (state.stopped) break;
|
|
582
|
+
if ((key === "$or" || key === "$and" || key === "$nor") && Array.isArray(query[key])) {
|
|
583
|
+
let array = query[key];
|
|
584
|
+
let copiedArray = false;
|
|
585
|
+
for (let i = 0, n = array.length; i < n; i++) {
|
|
586
|
+
if (state.stopped) break;
|
|
587
|
+
const nestedQuery = array[i];
|
|
588
|
+
const transformed = _walkQueryUtil(nestedQuery, walker, state, {
|
|
589
|
+
...options,
|
|
590
|
+
path: [
|
|
591
|
+
...options?.path || [],
|
|
592
|
+
key,
|
|
593
|
+
i
|
|
594
|
+
]
|
|
595
|
+
});
|
|
596
|
+
if (transformed !== nestedQuery) {
|
|
597
|
+
if (!copiedArray) {
|
|
598
|
+
array = [...array];
|
|
599
|
+
copiedArray = true;
|
|
600
|
+
}
|
|
601
|
+
array[i] = transformed;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (copiedArray) set(key, array);
|
|
605
|
+
} else if (typeof query[key] === "object" && query[key] !== null && !Array.isArray(query[key])) {
|
|
606
|
+
let hasOperator = false;
|
|
607
|
+
for (const operator in query[key]) {
|
|
608
|
+
if (state.stopped) break;
|
|
609
|
+
if (operator.startsWith("$")) {
|
|
610
|
+
hasOperator = true;
|
|
611
|
+
const value = walker({
|
|
612
|
+
operator,
|
|
613
|
+
path: [...options?.path ?? [], key],
|
|
614
|
+
property: key,
|
|
615
|
+
value: query[key][operator],
|
|
616
|
+
stop
|
|
617
|
+
});
|
|
618
|
+
if (value !== void 0 && value !== query[key][operator]) set(key, value, operator);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (!hasOperator) {
|
|
622
|
+
const value = walker({
|
|
623
|
+
operator: void 0,
|
|
624
|
+
path: [...options?.path ?? [], key],
|
|
625
|
+
property: key,
|
|
626
|
+
value: query[key],
|
|
627
|
+
stop
|
|
628
|
+
});
|
|
629
|
+
if (value !== void 0 && value !== query[key]) set(key, value);
|
|
630
|
+
}
|
|
631
|
+
} else {
|
|
632
|
+
const value = walker({
|
|
633
|
+
operator: void 0,
|
|
634
|
+
path: [...options?.path ?? [], key],
|
|
635
|
+
property: key,
|
|
636
|
+
value: query[key],
|
|
637
|
+
stop
|
|
638
|
+
});
|
|
639
|
+
if (value !== void 0 && value !== query[key]) set(key, value);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return query;
|
|
643
|
+
};
|
|
644
|
+
/**
|
|
645
|
+
* Walks every property of a Feathers query (including nested `$and`/`$or`/`$nor` arrays)
|
|
646
|
+
* and calls the `walker` function for each one. The walker receives the property name, operator,
|
|
647
|
+
* value, path, and a `stop` function, and can return a replacement value. Calling `stop()` halts
|
|
648
|
+
* the traversal early. Returns a new query only if changes were made.
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* ```ts
|
|
652
|
+
* import { walkQuery } from 'feathers-utils/utils'
|
|
653
|
+
*
|
|
654
|
+
* const query = walkQuery({ age: { $gt: '18' } }, ({ value, operator }) => {
|
|
655
|
+
* if (operator === '$gt') return Number(value)
|
|
656
|
+
* })
|
|
657
|
+
* // => { age: { $gt: 18 } }
|
|
658
|
+
* ```
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```ts
|
|
662
|
+
* // stop early once a property is found
|
|
663
|
+
* let found = false
|
|
664
|
+
* walkQuery(query, ({ property, stop }) => {
|
|
665
|
+
* if (property === 'isTemplate') {
|
|
666
|
+
* found = true
|
|
667
|
+
* stop()
|
|
668
|
+
* }
|
|
669
|
+
* })
|
|
670
|
+
* ```
|
|
671
|
+
*
|
|
672
|
+
* @see https://utils.feathersjs.com/utils/walk-query.html
|
|
673
|
+
*/
|
|
674
|
+
const walkQuery = (query, walker) => {
|
|
675
|
+
return _walkQueryUtil(query, walker, { stopped: false });
|
|
676
|
+
};
|
|
677
|
+
//#endregion
|
|
678
|
+
//#region src/utils/query-has-property/query-has-property.util.ts
|
|
679
|
+
/**
|
|
680
|
+
* Checks whether a Feathers query contains one or more properties — including
|
|
681
|
+
* properties nested inside `$and`/`$or`/`$nor` arrays. Returns `true` as soon as
|
|
682
|
+
* any of the given property names is found. The query is not mutated.
|
|
683
|
+
*
|
|
684
|
+
* @example
|
|
685
|
+
* ```ts
|
|
686
|
+
* import { queryHasProperty } from 'feathers-utils/utils'
|
|
687
|
+
*
|
|
688
|
+
* queryHasProperty({ isTemplate: true }, 'isTemplate') // true
|
|
689
|
+
* queryHasProperty({ $or: [{ isTemplate: true }] }, 'isTemplate') // true
|
|
690
|
+
* queryHasProperty({ age: { $gt: 18 } }, ['isTemplate', 'status']) // false
|
|
691
|
+
* ```
|
|
692
|
+
*
|
|
693
|
+
* @see https://utils.feathersjs.com/utils/query-has-property.html
|
|
694
|
+
*/
|
|
695
|
+
const queryHasProperty = (query, property) => {
|
|
696
|
+
const properties = new Set(toArray(property));
|
|
697
|
+
let found = false;
|
|
698
|
+
walkQuery(query, ({ property: key, stop }) => {
|
|
699
|
+
if (properties.has(key)) {
|
|
700
|
+
found = true;
|
|
701
|
+
stop();
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
return found;
|
|
705
|
+
};
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region src/utils/query-defaults/query-defaults.util.ts
|
|
708
|
+
/**
|
|
709
|
+
* Adds default properties to a Feathers query — but only for fields the query does
|
|
710
|
+
* not already constrain. Presence is checked with {@link queryHasProperty}, so a field
|
|
711
|
+
* referenced anywhere (including nested in `$and`/`$or`/`$nor`) is left untouched and
|
|
712
|
+
* the caller keeps control over it. The query is treated as the `data` equivalent of
|
|
713
|
+
* the `defaults` transformer. Each default is applied independently (per-field).
|
|
714
|
+
*
|
|
715
|
+
* @example
|
|
716
|
+
* ```ts
|
|
717
|
+
* import { queryDefaults } from 'feathers-utils/utils'
|
|
718
|
+
*
|
|
719
|
+
* queryDefaults({ status: 'active' }, { isTemplate: false })
|
|
720
|
+
* // => { status: 'active', isTemplate: false }
|
|
721
|
+
*
|
|
722
|
+
* queryDefaults({ $or: [{ isTemplate: true }] }, { isTemplate: false })
|
|
723
|
+
* // => { $or: [{ isTemplate: true }] } (untouched — already referenced)
|
|
724
|
+
* ```
|
|
725
|
+
*
|
|
726
|
+
* @see https://utils.feathersjs.com/utils/query-defaults.html
|
|
727
|
+
*/
|
|
728
|
+
const queryDefaults = (query, defaults) => {
|
|
729
|
+
const source = query ?? {};
|
|
730
|
+
const toAdd = {};
|
|
731
|
+
for (const key in defaults) if (!queryHasProperty(source, key)) toAdd[key] = defaults[key];
|
|
732
|
+
return addToQuery(source, toAdd);
|
|
733
|
+
};
|
|
734
|
+
//#endregion
|
|
380
735
|
//#region src/utils/skip-result/skip-result.util.ts
|
|
381
736
|
/**
|
|
382
737
|
* Sets `context.result` to an appropriate empty value based on the hook method.
|
|
@@ -533,100 +888,6 @@ function waitForServiceEvent(app, defaultOptions) {
|
|
|
533
888
|
};
|
|
534
889
|
}
|
|
535
890
|
//#endregion
|
|
536
|
-
//#region src/utils/walk-query/walk-query.util.ts
|
|
537
|
-
const _walkQueryUtil = (query, walker, options) => {
|
|
538
|
-
let cloned = false;
|
|
539
|
-
const clonedSecond = {};
|
|
540
|
-
function set(key, value, secondKey) {
|
|
541
|
-
if (!cloned) {
|
|
542
|
-
query = { ...query };
|
|
543
|
-
cloned = true;
|
|
544
|
-
}
|
|
545
|
-
if (secondKey !== void 0) {
|
|
546
|
-
if (!clonedSecond[key]) {
|
|
547
|
-
query[key] = { ...query[key] };
|
|
548
|
-
clonedSecond[key] = true;
|
|
549
|
-
}
|
|
550
|
-
query[key][secondKey] = value;
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
query[key] = value;
|
|
554
|
-
}
|
|
555
|
-
for (const key in query) if ((key === "$or" || key === "$and" || key === "$nor") && Array.isArray(query[key])) {
|
|
556
|
-
let array = query[key];
|
|
557
|
-
let copiedArray = false;
|
|
558
|
-
for (let i = 0, n = array.length; i < n; i++) {
|
|
559
|
-
const nestedQuery = array[i];
|
|
560
|
-
const transformed = _walkQueryUtil(nestedQuery, walker, {
|
|
561
|
-
...options,
|
|
562
|
-
path: [
|
|
563
|
-
...options?.path || [],
|
|
564
|
-
key,
|
|
565
|
-
i
|
|
566
|
-
]
|
|
567
|
-
});
|
|
568
|
-
if (transformed !== nestedQuery) {
|
|
569
|
-
if (!copiedArray) {
|
|
570
|
-
array = [...array];
|
|
571
|
-
copiedArray = true;
|
|
572
|
-
}
|
|
573
|
-
array[i] = transformed;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
if (copiedArray) set(key, array);
|
|
577
|
-
} else if (typeof query[key] === "object" && query[key] !== null && !Array.isArray(query[key])) {
|
|
578
|
-
let hasOperator = false;
|
|
579
|
-
for (const operator in query[key]) if (operator.startsWith("$")) {
|
|
580
|
-
hasOperator = true;
|
|
581
|
-
const value = walker({
|
|
582
|
-
operator,
|
|
583
|
-
path: [...options?.path ?? [], key],
|
|
584
|
-
property: key,
|
|
585
|
-
value: query[key][operator]
|
|
586
|
-
});
|
|
587
|
-
if (value !== void 0 && value !== query[key][operator]) set(key, value, operator);
|
|
588
|
-
}
|
|
589
|
-
if (!hasOperator) {
|
|
590
|
-
const value = walker({
|
|
591
|
-
operator: void 0,
|
|
592
|
-
path: [...options?.path ?? [], key],
|
|
593
|
-
property: key,
|
|
594
|
-
value: query[key]
|
|
595
|
-
});
|
|
596
|
-
if (value !== void 0 && value !== query[key]) set(key, value);
|
|
597
|
-
}
|
|
598
|
-
} else {
|
|
599
|
-
const value = walker({
|
|
600
|
-
operator: void 0,
|
|
601
|
-
path: [...options?.path ?? [], key],
|
|
602
|
-
property: key,
|
|
603
|
-
value: query[key]
|
|
604
|
-
});
|
|
605
|
-
if (value !== void 0 && value !== query[key]) set(key, value);
|
|
606
|
-
}
|
|
607
|
-
return query;
|
|
608
|
-
};
|
|
609
|
-
/**
|
|
610
|
-
* Walks every property of a Feathers query (including nested `$and`/`$or`/`$nor` arrays)
|
|
611
|
-
* and calls the `walker` function for each one. The walker receives the property name, operator,
|
|
612
|
-
* value, and path, and can return a replacement value. Returns a new query only if changes were made.
|
|
613
|
-
*
|
|
614
|
-
* @example
|
|
615
|
-
* ```ts
|
|
616
|
-
* import { walkQuery } from 'feathers-utils/utils'
|
|
617
|
-
*
|
|
618
|
-
* const query = walkQuery({ age: { $gt: '18' } }, ({ value, operator }) => {
|
|
619
|
-
* if (operator === '$gt') return Number(value)
|
|
620
|
-
* })
|
|
621
|
-
* // => { age: { $gt: 18 } }
|
|
622
|
-
* ```
|
|
623
|
-
*
|
|
624
|
-
* @see https://utils.feathersjs.com/utils/walk-query.html
|
|
625
|
-
*/
|
|
626
|
-
const walkQuery = (query, walker) => {
|
|
627
|
-
return _walkQueryUtil(query, walker);
|
|
628
|
-
};
|
|
629
|
-
//#endregion
|
|
630
891
|
//#region src/utils/zip-data-result/zip-data-result.util.ts
|
|
631
892
|
/**
|
|
632
893
|
* Pairs each item in `context.data` with its corresponding item in `context.result` by index.
|
|
@@ -665,6 +926,6 @@ function zipDataResult(context, options) {
|
|
|
665
926
|
return result;
|
|
666
927
|
}
|
|
667
928
|
//#endregion
|
|
668
|
-
export {
|
|
929
|
+
export { addToQuery as _, skipResult as a, walkQuery as c, iterateFind as d, getExposedMethods as f, checkContext as g, chunkFind as h, toPaginated as i, patchBatch as l, contextToJson as m, waitForServiceEvent as n, queryDefaults as o, defineHooks as p, transformParams as r, queryHasProperty as s, zipDataResult as t, mergeQuery as u, addSkip as v, sortQueryProperties as y };
|
|
669
930
|
|
|
670
|
-
//# sourceMappingURL=utils-
|
|
931
|
+
//# sourceMappingURL=utils-DByCpAsf.mjs.map
|