@loopback/repository
Version:
Define and implement a common set of interfaces for interacting with databases
405 lines • 14.7 kB
JavaScript
// 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
Object.defineProperty(exports, "__esModule", { value: true });
exports.createModelClassBinding = exports.RepositoryMixinDoc = exports.RepositoryMixin = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@loopback/core");
const debug_1 = tslib_1.__importDefault(require("debug"));
const keys_1 = require("../keys");
const repositories_1 = require("../repositories");
const debug = (0, debug_1.default)('loopback:repository:mixin');
/* eslint-enable @typescript-eslint/no-unused-vars */
/**
* A mixin class for Application that creates a .repository()
* function to register a repository automatically. Also overrides
* component function to allow it to register repositories automatically.
*
* @example
* ```ts
* class MyApplication extends RepositoryMixin(Application) {}
* ```
*
* Please note: the members in the mixin function are documented in a dummy class
* called <a href="#RepositoryMixinDoc">RepositoryMixinDoc</a>
*
* @param superClass - Application class
* @returns A new class that extends the super class with repository related
* methods
*
* @typeParam T - Type of the application class as the target for the mixin
*
*/
function RepositoryMixin(superClass) {
return class extends superClass {
/**
* Add a repository to this application.
*
* @param repoClass - The repository to add.
* @param nameOrOptions - Name or options for the binding
*
* @example
* ```ts
*
* class NoteRepo {
* model: any;
*
* constructor() {
* const ds: juggler.DataSource = new juggler.DataSource({
* name: 'db',
* connector: 'memory',
* });
*
* this.model = ds.createModel(
* 'note',
* {title: 'string', content: 'string'},
* {}
* );
* }
* };
*
* app.repository(NoteRepo);
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
repository(repoClass, nameOrOptions) {
const binding = (0, core_1.createBindingFromClass)(repoClass, {
namespace: keys_1.RepositoryBindings.REPOSITORIES,
type: keys_1.RepositoryTags.REPOSITORY,
defaultScope: core_1.BindingScope.TRANSIENT,
...toOptions(nameOrOptions),
}).tag(keys_1.RepositoryTags.REPOSITORY);
this.add(binding);
return binding;
}
/**
* Retrieve the repository instance from the given Repository class
*
* @param repo - The repository class to retrieve the instance of
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async getRepository(repo) {
return this.get(`repositories.${repo.name}`);
}
/**
* Add the dataSource to this application.
*
* @param dataSource - The dataSource to add.
* @param nameOrOptions - The binding name or options of the datasource;
* defaults to dataSource.name
*
* @example
* ```ts
*
* const ds: juggler.DataSource = new juggler.DataSource({
* name: 'db',
* connector: 'memory',
* });
*
* app.dataSource(ds);
*
* // The datasource can be injected with
* constructor(@inject('datasources.db') dataSource: DataSourceType) {
*
* }
* ```
*/
dataSource(dataSource, nameOrOptions) {
var _a;
const options = toOptions(nameOrOptions);
// We have an instance of
if (dataSource instanceof repositories_1.juggler.DataSource) {
const name = options.name || dataSource.name;
const namespace = (_a = options.namespace) !== null && _a !== void 0 ? _a : keys_1.RepositoryBindings.DATASOURCES;
const key = `${namespace}.${name}`;
return this.bind(key).to(dataSource).tag(keys_1.RepositoryTags.DATASOURCE);
}
else if (typeof dataSource === 'function') {
options.name = options.name || dataSource.dataSourceName;
const binding = (0, core_1.createBindingFromClass)(dataSource, {
namespace: keys_1.RepositoryBindings.DATASOURCES,
type: keys_1.RepositoryTags.DATASOURCE,
defaultScope: core_1.BindingScope.SINGLETON,
...options,
});
this.add(binding);
return binding;
}
else {
throw new Error('not a valid DataSource.');
}
}
/**
* Register a model class as a binding in the target context
* @param modelClass - Model class
*/
model(modelClass) {
const binding = createModelClassBinding(modelClass);
this.add(binding);
return binding;
}
/**
* Add a component to this application. Also mounts
* all the components repositories.
*
* @param component - The component to add.
* @param nameOrOptions - Name or options for the binding.
*
* @example
* ```ts
*
* export class ProductComponent {
* controllers = [ProductController];
* repositories = [ProductRepo, UserRepo];
* providers = {
* [AUTHENTICATION_STRATEGY]: AuthStrategy,
* [AUTHORIZATION_ROLE]: Role,
* };
* };
*
* app.component(ProductComponent);
* ```
*/
// Unfortunately, TypeScript does not allow overriding methods inherited
// from mapped types. https://github.com/microsoft/TypeScript/issues/38496
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
component(componentCtor, nameOrOptions) {
const binding = super.component(componentCtor, nameOrOptions);
const instance = this.getSync(binding.key);
this.mountComponentRepositories(instance);
this.mountComponentModels(instance);
return binding;
}
/**
* Get an instance of a component and mount all it's
* repositories. This function is intended to be used internally
* by `component()`.
*
* NOTE: Calling `mountComponentRepositories` with a component class
* constructor is deprecated. You should instantiate the component
* yourself and provide the component instance instead.
*
* @param componentInstanceOrClass - The component to mount repositories of
* @internal
*/
mountComponentRepositories(
// accept also component class to preserve backwards compatibility
// TODO(semver-major) Remove support for component class constructor
componentInstanceOrClass) {
const component = resolveComponentInstance(this);
if (component.repositories) {
for (const repo of component.repositories) {
this.repository(repo);
}
}
// `Readonly<Application>` is a hack to remove protected members
// and thus allow `this` to be passed as a value for `ctx`
function resolveComponentInstance(ctx) {
if (typeof componentInstanceOrClass !== 'function')
return componentInstanceOrClass;
const componentName = componentInstanceOrClass.name;
const componentKey = `${core_1.CoreBindings.COMPONENTS}.${componentName}`;
return ctx.getSync(componentKey);
}
}
/**
* Bind all model classes provided by a component.
* @param component
* @internal
*/
mountComponentModels(component) {
if (!component.models)
return;
for (const m of component.models) {
this.model(m);
}
}
/**
* Update or recreate the database schema for all repositories.
*
* **WARNING**: By default, `migrateSchema()` will attempt to preserve data
* while updating the schema in your target database, but this is not
* guaranteed to be safe.
*
* Please check the documentation for your specific connector(s) for
* a detailed breakdown of behaviors for automigrate!
*
* @param options - Migration options, e.g. whether to update tables
* preserving data or rebuild everything from scratch.
*/
async migrateSchema(options = {}) {
var _a;
const operation = options.existingSchema === 'drop' ? 'automigrate' : 'autoupdate';
// Instantiate all repositories to ensure models are registered & attached
// to their datasources
const repoBindings = this.findByTag('repository');
await Promise.all(repoBindings.map(b => this.get(b.key)));
// Look up all datasources and update/migrate schemas one by one
const dsBindings = this.findByTag(keys_1.RepositoryTags.DATASOURCE);
for (const b of dsBindings) {
const ds = await this.get(b.key);
const disableMigration = (_a = ds.settings.disableMigration) !== null && _a !== void 0 ? _a : false;
if (operation in ds &&
typeof ds[operation] === 'function' &&
!disableMigration) {
debug('Migrating dataSource %s', b.key);
await ds[operation](options.models);
}
else {
debug('Skipping migration of dataSource %s', b.key);
}
}
}
};
}
exports.RepositoryMixin = RepositoryMixin;
/**
* Normalize name or options to `BindingFromClassOptions`
* @param nameOrOptions - Name or options for binding from class
*/
function toOptions(nameOrOptions) {
if (typeof nameOrOptions === 'string') {
return { name: nameOrOptions };
}
return nameOrOptions !== null && nameOrOptions !== void 0 ? nameOrOptions : {};
}
/**
* A dummy class created to generate the tsdoc for the members in repository
* mixin. Please don't use it.
*
* The members are implemented in function
* <a href="#RepositoryMixin">RepositoryMixin</a>
*/
class RepositoryMixinDoc {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(...args) {
throw new Error('This is a dummy class created for apidoc!' + 'Please do not use it!');
}
/**
* Add a repository to this application.
*
* @param repo - The repository to add.
*
* @example
* ```ts
*
* class NoteRepo {
* model: any;
*
* constructor() {
* const ds: juggler.DataSource = new juggler.DataSource({
* name: 'db',
* connector: 'memory',
* });
*
* this.model = ds.createModel(
* 'note',
* {title: 'string', content: 'string'},
* {}
* );
* }
* };
*
* app.repository(NoteRepo);
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
repository(repo) {
throw new Error();
}
/**
* Retrieve the repository instance from the given Repository class
*
* @param repo - The repository class to retrieve the instance of
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async getRepository(repo) {
return new repo();
}
/**
* Add the dataSource to this application.
*
* @param dataSource - The dataSource to add.
* @param name - The binding name of the datasource; defaults to dataSource.name
*
* @example
* ```ts
*
* const ds: juggler.DataSource = new juggler.DataSource({
* name: 'db',
* connector: 'memory',
* });
*
* app.dataSource(ds);
*
* // The datasource can be injected with
* constructor(@inject('datasources.db') dataSource: DataSourceType) {
*
* }
* ```
*/
dataSource(dataSource, name) {
throw new Error();
}
/**
* Add a component to this application. Also mounts
* all the components repositories.
*
* @param component - The component to add.
*
* @example
* ```ts
*
* export class ProductComponent {
* controllers = [ProductController];
* repositories = [ProductRepo, UserRepo];
* providers = {
* [AUTHENTICATION_STRATEGY]: AuthStrategy,
* [AUTHORIZATION_ROLE]: Role,
* };
* };
*
* app.component(ProductComponent);
* ```
*/
component(component) {
throw new Error();
}
/**
* Get an instance of a component and mount all it's
* repositories. This function is intended to be used internally
* by component()
*
* @param component - The component to mount repositories of
*/
mountComponentRepository(component) { }
/**
* Update or recreate the database schema for all repositories.
*
* **WARNING**: By default, `migrateSchema()` will attempt to preserve data
* while updating the schema in your target database, but this is not
* guaranteed to be safe.
*
* Please check the documentation for your specific connector(s) for
* a detailed breakdown of behaviors for automigrate!
*
* @param options - Migration options, e.g. whether to update tables
* preserving data or rebuild everything from scratch.
*/
async migrateSchema(options) { }
}
exports.RepositoryMixinDoc = RepositoryMixinDoc;
/**
* Create a binding for the given model class
* @param modelClass - Model class
*/
function createModelClassBinding(modelClass) {
return core_1.Binding.bind(`${keys_1.RepositoryBindings.MODELS}.${modelClass.name}`)
.to(modelClass)
.tag(keys_1.RepositoryTags.MODEL);
}
exports.createModelClassBinding = createModelClassBinding;
//# sourceMappingURL=repository.mixin.js.map
;