Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: use LB3 models in an LB4 app [SPIKE - DO NOT MERGE] #2274

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/v3compat/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# @loopback/v3compat

A compatibility layer allowing LoopBack 3 projects to carry over their
models to LoopBack 4+.
A compatibility layer simplifying migration of LoopBack 3 models to LoopBack 4+.

## Installation

Expand Down
6 changes: 6 additions & 0 deletions packages/v3compat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@
"url": "https://github.com/strongloop/loopback-next.git"
},
"dependencies": {
"@loopback/context": "^1.4.0",
"@loopback/core": "^1.1.3",
"debug": "^4.1.1",
"loopback-datasource-juggler": "^4.5.0"
},
"devDependencies": {
"@loopback/build": "^1.1.0",
"@loopback/rest": "^1.5.1",
"@loopback/testlab": "^1.0.3",
"@loopback/tslint-config": "^1.0.0",
"@types/debug": "^0.0.31",
"@types/node": "^10.1.1"
},
"files": [
Expand Down
44 changes: 44 additions & 0 deletions packages/v3compat/src/compat.mixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/v3compat
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Constructor} from '@loopback/context';
import {Application} from '@loopback/core';
import {Lb3Application} from './core/lb3-application';

/**
* A mixin class for Application that adds `v3compat` property providing
* access to LB3 application-like object.
*
* ```ts
* class MyApplication extends CompatMixin(RestApplication) {
* constructor() {
* const lb3app = this.v3compat;
* const Todo = lb3app.registry.createModel(
* 'Todo',
* {title: {type: 'string', required: true}},
* {strict: true}
* );
* lb3app.dataSource('db', {connector: 'memory'});
* lb3app.model(Todo, {dataSource: 'db'});
* }
* }
* ```
*
* TODO: describe "app.v3compat" property, point users to documentation
* for Lb3Application class.
*
*/
// tslint:disable-next-line:no-any
export function CompatMixin<T extends Constructor<any>>(superClass: T) {
return class extends superClass {
v3compat: Lb3Application;

// tslint:disable-next-line:no-any
constructor(...args: any[]) {
super(...args);
this.v3compat = new Lb3Application((this as unknown) as Application);
}
};
}
3 changes: 3 additions & 0 deletions packages/v3compat/src/core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# v3compat/core

Source code migrated from [loopback](https://github.com/strongloop/loopback).
10 changes: 10 additions & 0 deletions packages/v3compat/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/v3compat
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './lb3-types';
export * from './lb3-application';
export * from './lb3-registry';
export * from './lb3-model';
export * from './lb3-persisted-model';
54 changes: 54 additions & 0 deletions packages/v3compat/src/core/lb3-application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/v3compat
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Application} from '@loopback/core';
import * as debugFactory from 'debug';
import {ModelClass} from './lb3-model';
import {Lb3Registry} from './lb3-registry';
import {DataSource, DataSourceConfig, ModelConfig} from './lb3-types';

const debug = debugFactory('loopback:v3compat:mixin');

export class Lb3Application {
readonly registry: Lb3Registry;

readonly dataSources: {
[name: string]: DataSource;
};

readonly models: {
[name: string]: ModelClass;
};

constructor(protected lb4app: Application) {
this.registry = new Lb3Registry(lb4app);
this.dataSources = Object.create(null);
this.models = Object.create(null);
}

dataSource(name: string, config: DataSourceConfig): DataSource {
debug('registering datasource %s with config %j', name, config);
// TODO: use the implementation from LB3's lib/application.js
const ds = this.registry.createDataSource(name, config);
this.dataSources[name] = ds;
return ds;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to return the created datasource here? I thought we were adding a datasource definition to the app (like app.model below).

}

model(modelCtor: ModelClass, config: ModelConfig) {
debug('registering model %s with config %s', modelCtor.modelName, config);
// TODO: use the implementation from LB3's lib/application.js
if (typeof config.dataSource === 'string') {
const dataSource = this.dataSources[config.dataSource];
config = Object.assign({}, config, {dataSource});
}
this.registry.configureModel(modelCtor, config);
this.models[modelCtor.modelName] = modelCtor;
modelCtor.app = this;
}

deleteModelByName(modelName: string): void {
throw new Error('Not implemented yet.');
}
}
61 changes: 61 additions & 0 deletions packages/v3compat/src/core/lb3-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/v3compat
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {ModelBase} from 'loopback-datasource-juggler';
import {
RemoteMethodOptions,
RemotingErrorHook,
RemotingHook,
} from '../remoting';
import {Lb3Application} from './lb3-application';
import {Lb3Registry} from './lb3-registry';
import {ModelProperties, ModelSettings} from './lb3-types';

export type ModelClass = typeof Model;

export declare class Model extends ModelBase {
static app: Lb3Application;
static registry: Lb3Registry;
// TODO: sharedClass

static setup(): void;
static beforeRemote(name: string, handler: RemotingHook): void;
static afterRemote(name: string, handler: RemotingHook): void;
static afterRemoteError(name: string, handler: RemotingErrorHook): void;
static remoteMethod(name: string, options: RemoteMethodOptions): void;
static disableRemoteMethodByName(name: string): void;

// TODO (later)
// - createOptionsFromRemotingContext
// - belongsToRemoting
// - hasOneRemoting
// - hasManyRemoting
// - scopeRemoting
// - nestRemoting

// TODO fix juggler typings and add this method to ModelBase
static extend<M extends typeof Model = typeof Model>(
modelName: string,
properties?: ModelProperties,
settings?: ModelSettings,
): M;

// LB3 models allow arbitrary additional properties by default
// NOTE(bajtos) I tried to allow consumers to specify a white-list of allowed
// properties, but failed to find a viable way. Also the complexity of such
// solution was quickly growing out of hands.
// tslint:disable-next-line:no-any
[prop: string]: any;
}

export function setupModelClass(registry: Lb3Registry): typeof Model {
const ModelCtor = registry.modelBuilder.define('Model') as typeof Model;
ModelCtor.registry = registry;

// TODO copy implementation of setup, before/after remote hooks, etc.
// from LB3's lib/model.js

return ModelCtor;
}
194 changes: 194 additions & 0 deletions packages/v3compat/src/core/lb3-persisted-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Copyright IBM Corp. 2018. All Rights Reserved.
// Node module: @loopback/v3compat
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Model} from './lb3-model';
import {Lb3Registry} from './lb3-registry';
import {
Callback,
Options,
PlainDataObject,
Filter,
ComplexValue,
Where,
} from './lb3-types';

export type PersistedModelClass = typeof PersistedModel;

export declare class PersistedModel extends Model {
static create(
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static create(
data: ModelData[],
options?: Options,
callback?: Callback<PersistedModel[]>,
): Promise<PersistedModel[]>;

static upsert(
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static updateOrCreate(
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static patchOrCreate(
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static upsertWithWhere(
where: Where,
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static patchOrCreateWithWhere(
where: Where,
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static replaceOrCreate(
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static findOrCreate(
filter: Filter,
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static exists(
id: ComplexValue,
options?: Options,
callback?: Callback<boolean>,
): Promise<boolean>;

static findById(
id: ComplexValue,
filter?: Filter,
options?: Options,
callback?: Callback<boolean>,
): Promise<PersistedModel>;

static find(
filter?: Filter,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel[]>;

static findOne(
filter?: Filter,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static destroyAll(
where?: Where,
options?: Options,
callback?: Callback<CountResult>,
): Promise<CountResult>;

static remove(
where?: Where,
options?: Options,
callback?: Callback<CountResult>,
): Promise<CountResult>;

static deleteAll(
where?: Where,
options?: Options,
callback?: Callback<CountResult>,
): Promise<CountResult>;

static updateAll(
where?: Where,
data?: ModelData,
options?: Options,
callback?: Callback<CountResult>,
): Promise<CountResult>;

static update(
where?: Where,
data?: ModelData,
options?: Options,
callback?: Callback<CountResult>,
): Promise<CountResult>;

static destroyById(
id: ComplexValue,
options?: Options,
callback?: Callback<CountResult>,
): Promise<CountResult>;

static removeById(
id: ComplexValue,
options?: Options,
callback?: Callback<CountResult>,
): Promise<CountResult>;

static deleteById(
id: ComplexValue,
options?: Options,
callback?: Callback<CountResult>,
): Promise<CountResult>;

static replaceById(
id: ComplexValue,
data: ModelData,
options?: Options,
callback?: Callback<PersistedModel>,
): Promise<PersistedModel>;

static count(
where?: Where,
options?: Options,
callback?: Callback<number>,
): Promise<number>;

// TODO: describe PersistedModel API
// - prototype.save
// - prototype.isNewRecord
// - prototype.delete
// - prototype.updateAttribute
// - prototype.updateAttributes
// - prototype.replaceAttributes
// - prototype.setId
// - prototype.getId
// - prototype.getIdName
}

export type ModelData = PlainDataObject | PersistedModel;

export interface CountResult {
count: number;
}

export function setupPersistedModelClass(
registry: Lb3Registry,
): typeof PersistedModel {
const ModelCtor = registry.getModel('Model');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: is the Model string the name for the default Model base class here?

const PersistedModelCtor = ModelCtor.extend<typeof PersistedModel>(
'PersistedModel',
);

// TODO copy impl of setup and DAO methods from LB3's lib/persisted-model.js

return PersistedModelCtor;
}
Loading