feathers-utils 5.2.0 → 7.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 +1 -2
- package/dist/index.cjs +65 -64
- package/dist/index.d.cts +21 -16
- package/dist/index.d.mts +21 -16
- package/dist/index.d.ts +21 -16
- package/dist/index.mjs +65 -64
- package/package.json +24 -24
- package/src/.DS_Store +0 -0
- package/src/hooks/.DS_Store +0 -0
- package/src/hooks/createRelated.ts +32 -31
- package/src/hooks/onDelete.ts +40 -41
- package/src/hooks/removeRelated.ts +2 -0
- package/src/typesInternal.ts +2 -0
- package/src/utils/optimizeBatchPatch.ts +54 -22
package/README.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# feathers-utils
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/feathers-utils)
|
|
4
|
-
[](https://github.com/fratzinger/feathers-utils/actions/workflows/node.js.yml?query=branch%3Amain)
|
|
5
5
|
[](https://codeclimate.com/github/fratzinger/feathers-utils)
|
|
6
6
|
[](https://codeclimate.com/github/fratzinger/feathers-utils)
|
|
7
|
-
[](https://libraries.io/npm/feathers-utils)
|
|
8
7
|
[](https://www.npmjs.com/package/feathers-utils)
|
|
9
8
|
[](https://github.com/fratzinger/feathers-utils/blob/main/LICENSE.md)
|
|
10
9
|
|
package/dist/index.cjs
CHANGED
|
@@ -620,13 +620,18 @@ const validateQueryProperty = (query, operators = []) => {
|
|
|
620
620
|
|
|
621
621
|
function optimizeBatchPatch(items, options) {
|
|
622
622
|
const map = [];
|
|
623
|
-
const
|
|
624
|
-
for (const
|
|
625
|
-
const
|
|
623
|
+
const idKey = options?.id ?? "id";
|
|
624
|
+
for (const _data of items) {
|
|
625
|
+
const data = _data;
|
|
626
|
+
const id = _data[idKey];
|
|
627
|
+
delete data[idKey];
|
|
628
|
+
const index = map.findIndex((item) => {
|
|
629
|
+
return fastEquals.deepEqual(item.data, data);
|
|
630
|
+
});
|
|
626
631
|
if (index === -1) {
|
|
627
|
-
map.push({ ids: [
|
|
632
|
+
map.push({ ids: [id], data });
|
|
628
633
|
} else {
|
|
629
|
-
map[index].ids.push(
|
|
634
|
+
map[index].ids.push(id);
|
|
630
635
|
}
|
|
631
636
|
}
|
|
632
637
|
return map.map(({ ids, data }) => {
|
|
@@ -635,7 +640,7 @@ function optimizeBatchPatch(items, options) {
|
|
|
635
640
|
data,
|
|
636
641
|
{
|
|
637
642
|
query: {
|
|
638
|
-
[
|
|
643
|
+
[idKey]: { $in: ids }
|
|
639
644
|
}
|
|
640
645
|
}
|
|
641
646
|
];
|
|
@@ -724,37 +729,35 @@ function checkMulti() {
|
|
|
724
729
|
};
|
|
725
730
|
}
|
|
726
731
|
|
|
727
|
-
function createRelated({
|
|
728
|
-
service,
|
|
729
|
-
multi = true,
|
|
730
|
-
data,
|
|
731
|
-
createItemsInDataArraySeparately = true
|
|
732
|
-
}) {
|
|
733
|
-
if (!service || !data) {
|
|
734
|
-
throw "initialize hook 'createRelated' completely!";
|
|
735
|
-
}
|
|
732
|
+
function createRelated(options) {
|
|
736
733
|
return async (context) => {
|
|
737
734
|
if (shouldSkip("createRelated", context)) {
|
|
738
735
|
return context;
|
|
739
736
|
}
|
|
740
737
|
feathersHooksCommon.checkContext(context, "after", void 0, "createRelated");
|
|
741
738
|
const { items } = getItemsIsArray(context);
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
739
|
+
const entries = Array.isArray(options) ? options : [options];
|
|
740
|
+
await Promise.all(
|
|
741
|
+
entries.map(async (entry) => {
|
|
742
|
+
const { data, service, createItemsInDataArraySeparately, multi } = entry;
|
|
743
|
+
let dataToCreate = (await Promise.all(items.map(async (item) => data(item, context)))).filter((x) => !!x);
|
|
744
|
+
if (createItemsInDataArraySeparately) {
|
|
745
|
+
dataToCreate = dataToCreate.flat();
|
|
746
|
+
}
|
|
747
|
+
if (!dataToCreate || dataToCreate.length <= 0) {
|
|
748
|
+
return context;
|
|
749
|
+
}
|
|
750
|
+
if (multi) {
|
|
751
|
+
await context.app.service(service).create(dataToCreate);
|
|
752
|
+
} else {
|
|
753
|
+
await Promise.all(
|
|
754
|
+
dataToCreate.map(
|
|
755
|
+
async (item) => context.app.service(service).create(item)
|
|
756
|
+
)
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
})
|
|
760
|
+
);
|
|
758
761
|
return context;
|
|
759
762
|
};
|
|
760
763
|
}
|
|
@@ -792,46 +795,44 @@ const forEach = (actionPerItem, options) => {
|
|
|
792
795
|
};
|
|
793
796
|
};
|
|
794
797
|
|
|
795
|
-
function onDelete(
|
|
796
|
-
keyThere,
|
|
797
|
-
keyHere = "id",
|
|
798
|
-
onDelete: onDelete2 = "cascade",
|
|
799
|
-
blocking = true
|
|
800
|
-
}) {
|
|
801
|
-
if (!service || !keyThere) {
|
|
802
|
-
throw "initialize hook 'removeRelated' completely!";
|
|
803
|
-
}
|
|
804
|
-
if (!["cascade", "set null"].includes(onDelete2)) {
|
|
805
|
-
throw "onDelete must be 'cascade' or 'set null'";
|
|
806
|
-
}
|
|
798
|
+
function onDelete(options) {
|
|
807
799
|
return async (context) => {
|
|
808
800
|
if (shouldSkip("onDelete", context)) {
|
|
809
801
|
return context;
|
|
810
802
|
}
|
|
811
803
|
feathersHooksCommon.checkContext(context, "after", "remove", "onDelete");
|
|
812
804
|
const { items } = getItemsIsArray(context);
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
$in: ids
|
|
805
|
+
const entries = Array.isArray(options) ? options : [options];
|
|
806
|
+
const promises = [];
|
|
807
|
+
entries.forEach(
|
|
808
|
+
async ({ keyHere, keyThere, onDelete: onDelete2, service, blocking }) => {
|
|
809
|
+
let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
|
|
810
|
+
ids = [...new Set(ids)];
|
|
811
|
+
if (!ids || ids.length <= 0) {
|
|
812
|
+
return context;
|
|
822
813
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
814
|
+
const params = {
|
|
815
|
+
query: {
|
|
816
|
+
[keyThere]: {
|
|
817
|
+
$in: ids
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
paginate: false
|
|
821
|
+
};
|
|
822
|
+
let promise = void 0;
|
|
823
|
+
if (onDelete2 === "cascade") {
|
|
824
|
+
promise = context.app.service(service).remove(null, params);
|
|
825
|
+
} else if (onDelete2 === "set null") {
|
|
826
|
+
const data = { [keyThere]: null };
|
|
827
|
+
promise = context.app.service(service).patch(null, data, params);
|
|
828
|
+
}
|
|
829
|
+
if (blocking) {
|
|
830
|
+
promises.push(promise);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
);
|
|
834
|
+
if (promises.length) {
|
|
835
|
+
await Promise.all(promises);
|
|
835
836
|
}
|
|
836
837
|
return context;
|
|
837
838
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -14,6 +14,7 @@ type MaybeArray<T> = T | T[];
|
|
|
14
14
|
type Promisable<T> = T | Promise<T>;
|
|
15
15
|
type Path = Array<string | number>;
|
|
16
16
|
type ReturnAsyncHook<H extends HookContext$1 = HookContext$1> = (context: H) => Promise<H>;
|
|
17
|
+
type KeyOf<T> = Extract<keyof T, string>;
|
|
17
18
|
|
|
18
19
|
interface CreateRelatedOptions<S = Record<string, any>> {
|
|
19
20
|
service: keyof S;
|
|
@@ -24,7 +25,7 @@ interface CreateRelatedOptions<S = Record<string, any>> {
|
|
|
24
25
|
/**
|
|
25
26
|
* hook to create related items
|
|
26
27
|
*/
|
|
27
|
-
declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(
|
|
28
|
+
declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<CreateRelatedOptions<S>>): (context: H) => Promise<H>;
|
|
28
29
|
|
|
29
30
|
type GetItemsIsArrayFrom = "data" | "result" | "automatic";
|
|
30
31
|
type GetItemsIsArrayOptions = {
|
|
@@ -34,7 +35,7 @@ interface GetItemsIsArrayResult<T = any> {
|
|
|
34
35
|
items: T[];
|
|
35
36
|
isArray: boolean;
|
|
36
37
|
}
|
|
37
|
-
declare const getItemsIsArray: <T = any, H extends HookContext
|
|
38
|
+
declare const getItemsIsArray: <T = any, H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
|
|
38
39
|
|
|
39
40
|
type HookForEachOptions<T = any, H = HookContext, R = any> = {
|
|
40
41
|
wait?: "sequential" | "parallel" | false;
|
|
@@ -48,10 +49,11 @@ type ActionPerItem<T, H, R> = (item: T, options: {
|
|
|
48
49
|
} & (undefined extends R ? {} : {
|
|
49
50
|
fromAll: R;
|
|
50
51
|
})) => Promisable<any>;
|
|
51
|
-
declare const forEach: <H extends HookContext
|
|
52
|
+
declare const forEach: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
|
|
52
53
|
|
|
53
54
|
type OnDeleteAction = "cascade" | "set null";
|
|
54
|
-
interface OnDeleteOptions {
|
|
55
|
+
interface OnDeleteOptions<Path extends string = string> {
|
|
56
|
+
service: Path;
|
|
55
57
|
keyThere: string;
|
|
56
58
|
keyHere: string;
|
|
57
59
|
onDelete: OnDeleteAction;
|
|
@@ -60,7 +62,7 @@ interface OnDeleteOptions {
|
|
|
60
62
|
/**
|
|
61
63
|
* hook to manipulate related items on delete
|
|
62
64
|
*/
|
|
63
|
-
declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(
|
|
65
|
+
declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<OnDeleteOptions<KeyOf<S>>>): (context: H) => Promise<H>;
|
|
64
66
|
|
|
65
67
|
/**
|
|
66
68
|
* Parse fields to date or number
|
|
@@ -78,6 +80,8 @@ interface RemoveRelatedOptions<S = Record<string, any>> {
|
|
|
78
80
|
}
|
|
79
81
|
/**
|
|
80
82
|
* hook to remove related items
|
|
83
|
+
*
|
|
84
|
+
* @deprecated use 'onDelete' instead
|
|
81
85
|
*/
|
|
82
86
|
declare function removeRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, keyThere, keyHere, blocking, }: RemoveRelatedOptions<S>): (context: H) => Promise<H>;
|
|
83
87
|
|
|
@@ -88,7 +92,7 @@ interface HookRunPerItemOptions {
|
|
|
88
92
|
* hook to run a hook for each item in the context
|
|
89
93
|
* uses `context.result` if it is existent. otherwise uses context.data
|
|
90
94
|
*/
|
|
91
|
-
declare const runPerItem: <H extends HookContext
|
|
95
|
+
declare const runPerItem: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
|
|
92
96
|
|
|
93
97
|
type Predicate<T = any> = (item: T) => boolean;
|
|
94
98
|
type PredicateWithContext<T = any> = (item: T, context: HookContext) => boolean;
|
|
@@ -102,14 +106,14 @@ interface HookSetDataOptions {
|
|
|
102
106
|
*/
|
|
103
107
|
declare function setData<H extends HookContext = HookContext>(from: PropertyPath, to: PropertyPath, _options?: HookSetDataOptions): (context: H) => H;
|
|
104
108
|
|
|
105
|
-
declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext
|
|
109
|
+
declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
|
|
106
110
|
/**
|
|
107
111
|
* a hook to move params to query._$client
|
|
108
112
|
* the server only receives 'query' from params. All other params are ignored.
|
|
109
113
|
* So, to use `$populateParams` on the server, we need to move the params to query._$client
|
|
110
114
|
* the server will move them back to params
|
|
111
115
|
*/
|
|
112
|
-
declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext
|
|
116
|
+
declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
|
|
113
117
|
|
|
114
118
|
declare function defineParamsFromClient(keyToHide: string): (...whitelist: string[]) => (context: HookContext) => HookContext;
|
|
115
119
|
declare const paramsFromClient: (...whitelist: string[]) => (context: HookContext) => HookContext;
|
|
@@ -175,7 +179,7 @@ declare function reassembleQuery(query: FilterQueryResult): Query;
|
|
|
175
179
|
* 2. it uses `service.options.paginate` if it exists
|
|
176
180
|
* 3. it uses `context.params.adapter` if it exists
|
|
177
181
|
*/
|
|
178
|
-
declare const getPaginate: <H extends HookContext
|
|
182
|
+
declare const getPaginate: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
|
|
179
183
|
|
|
180
184
|
/**
|
|
181
185
|
* util to check if a hook is a multi hook:
|
|
@@ -186,12 +190,12 @@ declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Applicati
|
|
|
186
190
|
* - patch: `context.id == null`
|
|
187
191
|
* - remove: `context.id == null`
|
|
188
192
|
*/
|
|
189
|
-
declare const isMulti: <H extends HookContext
|
|
193
|
+
declare const isMulti: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
|
|
190
194
|
|
|
191
195
|
/**
|
|
192
196
|
* util to check if a hook is a paginated hook using `getPaginate`
|
|
193
197
|
*/
|
|
194
|
-
declare const isPaginated: <H extends HookContext
|
|
198
|
+
declare const isPaginated: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
|
|
195
199
|
|
|
196
200
|
/**
|
|
197
201
|
* util to mark a hook for skip, has to be used with `shouldSkip`
|
|
@@ -236,7 +240,7 @@ declare const setQueryKeySafely: (params: Params, key: string, value: any, opera
|
|
|
236
240
|
/**
|
|
237
241
|
* util to set `context.result` to an empty array or object, depending on the hook type
|
|
238
242
|
*/
|
|
239
|
-
declare const setResultEmpty: <H extends HookContext
|
|
243
|
+
declare const setResultEmpty: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
|
|
240
244
|
|
|
241
245
|
type ShouldSkipOptions = {
|
|
242
246
|
notSkippable?: boolean;
|
|
@@ -244,7 +248,7 @@ type ShouldSkipOptions = {
|
|
|
244
248
|
/**
|
|
245
249
|
* util to detect if a hook should be skipped
|
|
246
250
|
*/
|
|
247
|
-
declare const shouldSkip: <H extends HookContext
|
|
251
|
+
declare const shouldSkip: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
|
|
248
252
|
|
|
249
253
|
declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
|
|
250
254
|
|
|
@@ -253,11 +257,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
|
|
|
253
257
|
*/
|
|
254
258
|
declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
|
|
255
259
|
|
|
256
|
-
type OptimizeBatchPatchOptions = {
|
|
257
|
-
id
|
|
260
|
+
type OptimizeBatchPatchOptions<IdKey extends string> = {
|
|
261
|
+
/** the key of the id property */
|
|
262
|
+
id?: IdKey;
|
|
258
263
|
};
|
|
259
264
|
type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
|
|
260
|
-
declare function optimizeBatchPatch<T extends Record<string,
|
|
265
|
+
declare function optimizeBatchPatch<T extends Record<string, any>, IdKey extends KeyOf<T>, P extends Params, R extends Omit<T, IdKey> = Omit<T, IdKey>>(items: T[], options?: OptimizeBatchPatchOptions<IdKey>): OptimizeBatchPatchResultItem<R, P>[];
|
|
261
266
|
|
|
262
267
|
declare function flattenQuery(q: Query): any;
|
|
263
268
|
|
package/dist/index.d.mts
CHANGED
|
@@ -14,6 +14,7 @@ type MaybeArray<T> = T | T[];
|
|
|
14
14
|
type Promisable<T> = T | Promise<T>;
|
|
15
15
|
type Path = Array<string | number>;
|
|
16
16
|
type ReturnAsyncHook<H extends HookContext$1 = HookContext$1> = (context: H) => Promise<H>;
|
|
17
|
+
type KeyOf<T> = Extract<keyof T, string>;
|
|
17
18
|
|
|
18
19
|
interface CreateRelatedOptions<S = Record<string, any>> {
|
|
19
20
|
service: keyof S;
|
|
@@ -24,7 +25,7 @@ interface CreateRelatedOptions<S = Record<string, any>> {
|
|
|
24
25
|
/**
|
|
25
26
|
* hook to create related items
|
|
26
27
|
*/
|
|
27
|
-
declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(
|
|
28
|
+
declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<CreateRelatedOptions<S>>): (context: H) => Promise<H>;
|
|
28
29
|
|
|
29
30
|
type GetItemsIsArrayFrom = "data" | "result" | "automatic";
|
|
30
31
|
type GetItemsIsArrayOptions = {
|
|
@@ -34,7 +35,7 @@ interface GetItemsIsArrayResult<T = any> {
|
|
|
34
35
|
items: T[];
|
|
35
36
|
isArray: boolean;
|
|
36
37
|
}
|
|
37
|
-
declare const getItemsIsArray: <T = any, H extends HookContext
|
|
38
|
+
declare const getItemsIsArray: <T = any, H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
|
|
38
39
|
|
|
39
40
|
type HookForEachOptions<T = any, H = HookContext, R = any> = {
|
|
40
41
|
wait?: "sequential" | "parallel" | false;
|
|
@@ -48,10 +49,11 @@ type ActionPerItem<T, H, R> = (item: T, options: {
|
|
|
48
49
|
} & (undefined extends R ? {} : {
|
|
49
50
|
fromAll: R;
|
|
50
51
|
})) => Promisable<any>;
|
|
51
|
-
declare const forEach: <H extends HookContext
|
|
52
|
+
declare const forEach: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
|
|
52
53
|
|
|
53
54
|
type OnDeleteAction = "cascade" | "set null";
|
|
54
|
-
interface OnDeleteOptions {
|
|
55
|
+
interface OnDeleteOptions<Path extends string = string> {
|
|
56
|
+
service: Path;
|
|
55
57
|
keyThere: string;
|
|
56
58
|
keyHere: string;
|
|
57
59
|
onDelete: OnDeleteAction;
|
|
@@ -60,7 +62,7 @@ interface OnDeleteOptions {
|
|
|
60
62
|
/**
|
|
61
63
|
* hook to manipulate related items on delete
|
|
62
64
|
*/
|
|
63
|
-
declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(
|
|
65
|
+
declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<OnDeleteOptions<KeyOf<S>>>): (context: H) => Promise<H>;
|
|
64
66
|
|
|
65
67
|
/**
|
|
66
68
|
* Parse fields to date or number
|
|
@@ -78,6 +80,8 @@ interface RemoveRelatedOptions<S = Record<string, any>> {
|
|
|
78
80
|
}
|
|
79
81
|
/**
|
|
80
82
|
* hook to remove related items
|
|
83
|
+
*
|
|
84
|
+
* @deprecated use 'onDelete' instead
|
|
81
85
|
*/
|
|
82
86
|
declare function removeRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, keyThere, keyHere, blocking, }: RemoveRelatedOptions<S>): (context: H) => Promise<H>;
|
|
83
87
|
|
|
@@ -88,7 +92,7 @@ interface HookRunPerItemOptions {
|
|
|
88
92
|
* hook to run a hook for each item in the context
|
|
89
93
|
* uses `context.result` if it is existent. otherwise uses context.data
|
|
90
94
|
*/
|
|
91
|
-
declare const runPerItem: <H extends HookContext
|
|
95
|
+
declare const runPerItem: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
|
|
92
96
|
|
|
93
97
|
type Predicate<T = any> = (item: T) => boolean;
|
|
94
98
|
type PredicateWithContext<T = any> = (item: T, context: HookContext) => boolean;
|
|
@@ -102,14 +106,14 @@ interface HookSetDataOptions {
|
|
|
102
106
|
*/
|
|
103
107
|
declare function setData<H extends HookContext = HookContext>(from: PropertyPath, to: PropertyPath, _options?: HookSetDataOptions): (context: H) => H;
|
|
104
108
|
|
|
105
|
-
declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext
|
|
109
|
+
declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
|
|
106
110
|
/**
|
|
107
111
|
* a hook to move params to query._$client
|
|
108
112
|
* the server only receives 'query' from params. All other params are ignored.
|
|
109
113
|
* So, to use `$populateParams` on the server, we need to move the params to query._$client
|
|
110
114
|
* the server will move them back to params
|
|
111
115
|
*/
|
|
112
|
-
declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext
|
|
116
|
+
declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
|
|
113
117
|
|
|
114
118
|
declare function defineParamsFromClient(keyToHide: string): (...whitelist: string[]) => (context: HookContext) => HookContext;
|
|
115
119
|
declare const paramsFromClient: (...whitelist: string[]) => (context: HookContext) => HookContext;
|
|
@@ -175,7 +179,7 @@ declare function reassembleQuery(query: FilterQueryResult): Query;
|
|
|
175
179
|
* 2. it uses `service.options.paginate` if it exists
|
|
176
180
|
* 3. it uses `context.params.adapter` if it exists
|
|
177
181
|
*/
|
|
178
|
-
declare const getPaginate: <H extends HookContext
|
|
182
|
+
declare const getPaginate: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
|
|
179
183
|
|
|
180
184
|
/**
|
|
181
185
|
* util to check if a hook is a multi hook:
|
|
@@ -186,12 +190,12 @@ declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Applicati
|
|
|
186
190
|
* - patch: `context.id == null`
|
|
187
191
|
* - remove: `context.id == null`
|
|
188
192
|
*/
|
|
189
|
-
declare const isMulti: <H extends HookContext
|
|
193
|
+
declare const isMulti: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
|
|
190
194
|
|
|
191
195
|
/**
|
|
192
196
|
* util to check if a hook is a paginated hook using `getPaginate`
|
|
193
197
|
*/
|
|
194
|
-
declare const isPaginated: <H extends HookContext
|
|
198
|
+
declare const isPaginated: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
|
|
195
199
|
|
|
196
200
|
/**
|
|
197
201
|
* util to mark a hook for skip, has to be used with `shouldSkip`
|
|
@@ -236,7 +240,7 @@ declare const setQueryKeySafely: (params: Params, key: string, value: any, opera
|
|
|
236
240
|
/**
|
|
237
241
|
* util to set `context.result` to an empty array or object, depending on the hook type
|
|
238
242
|
*/
|
|
239
|
-
declare const setResultEmpty: <H extends HookContext
|
|
243
|
+
declare const setResultEmpty: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
|
|
240
244
|
|
|
241
245
|
type ShouldSkipOptions = {
|
|
242
246
|
notSkippable?: boolean;
|
|
@@ -244,7 +248,7 @@ type ShouldSkipOptions = {
|
|
|
244
248
|
/**
|
|
245
249
|
* util to detect if a hook should be skipped
|
|
246
250
|
*/
|
|
247
|
-
declare const shouldSkip: <H extends HookContext
|
|
251
|
+
declare const shouldSkip: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
|
|
248
252
|
|
|
249
253
|
declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
|
|
250
254
|
|
|
@@ -253,11 +257,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
|
|
|
253
257
|
*/
|
|
254
258
|
declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
|
|
255
259
|
|
|
256
|
-
type OptimizeBatchPatchOptions = {
|
|
257
|
-
id
|
|
260
|
+
type OptimizeBatchPatchOptions<IdKey extends string> = {
|
|
261
|
+
/** the key of the id property */
|
|
262
|
+
id?: IdKey;
|
|
258
263
|
};
|
|
259
264
|
type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
|
|
260
|
-
declare function optimizeBatchPatch<T extends Record<string,
|
|
265
|
+
declare function optimizeBatchPatch<T extends Record<string, any>, IdKey extends KeyOf<T>, P extends Params, R extends Omit<T, IdKey> = Omit<T, IdKey>>(items: T[], options?: OptimizeBatchPatchOptions<IdKey>): OptimizeBatchPatchResultItem<R, P>[];
|
|
261
266
|
|
|
262
267
|
declare function flattenQuery(q: Query): any;
|
|
263
268
|
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ type MaybeArray<T> = T | T[];
|
|
|
14
14
|
type Promisable<T> = T | Promise<T>;
|
|
15
15
|
type Path = Array<string | number>;
|
|
16
16
|
type ReturnAsyncHook<H extends HookContext$1 = HookContext$1> = (context: H) => Promise<H>;
|
|
17
|
+
type KeyOf<T> = Extract<keyof T, string>;
|
|
17
18
|
|
|
18
19
|
interface CreateRelatedOptions<S = Record<string, any>> {
|
|
19
20
|
service: keyof S;
|
|
@@ -24,7 +25,7 @@ interface CreateRelatedOptions<S = Record<string, any>> {
|
|
|
24
25
|
/**
|
|
25
26
|
* hook to create related items
|
|
26
27
|
*/
|
|
27
|
-
declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(
|
|
28
|
+
declare function createRelated<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<CreateRelatedOptions<S>>): (context: H) => Promise<H>;
|
|
28
29
|
|
|
29
30
|
type GetItemsIsArrayFrom = "data" | "result" | "automatic";
|
|
30
31
|
type GetItemsIsArrayOptions = {
|
|
@@ -34,7 +35,7 @@ interface GetItemsIsArrayResult<T = any> {
|
|
|
34
35
|
items: T[];
|
|
35
36
|
isArray: boolean;
|
|
36
37
|
}
|
|
37
|
-
declare const getItemsIsArray: <T = any, H extends HookContext
|
|
38
|
+
declare const getItemsIsArray: <T = any, H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H, options?: GetItemsIsArrayOptions) => GetItemsIsArrayResult<T>;
|
|
38
39
|
|
|
39
40
|
type HookForEachOptions<T = any, H = HookContext, R = any> = {
|
|
40
41
|
wait?: "sequential" | "parallel" | false;
|
|
@@ -48,10 +49,11 @@ type ActionPerItem<T, H, R> = (item: T, options: {
|
|
|
48
49
|
} & (undefined extends R ? {} : {
|
|
49
50
|
fromAll: R;
|
|
50
51
|
})) => Promisable<any>;
|
|
51
|
-
declare const forEach: <H extends HookContext
|
|
52
|
+
declare const forEach: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any, R = any>(actionPerItem: ActionPerItem<T, H, R>, options?: HookForEachOptions<T, H, R>) => ReturnAsyncHook<H>;
|
|
52
53
|
|
|
53
54
|
type OnDeleteAction = "cascade" | "set null";
|
|
54
|
-
interface OnDeleteOptions {
|
|
55
|
+
interface OnDeleteOptions<Path extends string = string> {
|
|
56
|
+
service: Path;
|
|
55
57
|
keyThere: string;
|
|
56
58
|
keyHere: string;
|
|
57
59
|
onDelete: OnDeleteAction;
|
|
@@ -60,7 +62,7 @@ interface OnDeleteOptions {
|
|
|
60
62
|
/**
|
|
61
63
|
* hook to manipulate related items on delete
|
|
62
64
|
*/
|
|
63
|
-
declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(
|
|
65
|
+
declare function onDelete<S = Record<string, any>, H extends HookContext = HookContext>(options: MaybeArray<OnDeleteOptions<KeyOf<S>>>): (context: H) => Promise<H>;
|
|
64
66
|
|
|
65
67
|
/**
|
|
66
68
|
* Parse fields to date or number
|
|
@@ -78,6 +80,8 @@ interface RemoveRelatedOptions<S = Record<string, any>> {
|
|
|
78
80
|
}
|
|
79
81
|
/**
|
|
80
82
|
* hook to remove related items
|
|
83
|
+
*
|
|
84
|
+
* @deprecated use 'onDelete' instead
|
|
81
85
|
*/
|
|
82
86
|
declare function removeRelated<S = Record<string, any>, H extends HookContext = HookContext>({ service, keyThere, keyHere, blocking, }: RemoveRelatedOptions<S>): (context: H) => Promise<H>;
|
|
83
87
|
|
|
@@ -88,7 +92,7 @@ interface HookRunPerItemOptions {
|
|
|
88
92
|
* hook to run a hook for each item in the context
|
|
89
93
|
* uses `context.result` if it is existent. otherwise uses context.data
|
|
90
94
|
*/
|
|
91
|
-
declare const runPerItem: <H extends HookContext
|
|
95
|
+
declare const runPerItem: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>, T = any>(actionPerItem: (item: T, context: H) => Promisable<any>, _options?: HookRunPerItemOptions) => (context: H) => Promise<H>;
|
|
92
96
|
|
|
93
97
|
type Predicate<T = any> = (item: T) => boolean;
|
|
94
98
|
type PredicateWithContext<T = any> = (item: T, context: HookContext) => boolean;
|
|
@@ -102,14 +106,14 @@ interface HookSetDataOptions {
|
|
|
102
106
|
*/
|
|
103
107
|
declare function setData<H extends HookContext = HookContext>(from: PropertyPath, to: PropertyPath, _options?: HookSetDataOptions): (context: H) => H;
|
|
104
108
|
|
|
105
|
-
declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext
|
|
109
|
+
declare function defineParamsForServer(keyToHide: string): (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
|
|
106
110
|
/**
|
|
107
111
|
* a hook to move params to query._$client
|
|
108
112
|
* the server only receives 'query' from params. All other params are ignored.
|
|
109
113
|
* So, to use `$populateParams` on the server, we need to move the params to query._$client
|
|
110
114
|
* the server will move them back to params
|
|
111
115
|
*/
|
|
112
|
-
declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext
|
|
116
|
+
declare const paramsForServer: (...whitelist: string[]) => <H extends HookContext>(context: H) => H;
|
|
113
117
|
|
|
114
118
|
declare function defineParamsFromClient(keyToHide: string): (...whitelist: string[]) => (context: HookContext) => HookContext;
|
|
115
119
|
declare const paramsFromClient: (...whitelist: string[]) => (context: HookContext) => HookContext;
|
|
@@ -175,7 +179,7 @@ declare function reassembleQuery(query: FilterQueryResult): Query;
|
|
|
175
179
|
* 2. it uses `service.options.paginate` if it exists
|
|
176
180
|
* 3. it uses `context.params.adapter` if it exists
|
|
177
181
|
*/
|
|
178
|
-
declare const getPaginate: <H extends HookContext
|
|
182
|
+
declare const getPaginate: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => PaginationOptions | undefined;
|
|
179
183
|
|
|
180
184
|
/**
|
|
181
185
|
* util to check if a hook is a multi hook:
|
|
@@ -186,12 +190,12 @@ declare const getPaginate: <H extends HookContext<_feathersjs_feathers.Applicati
|
|
|
186
190
|
* - patch: `context.id == null`
|
|
187
191
|
* - remove: `context.id == null`
|
|
188
192
|
*/
|
|
189
|
-
declare const isMulti: <H extends HookContext
|
|
193
|
+
declare const isMulti: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
|
|
190
194
|
|
|
191
195
|
/**
|
|
192
196
|
* util to check if a hook is a paginated hook using `getPaginate`
|
|
193
197
|
*/
|
|
194
|
-
declare const isPaginated: <H extends HookContext
|
|
198
|
+
declare const isPaginated: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => boolean;
|
|
195
199
|
|
|
196
200
|
/**
|
|
197
201
|
* util to mark a hook for skip, has to be used with `shouldSkip`
|
|
@@ -236,7 +240,7 @@ declare const setQueryKeySafely: (params: Params, key: string, value: any, opera
|
|
|
236
240
|
/**
|
|
237
241
|
* util to set `context.result` to an empty array or object, depending on the hook type
|
|
238
242
|
*/
|
|
239
|
-
declare const setResultEmpty: <H extends HookContext
|
|
243
|
+
declare const setResultEmpty: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(context: H) => H;
|
|
240
244
|
|
|
241
245
|
type ShouldSkipOptions = {
|
|
242
246
|
notSkippable?: boolean;
|
|
@@ -244,7 +248,7 @@ type ShouldSkipOptions = {
|
|
|
244
248
|
/**
|
|
245
249
|
* util to detect if a hook should be skipped
|
|
246
250
|
*/
|
|
247
|
-
declare const shouldSkip: <H extends HookContext
|
|
251
|
+
declare const shouldSkip: <H extends HookContext = HookContext<_feathersjs_feathers.Application<any, any>, any>>(hookName: string, context: H, options?: ShouldSkipOptions) => boolean;
|
|
248
252
|
|
|
249
253
|
declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers.Application<any, any>, any>;
|
|
250
254
|
|
|
@@ -253,11 +257,12 @@ declare const toJSON: (context: HookContext) => HookContext<_feathersjs_feathers
|
|
|
253
257
|
*/
|
|
254
258
|
declare const validateQueryProperty: (query: any, operators?: string[]) => Query;
|
|
255
259
|
|
|
256
|
-
type OptimizeBatchPatchOptions = {
|
|
257
|
-
id
|
|
260
|
+
type OptimizeBatchPatchOptions<IdKey extends string> = {
|
|
261
|
+
/** the key of the id property */
|
|
262
|
+
id?: IdKey;
|
|
258
263
|
};
|
|
259
264
|
type OptimizeBatchPatchResultItem<T = Record<string, unknown>, P = Params> = [Id, T, P | undefined];
|
|
260
|
-
declare function optimizeBatchPatch<T extends Record<string,
|
|
265
|
+
declare function optimizeBatchPatch<T extends Record<string, any>, IdKey extends KeyOf<T>, P extends Params, R extends Omit<T, IdKey> = Omit<T, IdKey>>(items: T[], options?: OptimizeBatchPatchOptions<IdKey>): OptimizeBatchPatchResultItem<R, P>[];
|
|
261
266
|
|
|
262
267
|
declare function flattenQuery(q: Query): any;
|
|
263
268
|
|
package/dist/index.mjs
CHANGED
|
@@ -609,13 +609,18 @@ const validateQueryProperty = (query, operators = []) => {
|
|
|
609
609
|
|
|
610
610
|
function optimizeBatchPatch(items, options) {
|
|
611
611
|
const map = [];
|
|
612
|
-
const
|
|
613
|
-
for (const
|
|
614
|
-
const
|
|
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
|
+
});
|
|
615
620
|
if (index === -1) {
|
|
616
|
-
map.push({ ids: [
|
|
621
|
+
map.push({ ids: [id], data });
|
|
617
622
|
} else {
|
|
618
|
-
map[index].ids.push(
|
|
623
|
+
map[index].ids.push(id);
|
|
619
624
|
}
|
|
620
625
|
}
|
|
621
626
|
return map.map(({ ids, data }) => {
|
|
@@ -624,7 +629,7 @@ function optimizeBatchPatch(items, options) {
|
|
|
624
629
|
data,
|
|
625
630
|
{
|
|
626
631
|
query: {
|
|
627
|
-
[
|
|
632
|
+
[idKey]: { $in: ids }
|
|
628
633
|
}
|
|
629
634
|
}
|
|
630
635
|
];
|
|
@@ -713,37 +718,35 @@ function checkMulti() {
|
|
|
713
718
|
};
|
|
714
719
|
}
|
|
715
720
|
|
|
716
|
-
function createRelated({
|
|
717
|
-
service,
|
|
718
|
-
multi = true,
|
|
719
|
-
data,
|
|
720
|
-
createItemsInDataArraySeparately = true
|
|
721
|
-
}) {
|
|
722
|
-
if (!service || !data) {
|
|
723
|
-
throw "initialize hook 'createRelated' completely!";
|
|
724
|
-
}
|
|
721
|
+
function createRelated(options) {
|
|
725
722
|
return async (context) => {
|
|
726
723
|
if (shouldSkip("createRelated", context)) {
|
|
727
724
|
return context;
|
|
728
725
|
}
|
|
729
726
|
checkContext(context, "after", void 0, "createRelated");
|
|
730
727
|
const { items } = getItemsIsArray(context);
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
728
|
+
const entries = Array.isArray(options) ? options : [options];
|
|
729
|
+
await Promise.all(
|
|
730
|
+
entries.map(async (entry) => {
|
|
731
|
+
const { data, service, createItemsInDataArraySeparately, multi } = entry;
|
|
732
|
+
let dataToCreate = (await Promise.all(items.map(async (item) => data(item, context)))).filter((x) => !!x);
|
|
733
|
+
if (createItemsInDataArraySeparately) {
|
|
734
|
+
dataToCreate = dataToCreate.flat();
|
|
735
|
+
}
|
|
736
|
+
if (!dataToCreate || dataToCreate.length <= 0) {
|
|
737
|
+
return context;
|
|
738
|
+
}
|
|
739
|
+
if (multi) {
|
|
740
|
+
await context.app.service(service).create(dataToCreate);
|
|
741
|
+
} else {
|
|
742
|
+
await Promise.all(
|
|
743
|
+
dataToCreate.map(
|
|
744
|
+
async (item) => context.app.service(service).create(item)
|
|
745
|
+
)
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
})
|
|
749
|
+
);
|
|
747
750
|
return context;
|
|
748
751
|
};
|
|
749
752
|
}
|
|
@@ -781,46 +784,44 @@ const forEach = (actionPerItem, options) => {
|
|
|
781
784
|
};
|
|
782
785
|
};
|
|
783
786
|
|
|
784
|
-
function onDelete(
|
|
785
|
-
keyThere,
|
|
786
|
-
keyHere = "id",
|
|
787
|
-
onDelete: onDelete2 = "cascade",
|
|
788
|
-
blocking = true
|
|
789
|
-
}) {
|
|
790
|
-
if (!service || !keyThere) {
|
|
791
|
-
throw "initialize hook 'removeRelated' completely!";
|
|
792
|
-
}
|
|
793
|
-
if (!["cascade", "set null"].includes(onDelete2)) {
|
|
794
|
-
throw "onDelete must be 'cascade' or 'set null'";
|
|
795
|
-
}
|
|
787
|
+
function onDelete(options) {
|
|
796
788
|
return async (context) => {
|
|
797
789
|
if (shouldSkip("onDelete", context)) {
|
|
798
790
|
return context;
|
|
799
791
|
}
|
|
800
792
|
checkContext(context, "after", "remove", "onDelete");
|
|
801
793
|
const { items } = getItemsIsArray(context);
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
$in: ids
|
|
794
|
+
const entries = Array.isArray(options) ? options : [options];
|
|
795
|
+
const promises = [];
|
|
796
|
+
entries.forEach(
|
|
797
|
+
async ({ keyHere, keyThere, onDelete: onDelete2, service, blocking }) => {
|
|
798
|
+
let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
|
|
799
|
+
ids = [...new Set(ids)];
|
|
800
|
+
if (!ids || ids.length <= 0) {
|
|
801
|
+
return context;
|
|
811
802
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
803
|
+
const params = {
|
|
804
|
+
query: {
|
|
805
|
+
[keyThere]: {
|
|
806
|
+
$in: ids
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
paginate: false
|
|
810
|
+
};
|
|
811
|
+
let promise = void 0;
|
|
812
|
+
if (onDelete2 === "cascade") {
|
|
813
|
+
promise = context.app.service(service).remove(null, params);
|
|
814
|
+
} else if (onDelete2 === "set null") {
|
|
815
|
+
const data = { [keyThere]: null };
|
|
816
|
+
promise = context.app.service(service).patch(null, data, params);
|
|
817
|
+
}
|
|
818
|
+
if (blocking) {
|
|
819
|
+
promises.push(promise);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
);
|
|
823
|
+
if (promises.length) {
|
|
824
|
+
await Promise.all(promises);
|
|
824
825
|
}
|
|
825
826
|
return context;
|
|
826
827
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feathers-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
4
|
"description": "Some utils for projects using '@feathersjs/feathers'",
|
|
5
5
|
"author": "fratzinger",
|
|
6
6
|
"repository": {
|
|
@@ -30,43 +30,43 @@
|
|
|
30
30
|
"lib/**",
|
|
31
31
|
"dist/**"
|
|
32
32
|
],
|
|
33
|
-
"scripts": {
|
|
34
|
-
"build": "unbuild",
|
|
35
|
-
"version": "npm run build",
|
|
36
|
-
"release": "np",
|
|
37
|
-
"test": "vitest run",
|
|
38
|
-
"vitest": "vitest",
|
|
39
|
-
"coverage": "vitest run --coverage",
|
|
40
|
-
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
|
41
|
-
},
|
|
42
33
|
"dependencies": {
|
|
43
|
-
"@feathersjs/adapter-commons": "^5.0.
|
|
44
|
-
"@feathersjs/errors": "^5.0.
|
|
45
|
-
"@feathersjs/feathers": "^5.0.
|
|
34
|
+
"@feathersjs/adapter-commons": "^5.0.31",
|
|
35
|
+
"@feathersjs/errors": "^5.0.31",
|
|
36
|
+
"@feathersjs/feathers": "^5.0.31",
|
|
46
37
|
"fast-equals": "^5.0.1",
|
|
47
|
-
"feathers-hooks-common": "^8.
|
|
38
|
+
"feathers-hooks-common": "^8.2.1",
|
|
48
39
|
"lodash": "^4.17.21"
|
|
49
40
|
},
|
|
50
41
|
"devDependencies": {
|
|
51
|
-
"@feathersjs/memory": "^5.0.
|
|
52
|
-
"@types/lodash": "^4.
|
|
53
|
-
"@types/node": "^
|
|
42
|
+
"@feathersjs/memory": "^5.0.31",
|
|
43
|
+
"@types/lodash": "^4.17.13",
|
|
44
|
+
"@types/node": "^22.10.1",
|
|
54
45
|
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
|
55
46
|
"@typescript-eslint/parser": "^6.16.0",
|
|
56
|
-
"@vitest/coverage-v8": "^
|
|
47
|
+
"@vitest/coverage-v8": "^2.1.8",
|
|
57
48
|
"eslint": "^8.56.0",
|
|
58
49
|
"eslint-config-prettier": "^9.1.0",
|
|
59
|
-
"eslint-import-resolver-typescript": "^3.
|
|
50
|
+
"eslint-import-resolver-typescript": "^3.7.0",
|
|
60
51
|
"eslint-plugin-import": "^2.29.1",
|
|
61
52
|
"eslint-plugin-prettier": "^5.1.2",
|
|
62
|
-
"np": "^
|
|
63
|
-
"prettier": "^3.
|
|
53
|
+
"np": "^10.1.0",
|
|
54
|
+
"prettier": "^3.4.1",
|
|
64
55
|
"shx": "^0.3.4",
|
|
65
|
-
"typescript": "^5.
|
|
56
|
+
"typescript": "^5.7.2",
|
|
66
57
|
"unbuild": "^2.0.0",
|
|
67
|
-
"vitest": "^
|
|
58
|
+
"vitest": "^2.1.8"
|
|
68
59
|
},
|
|
69
60
|
"peerDependencies": {
|
|
70
61
|
"@feathersjs/feathers": "^5.0.0"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "unbuild",
|
|
65
|
+
"version": "npm run build",
|
|
66
|
+
"release": "np",
|
|
67
|
+
"test": "vitest run",
|
|
68
|
+
"vitest": "vitest",
|
|
69
|
+
"coverage": "vitest run --coverage",
|
|
70
|
+
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
|
71
71
|
}
|
|
72
|
-
}
|
|
72
|
+
}
|
package/src/.DS_Store
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HookContext } from "@feathersjs/feathers";
|
|
2
2
|
import { checkContext } from "feathers-hooks-common";
|
|
3
|
-
import type { Promisable } from "../typesInternal";
|
|
3
|
+
import type { MaybeArray, Promisable } from "../typesInternal";
|
|
4
4
|
import { getItemsIsArray, shouldSkip } from "../utils";
|
|
5
5
|
|
|
6
6
|
export interface CreateRelatedOptions<S = Record<string, any>> {
|
|
@@ -16,15 +16,7 @@ export interface CreateRelatedOptions<S = Record<string, any>> {
|
|
|
16
16
|
export function createRelated<
|
|
17
17
|
S = Record<string, any>,
|
|
18
18
|
H extends HookContext = HookContext,
|
|
19
|
-
>({
|
|
20
|
-
service,
|
|
21
|
-
multi = true,
|
|
22
|
-
data,
|
|
23
|
-
createItemsInDataArraySeparately = true,
|
|
24
|
-
}: CreateRelatedOptions<S>) {
|
|
25
|
-
if (!service || !data) {
|
|
26
|
-
throw "initialize hook 'createRelated' completely!";
|
|
27
|
-
}
|
|
19
|
+
>(options: MaybeArray<CreateRelatedOptions<S>>) {
|
|
28
20
|
return async (context: H) => {
|
|
29
21
|
if (shouldSkip("createRelated", context)) {
|
|
30
22
|
return context;
|
|
@@ -34,27 +26,36 @@ export function createRelated<
|
|
|
34
26
|
|
|
35
27
|
const { items } = getItemsIsArray(context);
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
29
|
+
const entries = Array.isArray(options) ? options : [options];
|
|
30
|
+
|
|
31
|
+
await Promise.all(
|
|
32
|
+
entries.map(async (entry) => {
|
|
33
|
+
const { data, service, createItemsInDataArraySeparately, multi } =
|
|
34
|
+
entry;
|
|
35
|
+
|
|
36
|
+
let dataToCreate = (
|
|
37
|
+
await Promise.all(items.map(async (item) => data(item, context)))
|
|
38
|
+
).filter((x) => !!x);
|
|
39
|
+
|
|
40
|
+
if (createItemsInDataArraySeparately) {
|
|
41
|
+
dataToCreate = dataToCreate.flat();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!dataToCreate || dataToCreate.length <= 0) {
|
|
45
|
+
return context;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (multi) {
|
|
49
|
+
await context.app.service(service as string).create(dataToCreate);
|
|
50
|
+
} else {
|
|
51
|
+
await Promise.all(
|
|
52
|
+
dataToCreate.map(async (item) =>
|
|
53
|
+
context.app.service(service as string).create(item),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
58
59
|
|
|
59
60
|
return context;
|
|
60
61
|
};
|
package/src/hooks/onDelete.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { HookContext } from "@feathersjs/feathers";
|
|
2
2
|
import { checkContext } from "feathers-hooks-common";
|
|
3
3
|
import { getItemsIsArray, shouldSkip } from "../utils";
|
|
4
|
+
import type { KeyOf, MaybeArray } from "../typesInternal";
|
|
4
5
|
|
|
5
6
|
export type OnDeleteAction = "cascade" | "set null";
|
|
6
7
|
|
|
7
|
-
export interface OnDeleteOptions {
|
|
8
|
+
export interface OnDeleteOptions<Path extends string = string> {
|
|
9
|
+
service: Path;
|
|
8
10
|
keyThere: string;
|
|
9
11
|
keyHere: string;
|
|
10
12
|
onDelete: OnDeleteAction;
|
|
@@ -17,22 +19,7 @@ export interface OnDeleteOptions {
|
|
|
17
19
|
export function onDelete<
|
|
18
20
|
S = Record<string, any>,
|
|
19
21
|
H extends HookContext = HookContext,
|
|
20
|
-
>(
|
|
21
|
-
service: keyof S,
|
|
22
|
-
{
|
|
23
|
-
keyThere,
|
|
24
|
-
keyHere = "id",
|
|
25
|
-
onDelete = "cascade",
|
|
26
|
-
blocking = true,
|
|
27
|
-
}: OnDeleteOptions,
|
|
28
|
-
) {
|
|
29
|
-
if (!service || !keyThere) {
|
|
30
|
-
throw "initialize hook 'removeRelated' completely!";
|
|
31
|
-
}
|
|
32
|
-
if (!["cascade", "set null"].includes(onDelete)) {
|
|
33
|
-
throw "onDelete must be 'cascade' or 'set null'";
|
|
34
|
-
}
|
|
35
|
-
|
|
22
|
+
>(options: MaybeArray<OnDeleteOptions<KeyOf<S>>>) {
|
|
36
23
|
return async (context: H) => {
|
|
37
24
|
if (shouldSkip("onDelete", context)) {
|
|
38
25
|
return context;
|
|
@@ -42,35 +29,47 @@ export function onDelete<
|
|
|
42
29
|
|
|
43
30
|
const { items } = getItemsIsArray(context);
|
|
44
31
|
|
|
45
|
-
|
|
46
|
-
ids = [...new Set(ids)];
|
|
32
|
+
const entries = Array.isArray(options) ? options : [options];
|
|
47
33
|
|
|
48
|
-
|
|
49
|
-
return context;
|
|
50
|
-
}
|
|
34
|
+
const promises: Promise<any>[] = [];
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
[
|
|
55
|
-
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
paginate: false,
|
|
59
|
-
};
|
|
36
|
+
entries.forEach(
|
|
37
|
+
async ({ keyHere, keyThere, onDelete, service, blocking }) => {
|
|
38
|
+
let ids = items.map((x) => x[keyHere]).filter((x) => !!x);
|
|
39
|
+
ids = [...new Set(ids)];
|
|
60
40
|
|
|
61
|
-
|
|
41
|
+
if (!ids || ids.length <= 0) {
|
|
42
|
+
return context;
|
|
43
|
+
}
|
|
62
44
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
45
|
+
const params = {
|
|
46
|
+
query: {
|
|
47
|
+
[keyThere]: {
|
|
48
|
+
$in: ids,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
paginate: false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
let promise: Promise<any> | undefined = undefined;
|
|
55
|
+
|
|
56
|
+
if (onDelete === "cascade") {
|
|
57
|
+
promise = context.app.service(service as string).remove(null, params);
|
|
58
|
+
} else if (onDelete === "set null") {
|
|
59
|
+
const data = { [keyThere]: null };
|
|
60
|
+
promise = context.app
|
|
61
|
+
.service(service as string)
|
|
62
|
+
.patch(null, data, params);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (blocking) {
|
|
66
|
+
promises.push(promise);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
);
|
|
71
70
|
|
|
72
|
-
if (
|
|
73
|
-
await
|
|
71
|
+
if (promises.length) {
|
|
72
|
+
await Promise.all(promises);
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
return context;
|
package/src/typesInternal.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Id, Params } from "@feathersjs/feathers";
|
|
2
2
|
import { deepEqual } from "fast-equals";
|
|
3
|
+
import type { KeyOf } from "../typesInternal";
|
|
3
4
|
|
|
4
|
-
export type OptimizeBatchPatchOptions = {
|
|
5
|
-
id
|
|
5
|
+
export type OptimizeBatchPatchOptions<IdKey extends string> = {
|
|
6
|
+
/** the key of the id property */
|
|
7
|
+
id?: IdKey;
|
|
6
8
|
};
|
|
7
9
|
|
|
8
10
|
export type OptimizeBatchPatchResultItem<
|
|
@@ -11,18 +13,26 @@ export type OptimizeBatchPatchResultItem<
|
|
|
11
13
|
> = [Id, T, P | undefined];
|
|
12
14
|
|
|
13
15
|
export function optimizeBatchPatch<
|
|
14
|
-
T extends Record<string,
|
|
16
|
+
T extends Record<string, any>,
|
|
17
|
+
IdKey extends KeyOf<T>,
|
|
15
18
|
P extends Params,
|
|
19
|
+
R extends Omit<T, IdKey> = Omit<T, IdKey>,
|
|
16
20
|
>(
|
|
17
|
-
items:
|
|
18
|
-
options?: OptimizeBatchPatchOptions
|
|
19
|
-
): OptimizeBatchPatchResultItem<
|
|
20
|
-
const map: { ids: Id[]; data:
|
|
21
|
+
items: T[],
|
|
22
|
+
options?: OptimizeBatchPatchOptions<IdKey>,
|
|
23
|
+
): OptimizeBatchPatchResultItem<R, P>[] {
|
|
24
|
+
const map: { ids: Id[]; data: R }[] = [];
|
|
21
25
|
|
|
22
|
-
const
|
|
26
|
+
const idKey = options?.id ?? "id";
|
|
23
27
|
|
|
24
|
-
for (const
|
|
25
|
-
const
|
|
28
|
+
for (const _data of items) {
|
|
29
|
+
const data = _data as unknown as R;
|
|
30
|
+
const id = _data[idKey];
|
|
31
|
+
delete data[idKey as any];
|
|
32
|
+
|
|
33
|
+
const index = map.findIndex((item) => {
|
|
34
|
+
return deepEqual(item.data, data);
|
|
35
|
+
});
|
|
26
36
|
|
|
27
37
|
if (index === -1) {
|
|
28
38
|
map.push({ ids: [id], data });
|
|
@@ -33,34 +43,56 @@ export function optimizeBatchPatch<
|
|
|
33
43
|
|
|
34
44
|
return map.map(({ ids, data }) => {
|
|
35
45
|
return ids.length === 1
|
|
36
|
-
? ([ids[0], data, undefined] as OptimizeBatchPatchResultItem<
|
|
46
|
+
? ([ids[0], data, undefined] as OptimizeBatchPatchResultItem<R, P>)
|
|
37
47
|
: ([
|
|
38
48
|
null,
|
|
39
49
|
data,
|
|
40
50
|
{
|
|
41
51
|
query: {
|
|
42
|
-
[
|
|
52
|
+
[idKey]: { $in: ids },
|
|
43
53
|
},
|
|
44
54
|
},
|
|
45
|
-
] as OptimizeBatchPatchResultItem<
|
|
55
|
+
] as OptimizeBatchPatchResultItem<R, P>);
|
|
46
56
|
});
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
if (import.meta.vitest) {
|
|
50
60
|
const { it, expect } = import.meta.vitest;
|
|
51
61
|
it("optimizeBatchPatch", () => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
expect(
|
|
63
|
+
optimizeBatchPatch(
|
|
64
|
+
[
|
|
65
|
+
{ id: "1", name: "John" },
|
|
66
|
+
{ id: "2", name: "Jane" },
|
|
67
|
+
{ id: "3", name: "John" },
|
|
68
|
+
{ id: "4", name: "Jane" },
|
|
69
|
+
{ id: 5, name: "Jack" },
|
|
70
|
+
],
|
|
71
|
+
{ id: "id" },
|
|
72
|
+
),
|
|
73
|
+
).toEqual([
|
|
61
74
|
[null, { name: "John" }, { query: { id: { $in: ["1", "3"] } } }],
|
|
62
75
|
[null, { name: "Jane" }, { query: { id: { $in: ["2", "4"] } } }],
|
|
63
76
|
[5, { name: "Jack" }, undefined],
|
|
64
77
|
]);
|
|
65
78
|
});
|
|
79
|
+
|
|
80
|
+
it("optimizeBatchPatch with _id", () => {
|
|
81
|
+
expect(
|
|
82
|
+
optimizeBatchPatch(
|
|
83
|
+
[
|
|
84
|
+
{ _id: "1", name: "John" },
|
|
85
|
+
{ _id: "2", name: "Jane" },
|
|
86
|
+
{ _id: "3", name: "John" },
|
|
87
|
+
{ _id: "4", name: "Jane" },
|
|
88
|
+
{ _id: 5, name: "Jack" },
|
|
89
|
+
],
|
|
90
|
+
{ id: "_id" },
|
|
91
|
+
),
|
|
92
|
+
).toEqual([
|
|
93
|
+
[null, { name: "John" }, { query: { _id: { $in: ["1", "3"] } } }],
|
|
94
|
+
[null, { name: "Jane" }, { query: { _id: { $in: ["2", "4"] } } }],
|
|
95
|
+
[5, { name: "Jack" }, undefined],
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
66
98
|
}
|