Skip to content

Commit

Permalink
feat(*): support multiple connections
Browse files Browse the repository at this point in the history
Closes #684

Signed-off-by: Will Soto <[email protected]>
  • Loading branch information
willsoto committed Jul 10, 2020
1 parent 6f0980a commit b95c77f
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 19 deletions.
28 changes: 18 additions & 10 deletions lib/core.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
/* eslint-disable new-cap */
import { DynamicModule, Module, Provider } from "@nestjs/common";
import {
DynamicModule,
FactoryProvider,
Module,
Provider,
ValueProvider,
} from "@nestjs/common";
import Knex from "knex";
import { Model } from "objection";
import {
Expand All @@ -22,18 +28,18 @@ export class ObjectionCoreModule {

BaseModel.knex(connection);

const objectionModuleOptions: Provider = {
const objectionModuleOptions: ValueProvider = {
provide: OBJECTION_MODULE_OPTIONS,
useValue: options,
};

const objectionBaseModelProvider: Provider = {
provide: OBJECTION_BASE_MODEL,
const objectionBaseModelProvider: ValueProvider = {
provide: BaseModel.name,
useValue: BaseModel,
};

const knexConnectionProvider: Provider = {
provide: KNEX_CONNECTION,
const knexConnectionProvider: ValueProvider = {
provide: options.name || KNEX_CONNECTION,
useValue: connection,
};

Expand All @@ -51,17 +57,19 @@ export class ObjectionCoreModule {
public static registerAsync(
options: ObjectionModuleAsyncOptions = {},
): DynamicModule {
const knexConnectionProvider: Provider = {
provide: KNEX_CONNECTION,
const connectionToken = options.name || KNEX_CONNECTION;

const knexConnectionProvider: FactoryProvider = {
provide: connectionToken,
inject: [OBJECTION_MODULE_OPTIONS],
useFactory(objectionModuleOptions: ObjectionModuleOptions): Knex {
return Knex(objectionModuleOptions.config);
},
};

const objectionBaseModelProvider: Provider = {
const objectionBaseModelProvider: FactoryProvider = {
provide: OBJECTION_BASE_MODEL,
inject: [KNEX_CONNECTION, OBJECTION_MODULE_OPTIONS],
inject: [connectionToken, OBJECTION_MODULE_OPTIONS],
useFactory(
connection: Connection,
objectionModuleOptions: ObjectionModuleOptions,
Expand Down
12 changes: 11 additions & 1 deletion lib/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Type } from "@nestjs/common";
import { ModuleMetadata } from "@nestjs/common/interfaces";
import * as Knex from "knex";
import Knex from "knex";
import { Model } from "objection";

export interface ObjectionModuleOptions {
/**
* The name for this connection if more than one database connection is required.
* This field is **required** if you use multiple connetions.
*/
name?: string;
Model?: typeof Model;
config: Knex.Config;
}
Expand All @@ -16,6 +21,11 @@ export interface ObjectionModuleOptionsFactory {

export interface ObjectionModuleAsyncOptions
extends Pick<ModuleMetadata, "imports"> {
/**
* The name for this connection if more than one database connection is required.
* This field is **required** if you use multiple connetions.
*/
name?: ObjectionModuleOptions["name"];
useExisting?: Type<ObjectionModuleOptionsFactory>;
useClass?: Type<ObjectionModuleOptionsFactory>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
15 changes: 9 additions & 6 deletions tests/core.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import knex from "knex";
import { Config } from "knex";
import { Model } from "objection";
import {
KNEX_CONNECTION,
OBJECTION_BASE_MODEL,
Expand All @@ -14,7 +15,7 @@ import {

describe("ObjectionCoreModule", () => {
let testingModule: TestingModule;
const config: knex.Config = {
const config: Config = {
client: "sqlite3",
useNullAsDefault: true,
connection: {
Expand All @@ -40,14 +41,14 @@ describe("ObjectionCoreModule", () => {
});

test("provides a base model", () => {
const model = testingModule.get("ObjectionBaseModel");
const model = testingModule.get(Model);

expect(model).toBeDefined();
expect(model).toBe(Model);
});
});

describe("#registerAsync", () => {
beforeEach(async () => {
beforeAll(async () => {
testingModule = await Test.createTestingModule({
imports: [
ObjectionCoreModule.registerAsync({
Expand All @@ -61,6 +62,8 @@ describe("ObjectionCoreModule", () => {
}).compile();
});

afterAll(() => testingModule.close());

test("provides a connection", () => {
const connection = testingModule.get(KNEX_CONNECTION);

Expand All @@ -70,7 +73,7 @@ describe("ObjectionCoreModule", () => {
test("provides a base model", () => {
const model = testingModule.get(OBJECTION_BASE_MODEL);

expect(model).toBeDefined();
expect(model).toBe(Model);
});
});

Expand Down
5 changes: 3 additions & 2 deletions tests/module.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Test, TestingModule } from "@nestjs/testing";
import knex from "knex";
import { KNEX_CONNECTION, OBJECTION_BASE_MODEL } from "../lib/constants";
import { Model } from "objection";
import { KNEX_CONNECTION } from "../lib/constants";
import { ObjectionModule } from "../lib/module";
import { User } from "./fixtures";

Expand Down Expand Up @@ -32,7 +33,7 @@ describe("ObjectionModule", () => {
});

test("provides a base model", () => {
const model = testingModule.get(OBJECTION_BASE_MODEL);
const model = testingModule.get(Model);

expect(model).toBeDefined();
});
Expand Down
170 changes: 170 additions & 0 deletions tests/multiple-connections.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { Test, TestingModule } from "@nestjs/testing";
import Knex from "knex";
import { Model } from "objection";
import { ObjectionCoreModule } from "../lib/core";

describe("when registering multiple connections", () => {
let testingModule: TestingModule;
let connection1: Knex;
let connection2: Knex;

class Author extends Model {
static tableName = "authors";
}

class Book extends Model {
static tableName = "books";
}

describe("#register", () => {
beforeAll(async () => {
testingModule = await Test.createTestingModule({
imports: [
ObjectionCoreModule.register({
name: "connection1",
Model: Author,
config: {
client: "sqlite3",
useNullAsDefault: true,
connection: {
filename: "./testing.sqlite",
},
},
}),
ObjectionCoreModule.register({
name: "connection2",
Model: Book,
config: {
client: "sqlite3",
useNullAsDefault: true,
connection: {
filename: "./testing2.sqlite",
},
},
}),
],
}).compile();

connection1 = testingModule.get("connection1");
connection2 = testingModule.get("connection2");

if (!(await connection1.schema.hasTable(Author.tableName))) {
await connection1.schema.createTable(Author.tableName, (t) => {
t.increments().primary().notNullable();
t.text("name").notNullable();
});
}

if (!(await connection2.schema.hasTable(Book.tableName))) {
await connection2.schema.createTable(Book.tableName, (t) => {
t.increments().primary().notNullable();
t.text("title").notNullable();
});
}
});

afterAll(() => testingModule.close());

test("uses the given token for each connection", () => {
expect(connection1).toBeDefined();
expect(connection2).toBeDefined();
});

test("queries using the correct connection", async () => {
expect(Author.knex()).toEqual(connection1);
expect(Book.knex()).toEqual(connection2);

await expect(Author.query()).resolves.toEqual([]);
await expect(Book.query()).resolves.toEqual([]);
});

test("created tables", async () => {
await expect(connection1.schema.hasTable(Book.tableName)).resolves.toBe(
false,
);
await expect(connection2.schema.hasTable(Author.tableName)).resolves.toBe(
false,
);
});
});

describe("#registerAsync", () => {
beforeAll(async () => {
testingModule = await Test.createTestingModule({
imports: [
ObjectionCoreModule.registerAsync({
name: "connection1",
useFactory() {
return {
Model: Author,
config: {
client: "sqlite3",
useNullAsDefault: true,
connection: {
filename: "./testing.sqlite",
},
},
};
},
}),
ObjectionCoreModule.registerAsync({
name: "connection2",
useFactory() {
return {
Model: Book,
config: {
client: "sqlite3",
useNullAsDefault: true,
connection: {
filename: "./testing2.sqlite",
},
},
};
},
}),
],
}).compile();

connection1 = testingModule.get("connection1");
connection2 = testingModule.get("connection2");

if (!(await connection1.schema.hasTable(Author.tableName))) {
await connection1.schema.createTable(Author.tableName, (t) => {
t.increments().primary().notNullable();
t.text("name").notNullable();
});
}

if (!(await connection2.schema.hasTable(Book.tableName))) {
await connection2.schema.createTable(Book.tableName, (t) => {
t.increments().primary().notNullable();
t.text("title").notNullable();
});
}
});

afterAll(() => testingModule.close());

test("uses the given token for each connection", () => {
expect(connection1).toBeDefined();
expect(connection2).toBeDefined();
});

test("queries using the correct connection", async () => {
expect(Author.knex()).toEqual(connection1);
expect(Book.knex()).toEqual(connection2);

await expect(Author.query()).resolves.toEqual([]);
await expect(Book.query()).resolves.toEqual([]);
});

test("created tables", async () => {
await expect(connection1.schema.hasTable(Book.tableName)).resolves.toBe(
false,
);
await expect(connection2.schema.hasTable(Author.tableName)).resolves.toBe(
false,
);
});
});
});

0 comments on commit b95c77f

Please sign in to comment.