@knorm/knorm
Advanced tools
Comparing version 1.9.0 to 1.9.1
@@ -0,1 +1,8 @@ | ||
## [1.9.1](https://github.com/knorm/knorm/compare/v1.9.0...v1.9.1) (2018-10-08) | ||
### Bug Fixes | ||
* **Query:** add model-name to query error ([bfdb861](https://github.com/knorm/knorm/commit/bfdb861)) | ||
<a name="1.9.0"></a> | ||
@@ -2,0 +9,0 @@ # [1.9.0](https://github.com/knorm/knorm/compare/v1.8.0...v1.9.0) (2018-09-28) |
# Credits | ||
Knorm is inspired by the [Mongoose JS](http://mongoosejs.com/) and | ||
[Bookshelf.js](http://bookshelfjs.org/) APIs. It was previously a built as a | ||
wrapper around [Knex.js](http://knexjs.org), hence the kn-orm name i.e. knex-orm; | ||
but it now builds it's own SQL via [SQL Bricks.js](http://csnw.github.io/sql-bricks/). | ||
[Bookshelf.js](http://bookshelfjs.org/) APIs. | ||
It was previously built as a wrapper around [Knex.js](http://knexjs.org), hence | ||
the name "kn-orm", that is knex-orm. It no longer depends on Knex, instead it | ||
now builds it's own SQL via [SQL Bricks.js](http://csnw.github.io/sql-bricks/). | ||
Thank you [Ramsés Cabello](https://twitter.com/ramsescabello) for the logo. |
# Fields | ||
Fields are synonymous to table columns. To add fields to a model, assign an | ||
object to [Model.fields](api/model.md#modelfields): | ||
object to [Model.fields](/api.md#model-fields-object): | ||
@@ -24,9 +24,11 @@ ```js | ||
!> field names should be unique. also, the `Model.fields` setter will throw if | ||
the field name is already a `Model.prototype` property or is already added as a | ||
[virtual](guides/virtuals.md#virtuals). | ||
::: warning NOTE | ||
Field names should be unique. The `Model.fields` setter will throw if a field | ||
name is already a `Model.prototype` property or is already added as a | ||
[virtual](/guides/virtuals.md#virtuals). | ||
::: | ||
Knorm uses field names as column names when running queries. To transform field | ||
names to different column names (e.g. snake-casing), use a `fieldToColumn` | ||
mapping function (ref. [Knorm options](api/knorm.md#options)) or specify a | ||
mapping function (ref. [Knorm options](/api.md#new-knorm-config)) or specify a | ||
different `column` name per field with the [field config](#field-config). | ||
@@ -130,4 +132,7 @@ | ||
!> Composite/multiple primary fields are not currently supported. If two fields | ||
are configured as primary, only the latter will be configured as a primary field. | ||
::: warning NOTE | ||
Composite/multiple primary fields are not currently supported. If two fields are | ||
configured as primary, only the latter will be configured as a primary | ||
field. | ||
::: | ||
@@ -179,5 +184,7 @@ If a field is configured as primary or unique, you may also want to prevent it | ||
**NOTE:** since these methods are intended to work with a single row, they will | ||
::: tip INFO | ||
Since these methods are intended to work with a single row, they will | ||
automatically throw a `NoRowsFetchedError`, `NoRowsUpdatedError` or | ||
`NoRowsDeletedError` error if the row is not found in the database. | ||
::: | ||
@@ -187,5 +194,3 @@ ## Value casting | ||
You can configure `forSave` and `forFetch` cast functions for every field. These | ||
are handy for type-casting or some other functionality. For example, in | ||
PostgreSQL you can configure a `forSave` cast function to | ||
[stringify JSON arrays](http://knexjs.org/#Schema-json): | ||
are handy for type-casting or some other functionality: | ||
@@ -196,8 +201,13 @@ ```js | ||
data: { | ||
type: 'json', | ||
type: 'email', | ||
cast: { | ||
forSave(value) { | ||
if (value) { | ||
return JSON.stringify(value); | ||
return value.toLowerCase(); | ||
} | ||
}, | ||
forFetch(value) { | ||
if (value) { | ||
return value.replace('@', '[at]'); | ||
} | ||
} | ||
@@ -209,4 +219,13 @@ } | ||
!> • `forSave` cast functions are called **after** validation <br /> | ||
• cast functions are not called if the field's value is `undefined`, but | ||
are called if it's `null` | ||
> `forSave` refers to both insert and update operations | ||
::: tip INFO | ||
`forSave` cast functions are called **after** validation. This allows specifying | ||
some field-type in the field's configuration while the database stores the | ||
values with a different type, if needed. | ||
::: | ||
::: tip INFO | ||
Cast functions are not called if the field's value is `undefined`, but are | ||
called if it's `null`. | ||
::: |
# Models | ||
Models are synonymous to database tables. They provide the core functionality | ||
for setting, validating, saving, updating and deleting data. All models inherit | ||
the base [Model](api/model.md#model) class. | ||
for setting, getting, validating, casting, saving, updating and deleting data. | ||
All models inherit the base [Model](/api.md#model) class. | ||
@@ -15,10 +15,8 @@ ## Model config | ||
| `Model.schema` | string | none | Configures the model's schema-name | | ||
| `Model.fields` | object | none | Configures the model's fields. See the [fields guide](guides/fields.md#fields) for more info | | ||
| `Model.virtuals` | object | none | Configures the model's virtual fields. See the [virtuals guide](guides/virtuals.md#virtuals) for more info | | ||
| `Model.fields` | object | none | Configures the model's fields. See the [fields guide](/guides/fields.md#fields) for more info | | ||
| `Model.virtuals` | object | none | Configures the model's virtual fields. See the [virtuals guide](/guides/virtuals.md#virtuals) for more info | | ||
| `Model.options` | object | none | Configures the model's default query and plugin options (for some plugins). See [customizing queries per model](#customizing-queries-per-model) for more info | | ||
| `Model.Query` | [Query](api/query.md#query) | [Query](api/query.md#query) | The `Query` class that the model uses to perform database operations. This allows [customizing queries per model](#customizing-queries-per-model). | | ||
| `Model.Field` | [Field](api/field.md#field) | [Field](api/field.md#field) | The `Field` class that the model uses to create field instances. Also allows customizing fields per model. | | ||
| `Model.Query` | [Query](/api.md#query) | [Query](/api.md#query) | The `Query` class that the model uses to perform database operations. This allows [customizing queries per model](#customizing-queries-per-model). | | ||
| `Model.Field` | [Field](/api.md#field) | [Field](/api.md#field) | The `Field` class that the model uses to create field instances. Also allows customizing fields per model. | | ||
> See the [Model](api/model.md#model) docs for Model API documentation | ||
## Setting data | ||
@@ -75,3 +73,3 @@ | ||
> See the [Model](api/model.md#model) docs for more info | ||
> See the [Model](/api.md#model) docs for more info | ||
@@ -101,9 +99,11 @@ ## Getting data | ||
!> Since `async` virtual getters are intrinsically supported, the methods that | ||
get virtual field data always return a `Promise`. However, you can stil use the | ||
::: tip INFO | ||
Since `async` virtual getters are intrinsically supported, the methods that get | ||
virtual field data always return a `Promise`. However, you can stil use the | ||
sync variants to ignore async virtual data. | ||
::: | ||
## Saving, fetching and deleting data | ||
For all [Model](api/model.md#model) instances, you can save, retrieve or delete | ||
For all [Model](/api.md#model) instances, you can save, retrieve or delete | ||
data in the database with these methods: | ||
@@ -126,19 +126,21 @@ | ||
> All the methods return a `Promise` | ||
> See [model.save](/api.md#model-save-options-%E2%87%92-promise-model), | ||
> [model.insert](/api.md#model-insert-options-%E2%87%92-promise-model), | ||
> [model.update](/api.md#model-update-options-%E2%87%92-promise-model), | ||
> [model.fetch](/api.md#model-fetch-options-%E2%87%92-promise-model) and | ||
> [model.delete](/api.md#model-delete-options-%E2%87%92-promise-model) for more | ||
> info | ||
> `options` are optional in all methods | ||
> See the [Query](api/query.md#query) docs for supported options | ||
All the methods update the instance with the latest data from the database, so | ||
after an update you do not need to re-fetch the row (this can be disabled with | ||
the `returning` option though). | ||
the [returning](/api.md#query-returning-fields-%E2%87%92-query) query option | ||
though). | ||
The [update](api/model.md#modelprototypeupdateoptions-promise-gt-model), | ||
[fetch](api/model.md#modelprototypefetchoptions-promise-gt-model) and | ||
[delete](api/model.md#modelprototypedeleteoptions-promise-gt-model) methods | ||
The [model.update](/api.md#model-update-options-%E2%87%92-promise-model), | ||
[model.fetch](/api.md#model-fetch-options-%E2%87%92-promise-model) and | ||
[model.delete](/api.md#model-delete-options-%E2%87%92-promise-model) methods | ||
require a primary or unique field to be set on the model in order to find the | ||
row in the database. See the | ||
[primary and unique fields guide](guides/field.md#primary-and-unique-fields) for | ||
more info. | ||
[primary and unique fields guide](/guides/field.md#primary-and-unique-fields) | ||
for more info. | ||
@@ -163,14 +165,22 @@ All the methods also have static variants that instead enable working with | ||
> All the methods return a `Promise` | ||
> See [Model.save](/api.md#model-save-data-options-%E2%87%92-promise), | ||
> [Model.insert](/api.md#model-insert-data-options-%E2%87%92-promise), | ||
> [Model.update](/api.md#model-update-data-options-%E2%87%92-promise), | ||
> [Model.fetch](/api.md#model-fetch-data-options-%E2%87%92-promise) and | ||
> [Model.delete](/api.md#model-delete-data-options-%E2%87%92-promise) for more | ||
> info | ||
> `options` are optional in all methods | ||
!> Static methods work on multiple rows while the instance methods only work on | ||
::: tip INFO | ||
Static methods work on multiple rows while the instance methods only work on | ||
a single row! | ||
::: | ||
!> Instance methods will throw automatically an error if the record is not found | ||
::: warning NOTE | ||
Instance methods will automatically throw an error if the record is not found | ||
in the database (for fetch, delete and update operations). | ||
::: | ||
In addition, you can configure [generated methods](guides/fields.md#generated-methods) | ||
with the `methods` [field config option](guides/fields.md#field-config): | ||
In addition, you can configure [generated | ||
methods](/guides/fields.md#generated-methods) | ||
with the `methods` [field config option](/guides/fields.md#field-config): | ||
@@ -185,11 +195,15 @@ ```js | ||
!> These methods also throw an error if the record is not found in the database | ||
::: warning NOTE | ||
These methods also throw an error if the record is not found in the database | ||
since they are intended to work with single records. | ||
::: | ||
## Customizing queries per model | ||
You can set default query options per model via the `Model.options` setter. | ||
You can set default query options per model via the | ||
[Model.options](/api.md#Model.fields) setter. | ||
For example, if your users table has some system users that should not be | ||
fetched/updated/deleted, you can add a default `where` option: | ||
fetched/updated/deleted, you can add a default | ||
[where](/api.md#query-where-fields-%E2%87%92-query) option: | ||
@@ -211,4 +225,6 @@ ```js | ||
> These options will also be inherited when the model is inherited. <br /> | ||
> Read more on [setting query options](guides/queries.md#setting-options) | ||
::: tip INFO | ||
These options will also be inherited when the model is inherited. <br /> | ||
Read more on [setting query options](/guides/queries.md#setting-options) | ||
::: | ||
@@ -253,3 +269,5 @@ You could then have a `SystemUser` model for interacting only with system users: | ||
!> Query options must return `this` to allow chaining | ||
::: warning NOTE | ||
Query options should return `this` to allow chaining. | ||
::: | ||
@@ -281,3 +299,3 @@ ## Model registry | ||
accessing them via Node's `require` function. This allows [runnning queries | ||
within transactions](guides/transactions.md#nested-queries-in-instance-or-class-methods) | ||
within transactions](/guides/transactions.md#nested-queries-in-instance-or-class-methods) | ||
without having to make any further code changes. | ||
@@ -288,3 +306,3 @@ | ||
configured, you can add it to the registry via | ||
[addModel](api/knorm.md#knormprototypeaddmodel): | ||
[addModel](/api.md#knorm-addmodel-model): | ||
@@ -304,3 +322,3 @@ ```js | ||
Since models are only automatically added when they are `require`d, it's | ||
recommended to load all the models syncronously when starting up a node server. | ||
recommended to load all the models syncronously when starting up a node app. | ||
For example: | ||
@@ -307,0 +325,0 @@ |
@@ -6,3 +6,4 @@ # Plugins | ||
To add plugins to the ORM: | ||
To load plugins into the ORM, use | ||
[knorm.use](/api.md#knorm-use-plugin-%E2%87%92-knorm): | ||
@@ -13,12 +14,7 @@ ```js | ||
const { Model, Query } = knorm({ | ||
/* knorm options */ | ||
}).use( | ||
knormRelations({ | ||
/* plugin options */ | ||
}) | ||
); | ||
const { Model, Query } = knorm(/* knorm options */) | ||
.use(knormRelations(/* plugin options */)); | ||
``` | ||
> see [@knorm/relations](knorm-relations.md) for this plugin's documentation | ||
> see [@knorm/relations](https://github.com/knorm/relations) for this plugin's documentation | ||
@@ -29,22 +25,28 @@ ## Available plugins | ||
| ------------------------------------------ | -------------------------------------------------------------------------- | | ||
| [@knorm/postgres](knorm-postgres.md) | enables connecting to postgres | | ||
| [@knorm/to-json](knorm-to-json.md) | adds a `toJSON` method to Model | | ||
| [@knorm/relations](knorm-relations.md) | enables SQL joins | | ||
| [@knorm/soft-delete](knorm-soft-delete.md) | enables soft-deletion: adds support for `deleted` and `deletedAt` fields | | ||
| [@knorm/paginate](knorm-paginate.md) | adds a `count` method to Model and Query and adds query pagination options | | ||
| [@knorm/timestamps](knorm-timestamps.md) | enables timestamps: adds support for `createdAt` and `updatedAt` fields | | ||
| [@knorm/postgres](https://github.com/knorm/postgres) | enables connecting to postgres | | ||
| [@knorm/to-json](https://github.com/knorm/to-json) | adds a `toJSON` method to Model | | ||
| [@knorm/relations](https://github.com/knorm/relations) | enables SQL joins | | ||
| [@knorm/soft-delete](https://github.com/knorm/soft-delete) | enables soft-deletion: adds support for `deleted` and `deletedAt` fields | | ||
| [@knorm/paginate](https://github.com/knorm/paginate) | adds a `count` method to Model and Query and adds query pagination options | | ||
| [@knorm/timestamps](https://github.com/knorm/timestamps) | enables timestamps: adds support for `createdAt` and `updatedAt` fields | | ||
## Creating custom plugins | ||
A knorm plugin can be a function or an object with an `init` function. When you | ||
add a plugin via `Knorm.prototype.use`, the function (or `init` function) is | ||
called with the knorm instance. You can then modify the classes on the instance: | ||
A Knorm plugin can be any *named* function or an object with an `init` function | ||
and a `name` property. See [knorm.use](/api.md#knorm-use-plugin-⇒-knorm) for | ||
more info. | ||
When a plugin is loaded via [knorm.use](/api.md#knorm-use-plugin-⇒-knorm), the | ||
function (or object's `init` function) is called with the `knorm` instance. The | ||
plugin can then modify the classes on the instance: | ||
```js | ||
const preventDelete = orm => { | ||
orm.Model.Query = orm.Query = class extends orm.Query { | ||
class YouShallNotDelete extends orm.Query { | ||
async delete() { | ||
throw new Error('deleting is not allowed!'); | ||
} | ||
}; | ||
} | ||
orm.Model.Query = orm.Query = YouShallNotDelete; | ||
}; | ||
@@ -59,4 +61,7 @@ | ||
!> If you update the `Query` class then also update `Model.Query` so that models | ||
use the updated `Query` class. Similarly, when you update the `Field` class, | ||
also update `Model.Field`. | ||
::: warning NOTE | ||
If you update knorm's `Query` class then also update | ||
[Model.Query](/api.html#knorm-query-query) so that models use the updated | ||
`Query` class. Similarly, when you update the `Field` class, also update | ||
[Model.Field](/api.html#knorm-field-field). | ||
::: |
# Queries | ||
For all database operations that [Model](api/model.md#model) performs, the | ||
[Query](api/query.md#query) class does all the heavy lifting. It transforms | ||
For all database operations that [Model](/api.md#model) performs, the | ||
[Query](/api.md#query) class does all the heavy lifting. It transforms | ||
JavaScript function calls into SQL and parses the data from the database into | ||
@@ -16,4 +16,2 @@ Model instances. | ||
> See the [Query](api/query.md#query) docs for Query API documentation | ||
## Initializing queries | ||
@@ -32,3 +30,4 @@ | ||
The most convenient way to run queries is through the `Model.query` getter: | ||
You can initialise query instances with the | ||
[Model.query](/api.md#model-query-query) getter: | ||
@@ -39,15 +38,12 @@ ```js | ||
Alternatively, you can initialize a query via the | ||
[Query constructor](api/query.md#querymodel): | ||
::: warning NOTE | ||
Query instances are not reusable! That is, you cannot use a query instance for | ||
one operation e.g. a fetch and then reuse it for another e.g. an insert. | ||
However, cloning query instances is supported via | ||
[query.clone](/api.md#query-clone-%E2%87%92-query). | ||
::: | ||
```js | ||
const query = new Query(User); // the model class is required | ||
``` | ||
!> Query instances are not reusable! Therefore, the recommended way of creating | ||
queries is the `Model.query` getter | ||
## Running queries | ||
Similar to [Model](api/model.md#model), you can save, retrieve or delete data in | ||
Similar to [Model](/api.md#model), you can save, retrieve or delete data in | ||
the database with the same CRUD methods: | ||
@@ -70,13 +66,18 @@ | ||
> All the methods return a `Promise` | ||
> See [query.save](/api.md#query-save-options-%E2%87%92-promise), | ||
> [query.insert](/api.md#query-insert-options-%E2%87%92-promise), | ||
> [query.update](/api.md#query-update-options-%E2%87%92-promise), | ||
> [query.fetch](/api.md#query-fetch-options-%E2%87%92-promise) and | ||
> [query.delete](/api.md#query-delete-options-%E2%87%92-promise) for more | ||
> info | ||
> `options` are optional in all methods | ||
::: warning NOTE | ||
The Query CRUD methods operate on multiple rows (similar to the Model statics). | ||
Only [update](/api.md#query-update-data-options-%E2%87%92-promise) | ||
behaves differently when passed an object with the primary field-value set: | ||
::: | ||
!> The Query CRUD methods operate on multiple rows (similar to the Model statics). | ||
Only [update](api/query.md#queryprototypeupdatedata-options-promise-gt-model) | ||
behaves differently when passed an object with the primary field set: | ||
```js | ||
User.query.update({ names: 'foo' }); // updates all rows | ||
User.query.update({ id: 1, names: 'foo' }); // updates only row 1 | ||
User.query.update({ id: 1, names: 'foo' }); // updates only the row with id 1 | ||
``` | ||
@@ -87,3 +88,3 @@ | ||
Raw queries can be run by directly invoking the | ||
[query](api/query.md#queryprototypequerysql-promise-gt-) method: | ||
[query](api.md#query-query-sql-%E2%87%92-promise) method: | ||
@@ -94,17 +95,30 @@ ```js | ||
To run prepared statements with parameter binding, use | ||
[sql](api/query.md#queryprototypesql), which by default is an | ||
To run queries with parameter bindings, you may use the | ||
[sql](/api.md#query-sql-sqlbricks) helper to construct an | ||
[sql-bricks](https://csnw.github.io/sql-bricks/) instance: | ||
```js | ||
const sqlBricks = User.query.sql; | ||
const query = User.query; | ||
const sqlBricks = query.sql; | ||
const sql = sqlBricks.select( | ||
sqlBricks.sql('select * from "user" where id = $1 and names = $2', [1, 'foo']) | ||
sqlBricks('select * from "user" where id = $1 and names = $2', [1, 'foo']) | ||
); | ||
const rows = await User.query.query(sql); | ||
const rows = await query.query(sql); | ||
``` | ||
> with the @knorm/postgres loaded, then `Query.prototype.sql` is overridden with | ||
> [sql-bricks-postgres](https://github.com/Suor/sql-bricks-postgres) | ||
::: tip | ||
with the [@knorm/postgres](https://github.com/knorm/postgres) plugin loaded, | ||
then `Query.prototype.sql` is overridden with | ||
[sql-bricks-postgres](https://github.com/Suor/sql-bricks-postgres) | ||
::: | ||
Alternatively, you can pass an object with `text` and `values` properties: | ||
```js | ||
const rows = await User.query.query({ | ||
text: 'select * from "user" where id = $1 and names = $2', | ||
values: [1, 'foo'] | ||
}); | ||
``` | ||
## Setting options | ||
@@ -132,3 +146,3 @@ | ||
Which is a proxy to [setOptions](api/query.md#queryprototypesetoptionsoptions-query): | ||
Which is a proxy to [query.setOptions](/api.md#query-setoptions-options-%E2%87%92-query): | ||
@@ -145,3 +159,6 @@ ```js | ||
> The object notation only works for options that take one argument (majority) | ||
::: tip INFO | ||
Until v2 of @knorm/knorm, the object notation only works for options that take | ||
one argument (majority) | ||
::: | ||
@@ -167,3 +184,4 @@ For most query options, calling the same option does not overwrite the previous | ||
You can set default query options per model via the `Model.options` setter: | ||
You can set default query options per model via the | ||
[Model.options](/api.md#model-options-object) setter: | ||
@@ -248,7 +266,9 @@ ```js | ||
User.query.insert({ id: 1 }); | ||
// => User.InsertError(new Error('duplicate primary key error, thrown by the database drver')); | ||
// => User.InsertError(new Error('duplicate primary key error')); | ||
``` | ||
In addition, if the `require` option is set on a query and no rows are | ||
inserted/updated/fetched/deleted, then the promise is rejected with a `NoRowsError`: | ||
In addition, if the [require](/api.md#query-require-require-%E2%87%92-query) | ||
option is set to `true` on a query and no rows are | ||
inserted/updated/fetched/deleted, then the promise is rejected with a | ||
`NoRowsError`: | ||
@@ -268,2 +288,2 @@ ```js | ||
> See the [Query](api/query.md#query) docs for more info on all the query errors | ||
> See the [Query](/api.md#query) docs for more info on all the query errors |
# Transactions | ||
Transactions are supported via the [Transaction](api/transaction.md#transaction) class. | ||
Transactions are supported via the [Transaction](/api.md#transaction) class. | ||
Creating a new transaction exposes a new set of models whose queries will run | ||
Creating a new transaction creates a new set of models whose queries will be run | ||
inside the transaction: | ||
@@ -37,3 +37,3 @@ | ||
await User.insert([{ id: 1, names: 'foo' }, { id: 2, names: 'bar' }]); | ||
// more queries | ||
// ... more queries | ||
return User.fetch(); | ||
@@ -45,3 +45,4 @@ }); | ||
Instead of calling [execute](api/transaction.md#transactionprototypeexecute-promise), you can also simply `await` the Transaction instance: | ||
Instead of calling [transaction.execute](/api.md#transaction-execute-promise), | ||
you can also simply `await` the `Transaction` instance: | ||
@@ -52,3 +53,3 @@ ```js | ||
await User.insert([{ id: 1, names: 'foo' }, { id: 2, names: 'bar' }]); | ||
// more queries | ||
// ... more queries | ||
return User.fetch(); | ||
@@ -70,3 +71,3 @@ }); | ||
]); | ||
// more queries | ||
// ... more queries | ||
const fetchedUsers = await User.fetch(); | ||
@@ -76,7 +77,11 @@ await transaction.commit(); | ||
!> [commit](api/transaction.md#transactionprototypecommit-promise) must be called | ||
when it's time to commit the transaction. | ||
::: warning NOTE | ||
[transaction.commit](/api.md#transaction-commit-promise) **must** be called when | ||
it's time to commit the transaction. | ||
::: | ||
> If there is any errors running queries, the transaction will be automatically | ||
> rolled back before rejecting the query's promise: | ||
::: tip INFO | ||
If there is any error while running a query, the transaction will be | ||
automatically rolled back before the query that failed is rejected: | ||
::: | ||
@@ -98,7 +103,8 @@ ```js | ||
> the second `User.insert` will be rejected with an | ||
> [InsertError](api/query.md#queryinserterror) and the transaction will be | ||
> rolled back, such that no rows will end up being inserted. | ||
> [InsertError](/api.md#query-inserterror-insert-error) and the transaction will | ||
> be rolled back, such that no rows will end up being inserted. | ||
To ensure transactions are rolled back due to other errors besides query errors, | ||
use a `try..catch` and call [rollback](api/transaction.md#transactionprototyperollback-promise): | ||
use a `try..catch` (or `Promise.prototype.catch`) and call | ||
[transaction.rollback](/api.md#transaction-rollback-promise) if an error occurs: | ||
@@ -114,3 +120,3 @@ ```js | ||
]); | ||
// do other things | ||
// ... do other things, that may throw an error | ||
await transaction.commit(); | ||
@@ -123,10 +129,17 @@ } catch (e) { | ||
## Nested queries in instance or class methods | ||
## Running queries inside other methods within transactions | ||
If you have instance methods that run nested queries using other models, access | ||
those models via [models](api/model.md#modelprototypemodels) (or [the static equivalent](api/model.md#models)) to ensure that those queries are run within | ||
transactions. This is as opposed to `require`ing the model directly. | ||
those models via [model.models](/api.md#model-models-models) (or | ||
[Model.models](/api/model.md#models-models-object-2) in static methods) to | ||
ensure that those queries are run within transactions. | ||
Assuming: | ||
::: tip INFO | ||
In transactions, the model regstry is replaced with a new set of models whose | ||
queries run inside a transaction. Therefore, the same code inside those methods | ||
will not require any changes for the queries to run inside transactions. | ||
::: | ||
For example, with this setup: | ||
```js | ||
@@ -141,2 +154,3 @@ User.fields = { groupId: 'integer' }; | ||
} | ||
Group.table = 'group'; | ||
@@ -146,6 +160,13 @@ Group.fields = { id: { type: 'integer', primary: true }, name: 'string' }; | ||
Then if groups are first fetched within a transaction, the users will also be | ||
fetched within the same transaction: | ||
To get a group's users, you would do something like: | ||
```js | ||
const group1 = await Group.fetch({ where: { id: 1 }, first: true }); | ||
const group1Users = await group1.getUsers(); | ||
``` | ||
In this case, the queries are sent normally, i.e. not within a transaction. To | ||
run the same queries within a transaction: | ||
```js | ||
const group1Users = await new Transaction(async transaction => { | ||
@@ -157,1 +178,9 @@ const { Group } = transaction.models; | ||
``` | ||
Without having to change the implementation of `Group.prototype.getUsers` at | ||
all. | ||
For this reason, when using models in instance and static methods, it's | ||
recommended to always access models via the [model | ||
registry](/guides/models.md#model-registry) instead of requiring them with | ||
Node's `require` method. |
# Validation | ||
Validation is configured per field using the | ||
[field config](guides/fields.md#field-config). | ||
[field config](/guides/fields.md#field-config). | ||
A model instance is validated before | ||
[insert](api/model.md#modelprototypeinsertoptions-promise-gt-model) and | ||
[update](api/model.md#modelprototypeupdateoptions-promise-gt-model). Before inserts, | ||
all fields will be validated (**_except the primary field if it's `undefined`_**) | ||
whereas before updates, only the fields that have values set will be validated. | ||
[model.insert](/api.md#model-insert-options-%E2%87%92-promise-model) and | ||
[model.update](/api.md#model-update-options-%E2%87%92-promise-model). Before | ||
inserts, all fields will be validated (**_except the primary field if it's | ||
`undefined`_**) whereas before updates, only the fields that have values set | ||
will be validated. | ||
You can trigger validation any time on a model instance via | ||
[Model.prototype.validate](api/model.md#modelprototypevalidateoptions-promise-gt-modelvalidationerror) | ||
[model.validate](/api.md#model-validate-options-%E2%87%92-promise) | ||
> you can configure validation for fields via the [field configs](guides/fields.md#field-config) | ||
> the `type` field config is also used as a validator | ||
::: tip | ||
Validation can be configured for each field via the [field | ||
configs](/guides/fields.md#field-config). | ||
::: | ||
@@ -33,3 +36,3 @@ ## Regex validation | ||
```js | ||
```js {6} | ||
class User extends Model {} | ||
@@ -47,3 +50,3 @@ | ||
```js | ||
```js {7} | ||
class User extends Model {} | ||
@@ -73,3 +76,3 @@ | ||
```js | ||
```js {7} | ||
class User extends Model {} | ||
@@ -99,3 +102,3 @@ | ||
```js | ||
```js {7,8} | ||
class User extends Model {} | ||
@@ -141,9 +144,9 @@ | ||
`false` or resolves the `Promise` with `false`, then validation fails with a | ||
[ValidationError](api/validation-error.md). | ||
[ValidationError](/api.md#field-validationerror). | ||
You can also continue validating by returning an object with the regular | ||
[validators](guides/fields.md#field-config) (or resolving the `Promise` with an | ||
[validators](/guides/fields.md#field-config) (or resolving the `Promise` with an | ||
object with validators), including another custom validator function! | ||
```js | ||
```js {13,14,15,16,17} | ||
class User extends Model {} | ||
@@ -183,4 +186,6 @@ | ||
!> Custom validators will **not** be called if the value is `undefined`, but | ||
will be called if the value is `null` | ||
::: tip INFO | ||
Custom validators will **not** be called if the value is `undefined`, but will | ||
be called if the value is `null`. | ||
::: | ||
@@ -193,5 +198,7 @@ ## JSON validation | ||
!> JSON fields are **only** validated if they contain a `schema` validator | ||
::: warning NOTE | ||
JSON fields are **only** validated if they contain a `schema` validator. | ||
::: | ||
```js | ||
```js {6,7,8,9,10} | ||
class Upload extends Model {} | ||
@@ -236,10 +243,13 @@ | ||
> JSON `schema` validators support all the | ||
> [validators](guides/fields.md#field-config), including nested `schema` | ||
> validators [for nested objects](#nested-objects) | ||
::: tip INFO | ||
JSON `schema` validators support all the | ||
[validators](/guides/fields.md#field-config), including nested `schema` | ||
validators [for nested objects](#nested-objects). | ||
::: | ||
Note that you may also define the schema with the `fieldName: fieldType` | ||
shorthand: | ||
::: tip | ||
You may also define the schema with the `fieldName: fieldType` shorthand: | ||
::: | ||
```js | ||
```js {7,8} | ||
class User extends Model {} | ||
@@ -262,5 +272,5 @@ | ||
single array item by passing a `schema` validation object with the regular | ||
[validators](guides/fields.md#field-config). | ||
[validators](/guides/fields.md#field-config). | ||
```js | ||
```js {12,14,15,16,17} | ||
class SomeData extends Model {} | ||
@@ -301,3 +311,3 @@ | ||
```js | ||
```js {8,9,10,11,12} | ||
class SomeData extends Model {} | ||
@@ -330,4 +340,6 @@ | ||
!> Nested object fields **must** contain a `type`, just like | ||
[Field](#api/field.md#field) instances | ||
::: warning NOTE | ||
Nested object fields **should** contain a `type`, just like regular | ||
[Field](/api.md#field) instances. | ||
::: | ||
@@ -339,3 +351,3 @@ ### Root-level JSON fields | ||
```js | ||
```js {5,6,7,8,9,18,19,20,21} | ||
class SomeData extends Model {} | ||
@@ -370,3 +382,3 @@ SomeData.fields = { | ||
```js | ||
```js {6} | ||
class User extends Model {} | ||
@@ -384,16 +396,23 @@ | ||
For example, to enforce a max-length of `255` for all `string` field types, | ||
Instead of repeating the same validation config for multiple related fieds, you | ||
could instead overload a validator. | ||
For example, to enforce a max-length of `500` for all `string` field types, | ||
instead of adding a `maxLength` validator for every field of type `string`, you | ||
could override the `string` validator to add max-length validation for every | ||
`string` field: | ||
`string` field. | ||
This can be easily done with a [plugin](/guides/plugins.md): | ||
```js | ||
const { Field: KnormField } = require('@knorm/knorm'); | ||
const stringsMaxLength500 = orm => { | ||
class StringsMaxLength500 extends orm.Field { | ||
validateIsString(value, type) { | ||
super.validateIsString(value, type); | ||
this.validateMaxLengthIs(value, 500); | ||
} | ||
} | ||
class Field extends KnormField { | ||
validateIsString(value, type) { | ||
super.validateIsString(value, type); | ||
this.validateMaxLengthIs(value, 255); | ||
} | ||
} | ||
orm.Model.Field = orm.Field = StringsMaxLength500; | ||
}; | ||
``` |
@@ -5,3 +5,12 @@ const { isUUID, isDecimal, isEmail } = require('validator'); | ||
/** | ||
* Creates and holds configuration for fields, e.g. how to validate or cast | ||
* fields. | ||
*/ | ||
class Field { | ||
/** | ||
* Creates a {@link Field} instance. | ||
* | ||
* @param {object} [config] The field's configuration. | ||
*/ | ||
constructor(config = {}) { | ||
@@ -8,0 +17,0 @@ const { |
121
lib/Knorm.js
@@ -0,2 +1,18 @@ | ||
/** | ||
* Creates and configures ORMs. | ||
*/ | ||
class Knorm { | ||
/** | ||
* Creates a new ORM instance. Every ORM instance has it's own set of classes | ||
* and configurations, which allows creating multiple ORM's in a single | ||
* application. | ||
* | ||
* @param {object} [config] The ORM's configuration. | ||
* @param {function} config.fieldToColumn A function to convert all | ||
* field-names to column names, for example | ||
* [snakeCase](https://lodash.com/docs/4.17.10#snakeCase). | ||
* @param {boolean} config.debug Whether or not to enable debug mode. See [the | ||
* debugging guide](/guides/debugging) for more info. | ||
* | ||
*/ | ||
constructor(config = {}) { | ||
@@ -30,2 +46,21 @@ this.plugins = {}; | ||
/** | ||
* Loads a plugin into the ORM. | ||
* | ||
* @param {object|function} plugin The plugin to load. If passed as a | ||
* function, the function is called with the ORM instance for initialisation. | ||
* Note that if an object is passed, it should have `name` and `init` | ||
* properties. | ||
* @param {string} plugin.name The name of the plugin. This allows later | ||
* accessing the plugin via the ORM's `plugins` object. Note that for | ||
* functions, the plugin's `name` is the function's name. | ||
* @param {function} plugin.init The function called to initialise the plugin. | ||
* | ||
* @throws {KnormError} if the plugin provided is not a function or is an | ||
* object without an `init` method. | ||
* @throws {KnormError} if the plugin has no `name` property. | ||
* @throws {KnormError} if the plugin has already been added. | ||
* | ||
* @returns {Knorm} the ORM instance. | ||
*/ | ||
use(plugin) { | ||
@@ -63,2 +98,17 @@ if (!plugin) { | ||
/** | ||
* Adds a model to a {@link Knorm} instance. | ||
* | ||
* @param {Model} model The model to add to the ORM. Note that models are | ||
* automatically added to a {@link Knorm} instance when they are configured, | ||
* therefore you will only need to call this method to add models that are not | ||
* configured. See {@link /guides/models.html#model-registry} for more info. | ||
* | ||
* @throws {KnormError} if the model passed does not inherit the ORM's | ||
* {@link Model} instance. This prevents one from (perhaps accidentally) | ||
* adding a model from ORM instance X to ORM instance Y. | ||
* @throws {KnormError} if the model has already been added. | ||
* | ||
* @todo return the {@link Knorm} instance. | ||
*/ | ||
addModel(model) { | ||
@@ -86,2 +136,8 @@ if (!model) { | ||
/** | ||
* Creates a clone of an existing {@link Knorm} instance, copying all the | ||
* models and plugins loaded into the original orm. | ||
* | ||
* @returns {Knorm} the newly cloned ORM instance. | ||
*/ | ||
clone() { | ||
@@ -102,8 +158,73 @@ const clone = new Knorm(this.config); | ||
/** | ||
* The ORM's {@link Field} class. | ||
* | ||
* @type {Field} | ||
*/ | ||
Knorm.prototype.Field = null; | ||
/** | ||
* The ORM's {@link Model} class. | ||
* | ||
* @type {Model} | ||
*/ | ||
Knorm.prototype.Model = null; | ||
/** | ||
* The ORM's {@link Query} class. | ||
* | ||
* @type {Query} | ||
*/ | ||
Knorm.prototype.Query = null; | ||
/** | ||
* The ORM's {@link Transaction} class. | ||
* | ||
* @type {Transaction} | ||
*/ | ||
Knorm.prototype.Transaction = null; | ||
/** | ||
* A reference to the {@link Model} class. | ||
* | ||
* @type {Model} | ||
* | ||
* @private | ||
*/ | ||
Knorm.Model = require('./Model'); | ||
/** | ||
* A reference to the {@link Query} class. | ||
* | ||
* @type {Query} | ||
* | ||
* @private | ||
*/ | ||
Knorm.Query = require('./Query'); | ||
/** | ||
* A reference to the {@link Field} class. | ||
* | ||
* @type {Field} | ||
* | ||
* @private | ||
*/ | ||
Knorm.Field = require('./Field'); | ||
/** | ||
* A reference to the {@link Transaction} class. | ||
* | ||
* @type {Transaction} | ||
* | ||
* @private | ||
*/ | ||
Knorm.Transaction = require('./Transaction'); | ||
/** | ||
* A reference to the {@link KnormError} class. | ||
* | ||
* @type {KnormError} | ||
*/ | ||
Knorm.KnormError = require('./KnormError'); | ||
module.exports = Knorm; |
268
lib/Model.js
const { upperFirst, merge } = require('lodash'); | ||
/** | ||
* Creates model instances and allows setting, getting, validating and casting | ||
* data before and/or after database operations. | ||
*/ | ||
class Model { | ||
/** | ||
* Creates a {@link Model} instance. | ||
* | ||
* @param {object} [data] Data to assign to the instance. This data can be | ||
* anything: data for fields (including virtual fields) or any arbitrary data. | ||
* If data is provided, it's set via {@link Model#setData}. | ||
*/ | ||
constructor(data = {}) { | ||
@@ -58,5 +69,13 @@ const config = this.constructor.config; | ||
// TODO: add support for casting referenced models to id ie this.someId = value.id; | ||
// TODO: strict mode: for virtues, check if it has a setter | ||
// TODO: strict mode: check if all fields in the data are valid field names | ||
/** | ||
* Sets an instance's data, via | ||
* [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). | ||
* | ||
* @param {object} data The data to assign to the instance. | ||
* | ||
* @returns {Model} the same model instance | ||
* | ||
* @todo strict mode: for virtues, check if it has a setter | ||
* @todo strict mode: check if all fields in the data are valid field names | ||
*/ | ||
setData(data) { | ||
@@ -222,12 +241,40 @@ Object.assign(this, data); | ||
async fetch(options) { | ||
const row = await this.getQuery(options).fetch(); | ||
/** | ||
* Inserts a single row into the database. | ||
* | ||
* @param {object} [options] {@link Query} options. | ||
* | ||
* @returns {Promise<Model>} the same instance populated with the inserted row | ||
* from the dabatase. The fields to be returned in the data can be configured | ||
* with the {@link Query#fields} or {@link Query#returning} options. | ||
*/ | ||
async insert(options) { | ||
const row = await this.getQuery(options, { forInsert: true }).insert(this); | ||
return row ? this.setData(row).cast({ forFetch: true }) : row; | ||
} | ||
async delete(options) { | ||
const row = await this.getQuery(options).delete(); | ||
return row ? this.setData(row).cast({ forFetch: true }) : row; | ||
} | ||
/** | ||
* Updates a single row in the database. | ||
* | ||
* ::: warning NOTE | ||
* This method requires a value for either a [primary or a unique | ||
* field](/guides/fields.md#primary-and-unique-fields) to be set on the | ||
* instance in order to know what row to update. | ||
* ::: | ||
* | ||
* ::: tip INFO | ||
* This method sets the {@link Query#first} (return only the first row) and | ||
* {@link Query#require} (throw a {@link NoRowsError} if no row is matched for | ||
* update) query options. However, the {@link Query#require} option can be | ||
* disabled via the `options` param. | ||
* ::: | ||
* | ||
* @param {object} [options] {@link Query} options. | ||
* | ||
* @returns {Promise<Model>} the same instance populated with the updated row | ||
* from the dabatase. The fields to be returned in the data can be configured | ||
* with the {@link Query#fields} or {@link Query#returning} options. | ||
* | ||
* @todo throw {@link ModelError} instead of plain Error | ||
*/ | ||
async update(options) { | ||
@@ -238,8 +285,15 @@ const row = await this.getQuery(options).update(this); | ||
async insert(options) { | ||
const row = await this.getQuery(options, { forInsert: true }).insert(this); | ||
return row ? this.setData(row).cast({ forFetch: true }) : row; | ||
} | ||
// TODO: this will throw if the model has no primary field.. is that expected? | ||
/** | ||
* Either inserts or updates a single row in the database, depending on | ||
* whether a value for the primary field is set or not. | ||
* | ||
* @param {object} [options] {@link Query} options. | ||
* | ||
* @returns {Promise<Model>} the same instance populated with the inserted or | ||
* updated row from the dabatase. The fields to be returned in the data can be | ||
* configured with the {@link Query#fields} or {@link Query#returning} | ||
* options. | ||
* | ||
* @todo throw {@link ModelError} instead of plain Error | ||
*/ | ||
async save(options) { | ||
@@ -251,6 +305,80 @@ return this[this._config.config.primary] === undefined | ||
static async save(data, options) { | ||
return this.query.save(data, options); | ||
/** | ||
* Fetches a single row from the database. | ||
* | ||
* ::: warning NOTE | ||
* This method requires a value for either a [primary or a unique | ||
* field](/guides/fields.md#primary-and-unique-fields) to be set on the | ||
* instance in order to know what row to fetch. | ||
* ::: | ||
* | ||
* ::: tip INFO | ||
* This method sets the {@link Query#first} (return only the first row) and | ||
* {@link Query#require} (throw a {@link NoRowsError} if no row is matched for | ||
* fetching) query options. However, the {@link Query#require} option can be | ||
* disabled via the `options` param. | ||
* ::: | ||
* | ||
* @param {object} [options] {@link Query} options. | ||
* | ||
* @returns {Promise<Model>} the same instance populated with data fetched from | ||
* the database. The fields to be returned in the data can be configured with | ||
* the {@link Query#fields} or {@link Query#returning} options. | ||
* | ||
* @todo throw {@link ModelError} instead of plain Error | ||
*/ | ||
async fetch(options) { | ||
const row = await this.getQuery(options).fetch(); | ||
return row ? this.setData(row).cast({ forFetch: true }) : row; | ||
} | ||
/** | ||
* Deletes a single row from the database. | ||
* | ||
* ::: warning NOTE | ||
* This method requires a value for either a [primary or a unique | ||
* field](/guides/fields.md#primary-and-unique-fields) to be set on the | ||
* instance in order to know what row to delete. | ||
* ::: | ||
* | ||
* ::: tip INFO | ||
* This method sets the {@link Query#first} (return only the first row) and | ||
* {@link Query#require} (throw a {@link NoRowsError} if no row is matched for | ||
* deleting) query options. However, the {@link Query#require} option can be | ||
* disabled via the `options` param. | ||
* ::: | ||
* | ||
* @param {object} [options] {@link Query} options. | ||
* | ||
* @returns {Promise<Model>} the same instance populated with the row deleted | ||
* from the dabatase. The fields to be returned in the data can be configured | ||
* with the {@link Query#fields} or {@link Query#returning} options. | ||
* | ||
* @todo throw {@link ModelError} instead of plain Error | ||
*/ | ||
async delete(options) { | ||
const row = await this.getQuery(options).delete(); | ||
return row ? this.setData(row).cast({ forFetch: true }) : row; | ||
} | ||
/** | ||
* Inserts a single or multiple rows into the database. | ||
* | ||
* @param {Model|object|array} data The data to insert. Can be a plain object, | ||
* a {@link Model} instance or an array of objects or instances. | ||
* @param {object} options {@link Query} options | ||
* | ||
* @returns {Promise} the promise is resolved with an array of the model's | ||
* instances, expect in the following cases: | ||
* - if the {@link Query#forge} query option was set to `false` (or | ||
* {@link Query#lean} set to `true`), then the array will contain plain | ||
* objects. | ||
* - if no rows were inserted, then the array will be empty. If the | ||
* {@link Query#require} query option was set to `true`, then the promise is | ||
* rejected with a {@link NoRowsInsertedError} instead. | ||
* - if the {@link Query#first} query option was set to `true`, then the | ||
* promise is resolved with a single model instance (by default) or plain | ||
* object (if the {@link Query#forge} query option was set to `true`), or | ||
* `null` if no rows were inserted. | ||
*/ | ||
static async insert(data, options) { | ||
@@ -264,2 +392,6 @@ return this.query.insert(data, options); | ||
static async save(data, options) { | ||
return this.query.save(data, options); | ||
} | ||
static async fetch(options) { | ||
@@ -469,2 +601,5 @@ return this.query.fetch(options); | ||
// TODO: document `Model.config = {}` for models that inherit others but have | ||
// no config | ||
static set config({ schema, table, fields, virtuals, options }) { | ||
@@ -528,2 +663,9 @@ if (!this._config) { | ||
/** | ||
* As a getter, returns the fields added to the model or a model that this | ||
* model inherits. As a setter, sets the model's fields or overrides fields | ||
* added to a parent model. | ||
* | ||
* @type {object} | ||
*/ | ||
static set fields(fields) { | ||
@@ -566,6 +708,90 @@ this.config = { fields }; | ||
Model.knorm = Model.prototype.knorm = null; | ||
Model.models = Model.prototype.models = {}; | ||
Model.transaction = Model.prototype.transaction = null; | ||
/** | ||
* A reference to the {@link Knorm} instance. | ||
* | ||
* ::: tip | ||
* This is the same instance assigned to the {@link Model.knorm} static | ||
* property, just added as a convenience for use in instance methods. | ||
* ::: | ||
* | ||
* @type {Knorm} | ||
*/ | ||
Model.prototype.knorm = null; | ||
/** | ||
* A reference to the {@link Knorm} instance. | ||
* | ||
* ::: tip | ||
* This is the same instance assigned to the {@link Model#knorm} instance | ||
* property, just added as a convenience for use in static methods. | ||
* ::: | ||
* | ||
* @type {Knorm} | ||
*/ | ||
Model.knorm = null; | ||
/** | ||
* The model registry. This is an object containing all the models added to the | ||
* ORM, keyed by name. See [model registry](/guides/models.md#model-registry) | ||
* for more info. | ||
* | ||
* ::: tip | ||
* This is the same object assigned to the {@link Model.models} static property, | ||
* just added as a convenience for use in instance methods. | ||
* ::: | ||
* | ||
* @type {object} | ||
*/ | ||
Model.prototype.models = {}; | ||
/** | ||
* The model registry. This is an object containing all the models added to the | ||
* ORM, keyed by name. See [model registry](/guides/models.md#model-registry) | ||
* for more info. | ||
* | ||
* ::: tip | ||
* This is the same object assigned to the {@link Model#models} instance | ||
* property, just added as a convenience for use in static methods. | ||
* ::: | ||
* | ||
* @type {object} | ||
*/ | ||
Model.models = {}; | ||
/** | ||
* For models accessed within a transaction, this is reference to the | ||
* {@link Transaction} instance. | ||
* | ||
* ::: warning NOTE | ||
* This is only set for models that are accessed within a transaction, otherwise | ||
* it's set to `null`. | ||
* ::: | ||
* | ||
* ::: tip | ||
* This is the same instance assigned to the {@link Model#transaction} static | ||
* property, just added as a convenience for use in static methods. | ||
* ::: | ||
* | ||
* @type {Transaction} | ||
*/ | ||
Model.prototype.transaction = null; | ||
/** | ||
* For models accessed within a transaction, this is reference to the | ||
* {@link Transaction} instance. | ||
* | ||
* ::: warning NOTE | ||
* This is only set for models that are accessed within a transaction, otherwise | ||
* it's set to `null`. | ||
* ::: | ||
* | ||
* ::: tip | ||
* This is the same instance assigned to the {@link Model#transaction} instance | ||
* property, just added as a convenience for use in static methods. | ||
* ::: | ||
* | ||
* @type {Transaction} | ||
*/ | ||
Model.transaction = null; | ||
module.exports = Model; | ||
@@ -572,0 +798,0 @@ |
@@ -11,4 +11,13 @@ const { difference } = require('lodash'); | ||
/** | ||
* Creates and runs queries and parses any data returned. | ||
*/ | ||
class Query { | ||
// TODO: use short table alias except in strict/debug mode | ||
/** | ||
* Creates a new Query instance. | ||
* | ||
* @param {Model} model | ||
* | ||
* @todo use short table alias except in strict/debug mode | ||
*/ | ||
constructor(model) { | ||
@@ -158,2 +167,32 @@ if (!model) { | ||
/** | ||
* Configures what fields to return from a database call. | ||
* | ||
* ::: tip INFO | ||
* This is also aliased as {@link Query#returning}. | ||
* ::: | ||
* | ||
* @param {string|array|object} fields The fields to return. When passed as an | ||
* object, the keys are used as aliases while the values are used in the | ||
* query. This also allows you to use raw SQL. | ||
* | ||
* @example For PostgreSQL: | ||
* ```js{10} | ||
* Model.insert( | ||
* { | ||
* firstName: 'Foo', | ||
* lastName: 'Bar', | ||
* }, | ||
* { | ||
* returning: { | ||
* firstName: 'firstName', | ||
* lastName: 'lastName', | ||
* fullNames: Model.query.sql(`"firstName" || ' ' || upper("lastName")`) | ||
* } | ||
* } | ||
* ); | ||
* ``` | ||
* | ||
* @returns {Query} The same {@link Query} instance to allow chaining. | ||
*/ | ||
fields(...fields) { | ||
@@ -163,2 +202,15 @@ return this.addFields(fields); | ||
/** | ||
* Configures what fields to return from a database call. | ||
* | ||
* ::: tip INFO | ||
* This is an alias for {@link Query#fields}. | ||
* ::: | ||
* | ||
* @param {string|array|object} fields The fields to return. | ||
* | ||
* @see {@link Query#fields} | ||
* @returns {Query} The same {@link Query} instance to allow chaining. | ||
*/ | ||
returning(...fields) { | ||
@@ -211,3 +263,5 @@ return this.addFields(fields); | ||
if (typeof this[option] !== 'function') { | ||
throw new this.constructor.QueryError(`unknown option \`${option}\``); | ||
throw new this.constructor.QueryError( | ||
`${this.model.name}: unknown option \`${option}\`` | ||
); | ||
} | ||
@@ -214,0 +268,0 @@ |
const KnormError = require('./KnormError'); | ||
/** | ||
* Creates and executes transactions, allowing multiple queries to be run within | ||
* a transaction. | ||
*/ | ||
class Transaction { | ||
/** | ||
* Creates a {@link Transaction} instance. | ||
* | ||
* @param {function} [callback] The transaction callback, when [running | ||
* transactions with a callback | ||
* function](/guides/transactions.md#transactions-with-a-callback). | ||
*/ | ||
constructor(callback) { | ||
@@ -5,0 +16,0 @@ if (typeof callback === 'function') { |
{ | ||
"name": "@knorm/knorm", | ||
"version": "1.9.0", | ||
"description": "A purely ES6 class-based ORM for Node.js", | ||
"version": "1.9.1", | ||
"description": "A JavaScript ORM written using ES6 classes", | ||
"main": "index.js", | ||
@@ -16,5 +16,5 @@ "scripts": { | ||
"todo": "grep -rn -e TODO -e FIXME lib", | ||
"docs": "docsify serve docs", | ||
"changelog": "standard-changelog", | ||
"release": "standard-version" | ||
"docs:jsdoc": "jsdoc2md --files lib/**/*.js --partial docs/.jsdoc2md/templates/link.hbs --partial docs/.jsdoc2md/templates/body.hbs > docs/api.md", | ||
"docs:dev": "npm run docs:jsdoc && vuepress dev docs", | ||
"docs:build": "npm run docs:jsdoc && vuepress build docs" | ||
}, | ||
@@ -24,29 +24,25 @@ "author": "Joel Mukuthu <joelmukuthu@gmail.com>", | ||
"devDependencies": { | ||
"coveralls": "^3.0.0", | ||
"docsify-cli": "^4.2.0", | ||
"eslint": "^4.19.1", | ||
"eslint-config-ganintegrity": "^2.0.2", | ||
"knex": "^0.14.4", | ||
"mocha": "^5.0.4", | ||
"npm-run-all": "^4.1.2", | ||
"nyc": "^11.7.1", | ||
"pg": "^7.4.1", | ||
"prettier": "^1.12.1", | ||
"proxyquire": "^2.0.0", | ||
"sinon": "^5.0.3", | ||
"sql-bricks-postgres": "^0.5.0", | ||
"standard-changelog": "^1.0.19", | ||
"standard-version": "^4.3.0", | ||
"unexpected": "^10.37.5", | ||
"unexpected-knex": "^1.2.0", | ||
"unexpected-sinon": "^10.9.0" | ||
"@semantic-release/changelog": "3.0.0", | ||
"@semantic-release/git": "7.0.4", | ||
"@semantic-release/github": "5.0.6", | ||
"@semantic-release/npm": "5.0.4", | ||
"coveralls": "3.0.2", | ||
"eslint": "4.19.1", | ||
"eslint-config-ganintegrity": "2.0.2", | ||
"jsdoc-to-markdown": "4.0.1", | ||
"knex": "0.15.2", | ||
"mocha": "5.2.0", | ||
"npm-run-all": "4.1.3", | ||
"nyc": "13.0.1", | ||
"pg": "7.5.0", | ||
"prettier": "1.12.1", | ||
"proxyquire": "2.1.0", | ||
"semantic-release": "15.9.17", | ||
"sinon": "6.3.5", | ||
"sql-bricks-postgres": "0.5.0", | ||
"unexpected": "10.39.1", | ||
"unexpected-knex": "1.2.1", | ||
"unexpected-sinon": "10.10.1", | ||
"vuepress": "0.14.4" | ||
}, | ||
"standard-version": { | ||
"skip": { | ||
"changelog": true | ||
}, | ||
"scripts": { | ||
"postbump": "npm run changelog" | ||
} | ||
}, | ||
"directories": { | ||
@@ -53,0 +49,0 @@ "doc": "docs", |
@@ -7,26 +7,31 @@ # @knorm/knorm | ||
[![dependency status](https://david-dm.org/knorm/knorm.svg)](https://david-dm.org/knorm/knorm) | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/knorm/knorm.svg)](https://greenkeeper.io/) | ||
> A purely ES6 class-based ORM for Node.js. | ||
> A JavaScript ORM written using ES6 classes. | ||
Knorm is a collection of classes that allow creating JavaScript | ||
[ORMs](https://en.wikipedia.org/wiki/Object-relational_mapping) to make it easier | ||
to work with relational databases. | ||
[ORM's](https://en.wikipedia.org/wiki/Object-relational_mapping) to make it | ||
easier to work with relational databases. | ||
Knorm can be used on any existing database without requiring any changes to the | ||
database layer. Also, knorm does not create or run any database migrations | ||
(creating or altering tables, columns, indexes etc). | ||
database layer. It does not (yet) create or run any database migrations | ||
(creating or altering tables, columns, indexes etc), nor does it generate models | ||
from existing database schema (yet). For creating and running migrations, | ||
consider a library such as [Knex.js](http://knexjs.org). | ||
You may also use it to create ORMs on the browser (e.g. for data validation). | ||
It can also be used on the browser to create models that do not interact with | ||
the database, perhaps for data validation. Please note that it's not secure to | ||
generate queries on the browser and send them for processing to a backend | ||
server. | ||
## Features | ||
* [validation](https://knorm.github.io/knorm/#/guides/validation), including | ||
[validation for JSON fields](https://knorm.github.io/knorm/#/guides/validation?id=json-validation) (similar to [Mongoose JS](http://mongoosejs.com/)) | ||
* [plugin support](https://knorm.github.io/knorm/#/guides/plugins) | ||
* [transactions](https://knorm.github.io/knorm/#/guides/transactions) | ||
* [relations](https://knorm.github.io/knorm/#/knorm-relations) through SQL joins | ||
* [field-name to column-name](https://knorm.github.io/knorm/#/api/knorm?id=knormoptions) mapping (e.g. snake-casing) | ||
* [virtual fields](https://knorm.github.io/knorm/#/guides/virtuals) with support for `async` getters | ||
* [value casting](https://knorm.github.io/knorm/#/guides/fields?id=value-casting) before save and after fetch | ||
* simplicity - easy to extend, configure or override, owing to ES6 classes. | ||
* [validation](https://knorm.netlify.com/guides/validation.html), including | ||
[validation for JSON fields](https://knorm.netlify.com/guides/validation.html#json-validation) (similar to [Mongoose JS](http://mongoosejs.com/)) | ||
* [plugin support](https://knorm.netlify.com/guides/plugins.html) | ||
* [transactions](https://knorm.netlify.com/guides/transactions.html) | ||
* [relations](https://knorm-relations.netlify.com) through SQL joins | ||
* [field-name to column-name](https://knorm.netlify.com/api.html#new-knorm-config) mapping (e.g. snake-casing) | ||
* [virtual fields](https://knorm.netlify.com/guides/virtuals.html) with support for `async` getters | ||
* [value casting](https://knorm.netlify.com/guides/fields.html#value-casting) before save and after fetch | ||
* custom error classes for database errors | ||
@@ -42,4 +47,4 @@ * extensive test coverage | ||
| Node.js | Version >= 7.6. | Knorm uses `async/await` | | ||
| Databases | PostgreSQL | via [@knorm/postgres](https://www.npmjs.com/package/@knorm/postgres) plugin | | ||
| Databases | PostgreSQL | via the [@knorm/postgres](https://www.npmjs.com/package/@knorm/postgres) plugin | | ||
## [Get started](https://knorm.github.io/knorm/#/guides/getting-started?id=getting-started) | ||
## [Get started](https://knorm.netlify.com/getting-started.html) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 2 instances in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
1909875
96
11341
49
22
1
80
3