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

feat: add TypeORM support #5801

Merged
merged 1 commit into from
Jul 8, 2020
Merged

feat: add TypeORM support #5801

merged 1 commit into from
Jul 8, 2020

Conversation

hacksparrow
Copy link
Contributor

@hacksparrow hacksparrow commented Jun 22, 2020

Taking the work in #4794 further, this PR adds TypeORM support in LB4. You can now use TypeORM entities and repositories in LB4.

The feature is implemented and working, for usage details refer to https://github.com/strongloop/loopback-next/blob/c7157f85cb8b05a43fdf59eabfd72892bf9a6662/packages/typeorm/README.md.

What's remaining?

  • CLEAN UP the repository
  • Write tests
  • Generate OpenAPI spec
  • Convert filters to TypeORM query (everything not required in the first version, just need to develop a functional high-level idea about how to go about it)
  • Add documentation to website

Checklist

👉 Read and sign the CLA (Contributor License Agreement) 👈

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

👉 Check out how to submit a PR 👈

@hacksparrow hacksparrow mentioned this pull request Jun 22, 2020
3 tasks
@hacksparrow hacksparrow force-pushed the typeorm-yaapa branch 2 times, most recently from af3d788 to 82385ba Compare June 22, 2020 11:11
Copy link
Member

@achrinza achrinza left a comment

Choose a reason for hiding this comment

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

Did a quick skim and noticed some minor issues.

Also, should we have a booter for TypeORM entities, similar to the ones for lb4 models?

Other than that, everything else LGTM.

@@ -0,0 +1,25 @@
Copyright (c) IBM Corp. 2017,2019.
Copy link
Member

Choose a reason for hiding this comment

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

Inconsistent copyright.

Ditto for the other copyright headers.

@@ -0,0 +1,25 @@
Copyright (c) IBM Corp. 2017,2019.
Node module: @loopback/repository
Copy link
Member

Choose a reason for hiding this comment

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

Inconsistent node module name header.

Ditto for the other module name headers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha, yea need to clean up.

@hacksparrow
Copy link
Contributor Author

Also, should we have a booter for TypeORM entities, similar to the ones for lb4 models?

Yes, there's a booter - https://github.com/strongloop/loopback-next/pull/5801/files#diff-2a23c8ed814e1a88b05bcacbeec6689c.

tsconfig.json Outdated
"path": "packages/typeorm/tsconfig.json"
},
{
"path": "sandbox/appa/tsconfig.json"
Copy link
Contributor

Choose a reason for hiding this comment

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

Please rm -rf sandbox/appa.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, need to clean up.

// Server,
// Subscription,
// } from '@loopback/core';

Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove the commented out code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.

export function connection(connectionName?: string) {
return inject(
'',
{decorator: '@typeormConnection'},
Copy link
Contributor

Choose a reason for hiding this comment

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

We typically use typeorm.connection as the name.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed. That's a remnant of something I was trying, will clean it up.

constructor(options: ApplicationConfig = {}) {
super(options);
...
this.component(TypeOrmComponent);
Copy link
Member

Choose a reason for hiding this comment

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

I am proposing to modify TypeOrmMixin to call this.component(TypeOrmComponent) automatically, to get the following user experience:

  • As a LB4 app developer wanting to add TypeORM to my app, I need to change a single place in my app file only.

To be honest, I think it should be possible to design this extension to provide the app Mixin only, it should not be necessary to have a Component class. See how @loopback/repository is implemented - it does not have any Component class either.

injection: Readonly<Injection>,
session: ResolutionSession,
) => {
return getConnection(ctx, connectionName);
Copy link
Member

Choose a reason for hiding this comment

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

IIUC, this creates "virtual" bindings that can be injected but that are not tracked by Context and therefore not visible in context-inspector UI.

Can we find a way how to create regular bindings for individual connections please?

The same comment applies to other inject-like decorators below.

Copy link
Member

Choose a reason for hiding this comment

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

IIUC, this creates "virtual" bindings that can be injected but that are not tracked by Context and therefore not visible in context-inspector UI.

Can we find a way how to create regular bindings for individual connections please?

☝️ Please make sure this comment is either addressed as part of this original PR or there is a follow-up issue to fix binding inspection ASAP.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to self: Create follow up task.

superClass: T,
) {
return class extends superClass {
typeormConnectionOptions: ConnectionOptions[] = [];
Copy link
Member

Choose a reason for hiding this comment

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

As we discussed in the call, I find the design where we keep an index-ordered list of connection options as suboptimal. Instead, I am proposing to store TypeORM's connection manager instance on the application object and create a method like app.connection to register new connections with the app. The API should also allow the user to give each connection a name.

// in the mixin
  return class extends superClass {
    connectionManager: ConnectionManager;
    constructor(...args: any[]) {
      super(args);
    
      this.connectionManager = new ConnectionManager();
   }

    connection(connectionConfig: ConnectionOptions) {
      const connection = this.connectionManager.create(connectionConfig);
      const name = connection.name;
      this.bind(`connections.${name}`).toDynamicValue(() => this.connectionManager.getConnection(name));
    }
}

A use case to consider:

Copy link
Member

Choose a reason for hiding this comment

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

As we discussed in the call, I find the design where we keep an index-ordered list of connection options as suboptimal. Instead, I am proposing to store TypeORM's connection manager instance on the application object and create a method like app.connection to register new connections with the app. The API should also allow the user to give each connection a name.

❗ IMO, this comment must be resolved before this pull request can be landed.

ExpoConnectionOptions,
ExpoDriver,
ExpoQueryRunner,
};
Copy link
Member

Choose a reason for hiding this comment

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

Do we really need that long list of exports 👆 and the long list of imports 👇 ? Isn't it enough to export * from 'typeorm';?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For some reason they seem to have decided to export only these from ./driver.

export {Driver} from "./driver/Driver";
export * from "./driver/mongodb/typings";
export * from "./driver/sqlserver/MssqlParameter";
export {ColumnType} from "./driver/types/ColumnTypes";
export * from "./driver/types/DatabaseType";

So we have to do the importing and exporting for our users.

Copy link
Member

Choose a reason for hiding this comment

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

Fair enough 🤷

* @param entityCtor - The entity constructor (e.g. `Product`)
* @param options - Additional options
*/
export function getModelSchemaRef<T extends object>(
Copy link
Member

Choose a reason for hiding this comment

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

As discussed in the call, I am proposing to drop Ref part and emit regular JSON schema. The ref-based schemas were needed before OpenAPI schema consolidation was introduced. I believe with consolidation in place, we no longer need to worry about avoiding duplicated schema definitions in controller methods.

export function modelToJsonSchema<T extends object>(
  ctor: Function & {prototype: T},
  jsonSchemaOptions: JsonSchemaOptions<T> = {},
): JsonSchema {
  const allColumns: ColumnMetadataArgs[]= getMetadataArgsStorage().columns;
  const modelColumns = allColumns.filter(c => c.target === modelCtor);
  const schema: JsonSchema = {
    // set title (to modelCtor.modelName?), etc.
    properties: {}
  };
  for (c of modelColumns) {
    schema.properties[c.propertyName] = {
      type: c.options.type,
    }
  }
  return schema;
}
// getJsonSchema(modelCtor) --> call modelToJsonSchema under the hood + cache the result
// getModelSchema(modelCtor) --> convert getJsonSchema() to OpenApiSpec format

Copy link
Member

Choose a reason for hiding this comment

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

As discussed in the call, I am proposing to drop Ref part and emit regular JSON schema. The ref-based schemas were needed before OpenAPI schema consolidation was introduced. I believe with consolidation in place, we no longer need to worry about avoiding duplicated schema definitions in controller methods.

❗I'd like you to address my comment before the PR is landed.

Copy link
Member

Choose a reason for hiding this comment

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

If it's too much work to get even a minimal conversion working, then I am proposing the following:

  • rename getModelSchemaRef to getModelSchema
  • change the return type from SchemaRef to SchemaObject
  • change the implementation to return a "dummy" schema describing any object value
export function getModelSchemaRef<T extends object>(
  entityCtor: Function & {prototype: T},
  options?: JsonSchemaOptions<T>,
): SchemaRef {
  // TODO: generate proper OpenAPI schema describing entity properties, etc.
  return {
    title: entityCtor.name,
    type: 'object'
  };
}

Personally, I'll try to get rid of SchemaRef (and thus the dependency on @loopback/rest) by removing the description of the return type or using object instead. For the first iteration, we don't need options either, since they are not supported anyways.

export function getModelSchemaRef<T extends object>(
  entityCtor: Function & {prototype: T},
) {
  return { 
    // ...
  };
}

@bajtos
Copy link
Member

bajtos commented Jun 23, 2020

Convert filters to TypeORM query (everything not required in the first version, just need to develop a functional high-level idea about how to go about it)

Posting few ideas from slack chat:

Juggler implementation starts here: https://github.com/strongloop/loopback-connector/blob/7d43b461d13e2e67fd07cee4ae22c1e19d0ea848/lib/sql.js#L1332-L1362

For TypeORM, we want something like this:

{where: {name: 'miroslav'}, limit: 10} => createQueryBuilder().where('user.name = :name', {name: 'miroslav'}).limit(10);

IMO, TypeORM users should not be forced to require @loopback/repository to get the Filter object (and also getFilterSchemaFor). In the spike, it may be ok to copy the relevant type descriptions & schema builder from @loopback/repository to your new extension. After the spike, I think we should extract Filter, getFilterSchemaFor and other related code into a new shared package, e.g. @loopback/filter.

In LB3, we created loopback-filters package with a similar intention, but it was half-baked and quickly got out of sync with the main filtering logic implemented in the memory connector. So I think it's a valid move to start building a shared filtering package again, and get it right this time :)

super(
projectRoot,
// Set TypeORM connection options if passed in via bootConfig
Object.assign({}, ConnectionDefaults, entityConfig),
Copy link
Contributor

Choose a reason for hiding this comment

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

This is wrong - all properties are merged into the 1st arg and we won't be able to access it afterward.

Copy link
Member

Choose a reason for hiding this comment

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

I believe Object.assign returns the 1st argument and therefore this line works as expected

$ node 
> console.log(Object.assign({}, {foo: 'bar'}))
{ foo: 'bar' }
undefined

/**
* Objects with open properties
*/
export interface AnyObject {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use Record<string, any> instead.

}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see test coverage for the booter.

async load() {
for (const file of this.discovered) {
const exported = require(file);
const connectionKey = Object.keys(exported)[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not clear to me how a connection artifact look like.

Copy link
Member

Choose a reason for hiding this comment

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

+1

I think the actual config object should have ConnectionOptions type, see https://github.com/typeorm/typeorm/blob/master/src/connection/ConnectionOptions.ts and https://github.com/typeorm/typeorm/blob/fc7a4f13f6270c9d5206358ebd78adb0ca690b3d/src/index.ts#L127

Suggested change
const connectionKey = Object.keys(exported)[0];
const connectionKey: ConnectionOptions = Object.keys(exported)[0];

IIUC, you are trying to register the first exported thing as the connection object. I find this design as providing poor user experience. If the file happens to export more than one config object, then we should either load all of them, or tell the user what has been ignored.

Please take a look at how existing booters are dealing with this edge case and implement the same behavior in your booter for consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Copy link
Member

@bajtos bajtos left a comment

Choose a reason for hiding this comment

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

I am afraid most of my comments are still not addressed. Unfortunately, it's not clear what is the scope of your work (and this pull request), therefore it's difficult to draw the line between things that must be resolved now and improvements that can be made later.

IMO, it's important to address the following:

  • Add few basic tests to ensure the code coverage is not dropping too much.
  • Change getModelSchemaRef to getModelSchema
  • Add a list of known limitations to README
  • Redesign how connections are registered in the app (remove an array of connection options, introduce app.connection() method).
  • Get rid of TypeOrmConnections or at least rename it to a singular form.
  • Either remove all coupling with REST/OpenAPI, or at least depend on @loopback/openapi-v3 instead of @loopback/rest

async load() {
for (const file of this.discovered) {
const exported = require(file);
const connectionKey = Object.keys(exported)[0];
Copy link
Member

Choose a reason for hiding this comment

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

+1

I think the actual config object should have ConnectionOptions type, see https://github.com/typeorm/typeorm/blob/master/src/connection/ConnectionOptions.ts and https://github.com/typeorm/typeorm/blob/fc7a4f13f6270c9d5206358ebd78adb0ca690b3d/src/index.ts#L127

Suggested change
const connectionKey = Object.keys(exported)[0];
const connectionKey: ConnectionOptions = Object.keys(exported)[0];

IIUC, you are trying to register the first exported thing as the connection object. I find this design as providing poor user experience. If the file happens to export more than one config object, then we should either load all of them, or tell the user what has been ignored.

Please take a look at how existing booters are dealing with this edge case and implement the same behavior in your booter for consistency.

npm install --save @loopback/typeorm
```

## Usage
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
## Usage
## Basic use

See https://loopback.io/doc/en/contrib/README-guidelines.html

packages/typeorm/README.md Outdated Show resolved Hide resolved
```ts
// src/controllers/book.controller.ts
import {get, post, Request, requestBody} from '@loopback/rest';
import {getModelSchemaRef, Repository, typeorm} from '@loopback/typeorm';
Copy link
Member

Choose a reason for hiding this comment

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

As discussed before, this should be just getModelSchema. (No need to use $ref schemas anymore, OpenAPI consolidation will take care of de-duplicating schemas.)

IMO, @loopback/typeorm should not be coupled with OpenAPI, I think we should either tell users to apply jsonToSchemaObject on the schema produced by @loopback/typeorm, or else introduce a new package for things that are specific to TypeORM+OpenAPI.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IMO, @loopback/typeorm should not be coupled with OpenAPI,

Need elaboration.

Copy link
Member

Choose a reason for hiding this comment

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

@loopback/openapi-v3 provides things like @get and @api decorators.

As a user building gRPC or Pub/Sub application using @loopback/typeorm for persistence, why do I have to install dependencies for OpenAPI, when I am not using any of that?

Copy link
Member

Choose a reason for hiding this comment

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

Also if we add gRPC support in the future and implement code to generate gRPC schemas (protocol buffer message type definitions) from TypeORM entities, are we going to add gRPC to @loopback/typeorm dependencies and thus force REST/OpenAPI users to install gRPC they are not using?

Anyhow, I think this is out of scope of this pull request, but please do create a follow-up task to continue this discussion and rework the code accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to self: Create follow up task.

const book = new Book();
book.title = data.title;
book.published = false;
return await this.bookRepo.save(book);
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 repository.create API?

this.bookRepo.create(data);

See https://typeorm.io/#/repository-api

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The object is not being saved for some reason; don't want to troubleshoot right now, let's handle the highest priority items first. Will give a try again later today, if not, will open a follow up task.

Note to self: Create follow up task.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps add a code comment to this code snippet, to explain the problem to our readers too?

packages/typeorm/src/services/connections.ts Outdated Show resolved Hide resolved
packages/typeorm/src/services/connections.ts Outdated Show resolved Hide resolved
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {JsonSchemaOptions, SchemaRef} from '@loopback/rest';
Copy link
Member

Choose a reason for hiding this comment

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

@loopback/typeorm should not require consumers to use @loopback/rest.

At minimum, change this line to import OpenAPI types from @loopback/openapi-v3.

As I mentioned above, I think it would be best to move OpenAPI-related parts into a new package, but I guess that can be done in a follow-up pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to self: Create follow up task.

* @param entityCtor - The entity constructor (e.g. `Product`)
* @param options - Additional options
*/
export function getModelSchemaRef<T extends object>(
Copy link
Member

Choose a reason for hiding this comment

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

As discussed in the call, I am proposing to drop Ref part and emit regular JSON schema. The ref-based schemas were needed before OpenAPI schema consolidation was introduced. I believe with consolidation in place, we no longer need to worry about avoiding duplicated schema definitions in controller methods.

❗I'd like you to address my comment before the PR is landed.

* @param entityCtor - The entity constructor (e.g. `Product`)
* @param options - Additional options
*/
export function getModelSchemaRef<T extends object>(
Copy link
Member

Choose a reason for hiding this comment

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

If it's too much work to get even a minimal conversion working, then I am proposing the following:

  • rename getModelSchemaRef to getModelSchema
  • change the return type from SchemaRef to SchemaObject
  • change the implementation to return a "dummy" schema describing any object value
export function getModelSchemaRef<T extends object>(
  entityCtor: Function & {prototype: T},
  options?: JsonSchemaOptions<T>,
): SchemaRef {
  // TODO: generate proper OpenAPI schema describing entity properties, etc.
  return {
    title: entityCtor.name,
    type: 'object'
  };
}

Personally, I'll try to get rid of SchemaRef (and thus the dependency on @loopback/rest) by removing the description of the return type or using object instead. For the first iteration, we don't need options either, since they are not supported anyways.

export function getModelSchemaRef<T extends object>(
  entityCtor: Function & {prototype: T},
) {
  return { 
    // ...
  };
}

Copy link
Member

@bajtos bajtos left a comment

Choose a reason for hiding this comment

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

Thank you for addressing my comments, @hacksparrow, I like the new version sooo much more! 😍

The pull request looks mostly good now, I left few non-blocking comments below.

Please work with @raymondfeng to decide which of my suggestions are ok to leave out and make sure to get his approval before landing the PR.

packages/boot/src/booters/typeorm-connection.booter.ts Outdated Show resolved Hide resolved
Comment on lines 29 to 32
constructor(options: ApplicationConfig = {}) {
super(options);
...
this.component(TypeOrmComponent);
...
}
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: we no longer need to show the constructor.

export class AppaApplication extends BootMixin(
  ServiceMixin(TypeOrmMixin(RestApplication)),
) {
  // ...
}

@@ -1,6 +1,7 @@
# @loopback/typeorm

This module enables TypeORM support in LoopBack.
This module enables TypeORM support in LoopBack. For pending features, refer to
the "Pending" section.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
the "Pending" section.
the "Limitations" section.

(To match the real section name.)

Even better:

Suggested change
the "Pending" section.
the [Limitations](#limitations) section below.

(Just make sure the link works this way, I am not sure what's the exact anchor URL.)

@@ -12,25 +13,22 @@ This module enables TypeORM support in LoopBack.
npm install --save @loopback/typeorm
```

## Usage
## Basic Usage
Copy link
Member

Choose a reason for hiding this comment

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

Per our README guidelines:

Suggested change
## Basic Usage
## Basic Use


1. Database migration
2. Custom repository
3. Query filters
Copy link
Member

Choose a reason for hiding this comment

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

Let's clarify this item. I believe it is already possible to use TypeORM filters, right? IIUC, we are missing support for LoopBack-style filters and exposing filters via REST API.

@@ -62,6 +62,6 @@ export namespace typeorm {
* @param connectionName - Optional connection name
*/
async function getConnection(ctx: Context, connectionName?: string) {
const connections = await ctx.get(TypeOrmBindings.CONNECTIONS);
return connections.manager.get(connectionName);
const manager = await ctx.get(TypeOrmBindings.MANAGER);
Copy link
Member

Choose a reason for hiding this comment

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

I expect this method to be called on hot paths, can we make this method sync to improve performance?

function getConnection(ctx: Context, connectionName?: string): Connection {
  const manager = ctx.getSync(TypeOrmBindings.MANAGER);
  return manager.get(connectionName);
}

Feel free to leave this improvement out of scope of this pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to self: Create follow up task.

@lifeCycleObserver('datasource', {
scope: BindingScope.SINGLETON,
})
export class StartStop implements LifeCycleObserver {
Copy link
Member

Choose a reason for hiding this comment

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

Please mention "typeorm" in the class name, to distinguish this class from other similar observers.

For example:

Suggested change
export class StartStop implements LifeCycleObserver {
export class StartStopTypeOrmConnections implements LifeCycleObserver {

I believe we use nouns in class names, perhaps something like TypeOrmLifeCycleManager would be a better name? @raymondfeng do you have any suggestions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using TypeOrmLifeCycleManager.

import {ColumnType} from 'typeorm/driver/types/ColumnTypes';
import {ColumnMetadataArgs} from 'typeorm/metadata-args/ColumnMetadataArgs';

const ModelSchemaCache = new WeakMap();
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: we use camelCase for variable names (CamelCase is for type/class names).

Suggested change
const ModelSchemaCache = new WeakMap();
const modelSchemaCache = new WeakMap();

const cached = ModelSchemaCache.get(modelCtor);
if (cached) {
return cached;
} else {
Copy link
Member

Choose a reason for hiding this comment

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

Please remove the else block to reduce the indentation level.

if (cached) {
  return cached;
}

const allColumns: ColumnMetadataArgs[] = getMetadataArgsStorage().columns;
// ...

return 'boolean';
} else {
throw new Error(
`Type conversion of ${func} to OpenAPI format not implemented.`,
Copy link
Member

Choose a reason for hiding this comment

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

UX improvement:

  • Please include the entity name and the property name in the error message.

Discussion point:

In your proposal, applications cannot use unsupported types at all. Isn't that too limiting, considering that those types works at ORM layer and we just don't know how to describe them in OpenAPI? How about changing this line to print a warning and return a value saying the property does not have any type constraints? (I think undefined should work.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

  } else {
    debug(
      `${entity}.${property}: Type conversion of ${func} to OpenAPI format not implemented.`,
    );
    return undefined;
  }

@hacksparrow hacksparrow force-pushed the typeorm-yaapa branch 2 times, most recently from beb8d98 to 23bb5f4 Compare June 26, 2020 10:33
@bajtos bajtos mentioned this pull request Jun 26, 2020
7 tasks
@hacksparrow hacksparrow force-pushed the typeorm-yaapa branch 2 times, most recently from e178db3 to 150dd4a Compare July 7, 2020 15:39
@hacksparrow hacksparrow marked this pull request as ready for review July 7, 2020 15:39
@hacksparrow hacksparrow requested a review from jannyHou as a code owner July 7, 2020 15:39
title: 'How to use TypeORM with LoopBack'
keywords: LoopBack 4.0, LoopBack 4, TypeORM
sidebar: lb4_sidebar
permalink: /doc/en/lb4/Using-TypeORM-with-LoopBack.html
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use Using-typeorm-with-loopback.html to be consistent with other links.


## Overview

If you would like to use TypeORM with LoopBack, it is possible with the use of
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's introduce TypeORM and have a link here.

}
```

### Creating Connections
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it's better to have Creating Connections come before Creating Entities?

[TypeORM documentation](https://github.com/typeorm/typeorm#installation) for the
supported databases and the underlying drivers.

### Creating controllers
Copy link
Contributor

Choose a reason for hiding this comment

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

Creating Controllers

- title: 'How to use TypeORM with LoopBack'
url: Using-TypeORM-with-LoopBack.html
output: 'web, pdf'

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we move it to be after Accessing Databases?


## Overview

**NOTE**: This module is experimental and evolving.
Copy link
Contributor

Choose a reason for hiding this comment

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

"main": "dist/index.js",
"types": "dist/index.d.ts",
"engines": {
"node": ">=10"
Copy link
Contributor

Choose a reason for hiding this comment

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

=10.16.

"@loopback/rest": "^5.2.0",
"@loopback/service-proxy": "2.3.4",
"@loopback/testlab": "^3.1.7",
"@types/json-schema": "^7.0.5",
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's use the following commands to update its deps on other LB4 packages:

cd packages/typeorm
lb4 update --yes --force --skip-install
cd ../..
node bin/rebuild-package-locks.js @loopback/typeorm

@@ -0,0 +1,53 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should move it to extensions/typeorm?

"access": "public"
},
"devDependencies": {
"@loopback/boot": "^2.3.5",
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be a regular dependency.

},
"dependencies": {
"@loopback/core": "^2.4.1",
"@loopback/openapi-v3": "3.4.4",
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 it as a regular dependency?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

* Default ArtifactOptions for TypeORM connection.
*/
export const ConnectionDefaults: ArtifactOptions = {
dirs: ['connections'],
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's prefix the directory, such as typeorm-connections.

name: 'my-db',
type: 'sqlite',
database: path.join(__dirname, './mydb.sql'),
entities: [Book],
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it makes sense to have a booter for TypeORM entities too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's the TypeORM convention, I think we should leave it alone. We just provide the glue, everything else - Entities, Connections, Respository methods - remain 100% pure TypeORM.

@hacksparrow hacksparrow force-pushed the typeorm-yaapa branch 2 times, most recently from dc00546 to 3e6edaf Compare July 8, 2020 13:21
(e.g. to implement bookRepo.findByTitle(title)).
6. [Database migration](https://github.com/strongloop/loopback-next/issues/5898)

We will be implementing them progressively.
Copy link
Member

Choose a reason for hiding this comment

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

We might want to make it as "Community contribution is welcome".

permalink: /doc/en/lb4/Using-typeorm-with-loopback.html
---

## Overview
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we can use the README.md content directly instead of duplicating the content?
See the JWT authentication extension docs as an example.

@@ -242,6 +242,13 @@ children:
url: Database-migrations.html
output: 'web, pdf'

- title: 'Using TypeORM'
Copy link
Member

Choose a reason for hiding this comment

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

any reason we need a parent item instead of having "How to use TypeORM with LoopBack" as the top level?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There will be more articles under it later. Eg: "How to create custom repositories", etc.

@raymondfeng raymondfeng changed the title [WIP] TypeORM support feat: add TypeORM support Jul 8, 2020
"repository": {
"type": "git",
"url": "https://github.com/strongloop/loopback-next.git",
"directory": "packages/typeorm"
Copy link
Contributor

Choose a reason for hiding this comment

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

extensions/typeorm

@@ -0,0 +1,16 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Node module: @loopback/boot
Copy link
Contributor

Choose a reason for hiding this comment

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

Please fix license header

Copy link
Contributor

Choose a reason for hiding this comment

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

You can use lb4 copyright command under extensions/typeorm.

@@ -0,0 +1,59 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/rest
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

@@ -0,0 +1,18 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

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

entities -> typeorm-entities?

@@ -0,0 +1,73 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/graphql
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

@@ -0,0 +1,73 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

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

typeorm-connection.booter.ts?

@dhmlau dhmlau added the feature label Jul 8, 2020
@dhmlau dhmlau added this to the Jul 2020 milestone Jul 8, 2020
@hacksparrow hacksparrow force-pushed the typeorm-yaapa branch 2 times, most recently from 6b611bd to 182ee62 Compare July 8, 2020 16:34
@hacksparrow hacksparrow merged commit b4e984a into master Jul 8, 2020
@hacksparrow hacksparrow deleted the typeorm-yaapa branch July 8, 2020 20:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants