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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ packages/*/dist*
examples/*/dist*
**/package
.sandbox
packages/cli/generators/datasource/connectors.json
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should commit the latest connectors.json. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's pulled in as part of the build process and will be shipped with the appropriate version of the @loopback/cli because we run build before a release.

Copy link
Contributor

Choose a reason for hiding this comment

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

If the file is added to .gitignore, npm publish will skip it by default. Please check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But we have the dist folders listed in .gitignore and I've verified that they still get published to npm.

Reason being npm searches for .gitignore in each project root and sub-directories ... but for us the .gitignore is at the top most level of the monorepo and not in any package hence no files are ignored.

I also ran npm pack within the cli package and can verify that connectors.json was packed.

18 changes: 18 additions & 0 deletions docs/site/Booting-an-Application.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,24 @@ The `repositories` object supports the following options:
| `nested` | `boolean` | `true` | Look in nested directories in `dirs` for Repository artifacts |
| `glob` | `string` | | A `glob` pattern string. This takes precendence over above 3 options (which are used to make a glob pattern). |

### DataSource Booter

This Booter's purpose is to discover [DataSource](DataSource.md) type Artifacts
and to bind them to the Application's Context. The use of this Booter requires
`RepositoryMixin` from `@loopback/repository` to be mixed into your Application
class.

You can configure the conventions used in your project for a DataSource by
passing a `datasources` object on `BootOptions` property of your Application.
The `datasources` object support the following options:

| Options | Type | Default | Description |
| ------------ | -------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------- |
| `dirs` | `string \| string[]` | `['datasources']` | Paths relative to projectRoot to look in for DataSource artifacts |
| `extensions` | `string \| string[]` | `['.datasource.js']` | File extensions to match for DataSource artifacts |
| `nested` | `boolean` | `true` | Look in nested directories in `dirs` for DataSource artifacts |
| `glob` | `string` | | A `glob` pattern string. This takes precendence over above 3 options (which are used to make a glob pattern). |

### Custom Booters

A custom Booter can be written as a Class that implements the `Booter`
Expand Down
3 changes: 3 additions & 0 deletions docs/site/Concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ LoopBack 4 introduces some new concepts that are important to understand:
`@loopback/repository-json-schema` module uses the decorators' metadata to
build a matching JSON Schema.

- [**DataSources**](DataSources.md): A named configuration for a Connector
instance that represents data in an external system.

- [**Repository**](Repositories.md): A type of service that represents a
collection of data within a DataSource.

Expand Down
6 changes: 3 additions & 3 deletions docs/site/Controller-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ the name.

The tool will prompt you for:

- Name of the controller. If the name had been supplied from the command line,
the prompt is skipped and the controller is built with the name from the
- **Name of the controller.** If the name had been supplied from the command
line, the prompt is skipped and the controller is built with the name from the
command-line argument.
- Type of the controller. You can select from the following types:
- **Type of the controller.** You can select from the following types:
- **Empty Controller** - An empty controller definition
- **REST Controller with CRUD Methods** - A controller wired up to a model and
repository definition, with pre-defined CRUD methods.
Expand Down
60 changes: 60 additions & 0 deletions docs/site/DataSource-generator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
lang: en
title: 'DataSource generator'
keywords: LoopBack 4.0, LoopBack 4
tags:
sidebar: lb4_sidebar
permalink: /doc/en/lb4/DataSource-generator.html
summary:
---

{% include content/generator-create-app.html lang=page.lang %}

### Synopsis

Adds new [DataSource](Datasources.md) class and config files to a LoopBack
application.

```sh
lb4 datasource [options] [<name>]
```

### Options

`--connector` : Name of datasource connector

This can be a connector supported by LoopBack / Community / Custom.

{% include_relative includes/CLI-std-options.md %}

### Arguments

`<name>` - Required name of the datasource to create as an argiment to the
command. If provided, the tool will use that as the default when it prompts for
the name.

### Interactive Prompts

The tool will prompt you for:

- **Name of the datasource.** _(dataSourceName)_ If the name had been supplied
from the command line, the prompt is skipped and the datasource is built with
the name from the command-line argument.
- **Name of connector.** If not supplied via command line, you will be presented
with a list of connector to select from (including an `other` option for
custom connector).
- **Connector configuration.** If the connector is not a custom connector, the
CLI will prompt for the connector configuration information.

### Output

Once all the prompts have been answered, the CLI will do the following:

- Install `@loopback/repository` and the connector package (if it's not a custom
connector).
- Create a file with the connector configuration as follows:
`/datasources/${dataSource.dataSourceName}.datasource.json`
- Create a DataSource class which recieves the connector config using
[Dependency Injection](Dependency-injection.md) as follows:
`/datasources/${dataSource.dataSourceName}.datasource.ts`
- Update `index.ts` to export the newly created DataSource class.
46 changes: 46 additions & 0 deletions docs/site/DataSources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
lang: en
title: 'DataSources'
keywords: LoopBack 4.0, LoopBack 4
tags:
sidebar: lb4_sidebar
permalink: /doc/en/lb4/DataSources.html
summary:
---

## Overview

A `DataSource` in LoopBack 4 is a named configuration for a Connector instance
that represents data in an external system. The Connector is used by
`legacy-juggler-bridge` to power LoopBack 4 Repositories for Data operations.

### Creating a DataSource

It is recommended to use the [`lb4 datasource` command](DataSource-generator.md)
provided by the CLI to generate a DataSource. The CLI will prompt for all
necessary connector information and create the following files:

- `${dataSource.dataSourceName}.datasource.json` containing the connector
configuration
- `${dataSource.dataSourceName}.datasource.ts` containing a class extending
`juggler.DataSource`. This class can be used to override the default
DataSource behaviour programaticaly. Note: The connector configuration stored
in the `.json` file is injected into this class using
[Dependency Injection](Dependency-injection.md).

Both the above files are generated in `src/datasources/` directory by the CLI.
It will also update `src/datasources/index.ts` to export the new DataSource
class.

Example DataSource Class:

```ts
import {inject} from '@loopback/core';
import {juggler, DataSource} from '@loopback/repository';

export class DbDataSource extends juggler.DataSource {
constructor(@inject('datasources.config.db') dsConfig: DataSource) {
super(dsConfig);
}
}
```
3 changes: 2 additions & 1 deletion docs/site/Glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ values for writing web applications and APIs. For more information, see
**Controller**: The implementation of API endpoints.

**DataSource**: A named configuration for a Connector instance that represents
data in an external system.
data in an external system. For more information, see
[DataSource](DataSource.md).

**Element:** The building blocks of a Sequence, such as route, params, and
result. For more information, see [Sequence](Sequence.md#elements).
Expand Down
8 changes: 8 additions & 0 deletions docs/site/sidebars/lb4_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ children:
url: Controllers.html
output: 'web, pdf'

- title: 'DataSources'
url: DataSources.html
output: 'web, pdf'

- title: 'Routes'
url: Routes.html
output: 'web, pdf'
Expand Down Expand Up @@ -127,6 +131,10 @@ children:
url: Controller-generator.html
output: 'web, pdf'

- title: 'DataSource generator'
url: DataSource-generator.html
output: 'web, pdf'

- title: 'Extension generator'
url: Extension-generator.html
output: 'web, pdf'
Expand Down
17 changes: 9 additions & 8 deletions docs/site/tables/lb4-artifact-commands.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
</thead>
<tbody>
<tr>
<td>
<code>lb4 controller</code>
</td>
<td> Add a new controller to a LoopBack 4 application
</td>
<td>
<a href="Controller-generator.html">Controller generator</a>
</td>
<td><code>lb4 controller</code></td>
<td>Add a new controller to a LoopBack 4 application</td>
<td><a href="Controller-generator.html">Controller generator</a></td>
</tr>

<tr>
<td><code>lb4 datasource</code></td>
<td>Add a new datasource to a LoopBack 4 application</td>
<td><a href="DataSource-generator.html">DataSource generator</a></td>
</tr>

</tbody>
</table>
48 changes: 16 additions & 32 deletions docs/site/todo-tutorial-datasource.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,22 @@ create a datasource definition to make this possible.

### Building a Datasource

Create a new folder in the root directory of the project called `config`, and
then inside that folder, create a `datasources.json` file. For the purposes of
this tutorial, we'll be using the memory connector provided with the Juggler.

#### config/datasources.json

```json
{
"name": "db",
"connector": "memory",
"file": "./data/db.json"
}
```

Inside the `src/datasources` directory create a new file called
`db.datasource.ts`. This file will create a strongly-typed export of our
datasource using the `juggler.DataSource`, which we can consume in our
application via injection.

#### src/datasources/db.datasource.ts

```ts
import * as path from 'path';
import {juggler} from '@loopback/repository';

const dsConfigPath = path.resolve(
__dirname,
'../../../config/datasources.json',
);
const config = require(dsConfigPath);

export const db = new juggler.DataSource(config);
From inside the project folder, we'll run the `lb4 datasource` command to create
a DataSource. For the purposes of this tutorial, we'll be using the memory
connector provided with the Juggler.

```sh
lb4 datasource
? Datasource name: db
? Select the connector for db: In-memory db (supported by StrongLoop)
? window.localStorage key to use for persistence (browser only):
? Full path to file for persistence (server only): ./data/db.json

create src/datasources/db.datasource.json
create src/datasources/db.datasource.ts
update src/datasources/index.ts

Datasource db is now created in src/datasources/
```

Create a `data` folder in the applications root and add a new file called
Expand Down
13 changes: 0 additions & 13 deletions examples/todo/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import {ApplicationConfig} from '@loopback/core';
import {RestApplication} from '@loopback/rest';
import {MySequence} from './sequence';
import {db} from './datasources/db.datasource';

/* tslint:disable:no-unused-variable */
// Binding and Booter imports are required to infer types for BootMixin!
Expand Down Expand Up @@ -40,17 +39,5 @@ export class TodoListApplication extends BootMixin(
nested: true,
},
};

this.setupDatasources();
}

setupDatasources() {
// This will allow you to test your application without needing to
// use a "real" datasource!
const datasource =
this.options && this.options.datasource
? new juggler.DataSource(this.options.datasource)
: db;
this.dataSource(datasource);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "db",
"connector": "memory",
"localStorage": "",
"file": "./data/db.json"
}
29 changes: 12 additions & 17 deletions examples/todo/src/datasources/db.datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,17 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import * as path from 'path';
// The juggler reference must exist for consuming code to correctly infer
// type info used in the "db" export (contained in juggler.DataSource).
// tslint:disable-next-line:no-unused-variable
import {juggler} from '@loopback/repository';
import {inject} from '@loopback/core';
import {juggler, DataSource} from '@loopback/repository';
const config = require('./db.datasource.json');
Copy link
Member

Choose a reason for hiding this comment

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

Can we use import * as config from './db.datasource.json' to get more type information?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I keep getting an error when I do this as follows: Type 'typeof FILENAME' is not assignable to type 'DataSource' --- and the new import from JSON in TS is in 2.9 but we had to revert back to 2.8. This will have to be addressed in a follow up PR once we can use TS 2.9


const dsConfigPath = path.resolve(
__dirname,
'../../../config/datasources.json',
);
const config = require(dsConfigPath);
export class DbDataSource extends juggler.DataSource {
static dataSourceName = 'db';

// TODO(bajtos) Ideally, datasources should be created by @loopback/boot
// and registered with the app for dependency injection.
// However, we need to investigate how to access these datasources from
// integration tests where we don't have access to the full app object.
// For example, @loopback/boot can provide a helper function for
// performing a partial boot that creates datasources only.
Copy link
Member

Choose a reason for hiding this comment

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

Could you please clarify how integration tests can access datasource configuration now? I guess the answer could be found somewhere in the code, but it will be easier for reviewers if you could summarize it in a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One can access the config / DataSource by getting it from the context. I'm not sure of the question here ...

Copy link
Member

Choose a reason for hiding this comment

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

In integration tests, there is no Application/Context object. We want to test the integration between a repository and the backing database, not the integration with the app/context.

Also test-data-builders need to access a repository instance directly, see e.g. http://loopback.io/doc/en/lb4/Implementing-features.html#update-test-helpers-and-the-controller-use-real-model-and-repository

async function givenEmptyDatabase() {
  await new ProductRepository().deleteAll();
}

async function givenProduct(data: Partial<Product>) {
  return await new ProductRepository().create(
    Object.assign(
      {
        name: 'a-product-name',
        slug: 'a-product-slug',
        price: 1,
        description: 'a-product-description',
        available: true,
      },
      data,
    ),
  );
}

Copy link
Member

Choose a reason for hiding this comment

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

For the scope of this pull request, it's enough to allow tests to instantiate DbDataSource without the need to create a custom datasource config:

const db = new DbDataSource();

This can be achieved e.g. by adding a default value for dsConfig argument, as discussed and shown in #1385 (comment).

I created a follow-up issue to improve the overall design to allow tests to modify the default datasource configuration, see #1396

export const db = new juggler.DataSource(config);
constructor(
@inject('datasources.config.db', {optional: true})
Copy link
Member

Choose a reason for hiding this comment

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

An idea to consider - out of scope of this pull request.

@inject.optional('datasources.config.db')

Copy link
Contributor Author

@virkt25 virkt25 Jun 13, 2018

Choose a reason for hiding this comment

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

@inject.optional doesn't exist yet in our code but I do like the idea.

Copy link
Contributor

Choose a reason for hiding this comment

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

We allow @inject('datasources.config.db', {optional: true});

dsConfig: DataSource = config,
) {
super(dsConfig);
}
}
6 changes: 6 additions & 0 deletions examples/todo/src/datasources/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: @loopback/example-todo
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './db.datasource';
18 changes: 14 additions & 4 deletions examples/todo/test/acceptance/application.acceptance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ describe('Application', () => {
before(givenAnApplication);
before(async () => {
await app.boot();

/**
* Override DataSource to not write to file for testing. Since we aren't
* persisting data to file and each injection normally instatiates a new
* instance, we must change the BindingScope to a singleton so only one
* instance is created and used for all injections (preserving access to
* the same memory space).
Copy link
Member

Choose a reason for hiding this comment

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

Every datasource must be always bound in a singleton scope! A datasource holds a list of Model classes, a connection pool, etc. We cannot create a new datasource for each incoming HTTP request, juggler was not designed that way.

*/
app.bind('datasources.config.db').to({
name: 'db',
connector: 'memory',
Copy link
Member

Choose a reason for hiding this comment

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

-1 for duplicating most of the configuration of db datasource. Could you please try to find a way how to rewrite this code so that it changes only the relevant flags (e.g. set file to undefined - I don't remember exact memory config options)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just binding {file: undefined}won't work as a DataSource needs the name and connector. See my comment above with regards to merging the bound value with the default config.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess an alternative would be to do the merge here instead of the datasource -- I think I like this option better.

const dsKey = 'datasources.config.db';
const config = await app.get(dsKey);
app.bind(dsKet).to(Object.assign(config, {file: undefined}));

Copy link
Member

Choose a reason for hiding this comment

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

I guess an alternative would be to do the merge here instead of the datasource -- I think I like this option better.

Yes please, that's what I had in mind 👍

Copy link
Member

Choose a reason for hiding this comment

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

I created a follow-up issue to improve the overall design to allow tests to modify the default datasource configuration, see #1396

For the scope of this pull request, I am proposing to go with a solution that's fastest to land. We can use the code snippet shown in https://github.com/strongloop/loopback-next/pull/1385/files#r192946885, or even leave {file: undefined} change out of scope and simply use the same datasource config as the application uses at runtime.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does datasources.config.db follow any convention?

Copy link
Contributor Author

@virkt25 virkt25 Jun 13, 2018

Choose a reason for hiding this comment

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

convention for naming or it's config? It's an optional binding a user can set to override the DataSource config (default coming from the generated JSON file). This is more for testing ... with #1396 looking to improve this further.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does @loopback/boot bind datasource config to datasources.config.<dsName>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope. It did in the earlier iteration but since we import the config this is the default key that gets set that a user can leverage in testing to override the default config if they wanted to -- it's injected with {optional: true}

});
Copy link
Contributor

Choose a reason for hiding this comment

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

.inScope(BindingScope.SINGLETON) here? (IIUC from the comment above)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Only a DataSource instance needs to be bound as a singleton. The config -- which is just the declarative JSON config can be bound as normal to the context.


// Start Application
await app.start();
});
before(givenARestServer);
Expand Down Expand Up @@ -109,10 +123,6 @@ describe('Application', () => {
rest: {
port: 0,
},
datasource: {
name: 'db',
connector: 'memory',
},
});
}

Expand Down
Loading