UNPKG

3.46 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2019. All Rights Reserved.
2// Node module: @loopback/boot
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6import {
7 config,
8 CoreBindings,
9 extensionPoint,
10 extensions,
11 Getter,
12 inject,
13} from '@loopback/core';
14import {
15 ModelApiBuilder,
16 ModelApiConfig,
17 MODEL_API_BUILDER_PLUGINS,
18} from '@loopback/model-api-builder';
19import {ApplicationWithRepositories} from '@loopback/repository';
20import debugFactory from 'debug';
21import * as path from 'path';
22import {BootBindings} from '../keys';
23import {ArtifactOptions, booter} from '../types';
24import {BaseArtifactBooter} from './base-artifact.booter';
25
26const debug = debugFactory('loopback:boot:model-api');
27
28@booter('modelApi')
29@extensionPoint(MODEL_API_BUILDER_PLUGINS)
30export class ModelApiBooter extends BaseArtifactBooter {
31 constructor(
32 @inject(CoreBindings.APPLICATION_INSTANCE)
33 public app: ApplicationWithRepositories,
34 @inject(BootBindings.PROJECT_ROOT) projectRoot: string,
35 @extensions()
36 public getModelApiBuilders: Getter<ModelApiBuilder[]>,
37 @config()
38 public booterConfig: ArtifactOptions = {},
39 ) {
40 // TODO assert that `app` has RepositoryMixin members
41
42 super(
43 projectRoot,
44 // Set booter options if passed in via bootConfig
45 Object.assign({}, RestDefaults, booterConfig),
46 );
47 }
48
49 /**
50 * Load the the model config files
51 */
52 async load(): Promise<void> {
53 // Important: don't call `super.load()` here, it would try to load
54 // classes via `loadClassesFromFiles` - that won't work for JSON files
55 await Promise.all(
56 this.discovered.map(async f => {
57 try {
58 // It's important to await before returning,
59 // otherwise the catch block won't receive errors
60 await this.setupModel(f);
61 } catch (err) {
62 const shortPath = path.relative(this.projectRoot, f);
63 err.message += ` (while loading ${shortPath})`;
64 throw err;
65 }
66 }),
67 );
68 }
69
70 /**
71 * Set up the loaded model classes
72 */
73 async setupModel(configFile: string): Promise<void> {
74 const cfg: ModelApiConfig = require(configFile);
75 debug(
76 'Loaded model config from %s',
77 path.relative(this.projectRoot, configFile),
78 cfg,
79 );
80
81 const modelClass = cfg.model;
82 if (typeof modelClass !== 'function') {
83 throw new Error(
84 `Invalid "model" field. Expected a Model class, found ${modelClass}`,
85 );
86 }
87
88 const builder = await this.getApiBuilderForPattern(cfg.pattern);
89 await builder.build(this.app, modelClass, cfg);
90 }
91
92 /**
93 * Retrieve the API builder that matches the pattern provided
94 * @param pattern - name of pattern for an API builder
95 */
96 async getApiBuilderForPattern(pattern: string): Promise<ModelApiBuilder> {
97 const allBuilders = await this.getModelApiBuilders();
98 const builder = allBuilders.find(b => b.pattern === pattern);
99 if (!builder) {
100 const availableBuilders = allBuilders.map(b => b.pattern).join(', ');
101 throw new Error(
102 `Unsupported API pattern "${pattern}". ` +
103 `Available patterns: ${availableBuilders || '<none>'}`,
104 );
105 }
106 return builder;
107 }
108}
109
110/**
111 * Default ArtifactOptions for ControllerBooter.
112 */
113export const RestDefaults: ArtifactOptions = {
114 dirs: ['model-endpoints'],
115 extensions: ['-config.js'],
116 nested: true,
117};