Skip to content

Commit

Permalink
feat(repository): allow components to contribute models for DI
Browse files Browse the repository at this point in the history
Add a new optional property `models` to `Component` interface, to allow
extension developers to declaratively contribute model classes to be
bound in the target context.

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
  • Loading branch information
bajtos committed May 21, 2020
1 parent 1013e4f commit e74fc57
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DefaultCrudRepository,
Entity,
juggler,
Model,
ModelDefinition,
Repository,
RepositoryMixin,
Expand Down Expand Up @@ -77,6 +78,23 @@ describe('RepositoryMixin', () => {
expectNoteRepoToBeBound(myApp);
});

it('binds user defined component with models', () => {
@model()
class MyModel extends Model {}

class MyModelComponent {
models = [MyModel];
}

const myApp = new AppWithRepoMixin();
myApp.component(MyModelComponent);

const boundModels = myApp.find('models.*').map(b => b.key);
expect(boundModels).to.containEql('models.MyModel');
const modelCtor = myApp.getSync<typeof MyModel>('models.MyModel');
expect(modelCtor).to.be.equal(MyModel);
});

context('migrateSchema', () => {
let app: AppWithRepoMixin;
let migrateStub: sinon.SinonStub;
Expand Down
69 changes: 59 additions & 10 deletions packages/repository/src/mixins/repository.mixin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,28 +221,59 @@ export function RepositoryMixin<T extends MixinTarget<Application>>(
nameOrOptions?: string | BindingFromClassOptions,
) {
const binding = super.component(componentCtor, nameOrOptions);
this.mountComponentRepositories(componentCtor);
const instance = this.getSync<C & RepositoryComponent>(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()
* by `component()`.
*
* @param component - The component to mount repositories of
* 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(component: Class<unknown>) {
const componentKey = `${CoreBindings.COMPONENTS}.${component.name}`;
const compInstance = this.getSync<{
repositories?: Class<Repository<Model>>[];
}>(componentKey);
mountComponentRepositories(
// accept also component class to preserve backwards compatibility
// TODO(semver-major) Remove support for component class constructor
componentInstanceOrClass: Class<unknown> | RepositoryComponent,
) {
const component = resolveComponentInstance(this);

if (compInstance.repositories) {
for (const repo of compInstance.repositories) {
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: Readonly<Application>) {
if (typeof componentInstanceOrClass !== 'function')
return componentInstanceOrClass;

const componentName = componentInstanceOrClass.name;
const componentKey = `${CoreBindings.COMPONENTS}.${componentName}`;
return ctx.getSync<RepositoryComponent>(componentKey);
}
}

/**
* Bind all model classes provided by a component.
* @param component
* @internal
*/
mountComponentModels(component: RepositoryComponent) {
if (!component.models) return;
for (const m of component.models) {
this.model(m);
}
}

/**
Expand Down Expand Up @@ -287,6 +318,24 @@ export function RepositoryMixin<T extends MixinTarget<Application>>(
};
}

/**
* This interface describes additional Component properties
* allowing components to contribute Repository-related artifacts.
*/
export interface RepositoryComponent {
/**
* An optional list of Repository classes to bind for dependency injection
* via `app.repository()` API.
*/
repositories?: Class<Repository<Model>>[];

/**
* An optional list of Model classes to bind for dependency injection
* via `app.model()` API.
*/
models?: Class<Model>[];
}

/**
* Normalize name or options to `BindingFromClassOptions`
* @param nameOrOptions - Name or options for binding from class
Expand Down

0 comments on commit e74fc57

Please sign in to comment.