Skip to content

Commit

Permalink
feat: Use decorator factories
Browse files Browse the repository at this point in the history
  • Loading branch information
Raymond Feng authored and raymondfeng committed Dec 8, 2017
1 parent 05c2d49 commit 41c4d33
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 122 deletions.
19 changes: 10 additions & 9 deletions packages/context/src/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,43 +64,44 @@ export function inject(
return function markParameterOrPropertyAsInjected(
// tslint:disable-next-line:no-any
target: any,
propertyKey?: string | symbol,
propertyKey: string | symbol,
propertyDescriptorOrParameterIndex?:
| TypedPropertyDescriptor<BoundValue>
| number,
) {
if (typeof propertyDescriptorOrParameterIndex === 'number') {
// The decorator is applied to a method parameter
// Please note propertyKey is `undefined` for constructor
const paramDecorator: ParameterDecorator = ParameterDecoratorFactory.getDecorator(
const paramDecorator: ParameterDecorator = ParameterDecoratorFactory.createDecorator(
PARAMETERS_KEY,
{
bindingKey,
metadata,
resolve,
},
);
return paramDecorator(
target,
propertyKey!,
propertyDescriptorOrParameterIndex,
);
paramDecorator(target, propertyKey!, propertyDescriptorOrParameterIndex);
} else if (propertyKey) {
if (typeof Object.getPrototypeOf(target) === 'function') {
const prop = target.name + '.' + propertyKey.toString();
throw new Error(
'@inject is not supported for a static property: ' + prop,
);
}
const propDecorator: PropertyDecorator = PropertyDecoratorFactory.getDecorator(
if (propertyDescriptorOrParameterIndex) {
throw new Error(
'@inject cannot be used on a method: ' + propertyKey.toString(),
);
}
const propDecorator: PropertyDecorator = PropertyDecoratorFactory.createDecorator(
PROPERTIES_KEY,
{
bindingKey,
metadata,
resolve,
},
);
return propDecorator(target, propertyKey!);
propDecorator(target, propertyKey!);
} else {
throw new Error(
'@inject can only be used on properties or method parameters.',
Expand Down
10 changes: 10 additions & 0 deletions packages/context/test/unit/inject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ describe('property injection', () => {
}).to.throw(/@inject is not supported for a static property/);
});

it('cannot decorate a method', () => {
expect(() => {
// tslint:disable-next-line:no-unused-variable
class TestClass {
@inject('bar')
foo() {}
}
}).to.throw(/@inject cannot be used on a method/);
});

it('supports inheritance without overriding property', () => {
class TestClass {
@inject('foo') foo: string;
Expand Down
35 changes: 16 additions & 19 deletions packages/repository/src/decorators/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Reflector} from '@loopback/context';
import {
Reflector,
ClassDecoratorFactory,
PropertyDecoratorFactory,
} from '@loopback/context';
import {ModelDefinition, ModelDefinitionSyntax} from '../model';
import {PropertyDefinition} from '../index';

export const MODEL_KEY = 'loopback:model';
export const PROPERTY_KEY = 'loopback:property';
export const MODEL_PROPERTIES_KEY = 'loopback:model-properties';

type PropertyMap = {[name: string]: PropertyDefinition};
Expand All @@ -21,13 +24,17 @@ type PropertyMap = {[name: string]: PropertyDefinition};
* @returns {(target:any)}
*/
export function model(definition?: ModelDefinitionSyntax) {
return function(target: any) {
return function(target: Function & {definition?: ModelDefinition}) {
if (!definition) {
definition = {name: target.name};
}

// Apply model definition to the model class
Reflector.defineMetadata(MODEL_KEY, definition, target);
const decorator = ClassDecoratorFactory.createDecorator(
MODEL_KEY,
definition,
);

decorator(target);

// Build "ModelDefinition" and store it on model constructor
const modelDef = new ModelDefinition(definition);
Expand All @@ -51,18 +58,8 @@ export function model(definition?: ModelDefinitionSyntax) {
* @returns {(target:any, key:string)}
*/
export function property(definition: PropertyDefinition) {
return function(target: any, key: string) {
// Apply model definition to the model class
Reflector.defineMetadata(PROPERTY_KEY, definition, target, key);

// Because there is no way how to iterate decorated properties at runtime,
// we need to keep an explicit map of decorated properties
let map: PropertyMap = Reflector.getMetadata(MODEL_PROPERTIES_KEY, target);
if (!map) {
map = Object.create(null);
Reflector.defineMetadata(MODEL_PROPERTIES_KEY, map, target);
}

map[key] = definition;
};
return PropertyDecoratorFactory.createDecorator(
MODEL_PROPERTIES_KEY,
definition,
);
}
60 changes: 19 additions & 41 deletions packages/repository/src/decorators/relation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import {Class} from '../common-types';
import {Entity} from '../model';

import {Reflector} from '@loopback/context';
import {PropertyDecoratorFactory} from '@loopback/context';

// tslint:disable:no-any

Expand All @@ -20,7 +20,7 @@ export enum RelationType {
referencesMany,
}

export const RELATION_KEY = 'loopback:relation';
export const RELATIONS_KEY = 'loopback:relations';

export class RelationMetadata {
type: RelationType;
Expand All @@ -34,10 +34,8 @@ export class RelationMetadata {
* @returns {(target:any, key:string)}
*/
export function relation(definition?: Object) {
return function(target: any, key: string) {
// Apply model definition to the model class
Reflector.defineMetadata(RELATION_KEY, definition, target, key);
};
// Apply relation definition to the model class
return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, definition);
}

/**
Expand All @@ -46,11 +44,9 @@ export function relation(definition?: Object) {
* @returns {(target:any, key:string)}
*/
export function belongsTo(definition?: Object) {
return function(target: any, key: string) {
// Apply model definition to the model class
const rel = Object.assign({type: RelationType.belongsTo}, definition);
Reflector.defineMetadata(RELATION_KEY, rel, target, key);
};
// Apply model definition to the model class
const rel = Object.assign({type: RelationType.belongsTo}, definition);
return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, rel);
}

/**
Expand All @@ -59,11 +55,8 @@ export function belongsTo(definition?: Object) {
* @returns {(target:any, key:string)}
*/
export function hasOne(definition?: Object) {
return function(target: any, key: string) {
// Apply model definition to the model class
const rel = Object.assign({type: RelationType.hasOne}, definition);
Reflector.defineMetadata(RELATION_KEY, rel, target, key);
};
const rel = Object.assign({type: RelationType.hasOne}, definition);
return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, rel);
}

/**
Expand All @@ -72,11 +65,8 @@ export function hasOne(definition?: Object) {
* @returns {(target:any, key:string)}
*/
export function hasMany(definition?: Object) {
return function(target: any, key: string) {
// Apply model definition to the model class
const rel = Object.assign({type: RelationType.hasMany}, definition);
Reflector.defineMetadata(RELATION_KEY, rel, target, key);
};
const rel = Object.assign({type: RelationType.hasMany}, definition);
return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, rel);
}

/**
Expand All @@ -85,11 +75,8 @@ export function hasMany(definition?: Object) {
* @returns {(target:any, key:string)}
*/
export function embedsOne(definition?: Object) {
return function(target: any, key: string) {
// Apply model definition to the model class
const rel = Object.assign({type: RelationType.embedsOne}, definition);
Reflector.defineMetadata(RELATION_KEY, rel, target, key);
};
const rel = Object.assign({type: RelationType.embedsOne}, definition);
return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, rel);
}

/**
Expand All @@ -98,11 +85,8 @@ export function embedsOne(definition?: Object) {
* @returns {(target:any, key:string)}
*/
export function embedsMany(definition?: Object) {
return function(target: any, key: string) {
// Apply model definition to the model class
const rel = Object.assign({type: RelationType.embedsMany}, definition);
Reflector.defineMetadata(RELATION_KEY, rel, target, key);
};
const rel = Object.assign({type: RelationType.embedsMany}, definition);
return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, rel);
}

/**
Expand All @@ -111,11 +95,8 @@ export function embedsMany(definition?: Object) {
* @returns {(target:any, key:string)}
*/
export function referencesOne(definition?: Object) {
return function(target: any, key: string) {
// Apply model definition to the model class
const rel = Object.assign({type: RelationType.referencesOne}, definition);
Reflector.defineMetadata(RELATION_KEY, rel, target, key);
};
const rel = Object.assign({type: RelationType.referencesOne}, definition);
return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, rel);
}

/**
Expand All @@ -124,9 +105,6 @@ export function referencesOne(definition?: Object) {
* @returns {(target:any, key:string)}
*/
export function referencesMany(definition?: Object) {
return function(target: any, key: string) {
// Apply model definition to the model class
const rel = Object.assign({type: RelationType.referencesMany}, definition);
Reflector.defineMetadata(RELATION_KEY, rel, target, key);
};
const rel = Object.assign({type: RelationType.referencesMany}, definition);
return PropertyDecoratorFactory.createDecorator(RELATIONS_KEY, rel);
}
73 changes: 20 additions & 53 deletions packages/repository/test/unit/decorator/model-and-relation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {expect} from '@loopback/testlab';
import {model, property, MODEL_KEY, PROPERTY_KEY} from '../../../';
import {model, property, MODEL_KEY, MODEL_PROPERTIES_KEY} from '../../../';
import {
relation,
hasOne,
Expand All @@ -14,7 +14,7 @@ import {
hasMany,
referencesMany,
referencesOne,
RELATION_KEY,
RELATIONS_KEY,
RelationType,
} from '../../../';

Expand Down Expand Up @@ -102,11 +102,10 @@ describe('model decorator', () => {

it('adds property metadata', () => {
const meta = Reflector.getOwnMetadata(
PROPERTY_KEY,
MODEL_PROPERTIES_KEY,
Order.prototype,
'quantity',
);
expect(meta).to.eql({
expect(meta.quantity).to.eql({
type: 'number',
mysql: {
column: 'QTY',
Expand All @@ -115,90 +114,58 @@ describe('model decorator', () => {
});

it('adds embedsOne metadata', () => {
const meta = Reflector.getOwnMetadata(
RELATION_KEY,
Customer.prototype,
'address',
);
expect(meta).to.eql({
const meta = Reflector.getOwnMetadata(RELATIONS_KEY, Customer.prototype);
expect(meta.address).to.eql({
type: RelationType.embedsOne,
});
});

it('adds embedsMany metadata', () => {
const meta = Reflector.getOwnMetadata(
RELATION_KEY,
Customer.prototype,
'phones',
);
expect(meta).to.eql({
const meta = Reflector.getOwnMetadata(RELATIONS_KEY, Customer.prototype);
expect(meta.phones).to.eql({
type: RelationType.embedsMany,
});
});

it('adds referencesMany metadata', () => {
const meta = Reflector.getOwnMetadata(
RELATION_KEY,
Customer.prototype,
'accounts',
);
expect(meta).to.eql({
const meta = Reflector.getOwnMetadata(RELATIONS_KEY, Customer.prototype);
expect(meta.accounts).to.eql({
type: RelationType.referencesMany,
});
});

it('adds referencesOne metadata', () => {
const meta = Reflector.getOwnMetadata(
RELATION_KEY,
Customer.prototype,
'profile',
);
expect(meta).to.eql({
const meta = Reflector.getOwnMetadata(RELATIONS_KEY, Customer.prototype);
expect(meta.profile).to.eql({
type: RelationType.referencesOne,
});
});

it('adds hasMany metadata', () => {
const meta = Reflector.getOwnMetadata(
RELATION_KEY,
Customer.prototype,
'orders',
);
expect(meta).to.eql({
const meta = Reflector.getOwnMetadata(RELATIONS_KEY, Customer.prototype);
expect(meta.orders).to.eql({
type: RelationType.hasMany,
});
});

it('adds belongsTo metadata', () => {
const meta = Reflector.getOwnMetadata(
RELATION_KEY,
Order.prototype,
'customer',
);
expect(meta).to.eql({
const meta = Reflector.getOwnMetadata(RELATIONS_KEY, Order.prototype);
expect(meta.customer).to.eql({
type: RelationType.belongsTo,
target: 'Customer',
});
});

it('adds hasOne metadata', () => {
const meta = Reflector.getOwnMetadata(
RELATION_KEY,
Customer.prototype,
'lastOrder',
);
expect(meta).to.eql({
const meta = Reflector.getOwnMetadata(RELATIONS_KEY, Customer.prototype);
expect(meta.lastOrder).to.eql({
type: RelationType.hasOne,
});
});

it('adds relation metadata', () => {
const meta = Reflector.getOwnMetadata(
RELATION_KEY,
Customer.prototype,
'recentOrders',
);
expect(meta).to.eql({
const meta = Reflector.getOwnMetadata(RELATIONS_KEY, Customer.prototype);
expect(meta.recentOrders).to.eql({
type: RelationType.hasMany,
});
});
Expand Down

0 comments on commit 41c4d33

Please sign in to comment.