Skip to content

Commit

Permalink
fix(graphql,hooks): Allow getting hooks from parent classes
Browse files Browse the repository at this point in the history
  • Loading branch information
doug-martin committed Sep 9, 2020
1 parent ef9ff11 commit 59a0aeb
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 11 deletions.
13 changes: 10 additions & 3 deletions packages/core/src/common/reflect.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ export const classMetadataDecorator = <Data>(key: string): ClassDecoratorDataFun
};
};

export function getClassMetadata<DTO, Data>(DTOClass: Class<DTO>, key: string): MetaValue<Data> {
export function getClassMetadata<DTO, Data>(
DTOClass: Class<DTO>,
key: string,
includeParents: boolean,
): MetaValue<Data> {
if (includeParents) {
return Reflect.getMetadata(key, DTOClass) as MetaValue<Data>;
}
return Reflect.getOwnMetadata(key, DTOClass) as MetaValue<Data>;
}

Expand Down Expand Up @@ -54,7 +61,7 @@ export class ValueReflector extends Reflector {

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

export class MapReflector<K = string> extends Reflector {
set<DTO, Data>(DTOClass: Class<DTO>, key: K, value: Data): void {
const metadata = getClassMetadata<DTO, Map<K, Data>>(DTOClass, this.metaKey) ?? new Map<K, Data>();
const metadata = getClassMetadata<DTO, Map<K, Data>>(DTOClass, this.metaKey, false) ?? new Map<K, Data>();
metadata.set(key, value);
this.defineMetadata(metadata, DTOClass);
}
Expand Down
279 changes: 279 additions & 0 deletions packages/query-graphql/__tests__/decorators/hook.decorator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
// eslint-disable-next-line max-classes-per-file
import {
BeforeCreateMany,
BeforeCreateOne,
BeforeUpdateOne,
BeforeUpdateMany,
BeforeDeleteOne,
BeforeDeleteMany,
BeforeQueryMany,
BeforeFindOne,
} from '../../src';
import {
getCreateManyHook,
getCreateOneHook,
getUpdateOneHook,
getUpdateManyHook,
getDeleteOneHook,
getDeleteManyHook,
getQueryManyHook,
getFindOneHook,
} from '../../src/decorators';

describe('hook decorators', () => {
describe('@BeforeCreateOne', () => {
it('should store the hook', () => {
const hookFn = jest.fn();
@BeforeCreateOne(hookFn)
class Test {}

expect(getCreateOneHook(Test)).toBe(hookFn);
});

it('should return a hook from the base class', () => {
const hookFn = jest.fn();
@BeforeCreateOne(hookFn)
class Base {}

class Test extends Base {}

expect(getCreateOneHook(Test)).toBe(hookFn);
});

it('should return a hook from the child class if there is a hook on both the base and child', () => {
const baseHookFn = jest.fn();
@BeforeCreateOne(baseHookFn)
class Base {}

const childHookFn = jest.fn();
@BeforeCreateOne(childHookFn)
class Test extends Base {}

expect(getCreateOneHook(Test)).toBe(childHookFn);
});
});

describe('@BeforeCreateMany', () => {
it('should store the hook', () => {
const hookFn = jest.fn();
@BeforeCreateMany(hookFn)
class Test {}

expect(getCreateManyHook(Test)).toBe(hookFn);
});

it('should return a hook from the base class', () => {
const hookFn = jest.fn();
@BeforeCreateMany(hookFn)
class Base {}

class Test extends Base {}

expect(getCreateManyHook(Test)).toBe(hookFn);
});

it('should return a hook from the child class if there is a hook on both the base and child', () => {
const baseHookFn = jest.fn();
@BeforeCreateMany(baseHookFn)
class Base {}

const childHookFn = jest.fn();
@BeforeCreateMany(childHookFn)
class Test extends Base {}

expect(getCreateManyHook(Test)).toBe(childHookFn);
});
});

describe('@BeforeUpdateOne', () => {
it('should store the hook', () => {
const hookFn = jest.fn();
@BeforeUpdateOne(hookFn)
class Test {}

expect(getUpdateOneHook(Test)).toBe(hookFn);
});

it('should return a hook from the base class', () => {
const hookFn = jest.fn();
@BeforeUpdateOne(hookFn)
class Base {}

class Test extends Base {}

expect(getUpdateOneHook(Test)).toBe(hookFn);
});

it('should return a hook from the child class if there is a hook on both the base and child', () => {
const baseHookFn = jest.fn();
@BeforeUpdateOne(baseHookFn)
class Base {}

const childHookFn = jest.fn();
@BeforeUpdateOne(childHookFn)
class Test extends Base {}

expect(getUpdateOneHook(Test)).toBe(childHookFn);
});
});

describe('@BeforeUpdateMany', () => {
it('should store the hook', () => {
const hookFn = jest.fn();
@BeforeUpdateMany(hookFn)
class Test {}

expect(getUpdateManyHook(Test)).toBe(hookFn);
});

it('should return a hook from the base class', () => {
const hookFn = jest.fn();
@BeforeUpdateMany(hookFn)
class Base {}

class Test extends Base {}

expect(getUpdateManyHook(Test)).toBe(hookFn);
});

it('should return a hook from the child class if there is a hook on both the base and child', () => {
const baseHookFn = jest.fn();
@BeforeUpdateMany(baseHookFn)
class Base {}

const childHookFn = jest.fn();
@BeforeUpdateMany(childHookFn)
class Test extends Base {}

expect(getUpdateManyHook(Test)).toBe(childHookFn);
});
});

describe('@BeforeDeleteOne', () => {
it('should store the hook', () => {
const hookFn = jest.fn();
@BeforeDeleteOne(hookFn)
class Test {}

expect(getDeleteOneHook(Test)).toBe(hookFn);
});

it('should return a hook from the base class', () => {
const hookFn = jest.fn();
@BeforeDeleteOne(hookFn)
class Base {}

class Test extends Base {}

expect(getDeleteOneHook(Test)).toBe(hookFn);
});

it('should return a hook from the child class if there is a hook on both the base and child', () => {
const baseHookFn = jest.fn();
@BeforeDeleteOne(baseHookFn)
class Base {}

const childHookFn = jest.fn();
@BeforeDeleteOne(childHookFn)
class Test extends Base {}

expect(getDeleteOneHook(Test)).toBe(childHookFn);
});
});

describe('@BeforeDeleteMany', () => {
it('should store the hook', () => {
const hookFn = jest.fn();
@BeforeDeleteMany(hookFn)
class Test {}

expect(getDeleteManyHook(Test)).toBe(hookFn);
});

it('should return a hook from the base class', () => {
const hookFn = jest.fn();
@BeforeDeleteMany(hookFn)
class Base {}

class Test extends Base {}

expect(getDeleteManyHook(Test)).toBe(hookFn);
});

it('should return a hook from the child class if there is a hook on both the base and child', () => {
const baseHookFn = jest.fn();
@BeforeDeleteMany(baseHookFn)
class Base {}

const childHookFn = jest.fn();
@BeforeDeleteMany(childHookFn)
class Test extends Base {}

expect(getDeleteManyHook(Test)).toBe(childHookFn);
});
});

describe('@BeforeQueryMany', () => {
it('should store the hook', () => {
const hookFn = jest.fn();
@BeforeQueryMany(hookFn)
class Test {}

expect(getQueryManyHook(Test)).toBe(hookFn);
});

it('should return a hook from the base class', () => {
const hookFn = jest.fn();
@BeforeQueryMany(hookFn)
class Base {}

class Test extends Base {}

expect(getQueryManyHook(Test)).toBe(hookFn);
});

it('should return a hook from the child class if there is a hook on both the base and child', () => {
const baseHookFn = jest.fn();
@BeforeQueryMany(baseHookFn)
class Base {}

const childHookFn = jest.fn();
@BeforeQueryMany(childHookFn)
class Test extends Base {}

expect(getQueryManyHook(Test)).toBe(childHookFn);
});
});

describe('@BeforeFindOne', () => {
it('should store the hook', () => {
const hookFn = jest.fn();
@BeforeFindOne(hookFn)
class Test {}

expect(getFindOneHook(Test)).toBe(hookFn);
});

it('should return a hook from the base class', () => {
const hookFn = jest.fn();
@BeforeFindOne(hookFn)
class Base {}

class Test extends Base {}

expect(getFindOneHook(Test)).toBe(hookFn);
});

it('should return a hook from the child class if there is a hook on both the base and child', () => {
const baseHookFn = jest.fn();
@BeforeFindOne(baseHookFn)
class Base {}

const childHookFn = jest.fn();
@BeforeFindOne(childHookFn)
class Test extends Base {}

expect(getFindOneHook(Test)).toBe(childHookFn);
});
});
});
16 changes: 8 additions & 8 deletions packages/query-graphql/src/decorators/hook.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@ export type CreateOneHook<DTO> = HookFunc<CreateOneInputType<DTO>>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const BeforeCreateOne = classMetadataDecorator<CreateOneHook<any>>(BEFORE_CREATE_ONE_KEY);
export function getCreateOneHook<DTO>(DTOClass: Class<DTO>): MetaValue<CreateOneHook<DTO>> {
return getClassMetadata(DTOClass, BEFORE_CREATE_ONE_KEY);
return getClassMetadata(DTOClass, BEFORE_CREATE_ONE_KEY, true);
}

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

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

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

export type DeleteOneHook = HookFunc<DeleteOneInputType>;
export const BeforeDeleteOne = classMetadataDecorator<DeleteOneHook>(BEFORE_DELETE_ONE_KEY);
export function getDeleteOneHook<DTO>(DTOClass: Class<DTO>): MetaValue<DeleteOneHook> {
return getClassMetadata(DTOClass, BEFORE_DELETE_ONE_KEY);
return getClassMetadata(DTOClass, BEFORE_DELETE_ONE_KEY, true);
}

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

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

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

0 comments on commit 59a0aeb

Please sign in to comment.