Skip to content

Commit

Permalink
Merge pull request #55 from BigRoomStudios/v4-finalize-docs
Browse files Browse the repository at this point in the history
v4 finalize docs
  • Loading branch information
devinivy committed Mar 11, 2018
2 parents db466cc + 66db1b9 commit 9ac8ab7
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 41 deletions.
93 changes: 56 additions & 37 deletions API.md
@@ -1,74 +1,93 @@
# API
## The hapi plugin
### Registration
Schwifty may be registered multiple timesit should be registered in any plugin that would like to use any of its features. It's suggested that registration options only be passed when schwifty is registered outside of a plugin (on the root server), and that within plugins [`server.schwifty()`](#serverschwiftyconfig) be used instead, at least for defining models. Upon each registration these options are collected until server initialization. Model `name`s must be unique across the entire server. Server initialization will fail if any knex instances handled by schwifty do not have basic database connectivity.
Schwifty may be registered multiple timesit should be registered in any plugin that would like to use any of its features. Upon each registration these options are collected until server initialization. Models and knex configurations passed during configuration are attributed to the registering plugin/server under schwifty's [ownership system](#plugin-ownership-of-knex-instances-and-models). Model `name`s must be unique across the entire server. Server initialization will fail if any knex instances handled by schwifty do not have basic database connectivity.

Schwifty takes the following registration options,

- `knex` - a knex instance or [configuration](http://knexjs.org/#Installation-client). This will determine the server-wide default knex instance; if a plugin does not declare its own knex instance using [`server.schwifty()`](#serverschwiftyconfig) then this one will be used. It cannot be specified more than once for the root server.
- `models` - An array of objection or [schwifty model classes](#schwiftymodel) associated with the root server. May also be a path to a module that exports such an arrayeither absolute, relative to the server's [path prefix](https://github.com/hapijs/hapi/blob/master/API.md#server.path()) when set, or otherwise relative to the current working directory.
- `migrationsDir` - specifies a directory of knex migrations associated with the root server. The directory path may be either absolute, relative to the server's [path prefix](https://github.com/hapijs/hapi/blob/master/API.md#server.path()) when set, or otherwise relative to the current working directory. It cannot be specified more than once for the root server.
- `migrateOnStart` - a boolean, `'latest'`, or `'rollback'`, to determine how to handle [knex migrations](http://knexjs.org/#Migrations) at server initialization time. Defaults to `false`, which indicates to not handle migrations at all. When `true` or `'latest'`, runs all migrations that have not been run. When `'rollback'`, rolls-back the latest group of migrations.
- `knex` - a knex instance or [configuration](http://knexjs.org/#Installation-client). It may only be specified once per plugin/server.
- `models` - an array of objection or [schwifty model classes](#schwiftymodel). May also be a path to a module that exports such an arrayeither absolute, relative to the server's [path prefix](https://github.com/hapijs/hapi/blob/master/API.md#server.path()) when set, or otherwise relative to the current working directory.
- `migrationsDir` - specifies a directory of knex migrations. The directory path may be either absolute, relative to the server's [path prefix](https://github.com/hapijs/hapi/blob/master/API.md#server.path()) when set, or otherwise relative to the current working directory. It may only be specified once per plugin/server.
- `migrateOnStart` - a boolean, `'latest'`, or `'rollback'`, to determine how to handle [knex migrations](http://knexjs.org/#Migrations) at server initialization time. Defaults to `false`, which indicates to not handle migrations at all. When `true` or `'latest'`, runs all migrations that have not been run. When `'rollback'`, rolls-back the latest group of migrations. It may only be specified once.
- `teardownOnStop` - a boolean indicating whether or not all knex connections should be torn-down when the hapi server stops (after server connections are drained). Defaults to `true`, and may only be specified once.


### Server decorations
#### `server.knex()`
Returns the knex instance used by the current plugin. In other words, this returns the knex instance for the active realm. If none exists, the first knex instance encountered on climbing up through the current plugin's ancestors until reaching the root server's knex instance is returned.

#### `server.models([all])`
Returns an object containing models keyed by `name`. When `all` is `true`, models across the entire server are returned. Otherwise, only models declared within a.) the current plugin (or, server's active realm) and b.) all children plugins of the current plugin (e.g. if the current plugin has registered a plugin that registers models, the current plugin would have access to the models registered by its child plugin) are returned.


#### `server.schwifty(config)`
Used to register models, knex instances, and migration directory information on a per-plugin basis or on the root server. In other words, these settings are particular to the active [realm](https://github.com/hapijs/hapi/blob/master/API.md#server.realm). The `config` may be either,
Used to declare models, knex instances, and migration directory information on a per-plugin basis or on the root server. In other words, these settings are particular to the current plugin under schwifty's [ownership system](#plugin-ownership-of-knex-instances-and-models). The `config` may be either,

- An objection or [schwifty model class](#schwiftymodel), or an array of such model classes associated with the current plugin or root server.
- An object specifying,
- `knex` - a knex instance or configuration. This will determine the knex instance that should be used within the current plugin. If it's specified on the root server, it will set the server-wide default knex instance. It cannot be specified more than once within a plugin or on the root server, but the same knex instance may be shared by multiple plugins.
- For example, any plugin that doesn't have its own knex instance can use the root server's. Note the root server isn't special here, just is the top link in our plugin ancestry. If a plugin isn't bound to a knex instance, it just reaches up this ancestry until it finds one, stopping when it reaches the end of our "family tree". This end just happens to be the current root server of your application, but would change, for example, if your application became a plugin of a different server, which would become the new root server, offering a new possible knex instance if this new server also registered schwifty
- `knex` - a knex instance or [configuration](http://knexjs.org/#Installation-client) associated with the current plugin or root server. It may only be specified once per plugin/server.
- `models` - An array of objection or [schwifty model classes](#schwiftymodel) associated with the current plugin or root server.
- `migrationsDir` - specifies a directory of knex migrations associated with the current plugin or root server. The directory path may be either absolute, relative to the plugin's [path prefix](https://github.com/hapijs/hapi/blob/master/API.md#server.path()) when set, or otherwise relative to the current working directory. It cannot be specified more than once within a plugin or on the root server.

#### `server.knex()`
Returns `server`'s knex instance under schwifty's [plugin ownership of knex instances](#knex-instances).

#### `server.models([all])`
Returns an object containing models keyed by their `name`. Includes `server`'s models based upon schwifty's [plugin ownership of models](#models). When `all` is `true`, models across the entire server are returned.

### Request decorations
#### `request.knex()`
Returns the knex instance used by `request.route`'s plugin. In other words, this returns the knex instance for `request.route`'s active realm. If none exists, the first knex instance encountered as you climb up through `request.route`'s ancestors until reaching the root server's knex instance is returned.
Returns knex instance under schwifty's [plugin ownership of knex instances](#knex-instances), where the active plugin is the one in which the `request`'s route was declared (i.e. based upon `request.route.realm`).


#### `request.models([all])`
Returns an object containing models keyed by `name`. When `all` is `true`, models across the entire server are returned. Otherwise, only models declared within a.) `request.route`'s plugin (or, active realm) and b.) all children plugins of the `request.route`'s plugin (e.g. if `request.route`'s plugin has registered a plugin that registers models, `request.route`'s plugin would have access to the models registered by its child plugin) are returned.
Returns an object containing models keyed by their `name`. Includes models based upon schwifty's [plugin ownership of models](#models), where the active plugin is the one in which the `request`'s route was declared (i.e. based upon `request.route.realm`). When `all` is `true`, models across the entire server are returned.


### Response toolkit decorations
#### `h.knex()`
Returns the knex instance used by the current route's plugin. In other words, this returns the knex instance for the active realm as identified by `h.realm`. If none exists, the first knex instance encountered on climbing up through the current route's plugin's ancestors until reaching the root server's knex instance is returned.
Returns knex instance under schwifty's [plugin ownership of knex instances](#knex-instances), where the active plugin is the one in which the corresponding route or server extension was declared (i.e. based upon `h.realm`).

#### `h.models([all])`
Returns an object containing models keyed by `name`. When `all` is `true`, models across the entire server are returned. Otherwise, only models declared within a.) the current route's plugin (or, active realm, as identified by `h.realm`) and b.) all children plugins of the current route's plugin (e.g. if the current route's plugin has registered a plugin that registers models, the current route's plugin would have access to the models registered by its child plugin) are returned.
Returns an object containing models keyed by their `name`. Includes models based upon schwifty's [plugin ownership of models](#models), where the active plugin is the one in which the corresponding route or server extension was declared (i.e. based upon `h.realm`). When `all` is `true`, models across the entire server are returned.

### What happens during server initialization?
Schwifty performs a few important routines during server initialization.

#### Binding models to knex
First, each model is bound to its plugin's or active realm's knex instance. If no knex instance is bound to the active realm, then the plugin's ancestry is indeterminately searched for one, binding the first knex instance found to the plugin's models.
### Plugin ownership of knex instances and models
> How do plugins "own" knex instances and models?
Schwifty cares a whole lot about plugin boundaries. Plugins represent the structure of your application, and we think that's not only practical but also very meaningful. Under schwifty plugins declare knex instances and models, and actually retain _ownership_ of them in a useful way that respects your application's plugin boundaries.

#### Knex instances
Whenever a plugin or the root server configures a knex instance, either by [registering](#registration) schwifty and passing `knex` or calling [`server.schwifty({ knex })`](#serverschwiftyconfig), an instance of knex is attributed to that plugin. Consider `plugin-x` that declares a knex instance. It becomes available by calling [`server.knex()`](#serverknex) within `plugin-x`, or [`request.knex()`](#requestknex) within one of `plugin-x`'s routes. But it goes further! This instance of knex is also available to all plugins _registered by_ `plugin-x` that do not have their own knex instances.

This allows us to handle very common use-cases, e.g. a single knex/database is configured on the root server: all other plugins using schwifty are registered under the root server and automatically inherit that database configuration. But it also allows us to handle complex cases, e.g. multiple applications with their own knex/databases all being deployed together as separate plugins on a single hapi server.

#### Models
Whenever a plugin or the root server declares some models, either by [registering](#registration) schwifty and passing `models` or calling [`server.schwifty(models)`](#serverschwiftyconfig), those models are attributed to that plugin. Consider `plugin-x` registering plugin `plugin-a`, which declares the model `Pets`. Inside `plugin-a` the `Pets` model is available by calling [`server.models()`](#servermodelsall) within `plugin-a`, or [`request.models()`](#requestmodelsall) within one of `plugin-a`'s routes. But, just as with [knex instances](#knex-instances), it goes further! `Pets` is also available to `plugin-x` since it registered `plugin-a`. In fact, `Pets` is available to the entire plugin chain up to the root server. In this way, the root server will have access to every model declared by any plugin.

This allows us to handle very common use-cases, e.g. a plugin simply wants to declare and use some models. But it also allows us to handle complex cases, e.g. authoring a plugin that declares some models that you would like to reuse across multiple applications.

For example, take 2 plugins: `pluginA` and `pluginB`:
Note that the [schmervice](https://github.com/devinivy/schmervice) plugin deals with plugin ownership of services in exactly the same way.

- `pluginA` registers `pluginB`
- `pluginA` registers the model `Dogs` and binds a knex instance
- `pluginB` registers the model `Zombies` and does not bind a knex instance
> As an escape hatch, you can always call [`server.models(true)`](#servermodelsall) (passing `true` to any of the server, request, or response toolkit's `models()` decoration) to break the plugin boundary and access any model declared by any plugin on the entire server.
On server initialization,
#### An example
Consider two plugins, `plugin-a` and `plugin-b`,

- `pluginA` will have access to both `Dogs` and `Zombies` (via the `models` decorations described above), as parents have access to their own and their children's models.
- `pluginB` will have acccess to only `Zombies`
- All models will be bound to `pluginA`'s knex instance. `Zombies` is bound to that instance because `pluginB` attempts to bind its own knex instance to `Zombies`, but finds that none was configured, so it then searches through its ancestors. It checks its parent first, immediately finding a knex instance, so binds that.
- the root server registers `plugin-a`.
- `plugin-a` registers `plugin-b`.
- `plugin-a` declares the model `Dogs` and a knex instance using `server.schwifty({ models, knex })`.
- `plugin-b` declares the model `Zombies` using `server.schwifty(models)` and does not declare a knex instance.

In short, models and knex instances pass through our plugin architecture as follows:
Then we can say the following,

- Models propagate upwards; as child plugins register their own models, their parents "know" about more models
- Knex instances propagate downwards; a child plugin will "adopt" a knex instance from up the ancestral chain if it doesn't own one
- The root server will have access to both `Dogs` and `Zombies` models, having inherited both of them from its "children" plugins.
- The root server has no knex instance of its own.
- `plugin-a` will have access to both `Dogs` and `Zombies` models, having inherited `Zombies` from `plugin-b`.
- `plugin-b` will have access to only the `Zombies` model.
- Both `Dogs` and `Zombies` models will be bound to `plugin-a`'s knex instance. `Zombies` is bound to that instance because `plugin-b` inherits its knex instance from its nearest "ancestor" with a knex instance, `plugin-a`.


### What happens during server initialization?
Schwifty performs a few important routines during server initialization.

#### Binding models to knex
First, models are bound to instances of knex on a per-plugin basis. Each model is bound to the knex instance of the plugin—under [plugin ownership of knex instances](#knex-instances)—in which the model was defined. If a model already is bound to a knex instance prior to initialization, it will not be bound to a new one.

If a model already is bound to a knex instance, it will not be bound to a new one. This means that prior to server initialization, calls to [`server.models()`](#servermodelsall) will provide models that will not be bound to a knex instance (unless you've done so manually). If you would like to perform some tasks during server initialization that rely on database-connected models, simply tell your `onPreStart` server extension to occur after schwifty, e.g.,
This means that prior to server initialization, calls to [`server.models()`](#servermodelsall) will provide models that will not be bound to a knex instance (unless you've done so manually). If you would like to perform some tasks during server initialization that rely on database-connected models, simply tell your `onPreStart` server extension to occur after schwifty, e.g.,
```js
server.ext('onPreStart', someDbConnectedTask, { after: 'schwifty' });
```
Expand All @@ -79,12 +98,12 @@ server.dependency('schwifty', someDbConnectedTask);
```

#### Database connectivity
Second, every knex instance declared during [plugin registration](#registration) or with [`server.schwifty()`](#serverschwiftyconfig) is checked for connectivity. If any instance of knex does not have database connectivity, you will receive an error and your server will not initialize. While this does not make any guarantees about table existence or structure, it does guarantee database connectivity at server initialization time.
Second, every knex instance declared during [plugin registration](#registration) or with [`server.schwifty({ knex })`](#serverschwiftyconfig) is checked for connectivity. If any instance of knex does not have database connectivity, you will receive an error and your server will not initialize. While this does not make any guarantees about table existence or structure, it does guarantee database connectivity at server initialization time.

#### Migrations
Lastly, if you specified `migrateOnStart` as `true`, `'latest'`, or `'rollback'`, then migrations will be run against each knex instance. Your instance of knex may specify its own [knex migration options](http://knexjs.org/#Migrations-API), except for `directory`, which will be ignored in favor of the migration directories declared using the `migrationsDir` option with [`server.schwifty()`](#serverschwiftyconfig).

If a knex instance is shared across plugins and each plugin specifies its own migrations directory using `migrationsDir`, then migrations from each of those plugin's migrations directories will simply be run against the knex instance. In short, schwifty pluginizes knex migrations.
If a knex instance is shared across plugins (under [plugin ownership of knex instances](#knex-instances)) and each plugin specifies its own migrations directory using `migrationsDir`, then migrations from each of those plugin's migrations directories will simply be run against the knex instance. In short, schwifty pluginizes knex migrations.

The `migrateOnStart` options `true` and `'latest'` correspond to [`knex.migrate.latest()`](http://knexjs.org/#Migrations-latest), while `'rollback'` corresponds to [`knex.migrate.rollback()`](http://knexjs.org/#Migrations-rollback).

Expand Down

0 comments on commit 9ac8ab7

Please sign in to comment.