Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@knorm/knorm

Package Overview
Dependencies
Maintainers
2
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@knorm/knorm - npm Package Compare versions

Comparing version 1.9.0 to 1.9.1

.releaserc.json

7

CHANGELOG.md

@@ -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)

8

docs/credits.md
# 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 @@ }

!> &bull; `forSave` cast functions are called **after** validation <br />
&bull; 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 {

@@ -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;
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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc