hapipal / schwifty Goto Github PK
View Code? Open in Web Editor NEWA model layer for hapi integrating Objection ORM
License: MIT License
A model layer for hapi integrating Objection ORM
License: MIT License
Now that we're all tested on hapi v20 and node v14, it's time to look ahead.
The supported versions of objection and knex are TBD, and an update will be proposed here.
Currently schwifty and schmervice have similar interfaces to each other, but they aren't as in-sync as they could be. For example:
server.schwifty()
does double-duty to handle that in addition to registering models. Schmervice's server.registerService()
only registers services.schwifty()
versus registerService()
.I would like to bring these closer in line with each other. I would propose that registering models should have a dedicated server decoration just for that purpose. Rather than keeping server.schwifty()
for additional configuration (e.g. server.schwifty({ knex, migrationsDir })
, I propose this configuration should always be specified by registering schwifty. There are two upsides to that approach: 1. it encourages all plugins that depend on schwifty to go ahead and register it themselves, and 2. it reduces duplication in the surface area of the API that currently exists between server.schwifty()
and schwifty's plugin options. By the same token, the models
option that currently may be provided as a plugin option would be removed.
If you want to declare a knex instance and register a model that will be bound with it then there would be exactly one way to write it (there are currently three!):
await server.register({
plugin: require('schwifty'),
options: {
knex: { client: 'sqlite3' }
}
});
server.registerModel(UserModel);
I started getting into the question: are the decorations models()
and registerModel()
too generic? What if another plugin on the user's server also wants to manage models of some sort? I realized that we could deal with that that by allowing the user to opt-in to namespacing these decorations to something like server.pal.models()
or server.schwifty.models()
, so we don't need to take this into account at the moment.
I am pretty happy with the rest of the plugin API now that namespacing and sandboxing have landed (#84). That's all I've got for now. Please feel free to weigh-in and let us know what you think!
See the the role of jsonSchema
in the description of patch()
here: http://vincit.github.io/objection.js/#patch. This should also work using joi.
knex.destroy()
is called on the same instance multiple times when multiple plugins use the same knex instance. Seems like one of those things that could cause unforeseen problems, esp as connections are drained in parallel.
The hapijs community prefers hapi.dev as the home of hapi-related documentation.
Objection is codifying the idea of "validators." This is good for us, since we're using a non-default validation library (joi!). Currently v0.7 is only in release-candidate phase, but when it comes out we'll want to make updates (shouldn't be giant!) and release the next version of schwifty. We don't know exactly how it's going to timeout in relation to the release of schwifty v1, so let's not worry about it too much.
Let's just keep an eye on this for now, https://github.com/Vincit/objection.js/releases
Validator reference, Vincit/objection.js@b553097
We will introduce a symbol Schwifty.bindKnex
that may be placed on a model (i.e. static
ly) that, when set to false
, will opt the model out of being bound to the plugin's knex instance. This will allow for smoother multi-tenant models on multi-plugin servers.
Just wondering but is this compatible with koa.js as well https://github.com/koajs?
If so, does it require any specific changes or can I just extend like in the given example for my objection.js models?
I wonder if it would be convenient if request.models()
and server.models()
could be used as such,
server.models('Movie'); // Returns Movie class
server.models('Dog'); // Returns null, since Dog is defined in another plugin. Or throws hard??
server.models('Dog', true); // Returns Dog class defined in another plugin
The user should be completely in charge of the version of knex they want to use. I don't mind falling back to generating a knex instance as we currently do.
First off, compliments on all of the excellent hapi pal libraries. I have really enjoyed using them so far. I am hoping to build a simple seed file using my project's Schwifty models. My project is set up according to the hapi pal docs boilerplate.
I can use the knex cli to make a seed file, but am not sure how to use my Schwifty models inside of a knex seed file, and if I just want to do all seeding via Schwifty models, I am not sure I need the knex seed boilerplate.
I attempted to manually create a seed.js file at my project root, trying to access the models from the server object, similar to how I imagine it works with hpal-debug. I was seeing a knex error when attempting to construct a simple query:
const server = require('./server');
server.deployment().then(serverInstance => {
const models = serverInstance.models()
const { Users } = models
const users = Users.query().then(users => {
console.log(users)
})
})
resulted in the following error:
Error: no database connection available for a query. You need to bind the model class or the query to a knex instance.
I seem to be missing a step to provide the models with a knex instance, but was thinking that step already happened during the server setup. I can see that the serverInstance
object returns a knex instance if I call serverInstance.knex()
and returns the expected models if I call serverInstance.models()
. I am not sure what the missing step is here?
We can benefit perf-wise from Toys.reacher()
vs Hoek.reach()
https://github.com/devinivy/toys
See also hapipal/schmervice#10 where similar features are implemented for schmervice.
Namespaces are an extension to the ability to pass true
to server.models(true)
in order to access all models (from the perspective of the root server), and similarly for server.knex(true)
. Now you may also pass a plugin name as a string server.models('my-plugin')
(and server.knex('my-plugin')
) in order to access models/knex from the perspective of that plugin. In other words, calling server.models()
inside my-plugin
is the same as calling server.models('my-plugin')
from any plugin; identically calling server.models()
on the root server is the same as calling server.models(true)
from any plugin.
Sandboxing is a mechanism to opt-out of models being available to all ancestor plugins/namespaces of the plugin that registered the model, and same goes with knex instances for child plugins/namespaces. This essentially is the schwifty-y way of having models and knex instances that are "private" within the plugin that registered them.
These features work together to enable better plugin encapsulation and greater testability, with the ability to not only access, but also mock-out models on a per-plugin basis.
This feature will allow Schwifty to retro-fit onto an existing database with strange or ugly column names, preserving your right to have sensible prop names that're easy on the eyes.
When writing a joiSchema, be able to specify the database's columnName that maps to this property like so:
{
fieldName: Joi.string().meta({
columnName: 'field_name'
})
}
Objection uses AnonymousModelSubclass
as the class name for models returned from Model.bindKnex()
. For debuggability, we should instead preserve the original model class names.
Where:
This is happening when trying to start the server with schwifty's master branch, and the add-schwifty
branch of user-boilerplate: https://github.com/WilliamSWoodruff/user-boilerplate/tree/add-schwifty.
Repro:
not
need a working db username, login, or db name, this error shows up before that.Stacktrace:
/Users/myUser/Developer/schwifty/node_modules/objection/lib/model/Model.js:809
this.jsonAttributes = [];
^
TypeError: Cannot set property jsonAttributes of class SchwiftyModel extends Objection.Model {
static get joiSchema() {}
// Caches schema, with and wi...<omitted>...
} which has only a getter
at Function.getJsonAttributes (/Users/myUser/Developer/schwifty/node_modules/objection/lib/model/Model.js:809:27)
at Tokens.$parseDatabaseJson (/Users/myUser/Developer/schwifty/node_modules/objection/lib/model/Model.js:297:37)
at Function.columnNameToPropertyName (/Users/myUser/Developer/schwifty/node_modules/objection/lib/model/ModelBase.js:601:50)
at Function.decorator$memoize [as columnNameToPropertyName] (/Users/myUser/Developer/schwifty/node_modules/objection/lib/utils/decorators/memoize.js:63:24)
at /Users/myUser/Developer/schwifty/node_modules/objection/lib/relations/Relation.js:565:37
at Array.map (native)
at BelongsToOneRelation.propertyName (/Users/myUser/Developer/schwifty/node_modules/objection/lib/relations/Relation.js:564:20)
at BelongsToOneRelation.setMapping (/Users/myUser/Developer/schwifty/node_modules/objection/lib/relations/Relation.js:272:27)
at /Users/myUser/Developer/schwifty/node_modules/objection/lib/model/Model.js:712:33
at /Users/myUser/Developer/schwifty/node_modules/lodash/lodash.js:935:11
at /Users/myUser/Developer/schwifty/node_modules/lodash/lodash.js:4944:15
at baseForOwn (/Users/myUser/Developer/schwifty/node_modules/lodash/lodash.js:3001:24)
at /Users/myUser/Developer/schwifty/node_modules/lodash/lodash.js:4913:18
at baseReduce (/Users/myUser/Developer/schwifty/node_modules/lodash/lodash.js:932:5)
at Function.reduce (/Users/myUser/Developer/schwifty/node_modules/lodash/lodash.js:9698:14)
at Function.getRelations (/Users/myUser/Developer/schwifty/node_modules/objection/lib/model/Model.js:710:36)
at Function.bindKnex (/Users/myUser/Developer/schwifty/node_modules/objection/lib/model/Model.js:567:32)
at models.forEach (/Users/myUser/Developer/schwifty/lib/index.js:107:56)
at Array.forEach (native)
at collector.knexGroups.forEach (/Users/myUser/Developer/schwifty/lib/index.js:103:16)
at Array.forEach (native)
at internals.initialize (/Users/myUser/Developer/schwifty/lib/index.js:98:26)
When importing schwifty in webpack, I get the following warning:
WARNING in ./node_modules/Schwifty/lib/index.js 94:28-50
Critical dependency: the request of a dependency is an expression
WARNING in ./node_modules/Schwifty/lib/index.js 98:28-76
Critical dependency: the request of a dependency is an expression
See webpack/webpack#196. There are some ways to get rid of these warnings in webpack, but it would be even better to avoid them in the first place.
Hi,
I generate my schwifty models from database using code generation tool. Tool generates Joi schema as well. I use model's joi schema as a base for validation in routes.
Overly simplified example:
routes/users.js
const UserModel = require("../models").User; // This causes coupling to model file
module.exports = {
method: "post",
path: "/users",
config: {
validate: { payload: UserModel.joiSchema },
response: {
schema: UserModel.responseSchema, // based on static get joiSchema()
},
},
handler: async request => {
const { User } = request.models();
return User.query();
},
}
My principles here are:
request.models()
And my question is:
To reuse joi schema from model, I have to require model file and this causes coupling. Is there any way to avoid coupling while accessing static methods of classes? Perhaps a static method from schwifty to access models (of course not instances but classes)
Thanks in advance.
This amounts to having a default implementation of jsonAttributes()
analogous to usage with jsonSchema
as described here http://vincit.github.io/objection.js/#jsonattributes
I'm getting a Joi validation error on configuration object inside the internals.schwifty
when trying to create a server inside Jest tests. Everywhere else (including NODE_ENV=test npm start
) the server is being created just fine. The error disappears if I don't export any models.
Details and steps to reproduce:
// package.json
"scripts": {
"test:jest": "NODE_ENV=test jest",
}
In any Jest test file:
// example.test.js
const Glue = require('glue');
const Manifest = require('../server/config/manifest');
const createServer = Glue.compose(Manifest.get('/'));
test('some test', async () => {
const server = await createServer;
});
the command npm run test:jest -- example.test.js
fails with the following:
ValidationError: {
"knex": {
"debug": false,
"client": "pg",
"connection": {
"host": "127.0.0.1",
"user": "user",
"password": "password",
"database": "db_test"
}
},
"models": [
function Users() {\n _classCallCheck(this, Users);\n\n return _possibleConstructorReturn(this, _getPrototypeOf(Users).apply(this, arguments));\n } [2]
],
"migrationsDir": "/server/config/migrations",
"value" [1]: -- missing --
}
[1] "value" must be a Function
[2] "0" must be a class
at Object.<anonymous>.exports.process (node_modules/joi/lib/errors.js:203:19)
at Object.<anonymous>.internals.Alternatives._validateWithOptions (node_modules/joi/lib/types/any/index.js:764:31)
at Object.<anonymous>.module.exports.internals.Any.root.validate (node_modules/joi/lib/index.js:147:23)
at Object.<anonymous>.module.exports.internals.Any.root.attempt (node_modules/joi/lib/index.js:177:29)
at Object.<anonymous>.internals.schwifty (node_modules/schwifty/lib/index.js:190:18)
at Object.register (node_modules/schwifty/lib/index.js:79:19)
at Object.<anonymous>.internals.Server.register (node_modules/hapi/lib/server.js:453:35)
but if I transform the test file into a plain JS script:
// example.js
const Glue = require('glue');
const Manifest = require('../server/config/manifest');
const createServer = Glue.compose(Manifest.get('/'));
(async () => {
const server = await createServer;
console.log(server);
})();
and run it with NODE_ENV=test node example.js
everything works fine and I see the server object being printed to stdout without any errors.
The error also disappears if I export an empty array for models:
// /server/models/index.js
const Users = require('./Users');
module.exports = [
// Users,
];
I'm using Schwifty 4.2.0
What am I doing wrong that I can't run .$relatedQuery('clients')
on my DataCentre
model?
I had no issues with objection.js before trying out schwifty.
Hi,
I'm trying to follow suggested best practice Extending schwifty models across plugins. However I'm using haute-couture
(hc) and hapipal boilerplate.
I put my model file in lib/models/user.js
file, and hc
registers my user model.
Then following best practice, conditional below throws exception Model "User" has already been registered.
, because hc
registered it.
server.schwifty(options.Model || UserModel);
What do you suggest to follow best practice using hc
while keeping my user model in models
directory.
Thanks,
We would like to know:
Is there a way for Schwifty to handle multiple database connections while using knex plugin? I am looking for a way to connect to 2 different DB's so as i can define 2 different DB hosts.
Here is how i have added the very first entry ->
{ plugin: Schwifty, options: [{ knex: { client: 'postgresql', connection: { user: process.env.AWS_DB_USER, host: process.env.AWS_DB_HOST, database: process.env.AWS_DB, password: process.env.AWS_DB_PASSWORD, port: process.env.AWS_DB_PORT }, pool: { min: 2, max: 10 } } }] }
Currently on knex master: #2735. This should simplify our migrator quite a bit!
Summarized by @WilliamSWoodruff here: https://github.com/BigRoomStudios/schwifty/pull/13/files#r96145284
Originally we hacked around objection to preserve model class name
s, but now it's no longer necessary– in objection v0.7.6 they started preserving class names in Model.bindKnex()
. With this change to schwifty, if you use objection v0.7.0-0.7.5 then class names will not be preserved, making this a breaking change.
Because we need more emails in the inbox
Instead it errors out when you call server.schwifty()
multiple times, even to specify a knex instance that isn't set yet or to add new models.
Root server must have a knex instance before adding models to this plugin without a knex instance.
Hi!
I'm new to Hapi and try to setup a new project with knex & objection, this plugin seems to be really cool but I'm probably missing something: is it me or you don't need the generated knexfile to specify all the config informations? I prefer to use it as it's the original knex stuff and I find it reassuring :)
Can you enlight me?
Thanks!
This is both,
Model.knexBind()
ing during init and,request.knex()
and server.knex()
.Hello!
Is it possible to access objection models in tests using Schwifty?
I'm trying the following:
it('can retrieve an auth token with valid credentials', async () => {
const Member = server.models().Member;
const newMember = await Member.query().insert({});
request.payload = {
data: {
type: 'token',
attributes: {
email: '[email protected]',
password: 'Q1w2e3r4'
}
}
};
const res = await server.inject(request);
expect(res.statusCode).to.equal(401);
});
When I log Member
, it's undefined
. If I log server.models()
, it returns an empty object.
THanks!
I'm building an API on Hapi pal with Haute Couture, Hoek, Knex, Objection and Schwifty all is fine but when i use the seedsDir it breaks Hapi itself. The server won't start.
Tho I am able to run migrations + seeds.
Maybe it's an idea to add this to the options? I've been looking into it but can't find where to add it. I'm not that skilled in Schwifty/node yet.
module.exports = {
plugins: {
options: {
migrationsDir: `${__dirname}/../migrations`,
seedsDir: `${__dirname}/../seeds`, // <== this is making problems
}
}
};
Introduce a symbol Schwifty.name
that may be placed on a model (i.e. static
ly) to override the name of the model available via server.models()
: an override for the model's class name
.
Knex 0.18.4+ allows the user to specify a different template for the output of knex migrate:make
. The current template doesn't follow the hapi style guide or use async/await, so it would be useful for schwifty to fill the gap here by exposing a path to a more suitable stub file Schwifty.migrationsStubPath
.
When specifying a relative path to migrations or models directory, if it is within a plugin with a path set using server.path()
, respect the plugin's path. Default to the current behavior, taking paths relative to the current working directory.
Hapi modules have been moved into a @Hapi namespace on npm. Update package.json to reflect this.
Hi!
Me again, sorry 😏 I've reading the doc again and again but still confused on what is the best practice to structure my project. For now I have an api folder with every models represented by a folder, containing the Model file, the routes etc... But as you register the models in the server file, how can I do it in each model.js file that I have?
I'm not sure I'm making myself clear, maybe a pic will help more:
Thanks a lot, again :)
The next major version of all packages in the org will be published under the @hapipal scope on npm. This module will be published as @hapipal/schwifty.
To be fully spec'd. But this should leverage the knex migration API.
For two models to be compatible in schwifty-land, one model must extend the other and they must share the same class name
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.