@abedmurrar/objection 0.0.0-stage → 4.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +105 -2
- package/lib/index.mjs +6 -0
- package/lib/initialize.js +11 -0
- package/lib/model/AjvValidator.js +278 -0
- package/lib/model/Model.js +772 -0
- package/lib/model/ModifierNotFoundError.js +10 -0
- package/lib/model/NotFoundError.js +19 -0
- package/lib/model/RelationDoesNotExistError.js +11 -0
- package/lib/model/ValidationError.js +46 -0
- package/lib/model/Validator.js +20 -0
- package/lib/model/getModel.js +7 -0
- package/lib/model/graph/ModelGraph.js +60 -0
- package/lib/model/graph/ModelGraphBuilder.js +188 -0
- package/lib/model/graph/ModelGraphEdge.js +40 -0
- package/lib/model/graph/ModelGraphNode.js +201 -0
- package/lib/model/inheritModel.js +19 -0
- package/lib/model/modelBindKnex.js +69 -0
- package/lib/model/modelClone.js +102 -0
- package/lib/model/modelColPropMap.js +24 -0
- package/lib/model/modelConcurrency.js +24 -0
- package/lib/model/modelId.js +57 -0
- package/lib/model/modelJsonAttributes.js +72 -0
- package/lib/model/modelParseRelations.js +83 -0
- package/lib/model/modelQueryProps.js +116 -0
- package/lib/model/modelSet.js +116 -0
- package/lib/model/modelTableMetadata.js +71 -0
- package/lib/model/modelToJson.js +157 -0
- package/lib/model/modelUtils.js +40 -0
- package/lib/model/modelValidate.js +40 -0
- package/lib/model/modelValues.js +113 -0
- package/lib/model/modelVisitor.js +34 -0
- package/lib/objection.js +89 -0
- package/lib/queryBuilder/FunctionBuilder.js +291 -0
- package/lib/queryBuilder/InternalOptions.js +24 -0
- package/lib/queryBuilder/JoinBuilder.js +124 -0
- package/lib/queryBuilder/QueryBuilder.js +1136 -0
- package/lib/queryBuilder/QueryBuilderBase.js +666 -0
- package/lib/queryBuilder/QueryBuilderContext.js +20 -0
- package/lib/queryBuilder/QueryBuilderContextBase.js +26 -0
- package/lib/queryBuilder/QueryBuilderOperationSupport.js +484 -0
- package/lib/queryBuilder/QueryBuilderUserContext.js +25 -0
- package/lib/queryBuilder/RawBuilder.js +78 -0
- package/lib/queryBuilder/ReferenceBuilder.js +246 -0
- package/lib/queryBuilder/RelationExpression.js +373 -0
- package/lib/queryBuilder/StaticHookArguments.js +122 -0
- package/lib/queryBuilder/ValueBuilder.js +115 -0
- package/lib/queryBuilder/expressions.js +121 -0
- package/lib/queryBuilder/functions/mysql.js +762 -0
- package/lib/queryBuilder/graph/GraphAction.js +54 -0
- package/lib/queryBuilder/graph/GraphData.js +12 -0
- package/lib/queryBuilder/graph/GraphFetcher.js +155 -0
- package/lib/queryBuilder/graph/GraphNodeDbExistence.js +146 -0
- package/lib/queryBuilder/graph/GraphOperation.js +21 -0
- package/lib/queryBuilder/graph/GraphOptions.js +165 -0
- package/lib/queryBuilder/graph/GraphUpsert.js +231 -0
- package/lib/queryBuilder/graph/delete/GraphDelete.js +15 -0
- package/lib/queryBuilder/graph/delete/GraphDeleteAction.js +56 -0
- package/lib/queryBuilder/graph/insert/GraphInsert.js +116 -0
- package/lib/queryBuilder/graph/insert/GraphInsertAction.js +183 -0
- package/lib/queryBuilder/graph/insert/JoinRowGraphInsertAction.js +70 -0
- package/lib/queryBuilder/graph/patch/GraphPatch.js +15 -0
- package/lib/queryBuilder/graph/patch/GraphPatchAction.js +205 -0
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsert.js +26 -0
- package/lib/queryBuilder/graph/recursiveUpsert/GraphRecursiveUpsertAction.js +96 -0
- package/lib/queryBuilder/join/JoinResultColumn.js +19 -0
- package/lib/queryBuilder/join/JoinResultParser.js +106 -0
- package/lib/queryBuilder/join/RelationJoiner.js +258 -0
- package/lib/queryBuilder/join/TableNode.js +136 -0
- package/lib/queryBuilder/join/TableTree.js +69 -0
- package/lib/queryBuilder/join/utils.js +19 -0
- package/lib/queryBuilder/operations/DelegateOperation.js +101 -0
- package/lib/queryBuilder/operations/DeleteOperation.js +47 -0
- package/lib/queryBuilder/operations/FindByIdOperation.js +29 -0
- package/lib/queryBuilder/operations/FindByIdsOperation.js +23 -0
- package/lib/queryBuilder/operations/FindOperation.js +147 -0
- package/lib/queryBuilder/operations/FirstOperation.js +22 -0
- package/lib/queryBuilder/operations/FromOperation.js +91 -0
- package/lib/queryBuilder/operations/InsertAndFetchOperation.js +38 -0
- package/lib/queryBuilder/operations/InsertGraphAndFetchOperation.js +35 -0
- package/lib/queryBuilder/operations/InsertGraphOperation.js +87 -0
- package/lib/queryBuilder/operations/InsertOperation.js +173 -0
- package/lib/queryBuilder/operations/InstanceDeleteOperation.js +37 -0
- package/lib/queryBuilder/operations/InstanceFindOperation.js +17 -0
- package/lib/queryBuilder/operations/InstanceInsertOperation.js +20 -0
- package/lib/queryBuilder/operations/InstanceUpdateOperation.js +45 -0
- package/lib/queryBuilder/operations/JoinRelatedOperation.js +83 -0
- package/lib/queryBuilder/operations/KnexOperation.js +11 -0
- package/lib/queryBuilder/operations/MergeOperation.js +43 -0
- package/lib/queryBuilder/operations/ObjectionToKnexConvertingOperation.js +173 -0
- package/lib/queryBuilder/operations/OnBuildKnexOperation.js +23 -0
- package/lib/queryBuilder/operations/OnBuildOperation.js +23 -0
- package/lib/queryBuilder/operations/OnErrorOperation.js +23 -0
- package/lib/queryBuilder/operations/QueryBuilderOperation.js +258 -0
- package/lib/queryBuilder/operations/RangeOperation.js +37 -0
- package/lib/queryBuilder/operations/RelateOperation.js +20 -0
- package/lib/queryBuilder/operations/ReturningOperation.js +24 -0
- package/lib/queryBuilder/operations/RunAfterOperation.js +23 -0
- package/lib/queryBuilder/operations/RunBeforeOperation.js +23 -0
- package/lib/queryBuilder/operations/UnrelateOperation.js +18 -0
- package/lib/queryBuilder/operations/UpdateAndFetchOperation.js +63 -0
- package/lib/queryBuilder/operations/UpdateOperation.js +130 -0
- package/lib/queryBuilder/operations/UpsertGraphAndFetchOperation.js +35 -0
- package/lib/queryBuilder/operations/UpsertGraphOperation.js +47 -0
- package/lib/queryBuilder/operations/WhereCompositeOperation.js +48 -0
- package/lib/queryBuilder/operations/WhereInOperation.js +46 -0
- package/lib/queryBuilder/operations/eager/EagerOperation.js +46 -0
- package/lib/queryBuilder/operations/eager/JoinEagerOperation.js +41 -0
- package/lib/queryBuilder/operations/eager/NaiveEagerOperation.js +10 -0
- package/lib/queryBuilder/operations/eager/WhereInEagerOperation.js +227 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonHasOperation.js +29 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonNotObjectOperation.js +37 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOperation.js +30 -0
- package/lib/queryBuilder/operations/jsonApi/WhereJsonOverlapsOperation.js +33 -0
- package/lib/queryBuilder/operations/jsonApi/mysqlJsonApi.js +155 -0
- package/lib/queryBuilder/operations/jsonApi/postgresJsonApi.js +135 -0
- package/lib/queryBuilder/operations/select/SelectOperation.js +50 -0
- package/lib/queryBuilder/operations/select/Selection.js +112 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeMsSqlOperation.js +84 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeOperation.js +90 -0
- package/lib/queryBuilder/operations/whereInComposite/WhereInCompositeSqliteOperation.js +95 -0
- package/lib/queryBuilder/parsers/jsonFieldExpressionParser.js +787 -0
- package/lib/queryBuilder/parsers/relationExpressionParser.js +930 -0
- package/lib/queryBuilder/transformations/CompositeQueryTransformation.js +17 -0
- package/lib/queryBuilder/transformations/QueryTransformation.js +9 -0
- package/lib/queryBuilder/transformations/WrapMysqlModifySubqueryTransformation.js +57 -0
- package/lib/queryBuilder/transformations/index.js +9 -0
- package/lib/queryBuilder/whereScopes.js +199 -0
- package/lib/relations/Relation.js +317 -0
- package/lib/relations/RelationDeleteOperation.js +23 -0
- package/lib/relations/RelationFindOperation.js +116 -0
- package/lib/relations/RelationInsertOperation.js +25 -0
- package/lib/relations/RelationOwner.js +199 -0
- package/lib/relations/RelationProperty.js +207 -0
- package/lib/relations/RelationUpdateOperation.js +23 -0
- package/lib/relations/belongsToOne/BelongsToOneDeleteOperation.js +18 -0
- package/lib/relations/belongsToOne/BelongsToOneInsertOperation.js +33 -0
- package/lib/relations/belongsToOne/BelongsToOneRelateOperation.js +39 -0
- package/lib/relations/belongsToOne/BelongsToOneRelation.js +45 -0
- package/lib/relations/belongsToOne/BelongsToOneUnrelateOperation.js +46 -0
- package/lib/relations/hasMany/HasManyInsertOperation.js +45 -0
- package/lib/relations/hasMany/HasManyRelateOperation.js +48 -0
- package/lib/relations/hasMany/HasManyRelation.js +41 -0
- package/lib/relations/hasMany/HasManyUnrelateOperation.js +26 -0
- package/lib/relations/hasOne/HasOneRelation.js +10 -0
- package/lib/relations/hasOneThrough/HasOneThroughRelation.js +10 -0
- package/lib/relations/manyToMany/ManyToManyModifyMixin.js +82 -0
- package/lib/relations/manyToMany/ManyToManyRelation.js +405 -0
- package/lib/relations/manyToMany/ManyToManySqliteModifyMixin.js +29 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperation.js +8 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteOperationBase.js +20 -0
- package/lib/relations/manyToMany/delete/ManyToManyDeleteSqliteOperation.js +8 -0
- package/lib/relations/manyToMany/find/ManyToManyFindOperation.js +82 -0
- package/lib/relations/manyToMany/insert/ManyToManyInsertOperation.js +40 -0
- package/lib/relations/manyToMany/relate/ManyToManyRelateOperation.js +64 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperation.js +11 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateOperationBase.js +27 -0
- package/lib/relations/manyToMany/unrelate/ManyToManyUnrelateSqliteOperation.js +11 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperation.js +8 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateOperationBase.js +65 -0
- package/lib/relations/manyToMany/update/ManyToManyUpdateSqliteOperation.js +8 -0
- package/lib/transaction.js +66 -0
- package/lib/utils/assert.js +19 -0
- package/lib/utils/buildUtils.js +19 -0
- package/lib/utils/classUtils.js +9 -0
- package/lib/utils/cloneDeep.js +365 -0
- package/lib/utils/createModifier.js +37 -0
- package/lib/utils/deprecate.js +11 -0
- package/lib/utils/identifierMapping.js +188 -0
- package/lib/utils/internalPropUtils.js +7 -0
- package/lib/utils/knexUtils.js +81 -0
- package/lib/utils/mixin.js +18 -0
- package/lib/utils/mysqlJsonPath.js +70 -0
- package/lib/utils/normalizeIds.js +127 -0
- package/lib/utils/objectUtils.js +314 -0
- package/lib/utils/parseFieldExpression.js +73 -0
- package/lib/utils/postgresJsonPath.js +96 -0
- package/lib/utils/promiseUtils/after.js +14 -0
- package/lib/utils/promiseUtils/afterReturn.js +14 -0
- package/lib/utils/promiseUtils/index.js +15 -0
- package/lib/utils/promiseUtils/isPromise.js +8 -0
- package/lib/utils/promiseUtils/map.js +96 -0
- package/lib/utils/promiseUtils/mapAfterAllReturn.js +23 -0
- package/lib/utils/promiseUtils/try.js +19 -0
- package/lib/utils/resolveModel.js +104 -0
- package/lib/utils/tmpColumnUtils.js +7 -0
- package/package.json +113 -4
- package/typings/objection/index.d.mts +362 -0
- package/typings/objection/index.d.ts +4131 -0
- package/typings/objection/relation-expression.d.ts +237 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Sami Koskimäki
|
|
4
|
+
Copyright (c) 2026 Abed Murrar
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
|
package/README.md
CHANGED
|
@@ -1,3 +1,106 @@
|
|
|
1
|
-
|
|
1
|
+
[](https://github.com/abedmurrar/objection.js/actions/workflows/test.yml)
|
|
2
|
+
[](https://www.npmjs.com/package/@abedmurrar/objection)
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# @abedmurrar/objection
|
|
5
|
+
|
|
6
|
+
**A fast, strictly typed SQL ORM for Node.js, built on [knex](https://knexjs.org).** Define your models and relations once, then query them with the full power of SQL: eager loading, graph inserts and upserts, transactions, JSON columns and expressions, with types that know what each query returns.
|
|
7
|
+
|
|
8
|
+
It started as a fork of [objection.js](https://github.com/Vincit/objection.js) 3.1.5 and keeps its API, so objection.js code runs on it apart from the [breaking changes](doc/fork/breaking-changes.md), most of which fix queries that read or changed the wrong rows.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @abedmurrar/objection@next knex
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Why this one
|
|
15
|
+
|
|
16
|
+
- **Types that know what you loaded.** Relations are inferred from `relationMappings`, query results only contain the relations the query fetched, and relation expressions, modifier names and `Model.ref()` columns are checked at compile time. See [TypeScript](doc/fork/typescript.md).
|
|
17
|
+
- **Fast.** objection's own work for a query takes 13% to 19% less time than in objection.js 3.1.5, `toJSON()` 44% less, validation 55% less, and a validated insert in a new transaction 91% less, with the same SQL and results. See [Performance](doc/fork/performance.md).
|
|
18
|
+
- **MySQL and MariaDB as first-class databases**, not only postgres: JSON field expressions, the `whereJson*` methods, batch inserts and an `fn.*` helper for every MySQL 8.4 function. MariaDB 10.6 and 11.4 are tested in CI. See [MySQL and MariaDB](doc/fork/mysql.md).
|
|
19
|
+
- **Expressions without raw SQL**, like `Pet.ref('age').gte(3)`, `fn.count().filter(...)`, `fn.case()` and window functions. See [Expressions](doc/fork/expressions.md).
|
|
20
|
+
- **Safer.** JSON field keys are escaped (in objection.js 3.1.5 they allowed SQL injection), an `orWhere()` can no longer match the rows of other relation owners, and `for()` is no longer silently ignored. See [Breaking changes](doc/fork/breaking-changes.md).
|
|
21
|
+
- **Modern.** Node.js 24+, ES modules and CommonJS from one package, knex 3.3, the knex methods objection didn't have, TypeScript 7, no lodash and no bluebird, and `npm audit` reports nothing. The source is moving to TypeScript.
|
|
22
|
+
|
|
23
|
+
## A quick look
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { Model, defineRelations } from '@abedmurrar/objection';
|
|
27
|
+
import { knex } from 'knex';
|
|
28
|
+
|
|
29
|
+
Model.knex(knex({ client: 'pg', connection: process.env.DATABASE_URL }));
|
|
30
|
+
|
|
31
|
+
class Pet extends Model {
|
|
32
|
+
static tableName = 'pets';
|
|
33
|
+
|
|
34
|
+
id!: number;
|
|
35
|
+
name!: string;
|
|
36
|
+
ownerId!: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class Person extends Model {
|
|
40
|
+
static tableName = 'persons';
|
|
41
|
+
|
|
42
|
+
id!: number;
|
|
43
|
+
firstName!: string;
|
|
44
|
+
|
|
45
|
+
static relationMappings = defineRelations(() => ({
|
|
46
|
+
pets: {
|
|
47
|
+
relation: Model.HasManyRelation,
|
|
48
|
+
modelClass: Pet,
|
|
49
|
+
join: { from: 'persons.id', to: 'pets.ownerId' },
|
|
50
|
+
},
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const people = await Person.query().where('firstName', 'like', 'J%').withGraphFetched('pets');
|
|
55
|
+
|
|
56
|
+
people[0].pets[0].name; // `pets` is in the type because the query loaded it
|
|
57
|
+
people[0].firstName;
|
|
58
|
+
|
|
59
|
+
// A person and their pets in one insert.
|
|
60
|
+
await Person.query().insertGraph({
|
|
61
|
+
firstName: 'Jennifer',
|
|
62
|
+
pets: [{ name: 'Doggo' }],
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
| Dependency | Version |
|
|
69
|
+
| ---------- | ------------------------------------------------------ |
|
|
70
|
+
| Node.js | 24 or newer (24 and 26 are tested) |
|
|
71
|
+
| knex | 3.3 or newer |
|
|
72
|
+
| MySQL | 8.0.17 or newer (tested on 8.4 with `mysql2`) |
|
|
73
|
+
| MariaDB | 10.6 or newer (tested on 10.6 and 11.4 with `mariadb`) |
|
|
74
|
+
| SQLite | 3.35 or newer for batch inserts |
|
|
75
|
+
|
|
76
|
+
Postgres is tested on the current release.
|
|
77
|
+
|
|
78
|
+
## Coming from objection.js
|
|
79
|
+
|
|
80
|
+
Install the package under the name `objection`, and existing `import { Model } from 'objection'` code keeps working:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
npm install objection@npm:@abedmurrar/objection@next
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Then read the [breaking changes](doc/fork/breaking-changes.md) and the [migration guide](doc/release-notes/migration.md). Most changes make queries do what they look like they do. The stricter types can flag code that only compiled because the old types were loose; the [TypeScript page](doc/fork/typescript.md) lists an opt-out for every check.
|
|
87
|
+
|
|
88
|
+
## Documentation
|
|
89
|
+
|
|
90
|
+
- [What this package adds](doc/fork/README.md) to objection.js, page by page.
|
|
91
|
+
- **[The documentation site](https://abedmurrar.github.io/objection.js/)**: the guide and the API reference for everything else (models, relations, queries, eager loading, graphs, transactions and validation), which come from objection.js.
|
|
92
|
+
- [Changelog](doc/release-notes/changelog.md) and [migration guide](doc/release-notes/migration.md).
|
|
93
|
+
- [Contributing](doc/guide/contributing.md) and [releasing](RELEASING.md).
|
|
94
|
+
|
|
95
|
+
## The approach
|
|
96
|
+
|
|
97
|
+
Like objection.js, this is less a classic ORM than **a relational query builder**: you work with queries, not with an object graph that hides the SQL.
|
|
98
|
+
|
|
99
|
+
- You get a declarative way to define models and their relations, the full power of SQL to fetch and change rows, eager loading, graph inserts and upserts, transactions, optional JSON schema validation, and a way to store documents in JSON columns.
|
|
100
|
+
- You don't get a query language of its own (the query builder is knex, and `raw()` is there for anything it can't express), or a schema generated from the models: [knex migrations](https://knexjs.org/guide/migrations.html) do that job.
|
|
101
|
+
|
|
102
|
+
## Credits
|
|
103
|
+
|
|
104
|
+
This project is built on [objection.js](https://github.com/Vincit/objection.js) by [Sami Koskimäki](https://github.com/koskimas) and its many contributors, maintained at [Vincit](https://github.com/Vincit). Its design, its API, most of its code and much of the documentation come from there. Thank you.
|
|
105
|
+
|
|
106
|
+
Released under the [MIT license](LICENSE), like objection.js.
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The ES module entry of the package. It re-exports the CommonJS build, so
|
|
2
|
+
// `import { Model }` and `require(...).Model` are the same class and there is
|
|
3
|
+
// only one copy of objection in memory (no dual package hazard).
|
|
4
|
+
import objection from './objection.js';
|
|
5
|
+
export const { Model, QueryBuilder, QueryBuilderBase, QueryBuilderOperation, RelationExpression, ValidationError, NotFoundError, AjvValidator, Validator, Relation, HasOneRelation, HasManyRelation, BelongsToOneRelation, HasOneThroughRelation, ManyToManyRelation, transaction, initialize, compose, mixin, defineRelations, ref, val, raw, fn, snakeCaseMappers, knexSnakeCaseMappers, knexIdentifierMapping, DBError, UniqueViolationError, NotNullViolationError, ForeignKeyViolationError, ConstraintViolationError, CheckViolationError, DataError, } = objection;
|
|
6
|
+
export default objection;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
async function initialize(knex, modelClasses) {
|
|
3
|
+
if (!modelClasses) {
|
|
4
|
+
modelClasses = knex;
|
|
5
|
+
knex = modelClasses[0].knex();
|
|
6
|
+
}
|
|
7
|
+
await Promise.all(modelClasses.map((modelClass) => modelClass.fetchTableMetadata({ knex })));
|
|
8
|
+
}
|
|
9
|
+
module.exports = {
|
|
10
|
+
initialize,
|
|
11
|
+
};
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const Ajv = require('ajv');
|
|
3
|
+
const addFormats = require('ajv-formats');
|
|
4
|
+
const { Validator } = require('./Validator');
|
|
5
|
+
const { ValidationErrorType } = require('../model/ValidationError');
|
|
6
|
+
const { isObject, cloneDeep: cloneDeepValue, omit } = require('../utils/objectUtils');
|
|
7
|
+
class AjvValidator extends Validator {
|
|
8
|
+
static init(self, conf) {
|
|
9
|
+
super.init(self, conf);
|
|
10
|
+
self.ajvOptions = Object.assign({}, conf.options, {
|
|
11
|
+
allErrors: true,
|
|
12
|
+
});
|
|
13
|
+
// Create a normal Ajv instance.
|
|
14
|
+
self.ajv = new Ajv(Object.assign({
|
|
15
|
+
useDefaults: true,
|
|
16
|
+
}, self.ajvOptions));
|
|
17
|
+
// Create an instance that doesn't set default values. We need this one
|
|
18
|
+
// to validate `patch` objects (objects that have a subset of properties).
|
|
19
|
+
self.ajvNoDefaults = new Ajv(Object.assign({}, self.ajvOptions, {
|
|
20
|
+
useDefaults: false,
|
|
21
|
+
}));
|
|
22
|
+
// Caches for the compiled validator functions: by the serialized schema,
|
|
23
|
+
// and by the schema object for the schemas of model classes.
|
|
24
|
+
self.cache = new Map();
|
|
25
|
+
self.schemaCache = new WeakMap();
|
|
26
|
+
const setupAjv = (ajv) => {
|
|
27
|
+
if (conf.onCreateAjv) {
|
|
28
|
+
conf.onCreateAjv(ajv);
|
|
29
|
+
}
|
|
30
|
+
// Only add Ajv formats if they weren't added in user-space already
|
|
31
|
+
if (!ajv.formats['date-time']) {
|
|
32
|
+
addFormats(ajv);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
setupAjv(self.ajv);
|
|
36
|
+
setupAjv(self.ajvNoDefaults);
|
|
37
|
+
}
|
|
38
|
+
beforeValidate({ json, model, options, ctx }) {
|
|
39
|
+
ctx.jsonSchema = model.constructor.getJsonSchema();
|
|
40
|
+
// Objection model's have a `$beforeValidate` hook that is allowed to modify the schema.
|
|
41
|
+
// We need to clone the schema in case the function modifies it. We only do this in the
|
|
42
|
+
// rare case that the given model has implemented the hook.
|
|
43
|
+
if (model.$beforeValidate !== model.$objectionModelClass.prototype.$beforeValidate) {
|
|
44
|
+
ctx.jsonSchema = cloneDeep(ctx.jsonSchema);
|
|
45
|
+
const ret = model.$beforeValidate(ctx.jsonSchema, json, options);
|
|
46
|
+
if (ret !== undefined) {
|
|
47
|
+
ctx.jsonSchema = ret;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
validate({ json, model, options, ctx }) {
|
|
52
|
+
if (!ctx.jsonSchema) {
|
|
53
|
+
return json;
|
|
54
|
+
}
|
|
55
|
+
const modelClass = model.constructor;
|
|
56
|
+
const validator = this.getValidator(modelClass, ctx.jsonSchema, !!options.patch);
|
|
57
|
+
// We need to clone the input json if we are about to set default values.
|
|
58
|
+
if (!options.mutable && !options.patch && setsDefaultValues(ctx.jsonSchema)) {
|
|
59
|
+
json = cloneDeep(json);
|
|
60
|
+
}
|
|
61
|
+
validator.call(model, json);
|
|
62
|
+
const error = parseValidationError(validator.errors, modelClass, options, this.ajvOptions);
|
|
63
|
+
if (error) {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
return json;
|
|
67
|
+
}
|
|
68
|
+
getValidator(modelClass, jsonSchema, isPatchObject) {
|
|
69
|
+
// Use the Ajv custom serializer if provided.
|
|
70
|
+
const createCacheKey = this.ajvOptions.serialize || JSON.stringify;
|
|
71
|
+
// The compiled validators are cached by the serialized schema. In the
|
|
72
|
+
// common case where jsonSchema is the model class's own schema, they are
|
|
73
|
+
// also cached by the schema object, so the costly createCacheKey function
|
|
74
|
+
// runs once per schema object. Classes bound to a knex instance share their
|
|
75
|
+
// validator with the class they are bound from, and a bound class can have
|
|
76
|
+
// a schema of its own, so the cache isn't keyed by the class name.
|
|
77
|
+
const isClassSchema = jsonSchema === modelClass.getJsonSchema() && isObject(jsonSchema);
|
|
78
|
+
let validators = isClassSchema ? this.schemaCache.get(jsonSchema) : undefined;
|
|
79
|
+
let validator = null;
|
|
80
|
+
if (!validators) {
|
|
81
|
+
const cacheKey = createCacheKey(jsonSchema);
|
|
82
|
+
validators = this.cache.get(cacheKey);
|
|
83
|
+
if (!validators) {
|
|
84
|
+
validators = {
|
|
85
|
+
// Validator created for the schema object without `required` properties
|
|
86
|
+
// using the Ajv instance that doesn't set default values.
|
|
87
|
+
patchValidator: null,
|
|
88
|
+
// Validator created for the unmodified schema.
|
|
89
|
+
normalValidator: null,
|
|
90
|
+
};
|
|
91
|
+
this.cache.set(cacheKey, validators);
|
|
92
|
+
}
|
|
93
|
+
if (isClassSchema) {
|
|
94
|
+
this.schemaCache.set(jsonSchema, validators);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (isPatchObject) {
|
|
98
|
+
validator = validators.patchValidator;
|
|
99
|
+
if (!validator) {
|
|
100
|
+
validator = this.compilePatchValidator(jsonSchema);
|
|
101
|
+
validators.patchValidator = validator;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
validator = validators.normalValidator;
|
|
106
|
+
if (!validator) {
|
|
107
|
+
validator = this.compileNormalValidator(jsonSchema);
|
|
108
|
+
validators.normalValidator = validator;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return validator;
|
|
112
|
+
}
|
|
113
|
+
compilePatchValidator(jsonSchema) {
|
|
114
|
+
jsonSchema = jsonSchemaWithoutRequired(jsonSchema);
|
|
115
|
+
// We need to use the ajv instance that doesn't set the default values.
|
|
116
|
+
return this.ajvNoDefaults.compile(jsonSchema);
|
|
117
|
+
}
|
|
118
|
+
compileNormalValidator(jsonSchema) {
|
|
119
|
+
return this.ajv.compile(jsonSchema);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function parseValidationError(errors, modelClass, options, ajvOptions) {
|
|
123
|
+
if (!errors) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const relationNames = modelClass.getRelationNames();
|
|
127
|
+
const errorHash = {};
|
|
128
|
+
let numErrors = 0;
|
|
129
|
+
for (const error of errors) {
|
|
130
|
+
// If additionalProperties = false, relations can pop up as additionalProperty
|
|
131
|
+
// errors. Skip those.
|
|
132
|
+
if (error.params &&
|
|
133
|
+
error.params.additionalProperty &&
|
|
134
|
+
relationNames.includes(error.params.additionalProperty)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
let path = error.instancePath.replace(/\//g, '.');
|
|
138
|
+
if (error.params) {
|
|
139
|
+
if (error.params.missingProperty) {
|
|
140
|
+
path += `.${error.params.missingProperty}`;
|
|
141
|
+
}
|
|
142
|
+
else if (error.params.additionalProperty) {
|
|
143
|
+
path += `.${error.params.additionalProperty}`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const key = `${options.dataPath || ''}${path}`.substring(1);
|
|
147
|
+
// More than one error can occur for the same key in Ajv, merge them in the array:
|
|
148
|
+
const array = errorHash[key] || (errorHash[key] = []);
|
|
149
|
+
// Prepare error object
|
|
150
|
+
const errorObj = {
|
|
151
|
+
message: error.message,
|
|
152
|
+
keyword: error.keyword,
|
|
153
|
+
params: error.params,
|
|
154
|
+
};
|
|
155
|
+
// Add data if verbose enabled
|
|
156
|
+
if (ajvOptions.verbose) {
|
|
157
|
+
errorObj.data = error.data;
|
|
158
|
+
}
|
|
159
|
+
// Use unshift instead of push so that the last error ends up at [0],
|
|
160
|
+
// preserving previous behavior where only the last error was stored.
|
|
161
|
+
array.unshift(errorObj);
|
|
162
|
+
++numErrors;
|
|
163
|
+
}
|
|
164
|
+
if (numErrors === 0) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
return modelClass.createValidationError({
|
|
168
|
+
type: ValidationErrorType.ModelValidation,
|
|
169
|
+
data: errorHash,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function cloneDeep(obj) {
|
|
173
|
+
if (isObject(obj) && obj.$isObjectionModel) {
|
|
174
|
+
return obj.$clone();
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
return cloneDeepValue(obj);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Whether a schema has default values is checked for every validated model,
|
|
181
|
+
// so the answer is cached per schema object.
|
|
182
|
+
const setsDefaultValuesCache = new WeakMap();
|
|
183
|
+
function setsDefaultValues(jsonSchema) {
|
|
184
|
+
if (!isObject(jsonSchema)) {
|
|
185
|
+
return jsonSchema && jsonSchema.properties && hasDefaults(jsonSchema.properties);
|
|
186
|
+
}
|
|
187
|
+
let result = setsDefaultValuesCache.get(jsonSchema);
|
|
188
|
+
if (result === undefined) {
|
|
189
|
+
result = !!(jsonSchema.properties && hasDefaults(jsonSchema.properties));
|
|
190
|
+
setsDefaultValuesCache.set(jsonSchema, result);
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
function hasDefaults(obj) {
|
|
195
|
+
if (Array.isArray(obj)) {
|
|
196
|
+
return arrayHasDefaults(obj);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
return objectHasDefaults(obj);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function arrayHasDefaults(arr) {
|
|
203
|
+
for (let i = 0, l = arr.length; i < l; ++i) {
|
|
204
|
+
const val = arr[i];
|
|
205
|
+
if (isObject(val) && hasDefaults(val)) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
function objectHasDefaults(obj) {
|
|
212
|
+
const keys = Object.keys(obj);
|
|
213
|
+
for (let i = 0, l = keys.length; i < l; ++i) {
|
|
214
|
+
const key = keys[i];
|
|
215
|
+
if (key === 'default') {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const val = obj[key];
|
|
220
|
+
if (isObject(val) && hasDefaults(val)) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
function jsonSchemaWithoutRequired(jsonSchema) {
|
|
228
|
+
const subSchemaProps = ['anyOf', 'oneOf', 'allOf', 'not', 'then', 'else', 'properties'];
|
|
229
|
+
return Object.assign(omit(jsonSchema, ['required', ...subSchemaProps]), ...subSchemaProps.map((prop) => subSchemaWithoutRequired(jsonSchema, prop)), jsonSchema && jsonSchema.definitions && Object.keys(jsonSchema.definitions).length > 0
|
|
230
|
+
? {
|
|
231
|
+
definitions: Object.assign(...Object.keys(jsonSchema.definitions).map((prop) => ({
|
|
232
|
+
[prop]: jsonSchemaWithoutRequired(jsonSchema.definitions[prop]),
|
|
233
|
+
}))),
|
|
234
|
+
}
|
|
235
|
+
: {}, jsonSchema.discriminator && jsonSchema.discriminator.propertyName
|
|
236
|
+
? { required: [jsonSchema.discriminator.propertyName] }
|
|
237
|
+
: {});
|
|
238
|
+
}
|
|
239
|
+
function subSchemaWithoutRequired(jsonSchema, prop) {
|
|
240
|
+
if (jsonSchema[prop]) {
|
|
241
|
+
if (Array.isArray(jsonSchema[prop])) {
|
|
242
|
+
const schemaArray = jsonSchemaArrayWithoutRequired(jsonSchema[prop]);
|
|
243
|
+
if (schemaArray.length !== 0) {
|
|
244
|
+
return {
|
|
245
|
+
[prop]: schemaArray,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (jsonSchema.type === 'object' && prop === 'properties') {
|
|
253
|
+
return {
|
|
254
|
+
[prop]: Object.fromEntries(Object.entries(jsonSchema[prop]).map(([key, schema]) => [
|
|
255
|
+
key,
|
|
256
|
+
jsonSchemaWithoutRequired(schema),
|
|
257
|
+
])),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
return {
|
|
262
|
+
[prop]: jsonSchemaWithoutRequired(jsonSchema[prop]),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
return {};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function jsonSchemaArrayWithoutRequired(jsonSchemaArray) {
|
|
271
|
+
return jsonSchemaArray.map(jsonSchemaWithoutRequired).filter(isNotEmptyObject);
|
|
272
|
+
}
|
|
273
|
+
function isNotEmptyObject(obj) {
|
|
274
|
+
return Object.keys(obj).length !== 0;
|
|
275
|
+
}
|
|
276
|
+
module.exports = {
|
|
277
|
+
AjvValidator,
|
|
278
|
+
};
|