Search code examples
typescriptbookshelf.jstyping

How can I create models on the fly [that have relations] using different Bookshelf.js connections in TypeScript?


I have individual MySQL databases per client, each database has the same tables so the schemas and models are the same between clients. I am writing my nodejs application in typescript using bookshelf to create the models. I have installed the knex and bookshelf node modules along with their ambient typings.

I have a script to create a client connection...

/config/database.ts:

import * as Knex from 'knex';
import * as Bookshelf from 'bookshelf';

export module MySql {
    const host = 'HOST';
    const user = 'USER';
    const password = 'PASSWORD';
    export function createClientConnection(client: number) {
        return Bookshelf(Knex({
            client: 'mysql',
            connection: {
                host: host,
                user: user,
                password: password,
                database: `client-${client}`,
                charset: 'utf8'
            }
        }));
    }
}

I have two models that relate to each other...

/models/campaign.ts:

import * as Bookshelf from 'bookshelf';

export function campaign(bookshelf: Bookshelf) {
    class Campaign extends bookshelf.Model<Campaign> {
        get tableName() { return 'campaigns'; }
        versions() {
            return this.hasMany(/*CampaignVersion*/);
        }
    }
    return new Campaign();
}

/models/campaign-version.ts:

import * as Bookshelf from 'bookshelf';

export function campaignVersion(bookshelf: Bookshelf) {
    class CampaignVersion extends bookshelf.Model<CampaignVersion> {
        get tableName() { return 'campaign_versions'; }
        campaign() {
            return this.belongsTo(/*Campaign*/);
        }
    }
    return new CampaignVersion();
}

And I thought I would use it like this...

/bin/app.ts:

import {MySql} from '../config/database';
import {campaign} from '../models/campaign';

var clientConnection = MySql.createClientConnection(1);
var Campaign = campaign(clientConnection);
Campaign.where('id', 1).fetch({withRelated: ['versions']}).then(campaign => console.log(campaign));

Solution

  • I have found a solution that works but it could soon become very convoluted as every model is defined in one file...

    You can define each model class within a client class where the constructor excepts a bookshelf connection. This way the type definitions follow through to use.

    /config/database.ts:

    import * as Knex from 'knex';
    import * as Bookshelf from 'bookshelf';
    
    export module MySql {
        const host = 'HOST';
        const user = 'USER';
        const password = 'PASSWORD';
        export function createClientConnection(client: number) {
            return Bookshelf(Knex({
                client: 'mysql',
                connection: {
                    host: host,
                    user: user,
                    password: password,
                    database: `client-${client}`,
                    charset: 'utf8'
                }
            }));
        }
        export class Client {
            constructor(private _connection: Bookshelf) {}
            get models() {
                class Campaign extends this._connection.Model<Campaign> {
                    get tableName() { return 'campaigns'; }
                    versions() {
                        return this.hasMany(CampaignVersion);
                    }
                }
                class CampaignVersion extends this._connection.Model<CampaignVersion> {
                    get tableName() { return 'campaign_versions'; }
                    campaign() {
                        return this.belongsTo(Campaign);
                    }
                }
                return {
                    Campaign: Campaign,
                    CampaignVersion: CampaignVersion
                };
            }
        }
    }
    

    /bin/app.ts:

    import {MySql} from '../config/database';
    
    var clientConnection = MySql.createClientConnection(1);
    var client = new MySql.Client(clientConnection);
    var campaign = new client.models.Campaign();
    campaign.where('id', 1).fetch({withRelated: ['versions']}).then(campaign => console.log(campaign));