-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
DataSource Declarative CLI + Boot #1385
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! I'll leave the high-level reviews to the senior developers.
It might be better to leave out the make-index
stuff outside of this PR
@@ -0,0 +1,750 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to use the json file in loopback-workspace
? Having a copy like this may be hard to maintain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for avoiding the need to maintain two identical lists.
I don't like the idea of depending on loopback-workspace, as that would add too many dependencies including LoopBack 3.x
What we can do instead:
- Move
connectors.json
from loopback-worspace into a new independent npm package. We can start a new GitHub project or keep the new package in loopback-next monorepo. - Keep connectors in loopback-workspace, add a build step to
@loopback/cli
to download the current/latest version of connectors from GitHub (URL: https://raw.githubusercontent.com/strongloop/loopback-workspace/master/available-connectors.json)
Thoughts?
cc @raymondfeng
describe('lb4 datasource', () => { | ||
beforeEach('reset sandbox', () => sandbox.reset()); | ||
|
||
it('does not run without package.json', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need to look into separating out the common tests across artifact generators like this one. Outside of scope in context of this PR though
a566b7c
to
02bec9a
Compare
import {juggler, DataSource} from '@loopback/repository'; | ||
|
||
export class <%= className %>DataSource extends juggler.DataSource { | ||
constructor(@inject('datasources.config.<%= name %>') dsConfig: DataSource) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dsConfig: DataSource
looks suspicious to me.
export interface DataSource {
name: string; // Name of the data source
connector?: Connector; // The underlying connector instance
settings: AnyObject; // Settings
// tslint:disable-next-line:no-any
[property: string]: any; // Other properties that vary by connectors
}
-
Configuration wise, we allow developers to specify the connector as a string, e.g.
'mysql'
instead ofrequire('loopback-connector-mysql')
. -
DataSource configuration does not contain any
settings
object, these settings (e.g.url
,database
, etc.) are top-level properties of the config object.
I think you should use juggler.Options
type instead, see juggler.DataSource constructors and [definition of Options](https://github.com/strongloop/loopback-datasource-juggler/blob/77f11cda3b6e9e39c1cc039808180129a23d67ff/types/common.d.ts#L13-L16.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make these datasource classes easier to use in tests, I think dsConfig
should have a default value.
import * as CONFIG from './<%= name %>.json';
export {CONFIG};
export class <%= className %>DataSource extends juggler.DataSource {
constructor(@inject('datasources.config.<%= name %>') dsConfig: juggler.Options = CONFIG) {
super(dsConfig);
}
}
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm ... I had used DI for the config based on your comment in the boot spike. That said looking at the case of overriding the dsConfig by binding a partial value (such as setting file
to undefined
in examples/todo
) I think it might be better to merge the default config from the file with the config that is bound to Context ... only question is that is that what a user would expect?
import * as CONFIG from './<%= name %>.json';
export class <%= className %>DataSource extends juggler.DataSource {
constructor(@inject('datasources.config.<%= name %>') dsConfig: juggler.Options = {}) {
super(Object.assign(CONFIG, dsConfig));
}
}
Thoughts? ... I'd like agreement before I go about making changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ With that my only concern is what a developer expects ... do they expect config that lives in context to be used or is a merge what they expect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With regards to the first comment, I wasn't too sure of the purpose of the settings
object since I did realize that the settings are actually a top level object. I can change it to juggler.Options
--
What is the purpose of the DataSource
interface as it's defined here? -- Is it just a remnant of something before the refactor / move to juggler.DataSource
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of the DataSource interface as it's defined here? -- Is it just a remnant of something before the refactor / move to juggler.DataSource?
No idea. @raymondfeng could you please clarify?
I had used DI for the config based on your comment in the boot spike.
Sure, I am fine with using DI, I think the Todo example app is already showing why we need that.
I think it might be better to merge the default config from the file with the config that is bound to Context .
That's a bad idea based on my past experience with loopback-boot. The trouble with merging is that users wishing to obtain a specific datasource configuration must unset all unwanted properties provided in the "default" config, which is pretty much impossible.
For example, consider the case where you want to configure a MySQL datasource to use a specific user+password+database combination while ensuring that no other datasource options are set. Now imagine somebody modifies the JSON configuration and adds let's say charset
property. Now we need to update the code wanting only user+password+database code to exclude charset
from the original config.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to use import
for the json directly within the generated TS file (supported by 2.9.x - https://blogs.msdn.microsoft.com/typescript/2018/05/31/announcing-typescript-2-9/#json-imports)
my-db.datasource.ts
import config from './my-db.json'; // Unfortunately it cannot be `my-db.datasource.json`.
export class MyDBDataSource extends juggler.DataSource {
constructor() {
super(config);
}
}
@@ -0,0 +1,750 @@ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for avoiding the need to maintain two identical lists.
I don't like the idea of depending on loopback-workspace, as that would add too many dependencies including LoopBack 3.x
What we can do instead:
- Move
connectors.json
from loopback-worspace into a new independent npm package. We can start a new GitHub project or keep the new package in loopback-next monorepo. - Keep connectors in loopback-workspace, add a build step to
@loopback/cli
to download the current/latest version of connectors from GitHub (URL: https://raw.githubusercontent.com/strongloop/loopback-workspace/master/available-connectors.json)
Thoughts?
cc @raymondfeng
*/ | ||
app.bind('datasources.config.db').to({ | ||
name: 'db', | ||
connector: 'memory', |
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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}));
There was a problem hiding this comment.
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 👍
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>
?
There was a problem hiding this comment.
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}
* 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). |
There was a problem hiding this comment.
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.
// 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ...
There was a problem hiding this comment.
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,
),
);
}
There was a problem hiding this comment.
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
name: 'db', | ||
connector: 'memory', | ||
}); | ||
app.find('datasources.db')[0].inScope(BindingScope.SINGLETON); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my earlier comment - BindingScope.SINGLETON
must be applied to all datasources by @loopback/boot
.
@@ -97,9 +97,11 @@ export function RepositoryMixin<T extends Class<any>>(superClass: T) { | |||
* } | |||
* ``` | |||
*/ | |||
dataSource(dataSource: juggler.DataSource, name?: string) { | |||
dataSource(dataSource: Class<juggler.DataSource>, name?: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel LB4 apps should not be required to always define a new DataSource class. Can we support dual usage allowing both a DataSource instance and a DataSource class?
dataSource(dataSource: juggler.DataSource | Class<juggler.DataSource>, name? string): void {
if (typeof juggler.DataSource === 'function') {
// handle the Class case
} else {
// handle the instance case
}
}
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a use case you have in mind where someone will prefer to create an instance instead of a DataSource class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import {Application} from '@loopback/core';
import {juggler} from '@loopback/repository';
const app = new Application();
app.dataSource(new juggler.DataSource('db', {connector: 'memory'}));
// etc.
While this is not a typical usage in LB4 apps scaffolded by our CLI tooling, it makes it much much simpler to write short scripts reproducing certain behavior (e.g. a bug).
@@ -97,9 +97,11 @@ export function RepositoryMixin<T extends Class<any>>(superClass: T) { | |||
* } | |||
* ``` | |||
*/ | |||
dataSource(dataSource: juggler.DataSource, name?: string) { | |||
dataSource(dataSource: Class<juggler.DataSource>, name?: string) { | |||
const dataSourceKey = `datasources.${name || dataSource.name}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using dataSource.name
as the default name value changes the behavior quite a lot.
Before:
app.dataSource(new DataSource({name: 'db', connector: 'memory'}));
// -> name is "db"
After your change:
app.bind('datasources.config.db').to(({name: 'db', connector: 'memory'});
app.dataSource(DbDataSource);
// -> name is DbDataSource
IMO, the name
argument must be required when registering a datasource via a class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The boot
module tries to mitigate this by passing in a name
to app.dataSource
where it removes the suffix DataSource
-- if present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, this API needs to support more than just our boot
module. We want to encourage people to build their own boot-strapper if they don't like our convention or have a specific use case were our conventions don't work well.
One use case is to write a short script that reproduces certain behavior and calls all LB APIs directly, without going through the boot module. Our API should guide the user to do the right thing, i.e. ask them to provide a datasource name if they are passing a class constructor.
this.bind(dataSourceKey).to(dataSource); | ||
this.bind(dataSourceKey) | ||
.toClass(dataSource) | ||
.tag('datasource'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in another comment, we must change the scope of all datasource binding to a SINGLETON.
); | ||
} else { | ||
this.classes.forEach(cls => { | ||
const name = kebabCase(cls.name.replace('DataSource', '')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bajtos This is where the name for binding the DataSource
class gets changed from DbDataSource
to db
.
Now that I look at it, maybe this would be better in the .dataSource
function so anyone using it directly also benefits from this and not just boot users. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, this adds too much coupling between our core/repository APIs, the way how boot operates and the naming conventions we use in files generated by our tooling.
Ideally, the core & repository APIs should be agnostic of any class-naming conventions.
Also, what if we have DbDataSource
class with JSON config file that says name: "mysql-db"
(imagine we started with the same name but then a change was made in one of these two places only)? Which name will take precedence? How are our users going to learn about these precedence rules? Isn't this making the learning curve more steeper than needed?
* Ask for DataSource Name -- Must be unique | ||
*/ | ||
promptArtifactName() { | ||
return super.promptArtifactName(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should prompt for the name
of a datasource, not the class name.
Talked with @raymondfeng and agreed with on the following:
Outstanding Questions / Todos / Concerns
|
+1
+1 Please note that
+1 to support more flavors. I have a concern though - can we reliably distinguish between a DataSource instance and a DataSource config object? Both values are an object. (DataSource class is a function, that's easy to tell apart.) I have posted two comments regarding application integration/acceptance tests, I hope they will help us to keep the scope of this PR small(er) and get it landed sooner: See also the follow-up issue I created: #1396 How to modify datasource configuration in integration/acceptance tests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new code looks mostly good, I have few minor comments to address.
docs/site/DataSource-generator.md
Outdated
- Install `@loopback/repository` and the connector package (if it's not a custom | ||
connector). | ||
- Create a file with the connector configuration as follows: | ||
`/datasources/${connector.name}.datasource.json` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the file name should be based on datasource name provided by the user (e.g. db
), not the connector name (e.g. memory
), because a single connector can be used for multiple datasources.
The same comment applies to the next line below.
docs/site/DataSources.md
Outdated
provided by the CLI to generate a DataSource. The CLI will prompt for all necessary connector information and create the following files: | ||
|
||
- `${connector.name}.datasource.json` containing the connector configuration | ||
- `${connector.name}.datasource.ts` containing a class extending `juggler.DataSource`. This class can be used to override the default DataSource |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto, I think we should use dataSourceName
instead of connector.name
.
? window.localStorage key to use for persistence (browser only): | ||
? Full path to file for persistence (server only): ./data/db.json | ||
|
||
create src/datasources/ds.datasource.json |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The prompt shows Datasource name: db
, but the file name is ds.datasource.json
. I think it should be db.datasource.json
?
import {juggler} from '@loopback/repository'; | ||
import {inject} from '@loopback/core'; | ||
import {juggler, DataSource} from '@loopback/repository'; | ||
const config = require('./db.datasource.json'); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
// performing a partial boot that creates datasources only. | ||
export const db = new juggler.DataSource(config); | ||
constructor( | ||
@inject('datasources.config.db', {optional: true}) |
There was a problem hiding this comment.
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')
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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});
dataSource(dataSource: juggler.DataSource, name?: string) { | ||
const dataSourceKey = `datasources.${name || dataSource.name}`; | ||
this.bind(dataSourceKey).to(dataSource); | ||
dataSource(dataSource: Class<juggler.DataSource>, name?: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we agreed that app.dataSource
should allow both Class<juggler.DataSource>
and juggler.DataSource
?
async function download() { | ||
var file = fs.createWriteStream(dest); | ||
var request = https | ||
.get(URL, function(response) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, why don't we keep a local copy with @loopback/cli
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize that it's used as part of the build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. Build pulls in the latest from loopback-workspace
as we didn't want to maintain two lists of connectors & didn't want to make loopback-workspace
a dependency.
}); | ||
} | ||
|
||
async function transformConnectorJSON() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we come up a json format to have more metadata about the prompts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what you mean -- using the same format as loopback-workspace
but transformed to be simpler for consumption by CLI prompts -- in LB3 the CLI does the transformation when the command is called, I am preferring to do it whenever we get the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few comments which can be addressed later.
debug(`npmModule - ${pkgs[0]}`); | ||
} | ||
|
||
pkgs.push('@loopback/repository'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to install @loopback/core
here as well since we import inject
from it in the template datasource ejs file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This command is meant to be run inside a loopback 4 project which if generated using CLI should already have @loopback/core
installed but we don't install @loopback/repository
.
* Get a validate function for object/array type | ||
* @param {String} type 'object' OR 'array' | ||
*/ | ||
exports.validateStringObject = function(type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function(type: string)
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CLI is a JS
project not a TS
project so no types here. -- In the future we should definitely consider switching to TS.
return true; | ||
} | ||
|
||
const err = `The value must be a stringified ${type}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this error message applicable to all types of errors we get below?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. This function comes from LB3 DataSource CLI command and it's the same error.
Here's the original reference code: https://github.com/strongloop/generator-loopback/blob/master/lib/helpers.js#L210-L228
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this PR's been ongoing forever and you want to merge it asap, so sorry to 🌧 on your 🎪 xd.
Aside from my comments below, we also need to update boot/docs.json
for the new files.
Also a question, what are the differences between datasources being bound to dataSource.config.NAME
key vs dataSource.NAME
key?
docs/site/DataSource-generator.md
Outdated
|
||
### Synopsis | ||
|
||
Adds new [DataSource] class and config files to a LoopBack application. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing link here
docs/site/DataSource-generator.md
Outdated
- 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.html) as follows: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dependency-injection.md
I just realized this is probably really inconsistent among our docs and probably needs to be fixed at one point (outside of scope).
docs/site/DataSources.md
Outdated
### Creating a DataSource | ||
|
||
It is recommended to use the `lb4 datasource` | ||
[command](DataSource-generator.html) provided by the CLI to generate a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: wrap the link around lb4 source
as well
docs/site/DataSources.md
Outdated
`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-inecjtion.html). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong spelling for the link and should me md
response.pipe(file); | ||
file.on('finish', function() { | ||
file.close(); | ||
transformConnectorJSON(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await here
* Ask for DataSource Name -- Must be unique | ||
*/ | ||
promptArtifactName() { | ||
debug('Prompting for artifact name'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to not just super.promptArtifactName()
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. super.promptArtifactName()
prompts for utils.toClassName(this.artifactInfo.type) + 'class name:'
but we need to just prompt for DataSource name
and not class name for this generator.
@@ -10,3 +10,4 @@ packages/*/dist* | |||
examples/*/dist* | |||
**/package | |||
.sandbox | |||
packages/cli/generators/datasource/connectors.json |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
const testlab = require('@loopback/testlab'); | ||
|
||
const expect = testlab.expect; | ||
const TestSandbox = testlab.TestSandbox; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a side, what's preventing us from using the import _ from _
pattern here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a JS
project, not a TS
project which is where the import _ from _
. Support for this will come to JS eventually -- current status is Unstable as documented here: https://nodejs.org/api/esm.html
* Get a validate function for object/array type | ||
* @param {String} type 'object' OR 'array' | ||
*/ | ||
exports.validateStringObject = function(type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This utility function needs a test of its own
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
packages/boot/package.json
Outdated
"debug": "^3.1.0", | ||
"glob": "^7.1.2" | ||
"glob": "^7.1.2", | ||
"lodash": "^4.17.10" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any changes with references to lodash. Probably left in as a mistake?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! I was using it for determining the binding name but we since switched to the static dataSourceName
property for that ... and as such no longer need lodash
.
app.bind('datasources.config.db').to({ | ||
name: 'db', | ||
connector: 'memory', | ||
}); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
@@ -155,7 +174,7 @@ export interface AppWithRepository extends Application { | |||
repository(repo: Class<any>): void; | |||
// tslint:disable-next-line:no-any | |||
getRepository<R extends Repository<any>>(repo: Class<R>): Promise<R>; | |||
dataSource(dataSource: juggler.DataSource, name?: string): void; | |||
dataSource(dataSource: Class<juggler.DataSource>, name?: string): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Class<juggler.DataSource> | juggler.DataSource
@@ -236,7 +255,7 @@ export class RepositoryMixinDoc { | |||
* } | |||
* ``` | |||
*/ | |||
dataSource(dataSource: juggler.DataSource, name?: string) {} | |||
dataSource(dataSource: Class<juggler.DataSource>, name?: string) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Class<juggler.DataSource> | juggler.DataSource
Signed-off-by: Taranveer Virk <[email protected]>
var request = https | ||
.get(URL, function(response) { | ||
response.pipe(file); | ||
file.on('finish', async function() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@virkt25 this is not ideal.
- If the async function fails, it returns a promise that will trigger unhandled rejection warning. I think you need to wrap the async function with a regular one that installs
.catch
handler. - I am not sure what happens if the file cannot be written - will
response.pipe(file)
forward the error fromfile
toresponse
stream? I don't think so. Have you considered usingawait pEvent(file, 'finish')
instead?
Alternatively, I think it may be easier to give up on async/await in this script and switch to callbacks . You are already using callbacks in several places, I think the code will become more consistent if callbacks are used everywhere.
Also, IIUC this code, the async function download
will immediately return a fulfilled promise - it's not waiting for the request to finish. If it's intentional, then please remove the async
keyword.
* - Transforms description to message so it can be used by CLI directly | ||
*/ | ||
async function transformConnectorJSON() { | ||
let data = await readFileAsync(DEST, 'utf-8'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are saving the downloaded JSON content to a file first, then read the file and overwrite its content in the second step. Have you considered passing the JSON content between download
and transformConnectorJSON
via in-memory value?
My recommendation is to use request-promise-native, it will take care of stream processing, error handling and flow control for you, and also add support for nice-to-have features like process.env.PROXY
.
A mock-up implementation:
const data = await request(URL, {json: true});
const out = {};
data.forEach(...);
await writeFileAsync(DEST, JSON.stringify(out, null, 2));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had thought of using the request module but didn't want to introduce a dependency unnecessarily. Will add as a dev dependency and address this in a follow up PR.
NOTE: This is based on top of the
indexer
PR -- which has feedback that I will be working to address so it can land before this PR.fixes #1225
PR is broken by commits for easier review
PENDING:
Booter Tests
Example Todo Tests
Example Todo Docs
Other Docs
PR fix(testlab): decache files in TestSandbox when resetting it #1391 needs to land first
PR feat(cli): auto-generate index.ts for exports #1363 needs to land first
Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated