Here's a problem I'm banging my head around since months now:
I'm searching for an elegant,consistent way to initialize a table, the first time it is created, whithin feathers-knex
I'm using feathers.js (5.0.5) + feathers-knex (8.0.1) + knex (2.4.2) Note that I came to my solution to my problem with version 4 of feathers already
I have a service generated from the provided generators:
...
const { Rootdir } = require('./rootdir.class');
const createModel = require('../../models/rootdir.model');
const hooks = require('./rootdir.hooks');
module.exports = function (app) {
const options = {
// id: 'id', // Optional
Model: createModel(app),
paginate: app.get('paginate')
};
app.use('/rootdir', new Rootdir(options, app));
const service = app.service('rootdir');
service.hooks(hooks);
}
where rootdir.model.js looks something like:
module.exports = function (app) {
const db = app.get('knexClient');
const tableName = 'rootdir';
db.schema.hasTable(tableName).then(exists => {
if (!exists) {
db.schema.createTable(tableName, table => {
table.string('id').primary();
table.string('dir');
})
.then(() => {
debug(`Created ${tableName} table`)
app.emit(`table.${tableName}.created`)
})
.catch(e => console.error(`Error creating ${tableName} table`, e));
}
// At this point the table is not yet created!
});
return db;
};
there's nothing special in rootdir.class.js - just the standard stuff emitted by the generator:
const { Service } = require('feathers-knex');
exports.Rootdir = class Rootdir extends Service {
constructor(options) {
super({
...options,
name: 'rootdir'
});
}
};
I need to initialize - that is to preload some data - the first time the table 'rootdir' is created. That must happen somewhere after service is created - but before the first request is served. The promise db.schema.hasTable(db.schema.createTable(...)) needs therefore to be fully resolved when it hits my 'initializing' code.
The only way I found around this was to:
app.on('table.rootdir.created' , async () => {
await populateRootdirTable()
})
However, I feel like there SHOULD be a way to intercept the moment when:
What I have tried:
I hoped that the createModel could return a promise, which would then be resolved 'somewhere' within the Service. But no, the Service expects a 'initialized knex object' as 'Model' attribute. So, if I try to pass a promise instead of a reference to knex, the code fails.
I tried to put my initialization code in the app setup hook:
app.hooks({
setup: [
async function setupHook(context,next) {
debug("setup hook called (before)");
await next();
const rootdirService = context.app.service('rootdir');
const ret = await rootdirService.find({
query: {
$limit: 0
}
});
console.log("ret:",ret);
}
],
teardown: []
}
)
but again when the code reaches the hook, even though the service exists, the table is not yet created (Just for correctness: it DOES EXIST the second time you start the app, as by then the table would be created already; but it fails the first time)
Where should I put this 'initialization' code? How should I trigger it? Or, Am I thinking it wrong completely?
Since you are using SQL, table creations and data initialisation is done with migration files. In a normal Feathers 5 app you run the migrations with
npm run migrate
And example for how to create a migration is shown in the guide.
Once you created the tables, in a migration you can also use the app
object by importing it:
import type { Knex } from 'knex'
import { app } from '../src/app'
export async function up(knex: Knex): Promise<void> {
// create tables or make changes
app.service('users').create(/* create admin user here */)
}