@loopback/boot
Version:
A collection of Booters for LoopBack 4 Applications
118 lines (107 loc) • 3.46 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2019. All Rights Reserved.
// Node module: @loopback/boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
import {
config,
CoreBindings,
extensionPoint,
extensions,
Getter,
inject,
} from '@loopback/core';
import {
ModelApiBuilder,
ModelApiConfig,
MODEL_API_BUILDER_PLUGINS,
} from '@loopback/model-api-builder';
import {ApplicationWithRepositories} from '@loopback/repository';
import debugFactory from 'debug';
import * as path from 'path';
import {BootBindings} from '../keys';
import {ArtifactOptions, booter} from '../types';
import {BaseArtifactBooter} from './base-artifact.booter';
const debug = debugFactory('loopback:boot:model-api');
('modelApi')
(MODEL_API_BUILDER_PLUGINS)
export class ModelApiBooter extends BaseArtifactBooter {
constructor(
public app: ApplicationWithRepositories,
(BootBindings.PROJECT_ROOT) projectRoot: string,
()
public getModelApiBuilders: Getter<ModelApiBuilder[]>,
()
public booterConfig: ArtifactOptions = {},
) {
(CoreBindings.APPLICATION_INSTANCE)
// TODO assert that `app` has RepositoryMixin members
super(
projectRoot,
// Set booter options if passed in via bootConfig
Object.assign({}, RestDefaults, booterConfig),
);
}
/**
* Load the the model config files
*/
async load(): Promise<void> {
// Important: don't call `super.load()` here, it would try to load
// classes via `loadClassesFromFiles` - that won't work for JSON files
await Promise.all(
this.discovered.map(async f => {
try {
// It's important to await before returning,
// otherwise the catch block won't receive errors
await this.setupModel(f);
} catch (err) {
const shortPath = path.relative(this.projectRoot, f);
err.message += ` (while loading ${shortPath})`;
throw err;
}
}),
);
}
/**
* Set up the loaded model classes
*/
async setupModel(configFile: string): Promise<void> {
const cfg: ModelApiConfig = require(configFile);
debug(
'Loaded model config from %s',
path.relative(this.projectRoot, configFile),
cfg,
);
const modelClass = cfg.model;
if (typeof modelClass !== 'function') {
throw new Error(
`Invalid "model" field. Expected a Model class, found ${modelClass}`,
);
}
const builder = await this.getApiBuilderForPattern(cfg.pattern);
await builder.build(this.app, modelClass, cfg);
}
/**
* Retrieve the API builder that matches the pattern provided
* @param pattern - name of pattern for an API builder
*/
async getApiBuilderForPattern(pattern: string): Promise<ModelApiBuilder> {
const allBuilders = await this.getModelApiBuilders();
const builder = allBuilders.find(b => b.pattern === pattern);
if (!builder) {
const availableBuilders = allBuilders.map(b => b.pattern).join(', ');
throw new Error(
`Unsupported API pattern "${pattern}". ` +
`Available patterns: ${availableBuilders || '<none>'}`,
);
}
return builder;
}
}
/**
* Default ArtifactOptions for ControllerBooter.
*/
export const RestDefaults: ArtifactOptions = {
dirs: ['model-endpoints'],
extensions: ['-config.js'],
nested: true,
};