Skip to content
This repository has been archived by the owner on Feb 2, 2018. It is now read-only.

Commit

Permalink
feature(main): Basic mixin implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Delisle committed Nov 23, 2017
1 parent 6ff849a commit 9696903
Show file tree
Hide file tree
Showing 9 changed files with 656 additions and 26 deletions.
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,96 @@
# loopback-typeorm
A component to provide TypeORM in Loopback 4

## Usage
Note: These instructions aren't entirely applicable yet, this module has not
been published.

1. Install this plugin
```ts
npm install --save loopback-typeorm
```
2. In your application, make your own Application class, but instead of
extending `Application`, you'll want to call the provided mixin as your base
class.
```ts
import {Application} from '@loopback/core';
import {TypeORMRepositoryMixin} from 'loopback-typeorm';

export class MyApplication extends TypeORMRepositoryMixin(Application) {
constructor() {
super(...);
}
}
```
3. Create a connection (or multiple!) in your new subclass, and define
whatever repositories you'd like to create.

A helpful way to ensure that your configuration has all of the _required_ values
is to import the `ConnectionOptions` type from TypeORM directly.

**Note**: There are connection options that become required within different
use cases and contexts. For info on how to configure your database connection,
see the [TypeORM docs](https://github.com/typeorm/typeorm).

```ts
import {Application} from '@loopback/core';
import {TypeORMRepositoryMixin} from 'loopback-typeorm';
import {ConnectionOptions} from 'typeorm';
import {Order, Customer} from './models';

export class MyApplication extends TypeORMRepositoryMixin(Application) {
mySqlConnection: Connection;
constructor() {
super();
const connectionOptions: ConnectionOptions = {
name: 'connectionName',
host: 'somehost.com',
database: 'mydb',
port: 3306,
type: 'mysql',
username: 'admin',
password: 'secretpassword',
// etc...
};
this.mySqlConnection = this.createTypeOrmConnection(connectionOptions);

// Automatically uses the connection to bind repositories to
// your application context.
this.typeOrmRepository(this.mySqlConnection, Order);
this.typeOrmRepository(this.mySqlConnection, Customer);
}
}
```
4. Finally, consume your repositories in your controllers!
```ts
import {Customer, CustomerSchema} from '../models';
import {Repository} from 'typeorm';

export class CustomerController {
constructor(@inject('repositories.Customer') customerRepo: Repository) {
// ...
}

@get('/customer/{id}')
@param.path.number('id');
async getCustomerById(id: number) {
// Using TypeORM's repository!
return await this.customerRepo.findOneById(id);
}

@post('/customer')
@param.body('customer', CustomerSchema);
async createCustomer(customer: Customer) {
return await this.customerRepo.save(customer);
}
}
```

## Testing
To run tests, you'll need an installation of Docker.
```
npm it
```

[![LoopBack](http://loopback.io/images/overview/powered-by-LB-xs.png)](http://loopback.io/)

10 changes: 0 additions & 10 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1 @@
// Copyright IBM Corp. 2017. All Rights Reserved.
// Node module: loopback-typeorm
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

// NOTE(bajtos) This file is used by TypeScript compiler to resolve imports
// from "test" files against original TypeScript sources in "src" directory.
// As a side effect, `tsc` also produces "dist/index.{js,d.ts,map} files
// that allow test files to import paths pointing to {src,test} root directory,
// which is project root for TS sources but "dist" for transpiled sources.
export * from './src';
29 changes: 23 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
"name": "loopback-typeorm",
"version": "0.0.1",
"description": "A component to provide TypeORM in Loopback 4",
"keywords": ["loopback-application", "loopback"],
"keywords": [
"loopback-application",
"loopback"
],
"main": "index.js",
"engines": {
"node": ">=8"
Expand All @@ -16,28 +19,42 @@
"prettier:cli": "prettier \"**/*.ts\" \"**/*.js\"",
"prettier:check": "npm run prettier:cli -- -l",
"prettier:fix": "npm run prettier:cli -- --write",
"tslint": "tslint",
"tslint": "tslint .",
"tslint:fix": "npm run tslint -- --fix",
"pretest": "npm run clean && npm run build",
"posttest": "npm run lint",
"start": "npm run build && node .",
"prepare": "npm run build"
"prepare": "npm run build",
"test": "npm run build && node ./dist/test/setup.js"
},
"repository": {
"type": "git"
},
"author": "",
"license": "MIT",
"files": ["README.md", "index.js", "index.d.ts", "dist"],
"files": [
"README.md",
"index.js",
"index.d.ts",
"dist"
],
"dependencies": {
"@loopback/context": "^4.0.0-alpha.18",
"@loopback/core": "^4.0.0-alpha.20",
"@loopback/rest": "^4.0.0-alpha.7"
"typeorm": "^0.1.2"
},
"devDependencies": {
"@loopback/build": "^4.0.0-alpha.5",
"@loopback/testlab": "^4.0.0-alpha.14",
"@types/debug": "0.0.30",
"@types/dockerode": "^2.5.1",
"@types/mocha": "^2.2.44",
"debug": "^3.1.0",
"dockerode": "^2.5.3",
"mocha": "^4.0.1",
"mysql": "^2.15.0",
"prettier": "^1.7.3",
"tslint": "^5.7.0",
"@loopback/testlab": "^4.0.0-alpha.13",
"typescript": "^2.5.3"
}
}
7 changes: 7 additions & 0 deletions src/connection-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {ConnectionManager, Connection} from 'typeorm';

export class TypeORMConnectionManager extends ConnectionManager {
// This is to allow more direct access to the connection objects
// during start/stop of the application.
public connections: Connection[];
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './typeorm-mixin';
103 changes: 103 additions & 0 deletions src/typeorm-mixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import {Application, Component, Server} from '@loopback/core';
import {Context, Binding, Constructor} from '@loopback/context';
import {Connection, Entity, BaseEntity, ConnectionOptions} from 'typeorm';
import {TypeORMConnectionManager} from './connection-manager';

// tslint:disable:no-any
export function TypeORMMixin(
superClass: typeof Application,
): TypeORMApplicationClass {
return class extends superClass {
typeOrmConnectionManager: TypeORMConnectionManager;
constructor(...args: any[]) {
super(...args);
this.typeOrmConnectionManager = new TypeORMConnectionManager();
this.bind('typeorm.connections.manager').to(
this.typeOrmConnectionManager,
);
}

async start() {
for (const connection of this.typeOrmConnectionManager.connections) {
await connection.connect();
}
await super.start();
}

async stop() {
for (const connection of this.typeOrmConnectionManager.connections) {
await connection.close();
}
await super.stop();
}

/**
* Register a TypeORM-based repository instance of the given class.
* Generated repositories will be bound using the `repositories.{name}`
* convention.
*
* ```ts
* this.typeOrmRepository(Foo);
* const fooRepo = this.getSync(`repositories.Foo`);
* ```
*
* @param ctor The constructor (class) that represents the entity to
* generate a repository for.
*/
typeOrmRepository<S>(
connection: Connection,
ctor: Constructor<S>,
): Binding {
// XXX(kjdelisle): I wanted to make this a provider, but it requires
// the constructor instance to be available in the provider scope, which
// would require injection of each constructor, so I had to settle for
// this instead.
return this.bind(`repositories.${ctor.name}`).toDynamicValue(async () => {
if (!connection.isConnected) {
await connection.connect();
}
return connection.getRepository(ctor);
});
}

/**
* Get an existing connection instance from the connection manager,
* or create one if it does not exist. If you do not provide a name, a
* default connection instance will be provided.
* @param name The name of the connection (if it already exists)
*/
getTypeOrmConnection(name?: string): Connection {
return this.typeOrmConnectionManager.get(name);
}

/**
* Create a new TypeORM connection with the provided set of options.
* @param options
*/
createTypeOrmConnection(options: ConnectionOptions): Connection {
if (!options) {
throw new Error('Connection options are required!');
}
return this.typeOrmConnectionManager.create(options);
}
};
}

/**
* Define any implementation of Application.
*/

export interface TypeORMApplication extends Application {
typeOrmRepository<S>(connection: Connection, ctor: Constructor<S>): Binding;
createTypeOrmConnection(options: ConnectionOptions): Connection;
getTypeOrmConnection(name?: string): Connection;
}

export interface TypeORMApplicationClass
extends Constructor<TypeORMApplication> {
[property: string]: any;
}

export interface Options {
[property: string]: any;
}
Loading

0 comments on commit 9696903

Please sign in to comment.