Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
113 changes: 113 additions & 0 deletions docs/site/Importing-LB3-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
lang: en
title: 'Importing models from LoopBack 3 projects'
keywords: LoopBack 4.0, Migration
sidebar: lb4_sidebar
permalink: /doc/en/lb4/Importing-LB3-models.html
---

## Synopsis

To simplify migration from LoopBack 3, LoopBack 4 provides a CLI tool to import
LoopBack 3 models into your LoopBack 4 project.

{% include warning.html content="
This command is experimental and not feature-complete yet.
See the list of known limitations below.
" %}

## Overview

Import one or more models from your LB 3.x application by running
`lb4 import-lb3-models` command.

### Arguments

`lb3app`: Path to the directory containing your LoopBack 3.x application.

{% include important.html content="
The generator loads the application via `require()`, it does not
support applications that are unable to boot (throw errors at startup).
" %}

### Options

`outDir`: Directory where to write the generated source file. Default:
`src/models`

## Known limitations

{% include note.html content="
Please up-vote the tracking GitHub issue for scenarios that are important to
your project. It will help us to better prioritize which limitations to remove
first.
" %}

### Connector-specific metadata in property definitions is not imported

_The tracking GitHub issue:
[loopback-next#3810](https://github.com/strongloop/loopback-next/issues/3810)_

Workaround: Add this metadata manually to the generated file.

### Nested properties are not upgraded

_The tracking GitHub issue:
[loopback-next#3811](https://github.com/strongloop/loopback-next/issues/3811)_

When a property is defined with a complex object type, the nested property
definitions are not converted from LB3 to LB4 format.

Workaround: Fix the generated definition manually.

### Model relations are not imported

_The tracking GitHub issue:
[loopback-next#3812](https://github.com/strongloop/loopback-next/issues/3812)_

Workaround: define relational metadata & navigational properties manually.

### Models inheriting from custom base class

_The tracking GitHub issue:
[loopback-next#3813](https://github.com/strongloop/loopback-next/issues/3813)_

Models inheriting from application-specific models (including LB3 built-in
models like `User`) cannot be imported yet.

Workaround:

1. Modify your LB3 model to inherit from `Model`, `PersistedModel` or
`KeyValueModel`.

2. Import the model to LB4

3. Update the imported model to inherit for the desired application-specific
model.

### MongoDB's `ObjectID` type

The tracking GitHub issue:
[loopback-next#3814](https://github.com/strongloop/loopback-next/issues/3814).

For models attached to MongoDB datasource, the imported LB4 model contains
incorrect definition of the primary key property of `ObjectID` type.

As a workaround, you can change the property definition from:

```ts
@property({
type: ObjectID;
})
id: ObjectID;
```

to:

```ts
@property({
type: 'string',
mongodb: {dataType: 'ObjectID'}
})
id: string;
```
4 changes: 4 additions & 0 deletions docs/site/sidebars/lb4_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,10 @@ children:
url: DataSource-generator.html
output: 'web, pdf'

- title: 'Import models from LoopBack 3'
url: Importing-LB3-models.html
output: 'web, pdf'

- title: 'Model generator'
url: Model-generator.html
output: 'web, pdf'
Expand Down
7 changes: 5 additions & 2 deletions docs/site/tables/lb4-artifact-commands.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
<td><a href="DataSource-generator.html">DataSource generator</a></td>
</tr>

<tr>
<td><code>lb4 import-lb3-models</code></td>
<td>Import one or more LoopBack 3 models to a LoopBack 4 application</td>
<td><a href="Importing-LB3-models.html">Importer for LoopBack 3 models</a></td>
</tr>
<tr>
<td><code>lb4 model</code></td>
<td>Add a new model to a LoopBack 4 application</td>
Expand Down Expand Up @@ -70,7 +75,5 @@
<td>Generate interceptors</td>
<td><a href="Interceptor-generator.html">Global interceptor generator</a></td>
</tr>


</tbody>
</table>
162 changes: 162 additions & 0 deletions packages/cli/generators/import-lb3-models/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

const chalk = require('chalk');
const path = require('path');
const BaseGenerator = require('../../lib/base-generator');
const modelUtils = require('../../lib/model-discoverer');
const debug = require('../../lib/debug')('import-lb3-models');
const utils = require('../../lib/utils');
const {loadLb3App} = require('./lb3app-loader');
const {importLb3ModelDefinition} = require('./migrate-model');
const {canImportModelName} = require('./model-names');

module.exports = class Lb3ModelImporter extends BaseGenerator {
constructor(args, opts) {
super(args, opts);

this.argument('lb3app', {
type: String,
required: true,
description:
'Path to your LoopBack 3.x application. ' +
'This can be a project directory (e.g. "my-lb3-app") or ' +
'the server file (e.g. "my-lb3-app/server/server.js").',
});

this.option('outDir', {
type: String,
description: 'Directory where to write the generated source file',
default: 'src/models',
});
}

async processOptions() {
this.sourceAppDir = this.args[0];
this.artifactInfo.outDir = this.options.outDir;
this.artifactInfo.relPath = path.relative(
this.destinationPath(),
this.artifactInfo.outDir,
);
return super.setOptions();
}

async logExperimentalStatus() {
this.log(
chalk.red(`
WARNING: This command is experimental and not feature-complete yet.
Learn more at https://loopback.io/doc/en/lb4/Importing-LB3-models.html
`),
);
}

/**
* Ensure CLI is being run in a LoopBack 4 project.
*/
checkLoopBackProject() {
if (this.shouldExit()) return;
return super.checkLoopBackProject();
}

async loadTheApp() {
this.lb3app = await loadLb3App(this.sourceAppDir);
this.modelRegistry = this.lb3app.registry.modelBuilder.models;
}

async promptModels() {
const modelNames = Object.keys(this.modelRegistry).filter(
canImportModelName,
);

debug('Available LB3 models', modelNames);

const prompts = [
{
name: 'modelNames',
message: 'Select models to import:',
type: 'checkbox',
choices: modelNames,
validate: result => !!result.length,
// TODO: add a CLI flag to supply these names programmatically
},
];

const answers = await this.prompt(prompts);
debug('Models chosen:', answers.modelNames);
this.modelNames = answers.modelNames;
}

async migrateSelectedModels() {
if (this.shouldExit()) return;
this.modelFiles = [];

try {
for (const name of this.modelNames) {
await this._migrateSingleModel(name);
}
} catch (err) {
if (err.exit) {
this.exit(err.message);
} else {
throw err;
}
}
}

async _migrateSingleModel(name) {
utils.logClassCreation('model', 'models', name, this.log.bind(this));
const modelCtor = this.modelRegistry[name];
if (typeof modelCtor !== 'function') {
const availableModels = Object.keys(this.modelRegistry)
.filter(canImportModelName)
.join(', ');

this.exit(
`Unknown model name ${name}. Available models: ${availableModels}.`,
);
return;
}

const templateData = importLb3ModelDefinition(
modelCtor,
this.log.bind(this),
);
debug('LB4 model data', templateData);

const fileName = utils.getModelFileName(name);
const fullTargetPath = path.resolve(this.artifactInfo.relPath, fileName);
debug('Model %s output file', name, fullTargetPath);

this.copyTemplatedFiles(
modelUtils.MODEL_TEMPLATE_PATH,
fullTargetPath,
templateData,
);

this.modelFiles.push(fileName);
}

/**
* Iterate through all the models we have discovered and scaffold
*/
async scaffold() {
if (this.shouldExit()) return;
}

async end() {
if (this.shouldExit() || !this._isGenerationSuccessful()) {
await super.end();
return;
}

for (const f of this.modelFiles) {
await this._updateIndexFile(this.artifactInfo.outDir, f);
}

await super.end();
}
};
31 changes: 31 additions & 0 deletions packages/cli/generators/import-lb3-models/lb3app-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

const debug = require('../../lib/debug')('import-lb3-models');
const path = require('path');
const pEvent = require('p-event');

module.exports = {
loadLb3App,
};

// TODO: do we want to share this code with `Lb3AppBooter.loadAndBootTheApp`?
async function loadLb3App(dir) {
debug('Loading LB3 app from', dir);
const lb3App = require(path.resolve(dir));

debug(
'If your LB3 app does not boot correctly then make sure it is using loopback-boot version 3.2.1 or higher.',
);

if (lb3App.booting) {
debug(' waiting for boot process to finish');
// Wait until the legacy LB3 app boots
await pEvent(lb3App, 'booted');
}
return lb3App;
}
Loading