UNPKG

@loopback/repository

Version:

Define and implement a common set of interfaces for interacting with databases

167 lines (155 loc) 5.06 kB
// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved. // Node module: @loopback/repository // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import { ClassDecoratorFactory, MetadataAccessor, MetadataInspector, MetadataMap, PropertyDecoratorFactory, } from '@loopback/core'; import { ModelDefinition, ModelDefinitionSyntax, PropertyDefinition, PropertyType, RelationDefinitionMap, } from '../model'; import {RELATIONS_KEY} from '../relations/relation.decorator'; export const MODEL_KEY = MetadataAccessor.create< Partial<ModelDefinitionSyntax>, ClassDecorator >('loopback:model'); export const MODEL_PROPERTIES_KEY = MetadataAccessor.create< PropertyDefinition, PropertyDecorator >('loopback:model-properties'); export const MODEL_WITH_PROPERTIES_KEY = MetadataAccessor.create< ModelDefinition, ClassDecorator >('loopback:model-and-properties'); export type PropertyMap = MetadataMap<Partial<PropertyDefinition>>; /** * Decorator for model definitions * @param definition * @returns A class decorator for `model` */ export function model(definition?: Partial<ModelDefinitionSyntax>) { return function (target: Function & {definition?: ModelDefinition}) { definition = definition ?? {}; const def: ModelDefinitionSyntax = Object.assign(definition, { name: definition.name ?? target.name, }); const decorator = ClassDecoratorFactory.createDecorator( MODEL_KEY, definition, {decoratorName: '@model'}, ); decorator(target); // Build "ModelDefinition" and store it on model constructor buildModelDefinition(target, def); }; } /** * Build model definition from decorations * @param target - Target model class * @param def - Model definition spec */ export function buildModelDefinition( target: Function & {definition?: ModelDefinition | undefined}, def?: ModelDefinitionSyntax, ) { // Check if the definition for this class has been built (not from the super // class) const baseClass = Object.getPrototypeOf(target); if ( !def && target.definition && baseClass && target.definition !== baseClass.definition ) { return target.definition; } const modelDef = new ModelDefinition(def ?? {name: target.name}); const prototype = target.prototype; const propertyMap: PropertyMap = MetadataInspector.getAllPropertyMetadata(MODEL_PROPERTIES_KEY, prototype) ?? {}; for (const [propName, propDef] of Object.entries(propertyMap)) { const designType = propDef.type ?? MetadataInspector.getDesignTypeForProperty(prototype, propName); if (!designType) { const err: Error & {code?: string} = new Error( `The definition of model property ${modelDef.name}.${propName} is missing ` + '`type` field and TypeScript did not provide any design-time type. ' + 'Learn more at https://loopback.io/doc/en/lb4/Error-codes.html#cannot_infer_property_type', ); err.code = 'CANNOT_INFER_PROPERTY_TYPE'; throw err; } if (propDef.hidden) { modelDef.settings.hiddenProperties = modelDef.settings.hiddenProperties ?? []; modelDef.settings.hiddenProperties.push(propName); } propDef.type = designType; modelDef.addProperty(propName, propDef); } target.definition = modelDef; const relationMeta: RelationDefinitionMap = MetadataInspector.getAllPropertyMetadata(RELATIONS_KEY, prototype) ?? {}; const relations: RelationDefinitionMap = {}; // Build an object keyed by relation names Object.values(relationMeta).forEach(r => { relations[r.name] = r; }); target.definition.relations = relations; return modelDef; } /** * Decorator for model properties * @param definition * @returns A property decorator */ export function property(definition?: Partial<PropertyDefinition>) { return PropertyDecoratorFactory.createDecorator( MODEL_PROPERTIES_KEY, Object.assign({}, definition), {decoratorName: '@property'}, ); } export namespace property { export const ERR_PROP_NOT_ARRAY = '@property.array can only decorate array properties!'; export const ERR_NO_ARGS = 'decorator received less than two parameters'; /** * * @param itemType - The type of array items. * Examples: `number`, `Product`, `() => Order`. * @param definition - Optional PropertyDefinition object for additional * metadata */ export function array( itemType: PropertyType, definition?: Partial<PropertyDefinition>, ) { return function (target: object, propertyName: string) { const propType = MetadataInspector.getDesignTypeForProperty( target, propertyName, ); if (propType !== Array) { throw new Error(ERR_PROP_NOT_ARRAY); } else { property( Object.assign( {type: Array, itemType} as Partial<PropertyDefinition>, definition, ), )(target, propertyName); } }; } }