Skip to content

Commit

Permalink
feat: poc
Browse files Browse the repository at this point in the history
  • Loading branch information
jannyHou committed Nov 29, 2018
1 parent 7be76a4 commit 976334c
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 2 deletions.
14 changes: 12 additions & 2 deletions examples/todo-list/src/controllers/todo-list.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Filter,
repository,
Where,
AnyObject,
} from '@loopback/repository';
import {
del,
Expand Down Expand Up @@ -92,8 +93,17 @@ export class TodoListController {
},
},
})
async findById(@param.path.number('id') id: number): Promise<TodoList> {
return await this.todoListRepository.findById(id);
async findById(
@param.path.number('id') id: number,
@param.query.object('filter') filter?: Filter<TodoList>,
): Promise<TodoList> {
// somehow the filter sent in the request query is undefined
// will dig more.
// hardcoded the inclusion filter in the PoC PR
const hardcodedFilterForPoC = {
include: [{relation: 'todos'}],
};
return await this.todoListRepository.findById(id, hardcodedFilterForPoC);
}

@patch('/todo-lists/{id}', {
Expand Down
12 changes: 12 additions & 0 deletions examples/todo-list/src/repositories/todo-list.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@loopback/repository';
import {Todo, TodoList} from '../models';
import {TodoRepository} from './todo.repository';
import {InclusionHandlerFactory} from '@loopback/repository';

export class TodoListRepository extends DefaultCrudRepository<
TodoList,
Expand All @@ -32,6 +33,17 @@ export class TodoListRepository extends DefaultCrudRepository<
'todos',
todoRepositoryGetter,
);

// to save time the fk name `todoListId` is provided here as a parameter,
// but should be calculated in the inclusion handler factory,
// details see my comment in the inclusion.ts file

const todoHandler = InclusionHandlerFactory<
Todo,
typeof Todo.prototype.id,
typeof TodoList.prototype.id
>(todoRepositoryGetter, 'todoListId');
this._inclusionHandler.todos = todoHandler;
}

public findByTitle(title: string) {
Expand Down
3 changes: 3 additions & 0 deletions packages/repository/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ export type Fields<MT = AnyObject> = {[P in keyof MT]?: boolean};
* Example:
* `{relation: 'aRelationName', scope: {<AFilterObject>}}`
*/

// The entity type provided for the scope filter is the source model
// while it should be the target(related) model
export interface Inclusion<MT extends object = AnyObject> {
relation: string;
scope?: Filter<MT>;
Expand Down
38 changes: 38 additions & 0 deletions packages/repository/src/repositories/inclusion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// RT means related repository's type
// T
// ID means source model's id type
import {Entity} from '../model';
import {Getter, inject} from '@loopback/core';
import {Filter} from '../';
import {AnyObject} from '..';
import {DefaultCrudRepository} from './legacy-juggler-bridge';
import {inspect} from 'util';

// TE: the target entity
// TID: the ID of target entity
// SID: the ID of source entity
export function InclusionHandlerFactory<TE extends Entity, TID, SID>(
targetRepoGetter: Getter<DefaultCrudRepository<TE, TID>>,
// to support composed fk, eventually the fkName should be an array
// fkNames: string[]
fkName: string,
) {
return async function inclusionHandler(
fk: SID,
// There are two concerns of the Filter interface
// 1st: the Filter interface doesn't provide the related model's
// type for the inclusion filter, see my comment in query.ts
// 2nd: when apply the fk constraint on `where`, it doesn't recognize
// the fkName as a property defined in the `where` filter,
// but an arbitrary string instead. As a workaround I used
// a loose type: Filter<TE & AnyObject>
filter?: Filter<TE & AnyObject>,
): Promise<TE[]> {
const targetRepo = await targetRepoGetter();
filter = filter || {};
filter.where = filter.where || {};
filter.where[fkName] = fk;
// console.log(`inclusion filter: ${inspect(filter)}`);
return await targetRepo.find(filter);
};
}
1 change: 1 addition & 0 deletions packages/repository/src/repositories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './legacy-juggler-bridge';
export * from './kv.repository.bridge';
export * from './repository';
export * from './constraint-utils';
export * from './inclusion';
38 changes: 38 additions & 0 deletions packages/repository/src/repositories/legacy-juggler-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from '../relations';
import {resolveType} from '../type-resolver';
import {EntityCrudRepository} from './repository';
import * as utils from 'util';

export namespace juggler {
export import DataSource = legacy.DataSource;
Expand Down Expand Up @@ -77,6 +78,7 @@ export function ensurePromise<T>(p: legacy.PromiseOrVoid<T>): Promise<T> {
export class DefaultCrudRepository<T extends Entity, ID>
implements EntityCrudRepository<T, ID> {
modelClass: juggler.PersistedModelClass;
_inclusionHandler: {[relation: string]: Function} = {};

/**
* Constructor of DefaultCrudRepository
Expand Down Expand Up @@ -234,15 +236,51 @@ export class DefaultCrudRepository<T extends Entity, ID>
}

async findById(id: ID, filter?: Filter<T>, options?: Options): Promise<T> {
// advanced discussion: cache the related items
const relatedItems = {} as AnyObject;
if (filter && filter.include) {
for (let i of filter.include) {
relatedItems[i.relation] = await this._fetchIncludedItems(
i.relation,
id,
i.scope,
);
}
delete filter.include;
}

const model = await ensurePromise(
this.modelClass.findById(id, filter as legacy.Filter, options),
);
if (!model) {
throw new EntityNotFoundError(this.entityClass, id);
}
// console.log(`related items: ${utils.inspect(relatedItems, {depth: 3})}`);
// console.log(`model: ${utils.inspect(model, {depth: 3})}`);

Object.assign(model, relatedItems);
return this.toEntity(model);
}

async _fetchIncludedItems(
relation: string,
id: ID,
filter?: Filter<AnyObject>,
) {
const handler = this._inclusionHandler[relation];
if (!handler) {
throw new Error('Fetch included items is not supported');
}
const includedItems = await handler(id, filter);
console.log(
`legacy-juggler-bridge fetch includedItems : ${utils.inspect(
includedItems,
{depth: 3},
)}`,
);
return includedItems;
}

update(entity: T, options?: Options): Promise<void> {
return this.updateById(entity.getId(), entity, options);
}
Expand Down

0 comments on commit 976334c

Please sign in to comment.