Skip to content

Commit

Permalink
Merge pull request #56 from indigotech/feature/after-decorator
Browse files Browse the repository at this point in the history
Add @after decorator
  • Loading branch information
marcelorisse authored Nov 23, 2017
2 parents 75f4bb1 + d65938f commit 914e18c
Show file tree
Hide file tree
Showing 26 changed files with 262 additions and 33 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
- Fixed `@Root` decorator now being correctly exported - #51 - [@babbarankit](https://github.com/babbarankit)
- Fixed README examples - #51 - [@babbarankit](https://github.com/babbarankit)
- Improved tests by adding @Root ones - #52 - [@babbarankit](https://github.com/babbarankit)
- Added @InterfaceType decorator - #46 - [@felipesabino](https://github.com/felipesabino)
- Added @After middleware decorator - #56 - [@marcelorisse](https://github.com/marcelorisse)

### Breaking changes

Expand Down
49 changes: 49 additions & 0 deletions src/decorator/after.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AfterMiddleware } from '../middleware';
import { AfterOption } from '../metadata';
import { getMetadataArgsStorage } from '../metadata-builder';

/**
* Adding ability to declarative override function resolution on schemas with an implementation analog to an express middleware.
*
* Usage example:
* ```
* let middleware: AfterMiddleware = (
* context: any,
* args: { [key: string]: any },
* result: Promise<any> | any,
* next: (error?: Error, value?: any) => any
* ): any => {
* if(context.user.role != 'any role') {
* // can use context and resolve/return value as `1000`, for example, regardless of what resolve function actually implements
* next(null, 1000);
* } else {
* // keeps regular resolution flow
* next();
* }
* };
*
* ...
*
* class ObjType {
* @Field()
* @After(middleware)
* myFunction(input: number): number {
* return input * 2;
* }
* }
*
* ```
* @param option Options for an Schema
*/
export function After(option: AfterOption | AfterMiddleware) {
return function (target: any, propertyKey: any, index: number) {
getMetadataArgsStorage().afters.push({
target: target,
name: target.name || propertyKey,
description: option && (option as AfterOption).description ? (option as AfterOption).description : null,
index: index,
property: propertyKey,
middleware: option && (option as AfterOption).middleware ? (option as AfterOption).middleware : option as AfterMiddleware,
});
} as Function;
}
8 changes: 4 additions & 4 deletions src/decorator/before.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { BeforeMiddleware } from '../middleware';
import { BeforeOption } from '../metadata';
import { Middleware } from '../middleware';
import { getMetadataArgsStorage } from '../metadata-builder';

/**
* Adding ability to declarative override function resolution on schemas with an implementation analog to an express middleware.
*
* Usage example:
* ```
* let middleware: Middleware = (context: any, args: { [key: string]: any }, next: (error?: Error, value?: any) => any): any => {
* let middleware: BeforeMiddleware = (context: any, args: { [key: string]: any }, next: (error?: Error, value?: any) => any): any => {
* if(context.user.role != 'any role') {
* // can use context and resolve/return value as `1000`, for example, regardless of what resolve function actually implements
* next(null, 1000);
Expand All @@ -30,15 +30,15 @@ import { getMetadataArgsStorage } from '../metadata-builder';
* ```
* @param option Options for an Schema
*/
export function Before(option: BeforeOption | Middleware) {
export function Before(option: BeforeOption | BeforeMiddleware) {
return function (target: any, propertyKey: any, index: number) {
getMetadataArgsStorage().befores.push({
target: target,
name: target.name || propertyKey,
description: option && (option as BeforeOption).description ? (option as BeforeOption).description : null,
index: index,
property: propertyKey,
middleware: option && (option as BeforeOption).middleware ? (option as BeforeOption).middleware : option as Middleware,
middleware: option && (option as BeforeOption).middleware ? (option as BeforeOption).middleware : option as BeforeMiddleware,
});
} as Function;
}
2 changes: 1 addition & 1 deletion src/decorator/field.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SchemaFactoryError, SchemaFactoryErrorType } from '../type-factory';

import { FieldOption } from '../metadata';
import { PaginationMiddleware } from '../pagination.middleware';
import { PaginationMiddleware } from '../middleware';
import { getMetadataArgsStorage } from '../metadata-builder';

/**
Expand Down
1 change: 1 addition & 0 deletions src/decorator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './order-by.decorator';
export * from './root.decorator';
export * from './before.decorator';
export * from './interface-type.decorator';
export * from './after.decorator';
12 changes: 9 additions & 3 deletions src/metadata-builder/metadata-args.storage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AfterArg,
ArgumentArg,
BeforeArg,
ContextArg,
Expand All @@ -7,12 +8,12 @@ import {
EnumTypeArg,
EnumValueArg,
FieldArg,
InterfaceTypeArg,
ObjectTypeArg,
OrderByArg,
RootArg,
SchemaArg,
UnionTypeArg,
InterfaceTypeArg,
} from '../metadata/args';

import { MetadataUtils } from './metadata.utils';
Expand All @@ -31,6 +32,7 @@ export class MetadataArgsStorage {
orderBys: OrderByArg[] = [];
befores: BeforeArg[] = [];
interfaces: InterfaceTypeArg[] = [];
afters: AfterArg[] = [];

filterEnumsByClass(target: any): EnumTypeArg[] {
return this.enums.filter(item => item.target === target);
Expand Down Expand Up @@ -85,9 +87,13 @@ export class MetadataArgsStorage {
return this.befores.filter(item => item.target === target && item.property === property);
}

filterAfterByByClassAndProperty(target: any, property: string): AfterArg[] {
return this.afters.filter(item => item.target === target && item.property === property);
}

/**
* Filters given array by a given target or targets and prevents duplicate property names.
*/
* Filters given array by a given target or targets and prevents duplicate property names.
*/
protected filterByTargetAndWithoutDuplicateProperties<T extends { target: Function | string, property: string }>(
array: T[],
target: (Function | string) | (Function | string)[],
Expand Down
16 changes: 16 additions & 0 deletions src/metadata-builder/metadata.builder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AfterMetadata,
ArgumentMetadata,
BeforeMetadata,
ContextMetadata,
Expand Down Expand Up @@ -105,6 +106,7 @@ export class MetadataBuilder {
root: this.buildRootMetadata(target, arg.property),
orderBy: this.buildOrderByMetadata(target, arg.property),
before: this.buildBeforeMetadata(target, arg.property),
after: this.buildAfterMetadata(target, arg.property),
}));
}

Expand Down Expand Up @@ -191,6 +193,20 @@ export class MetadataBuilder {
}));
}

protected buildAfterMetadata(target: any, property: string): AfterMetadata | undefined {
return getMetadataArgsStorage()
.filterAfterByByClassAndProperty(target, property)
.map(arg => ({
target: arg.target,
name: arg.name,
description: arg.description,
index: arg.index,
property: arg.property,
middleware: arg.middleware,
}))
.find((_, index) => index === 0);
}

}

/**
Expand Down
8 changes: 8 additions & 0 deletions src/metadata/args/after.arg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AfterMiddleware } from '../../middleware';
import { Argument } from './argument';

export interface AfterArg extends Argument {
index: number;
property: string;
middleware: AfterMiddleware;
}
4 changes: 2 additions & 2 deletions src/metadata/args/before.arg.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Argument } from './argument';
import { Middleware } from '../../middleware';
import { BeforeMiddleware } from '../../middleware';

export interface BeforeArg extends Argument {
index: number;
property: string;
middleware: Middleware;
middleware: BeforeMiddleware;
}
1 change: 1 addition & 0 deletions src/metadata/args/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './root.arg';
export * from './order-by.arg';
export * from './before.arg';
export * from './interface-type.arg';
export * from './after.arg';
13 changes: 13 additions & 0 deletions src/metadata/options/after.option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { AfterMiddleware } from '../../middleware';
import { Option } from './option';

/**
* After (aka AfterMiddleware) options
*/
export interface AfterOption extends Option {
/**
* Middeware to change resolver behavior.
* Check decorator docs for example usage.
*/
middleware: AfterMiddleware;
}
4 changes: 2 additions & 2 deletions src/metadata/options/before.option.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Middleware } from '../../middleware';
import { BeforeMiddleware } from '../../middleware';
import { Option } from './option';

/**
Expand All @@ -9,5 +9,5 @@ export interface BeforeOption extends Option {
* Middeware to change resolver behavior.
* Check decorator docs for example usage.
*/
middleware: Middleware;
middleware: BeforeMiddleware;
}
1 change: 1 addition & 0 deletions src/metadata/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './argument.option';
export * from './order-by.option';
export * from './before.option';
export * from './interface.option';
export * from './after.option';
6 changes: 6 additions & 0 deletions src/metadata/types/after.metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { AfterMiddleware } from '../../middleware';
import { FieldArgumentMetadata } from './field-argument.metadata';

export interface AfterMetadata extends FieldArgumentMetadata {
middleware: AfterMiddleware;
}
4 changes: 2 additions & 2 deletions src/metadata/types/before.metadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BeforeMiddleware } from '../../middleware';
import { FieldArgumentMetadata } from './field-argument.metadata';
import { Middleware } from '../../middleware';

export interface BeforeMetadata extends FieldArgumentMetadata {
middleware: Middleware;
middleware: BeforeMiddleware;
}
2 changes: 2 additions & 0 deletions src/metadata/types/field.metadata.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AfterMetadata } from './after.metadata';
import { ArgumentMetadata } from './argument.metadata';
import { BeforeMetadata } from './before.metadata';
import { ContextMetadata } from './context.metadata';
Expand All @@ -16,4 +17,5 @@ export interface FieldMetadata extends Metadata {
root?: RootMetadata;
orderBy?: OrderByMetadata;
before?: BeforeMetadata;
after?: AfterMetadata;
}
1 change: 1 addition & 0 deletions src/metadata/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './order-by.metadata';
export * from './root.metadata';
export * from './field.metadata';
export * from './interface.metadata';
export * from './after.metadata';
1 change: 0 additions & 1 deletion src/middleware.ts

This file was deleted.

6 changes: 6 additions & 0 deletions src/middleware/after.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type AfterMiddleware = (
context: any,
args: { [key: string]: any },
result: Promise<any> | any,
next: (error?: Error, value?: any) => any,
) => Promise<any> | any;
5 changes: 5 additions & 0 deletions src/middleware/before.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type BeforeMiddleware = (
context: any,
args: { [key: string]: any },
next: (error?: Error, value?: any) => any,
) => Promise<any> | any;
3 changes: 3 additions & 0 deletions src/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './after.middleware';
export * from './before.middleware';
export * from './pagination.middleware';
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FieldMetadata } from './metadata';
import { PageInfo } from './page-info.type';
import { PaginationResponse } from './pagination.type';
import { getMetadataBuilder } from './metadata-builder';
import { FieldMetadata } from '../metadata';
import { PageInfo } from '../page-info.type';
import { PaginationResponse } from '../pagination.type';
import { getMetadataBuilder } from '../metadata-builder';

export function PaginationMiddleware(target: any, propertyKey: string, methodDescriptor: TypedPropertyDescriptor<any>): any {
return {
Expand Down
8 changes: 4 additions & 4 deletions src/specs/field.type-factory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import * as graphql from 'graphql';

import { clearFieldTypeCache, clearObjectTypeRepository, fieldTypeFactory, resolverFactory } from '../type-factory';

import { BeforeMiddleware } from '../middleware';
import { FieldMetadata } from '../metadata';
import { Middleware } from '../middleware';
import { getMetadataBuilder } from '../metadata-builder';

const assert = require('assert');
Expand Down Expand Up @@ -80,7 +80,7 @@ describe('field specs', function () {

it('makes sure Before is executed before resolving', function (done) {

let middleware: Middleware = (context: any, args: { [key: string]: any }, next: (error?: Error, value?: any) => any): any => {
let middleware: BeforeMiddleware = (context: any, args: { [key: string]: any }, next: (error?: Error, value?: any) => any): any => {
assert(true);
done();
};
Expand All @@ -92,7 +92,7 @@ describe('field specs', function () {

it('makes sure middleware can override function execution if next is called with a value', function () {

let middleware: Middleware = (context: any, args: { [key: string]: any }, next: (error?: Error, value?: any) => any): any => {
let middleware: BeforeMiddleware = (context: any, args: { [key: string]: any }, next: (error?: Error, value?: any) => any): any => {
next(null, 5);
};
class Obj { @D.Field() @D.Before(middleware) twice(input: number): number { return input * 2; } }
Expand All @@ -105,7 +105,7 @@ describe('field specs', function () {
// tslint:disable-next-line:max-line-length
it('makes sure middleware can override function execution if next is called with a value even if it is null (as long it ir not undefined)', function () {

let middleware: Middleware = (context: any, args: { [key: string]: any }, next: (error?: Error, value?: any) => any): any => {
let middleware: BeforeMiddleware = (context: any, args: { [key: string]: any }, next: (error?: Error, value?: any) => any): any => {
next(null, null);
};
class Obj { @D.Field() @D.Before(middleware) twice(input: number): number { return input * 2; } }
Expand Down
Loading

0 comments on commit 914e18c

Please sign in to comment.