Skip to content

Commit 59a0aeb

Browse files
committed
fix(graphql,hooks): Allow getting hooks from parent classes
1 parent ef9ff11 commit 59a0aeb

File tree

3 files changed

+297
-11
lines changed

3 files changed

+297
-11
lines changed

packages/core/src/common/reflect.utils.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ export const classMetadataDecorator = <Data>(key: string): ClassDecoratorDataFun
1010
};
1111
};
1212

13-
export function getClassMetadata<DTO, Data>(DTOClass: Class<DTO>, key: string): MetaValue<Data> {
13+
export function getClassMetadata<DTO, Data>(
14+
DTOClass: Class<DTO>,
15+
key: string,
16+
includeParents: boolean,
17+
): MetaValue<Data> {
18+
if (includeParents) {
19+
return Reflect.getMetadata(key, DTOClass) as MetaValue<Data>;
20+
}
1421
return Reflect.getOwnMetadata(key, DTOClass) as MetaValue<Data>;
1522
}
1623

@@ -54,7 +61,7 @@ export class ValueReflector extends Reflector {
5461

5562
export class ArrayReflector extends Reflector {
5663
append<DTO, Data>(DTOClass: Class<DTO>, data: Data): void {
57-
const metadata = getClassMetadata<DTO, Data[]>(DTOClass, this.metaKey) ?? [];
64+
const metadata = getClassMetadata<DTO, Data[]>(DTOClass, this.metaKey, false) ?? [];
5865
metadata.push(data);
5966
this.defineMetadata(metadata, DTOClass);
6067
}
@@ -66,7 +73,7 @@ export class ArrayReflector extends Reflector {
6673

6774
export class MapReflector<K = string> extends Reflector {
6875
set<DTO, Data>(DTOClass: Class<DTO>, key: K, value: Data): void {
69-
const metadata = getClassMetadata<DTO, Map<K, Data>>(DTOClass, this.metaKey) ?? new Map<K, Data>();
76+
const metadata = getClassMetadata<DTO, Map<K, Data>>(DTOClass, this.metaKey, false) ?? new Map<K, Data>();
7077
metadata.set(key, value);
7178
this.defineMetadata(metadata, DTOClass);
7279
}
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
// eslint-disable-next-line max-classes-per-file
2+
import {
3+
BeforeCreateMany,
4+
BeforeCreateOne,
5+
BeforeUpdateOne,
6+
BeforeUpdateMany,
7+
BeforeDeleteOne,
8+
BeforeDeleteMany,
9+
BeforeQueryMany,
10+
BeforeFindOne,
11+
} from '../../src';
12+
import {
13+
getCreateManyHook,
14+
getCreateOneHook,
15+
getUpdateOneHook,
16+
getUpdateManyHook,
17+
getDeleteOneHook,
18+
getDeleteManyHook,
19+
getQueryManyHook,
20+
getFindOneHook,
21+
} from '../../src/decorators';
22+
23+
describe('hook decorators', () => {
24+
describe('@BeforeCreateOne', () => {
25+
it('should store the hook', () => {
26+
const hookFn = jest.fn();
27+
@BeforeCreateOne(hookFn)
28+
class Test {}
29+
30+
expect(getCreateOneHook(Test)).toBe(hookFn);
31+
});
32+
33+
it('should return a hook from the base class', () => {
34+
const hookFn = jest.fn();
35+
@BeforeCreateOne(hookFn)
36+
class Base {}
37+
38+
class Test extends Base {}
39+
40+
expect(getCreateOneHook(Test)).toBe(hookFn);
41+
});
42+
43+
it('should return a hook from the child class if there is a hook on both the base and child', () => {
44+
const baseHookFn = jest.fn();
45+
@BeforeCreateOne(baseHookFn)
46+
class Base {}
47+
48+
const childHookFn = jest.fn();
49+
@BeforeCreateOne(childHookFn)
50+
class Test extends Base {}
51+
52+
expect(getCreateOneHook(Test)).toBe(childHookFn);
53+
});
54+
});
55+
56+
describe('@BeforeCreateMany', () => {
57+
it('should store the hook', () => {
58+
const hookFn = jest.fn();
59+
@BeforeCreateMany(hookFn)
60+
class Test {}
61+
62+
expect(getCreateManyHook(Test)).toBe(hookFn);
63+
});
64+
65+
it('should return a hook from the base class', () => {
66+
const hookFn = jest.fn();
67+
@BeforeCreateMany(hookFn)
68+
class Base {}
69+
70+
class Test extends Base {}
71+
72+
expect(getCreateManyHook(Test)).toBe(hookFn);
73+
});
74+
75+
it('should return a hook from the child class if there is a hook on both the base and child', () => {
76+
const baseHookFn = jest.fn();
77+
@BeforeCreateMany(baseHookFn)
78+
class Base {}
79+
80+
const childHookFn = jest.fn();
81+
@BeforeCreateMany(childHookFn)
82+
class Test extends Base {}
83+
84+
expect(getCreateManyHook(Test)).toBe(childHookFn);
85+
});
86+
});
87+
88+
describe('@BeforeUpdateOne', () => {
89+
it('should store the hook', () => {
90+
const hookFn = jest.fn();
91+
@BeforeUpdateOne(hookFn)
92+
class Test {}
93+
94+
expect(getUpdateOneHook(Test)).toBe(hookFn);
95+
});
96+
97+
it('should return a hook from the base class', () => {
98+
const hookFn = jest.fn();
99+
@BeforeUpdateOne(hookFn)
100+
class Base {}
101+
102+
class Test extends Base {}
103+
104+
expect(getUpdateOneHook(Test)).toBe(hookFn);
105+
});
106+
107+
it('should return a hook from the child class if there is a hook on both the base and child', () => {
108+
const baseHookFn = jest.fn();
109+
@BeforeUpdateOne(baseHookFn)
110+
class Base {}
111+
112+
const childHookFn = jest.fn();
113+
@BeforeUpdateOne(childHookFn)
114+
class Test extends Base {}
115+
116+
expect(getUpdateOneHook(Test)).toBe(childHookFn);
117+
});
118+
});
119+
120+
describe('@BeforeUpdateMany', () => {
121+
it('should store the hook', () => {
122+
const hookFn = jest.fn();
123+
@BeforeUpdateMany(hookFn)
124+
class Test {}
125+
126+
expect(getUpdateManyHook(Test)).toBe(hookFn);
127+
});
128+
129+
it('should return a hook from the base class', () => {
130+
const hookFn = jest.fn();
131+
@BeforeUpdateMany(hookFn)
132+
class Base {}
133+
134+
class Test extends Base {}
135+
136+
expect(getUpdateManyHook(Test)).toBe(hookFn);
137+
});
138+
139+
it('should return a hook from the child class if there is a hook on both the base and child', () => {
140+
const baseHookFn = jest.fn();
141+
@BeforeUpdateMany(baseHookFn)
142+
class Base {}
143+
144+
const childHookFn = jest.fn();
145+
@BeforeUpdateMany(childHookFn)
146+
class Test extends Base {}
147+
148+
expect(getUpdateManyHook(Test)).toBe(childHookFn);
149+
});
150+
});
151+
152+
describe('@BeforeDeleteOne', () => {
153+
it('should store the hook', () => {
154+
const hookFn = jest.fn();
155+
@BeforeDeleteOne(hookFn)
156+
class Test {}
157+
158+
expect(getDeleteOneHook(Test)).toBe(hookFn);
159+
});
160+
161+
it('should return a hook from the base class', () => {
162+
const hookFn = jest.fn();
163+
@BeforeDeleteOne(hookFn)
164+
class Base {}
165+
166+
class Test extends Base {}
167+
168+
expect(getDeleteOneHook(Test)).toBe(hookFn);
169+
});
170+
171+
it('should return a hook from the child class if there is a hook on both the base and child', () => {
172+
const baseHookFn = jest.fn();
173+
@BeforeDeleteOne(baseHookFn)
174+
class Base {}
175+
176+
const childHookFn = jest.fn();
177+
@BeforeDeleteOne(childHookFn)
178+
class Test extends Base {}
179+
180+
expect(getDeleteOneHook(Test)).toBe(childHookFn);
181+
});
182+
});
183+
184+
describe('@BeforeDeleteMany', () => {
185+
it('should store the hook', () => {
186+
const hookFn = jest.fn();
187+
@BeforeDeleteMany(hookFn)
188+
class Test {}
189+
190+
expect(getDeleteManyHook(Test)).toBe(hookFn);
191+
});
192+
193+
it('should return a hook from the base class', () => {
194+
const hookFn = jest.fn();
195+
@BeforeDeleteMany(hookFn)
196+
class Base {}
197+
198+
class Test extends Base {}
199+
200+
expect(getDeleteManyHook(Test)).toBe(hookFn);
201+
});
202+
203+
it('should return a hook from the child class if there is a hook on both the base and child', () => {
204+
const baseHookFn = jest.fn();
205+
@BeforeDeleteMany(baseHookFn)
206+
class Base {}
207+
208+
const childHookFn = jest.fn();
209+
@BeforeDeleteMany(childHookFn)
210+
class Test extends Base {}
211+
212+
expect(getDeleteManyHook(Test)).toBe(childHookFn);
213+
});
214+
});
215+
216+
describe('@BeforeQueryMany', () => {
217+
it('should store the hook', () => {
218+
const hookFn = jest.fn();
219+
@BeforeQueryMany(hookFn)
220+
class Test {}
221+
222+
expect(getQueryManyHook(Test)).toBe(hookFn);
223+
});
224+
225+
it('should return a hook from the base class', () => {
226+
const hookFn = jest.fn();
227+
@BeforeQueryMany(hookFn)
228+
class Base {}
229+
230+
class Test extends Base {}
231+
232+
expect(getQueryManyHook(Test)).toBe(hookFn);
233+
});
234+
235+
it('should return a hook from the child class if there is a hook on both the base and child', () => {
236+
const baseHookFn = jest.fn();
237+
@BeforeQueryMany(baseHookFn)
238+
class Base {}
239+
240+
const childHookFn = jest.fn();
241+
@BeforeQueryMany(childHookFn)
242+
class Test extends Base {}
243+
244+
expect(getQueryManyHook(Test)).toBe(childHookFn);
245+
});
246+
});
247+
248+
describe('@BeforeFindOne', () => {
249+
it('should store the hook', () => {
250+
const hookFn = jest.fn();
251+
@BeforeFindOne(hookFn)
252+
class Test {}
253+
254+
expect(getFindOneHook(Test)).toBe(hookFn);
255+
});
256+
257+
it('should return a hook from the base class', () => {
258+
const hookFn = jest.fn();
259+
@BeforeFindOne(hookFn)
260+
class Base {}
261+
262+
class Test extends Base {}
263+
264+
expect(getFindOneHook(Test)).toBe(hookFn);
265+
});
266+
267+
it('should return a hook from the child class if there is a hook on both the base and child', () => {
268+
const baseHookFn = jest.fn();
269+
@BeforeFindOne(baseHookFn)
270+
class Base {}
271+
272+
const childHookFn = jest.fn();
273+
@BeforeFindOne(childHookFn)
274+
class Test extends Base {}
275+
276+
expect(getFindOneHook(Test)).toBe(childHookFn);
277+
});
278+
});
279+
});

packages/query-graphql/src/decorators/hook.decorator.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,21 @@ export type CreateOneHook<DTO> = HookFunc<CreateOneInputType<DTO>>;
2626
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2727
export const BeforeCreateOne = classMetadataDecorator<CreateOneHook<any>>(BEFORE_CREATE_ONE_KEY);
2828
export function getCreateOneHook<DTO>(DTOClass: Class<DTO>): MetaValue<CreateOneHook<DTO>> {
29-
return getClassMetadata(DTOClass, BEFORE_CREATE_ONE_KEY);
29+
return getClassMetadata(DTOClass, BEFORE_CREATE_ONE_KEY, true);
3030
}
3131

3232
export type CreateManyHook<DTO> = HookFunc<CreateManyInputType<DTO>>;
3333
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3434
export const BeforeCreateMany = classMetadataDecorator<CreateManyHook<any>>(BEFORE_CREATE_MANY_KEY);
3535
export function getCreateManyHook<DTO>(DTOClass: Class<DTO>): MetaValue<CreateManyHook<DTO>> {
36-
return getClassMetadata(DTOClass, BEFORE_CREATE_MANY_KEY);
36+
return getClassMetadata(DTOClass, BEFORE_CREATE_MANY_KEY, true);
3737
}
3838

3939
export type UpdateOneHook<DTO> = HookFunc<UpdateOneInputType<DTO>>;
4040
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4141
export const BeforeUpdateOne = classMetadataDecorator<UpdateOneHook<any>>(BEFORE_UPDATE_ONE_KEY);
4242
export function getUpdateOneHook<DTO, U extends DeepPartial<DTO>>(DTOClass: Class<DTO>): MetaValue<UpdateOneHook<U>> {
43-
return getClassMetadata(DTOClass, BEFORE_UPDATE_ONE_KEY);
43+
return getClassMetadata(DTOClass, BEFORE_UPDATE_ONE_KEY, true);
4444
}
4545

4646
export type UpdateManyHook<DTO, U extends DeepPartial<DTO>> = HookFunc<UpdateManyInputType<DTO, U>>;
@@ -49,32 +49,32 @@ export const BeforeUpdateMany = classMetadataDecorator<UpdateManyHook<any, any>>
4949
export function getUpdateManyHook<DTO, U extends DeepPartial<DTO>>(
5050
DTOClass: Class<DTO>,
5151
): MetaValue<UpdateManyHook<DTO, U>> {
52-
return getClassMetadata(DTOClass, BEFORE_UPDATE_MANY_KEY);
52+
return getClassMetadata(DTOClass, BEFORE_UPDATE_MANY_KEY, true);
5353
}
5454

5555
export type DeleteOneHook = HookFunc<DeleteOneInputType>;
5656
export const BeforeDeleteOne = classMetadataDecorator<DeleteOneHook>(BEFORE_DELETE_ONE_KEY);
5757
export function getDeleteOneHook<DTO>(DTOClass: Class<DTO>): MetaValue<DeleteOneHook> {
58-
return getClassMetadata(DTOClass, BEFORE_DELETE_ONE_KEY);
58+
return getClassMetadata(DTOClass, BEFORE_DELETE_ONE_KEY, true);
5959
}
6060

6161
export type DeleteManyHook<DTO> = HookFunc<DeleteManyInputType<DTO>>;
6262
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6363
export const BeforeDeleteMany = classMetadataDecorator<DeleteManyHook<any>>(BEFORE_DELETE_MANY_KEY);
6464
export function getDeleteManyHook<DTO>(DTOClass: Class<DTO>): MetaValue<DeleteManyHook<DTO>> {
65-
return getClassMetadata(DTOClass, BEFORE_DELETE_MANY_KEY);
65+
return getClassMetadata(DTOClass, BEFORE_DELETE_MANY_KEY, true);
6666
}
6767

6868
export type BeforeQueryManyHook<DTO> = HookFunc<Query<DTO>>;
6969
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7070
export const BeforeQueryMany = classMetadataDecorator<BeforeQueryManyHook<any>>(BEFORE_QUERY_MANY_KEY);
7171
export function getQueryManyHook<DTO>(DTOClass: Class<DTO>): MetaValue<BeforeQueryManyHook<DTO>> {
72-
return getClassMetadata(DTOClass, BEFORE_QUERY_MANY_KEY);
72+
return getClassMetadata(DTOClass, BEFORE_QUERY_MANY_KEY, true);
7373
}
7474

7575
export type BeforeFindOneHook = HookFunc<FindOneArgsType>;
7676
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7777
export const BeforeFindOne = classMetadataDecorator<BeforeFindOneHook>(BEFORE_FIND_ONE_KEY);
7878
export function getFindOneHook<DTO>(DTOClass: Class<DTO>): MetaValue<BeforeFindOneHook> {
79-
return getClassMetadata(DTOClass, BEFORE_FIND_ONE_KEY);
79+
return getClassMetadata(DTOClass, BEFORE_FIND_ONE_KEY, true);
8080
}

0 commit comments

Comments
 (0)