Skip to content
Draft

4.0 #198

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
db9cb2c
Don't deploy the docs from the esm branch
lehni Oct 10, 2026
bb3d52f
Convert lib and typings to ES modules
lehni Oct 10, 2026
b70f908
Vendor db-errors into lib
lehni Oct 10, 2026
0c64b38
Run the tests with vitest
lehni Oct 10, 2026
fcd8ec7
Update TypeScript to 7
lehni Oct 10, 2026
778500a
Test Node 20.19.0 and up in CI and drop the knex 2.3.0 job
lehni Oct 10, 2026
a286933
Convert the examples to ES modules
lehni Oct 10, 2026
df23b6d
Use ES modules in the docs
lehni Oct 10, 2026
f4580f7
Update sqlite3 to 6.0.1
lehni Oct 10, 2026
6ee90e2
Export declarations inline
lehni Oct 10, 2026
f8fc8bf
Run CI on the next branch
lehni Oct 10, 2026
c5cdaa0
Start the 4.0.0 changelog
lehni Oct 11, 2026
b9e1eb8
Switch from npm to pnpm
lehni Oct 11, 2026
23689ff
Return an empty array from asArray() for undefined (#129)
lehni Oct 11, 2026
7126a51
Remove the deprecated skipUndefined()
lehni Oct 11, 2026
390e4a1
Apply limit(), offset() and first() in graph modifiers per parent by …
lehni Oct 11, 2026
ff64206
Throw on onConflict() with insertGraph() and upsertGraph() (#2156)
lehni Oct 11, 2026
0de7ccf
Remove the deprecated allowMarkers from InsertGraphOptions
lehni Oct 11, 2026
a4f62d5
Drop the knex < 3.3 note from the limitPerParent docs (#95)
lehni Oct 11, 2026
2a509fc
Reject field expression keys in update() (#160)
lehni Oct 11, 2026
40ec158
Preserve the JSON keys of field expressions in snakeCaseMappers() by …
lehni Oct 11, 2026
e423aa7
Throw for relation expressions that name a relation twice (#131)
lehni Oct 11, 2026
3b5165d
Throw on unknown graph options (#84)
lehni Oct 11, 2026
14875b5
Order the fetched relations in the upsertGraphAndFetch() graph test
lehni Oct 12, 2026
b1e7316
Speed up type checking of query builders
lehni Oct 12, 2026
528fe86
Require TypeScript 5.8 or newer
lehni Oct 12, 2026
2770cb7
Convert declared keys with a colon as a whole in snakeCaseMappers() (…
lehni Oct 12, 2026
198925d
Throw in insertGraph() for new models below related models (#217)
lehni Oct 12, 2026
d3397ba
Reject field expression keys in insert() (#160)
lehni Oct 12, 2026
44d9199
Always clear the foreign key before upsertGraph() deletes the row of …
lehni Oct 12, 2026
8d9a0db
Keep the current HasOneRelation model in upsertGraph() when the graph…
lehni Oct 12, 2026
49ec2b5
Drop support for MySQL 5.7 (#292)
lehni Oct 12, 2026
38356a5
Apply the relation's modify to BelongsToOne unrelate() without a wher…
lehni Oct 12, 2026
c8dbd24
Match aggregates only by their alias in selections (#111)
lehni Oct 12, 2026
e0ecfa0
Throw for anything after * or ^ in relation expressions (#221)
lehni Oct 12, 2026
9445531
Throw for offset() in updates and deletes and for ordered, limited de…
lehni Oct 12, 2026
92eee0c
Throw for JSON field expressions on MySQL, SQLite, SQL Server and Ora…
lehni Oct 12, 2026
6b048fd
Move the field expression helpers out of UpdateOperation
lehni Oct 12, 2026
959e73a
Keep foreign keys in sync in $setRelated() and $appendRelated() (#2001)
lehni Oct 11, 2026
039c2c9
Throw for missing and array join properties when fetching relations (…
lehni Oct 12, 2026
6a14a6b
Plan the actions of upsertGraph() before the first write (#214)
lehni Oct 12, 2026
2796103
Throw in upsertGraph() when a relate or update finds no row (#214)
lehni Oct 12, 2026
058ab70
Relate moved rows in upsertGraph() before deleting the removed rows (…
lehni Oct 12, 2026
b78d0f4
Pass static after hooks the result in the shape that the query return…
lehni Oct 12, 2026
70b2417
Assign what the relation queries of withGraphFetched() return to the …
lehni Oct 12, 2026
c143350
Call the afterFind hooks of the related model classes in withGraphJoi…
lehni Oct 12, 2026
759d684
Keep the id condition of findById() and findByIds() in its own where …
lehni Oct 12, 2026
e02994e
Clean up the 4.0 changelog and migration guide
lehni Oct 12, 2026
706fc23
Add modifyJoin() to modify the ON clause of relation joins (#1975)
lehni Oct 12, 2026
5be5730
Pick the related ids of limited many-to-many patches from their filte…
lehni Oct 12, 2026
7c7d9ad
Declare custom query builder types through a single RebindType member…
lehni Oct 12, 2026
f665978
Leave JSON attributes as the Postgres drivers return them, and add js…
lehni Oct 12, 2026
294fbc6
Derive model properties from the JSON schema with FromSchema (#16)
lehni Oct 12, 2026
0feff45
Reject relation data in the types of insert(), patch() and update() (…
lehni Oct 12, 2026
373878b
Render the JSON field expressions of ref() for MySQL and SQLite (#276)
lehni Oct 12, 2026
058463e
Write JSON field expressions in patch() and merge() on MySQL and SQLi…
lehni Oct 12, 2026
7001943
Render the whereJson*() methods for MySQL and SQLite (#276)
lehni Oct 12, 2026
63d96a8
Check the columns and values of queries against the models in the typ…
lehni Oct 12, 2026
cf858ce
Check the snake case columns of models with snakeCaseMappers() in the…
lehni Oct 12, 2026
4d5d737
Narrow the result rows of select() and the aggregates in the typings …
lehni Oct 12, 2026
c014fc0
Type $relatedQuery() of to-one relations as possibly undefined (#2355)
lehni Oct 12, 2026
cb8933d
Type the result of toJSON() as the model's JSON, with related models …
lehni Oct 12, 2026
8df6f10
Type the rows of relate().returning() as plain objects (#53)
lehni Oct 12, 2026
8acc634
Add BelongsToManyRelation for owners that hold an array of keys (#2667)
lehni Oct 12, 2026
9e7694a
Support relate(), unrelate(), insert(), delete() and graph writes for…
lehni Oct 12, 2026
b12721c
Batch inserts on SQLite, and inserts of models with ids on all databases
lehni Oct 12, 2026
2d72c42
Name $query() in the error of upsertGraph() queries with other query …
lehni Oct 12, 2026
6cfd59c
Prepare release candidate docs and packaging for 4.0 (#53)
lehni Oct 12, 2026
d3365ce
Load the graph of upsertGraphAndFetch() queries into the fetched mode…
lehni Oct 12, 2026
a28432b
Wait for started queries before rejecting graph inserts and upserts (…
lehni Oct 12, 2026
9625e4b
Set the foreign key of #ref models related into a HasManyRelation or …
lehni Oct 12, 2026
bbae4c1
Apply first() of relation mapping modifiers and onBuild() hooks per p…
lehni Oct 12, 2026
209fd52
Export the brand of selected rows for declaration emit (#16)
lehni Oct 12, 2026
5886d9d
Serialize arrays in SQLite inserts, and return ids from batch inserts…
lehni Oct 12, 2026
b0fe73c
Fix select narrowing of snake case models, distinctOn(), clearSelect(…
lehni Oct 12, 2026
bb9260c
Accept field expression keys in the typings of patch() (#160)
lehni Oct 12, 2026
a1d1877
Keep field expression keys in patch() and merge() data when building …
lehni Oct 12, 2026
d6dc7dd
Don't raise the zero-row NotFoundError of upsertGraph() for writes th…
lehni Oct 12, 2026
5de188f
Relate a row into the slot of a HasOneRelation after the relate that …
lehni Oct 12, 2026
5a25790
Only clear the keys of the owner models that a filtered BelongsToOne …
lehni Oct 12, 2026
e732161
Keep selections aliased as join columns in withGraphFetched() and rel…
lehni Oct 12, 2026
5cdf15d
Write the foreign keys of has-many and has-one relates in upsertGraph…
lehni Oct 12, 2026
7b14a78
Run the internal reads of BelongsToManyRelation writes without find h…
lehni Oct 12, 2026
fee6973
Rewrite upsertGraph() and insertGraph() (#1268, #1378, #1694, #853)
lehni Oct 12, 2026
a404438
Resolve writes with returning() to one model on to-one relation queri…
lehni Oct 12, 2026
8a7e899
Read #ref{} to existing models after the graph fills in their values …
lehni Oct 13, 2026
47e7204
Filter many-to-many patches with join table extras by ids bound as on…
lehni Oct 13, 2026
9c3a38a
Patch the join rows of the related rows that limit() picks by their r…
lehni Oct 13, 2026
14a36a4
Insert SQLite batches by column set and in chunks within insert() (#349)
lehni Oct 13, 2026
c8864ae
Unrelate the rows of each owner in upsertGraph(), and relate rows tha…
lehni Oct 13, 2026
83577d2
Patch the rows of upsertGraph() one by one, and keep hook-resolved re…
lehni Oct 13, 2026
2797157
Reject #ref to new models and #ref{} into branches that insertGraph()…
lehni Oct 13, 2026
5b75e54
Insert the 200000 toys of the graph argument test in one transaction …
lehni Oct 13, 2026
fad35c3
Upsert the models below rows that upsertGraph() moves after the move,…
lehni Oct 13, 2026
356c22c
Omit the owner join aliases of many-to-many relations from the graphs…
lehni Oct 13, 2026
3987545
Relate models to themselves and through related rows with #ref in ups…
lehni Oct 13, 2026
b36566e
Keep the rows that an earlier pass of upsertGraph() keeps when a late…
lehni Oct 13, 2026
ddf4f09
Test and document upsertGraph() graphs that 3.x wrote wrong
lehni Oct 13, 2026
2922321
Fold the duplicated 4.0.0 changelog entries (#53)
lehni Oct 13, 2026
2058dd5
Reject graphs that place one row in conflicting ways in insertGraph()…
lehni Oct 13, 2026
4b813a3
Insert into a HasOneRelation slot after the rows that move out of it …
lehni Oct 13, 2026
0d0e492
Give the models that upsertGraph() returns back the props it leaves o…
lehni Oct 13, 2026
b5e31ff
Check that the models that upsertGraph() relates exist before writing…
lehni Oct 13, 2026
e0ac79d
Start the data paths of graph errors with the index of array input in…
lehni Oct 13, 2026
6a4462b
Keep only the referenced branches below models that noInsert skips in…
lehni Oct 13, 2026
2034a28
Reject #ref and #ref{} to the ids of new models that insertGraph() an…
lehni Oct 13, 2026
1a2a6a6
Patch rows that reference each other one after the other in upsertGra…
lehni Oct 13, 2026
48c0d25
Document the #ref rule for new models that the graph doesn't write (#…
lehni Oct 13, 2026
a2eda1a
Write the rows of #ref models in upsertGraph() once, with their final…
lehni Oct 13, 2026
178b791
Document the rows that upsertGraph() removes below noInsert models (#…
lehni Oct 13, 2026
37b2a3b
Note the open later-pass slot case and test #ref writes of #unrelate …
lehni Oct 13, 2026
bc9ced5
Find the rows in HasOneRelation slots by key, and leave out unchanged…
lehni Oct 13, 2026
f7f8452
Keep new models that moved rows refer to through a #ref in upsertGraph()
lehni Oct 13, 2026
03f2eff
Unrelate the rows that a later pass of upsertGraph() moves out of Has…
lehni Oct 13, 2026
369fae6
Write one join row per pair of models in upsertGraph(), and reject a …
lehni Oct 13, 2026
78cec59
Test upsertGraph() and insertGraph() on generated graphs
lehni Oct 13, 2026
f991846
Document that the key of a model placed by a relation follows the rel…
lehni Oct 13, 2026
1d605ca
Note that upsertGraph() on SQLite throws for BigInt ids in the 4.0 ch…
lehni Oct 13, 2026
160a28c
Reject a many-to-many pair that upsertGraph() unrelates in one place …
lehni Oct 13, 2026
d7a4e96
Reject graphs that change keys that are part of ids in upsertGraph() …
lehni Oct 13, 2026
16f7b69
Test generated graphs with keys that their relations replace and with…
lehni Oct 13, 2026
5ce0b44
Move rows with the keys that upsertGraph() fills in from existing row…
lehni Oct 13, 2026
3107baf
Test how upsertGraph() writes moved rows and their relations
lehni Oct 13, 2026
58e37b3
Generate graphs with relations keyed on the owners of moved rows
lehni Oct 13, 2026
ce41ff4
Keep the rows that upsertGraph() keeps through the same key column in…
lehni Oct 13, 2026
b4040c5
Fetch generated keys on SQL Server next to relations through the id (…
lehni Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 6 additions & 16 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"root": true,
"parserOptions": {
"ecmaVersion": "latest"
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"prettier"
Expand All @@ -11,25 +12,14 @@
"no-undef": "error"
},
"env": {
"es6": true,
"node": true,
"mocha": true
"es2021": true,
"node": true
},
"overrides": [
{
"files": [
"lib/**/*.js"
],
"files": ["tests/unit/relations/files/*.js"],
"parserOptions": {
"ecmaVersion": 2020
}
},
{
"files": [
"tests/**/*.js"
],
"env": {
"es2021": true
"sourceType": "script"
}
}
]
Expand Down
9 changes: 5 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- 'doc/**'
- '.github/workflows/docs.yml'
- 'package.json'
- 'package-lock.json'
- 'pnpm-lock.yaml'
workflow_dispatch:

permissions:
Expand All @@ -24,13 +24,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
cache: pnpm
# Native modules (sqlite3) aren't needed to build the docs.
- run: npm ci --ignore-scripts
- run: npm run docs:build
- run: pnpm install --frozen-lockfile --ignore-scripts
- run: pnpm docs:build
- uses: actions/upload-pages-artifact@v5
with:
path: doc/.vitepress/dist
Expand Down
68 changes: 30 additions & 38 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: tests

on:
push:
branches: [main]
branches: [next]
pull_request:
branches: [main]
branches: [next]

jobs:
run-tests:
Expand All @@ -13,55 +13,49 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [16.x, 18.x, 20.x, 22.x, 24.x]
knex: [default]
# 20.19.0 is the oldest supported version, the first one that can
# require() ES modules without a flag.
node-version: [20.19.0, 20.x, 22.x, 24.x]
database: [default]
# Also test the oldest supported knex version, MySQL 5.7 and SQL Server,
# each as a separate job.
# Also test SQL Server as a separate job.
include:
- node-version: 16.x
knex: 2.3.0
database: default
- node-version: 24.x
knex: default
database: mysql-5.7
- node-version: 24.x
knex: default
database: mssql

env:
COMPOSE_FILE: ${{ matrix.database == 'mysql-5.7' && 'docker-compose.yml:docker-compose.mysql57.yml' || 'docker-compose.yml' }}
# The MySQL 5.7 and SQL Server jobs only need their database, the default
# services start otherwise.
SERVICES: ${{ matrix.database == 'mysql-5.7' && 'mysql' || matrix.database == 'mssql' && 'mssql' || '' }}
# The SQL Server job only needs its database, the default services start
# otherwise.
SERVICES: ${{ matrix.database == 'mssql' && 'mssql' || '' }}

steps:
- uses: actions/checkout@v7

- uses: pnpm/action-setup@v6

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
cache: pnpm

- name: Install dependencies
run: npm ci

- name: Install knex ${{ matrix.knex }}
if: matrix.knex != 'default'
run: npm install --no-save knex@${{ matrix.knex }}
run: pnpm install --frozen-lockfile

# The generated parsers have to match their grammars. One job is enough.
- name: Check the generated parsers
if: matrix.node-version == '24.x' && matrix.knex == 'default' && matrix.database == 'default'
if: matrix.node-version == '24.x' && matrix.database == 'default'
run: |
npm run build:parsers
pnpm build:parsers
git diff --exit-code lib/queryBuilder/parsers

# The documented minimum TypeScript version has to compile the typings
# and their tests. One job is enough.
- name: Check the typings with TypeScript 5.1
if: matrix.node-version == '24.x' && matrix.knex == 'default' && matrix.database == 'default'
run: npx --yes -p typescript@5.1.6 tsc
# and their tests, `pnpm test` checks them with the latest one. One job
# is enough.
- name: Check the typings with TypeScript 5.8
if: matrix.node-version == '24.x' && matrix.database == 'default'
run: |
pnpm dlx --package=typescript@5.8.3 tsc
pnpm dlx --package=typescript@5.8.3 tsc -p tests/ts/strict

# Cache the database images, as their registries rate limit anonymous
# pulls. The month in the key picks up updates of their floating tags.
Expand Down Expand Up @@ -104,7 +98,7 @@ jobs:
docker save -o "$IMAGES_PATH" $IMAGES
fi
fi
npm run db:up -- $SERVICES
pnpm db:up $SERVICES

- name: Save database images
if: steps.cache.outcome == 'success' && steps.cache.outputs.cache-hit != 'true'
Expand All @@ -114,15 +108,13 @@ jobs:
key: ${{ steps.images.outputs.key }}

- name: Run tests
if: matrix.knex == 'default' && matrix.database == 'default'
run: npm test
if: matrix.database == 'default'
run: pnpm test

# The typings of older knex versions don't compile with current
# TypeScript, so only the test suite runs against them. The MySQL 5.7 and
# SQL Server jobs only run the tests of their database, the other jobs
# The SQL Server job only runs the tests of its database, the other jobs
# already lint and check typings.
- name: Run tests without linting and typings
if: matrix.knex != 'default' || matrix.database != 'default'
- name: Run the tests of the database
if: matrix.database != 'default'
env:
DATABASES: ${{ matrix.database == 'mysql-5.7' && 'mysql' || matrix.database == 'mssql' && 'mssql' || '' }}
run: npx mocha --slow 10 --timeout 15000 --reporter spec --recursive tests --exclude "tests/unit/relations/files/**"
DATABASES: ${{ matrix.database }}
run: pnpm vitest run
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ npm-debug.log*
.vscode
/**/yarn.lock
doc/.vitepress/cache
doc/.vitepress/dist
doc/.vitepress/dist
tests/bench/typings/.generated
2 changes: 1 addition & 1 deletion doc/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export default defineConfig({
activeMatch: '^/release-notes/',
items: [
{ text: 'Changelog', link: '/release-notes/changelog' },
{ text: 'Migration to 3.0', link: '/release-notes/migration' },
{ text: 'Migration to 4.0', link: '/release-notes/migration' },
{
text: 'v2.x documentation',
link: 'https://github.com/Vincit/objection.js/tree/v2/doc',
Expand Down
23 changes: 19 additions & 4 deletions doc/api/model/instance-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,8 @@ See [this section](/api/model/overview.html#model-data-lifecycle) for more infor
| ------ | ----------------------- |
| Object | Model as a JSON object. |

In TypeScript, the JSON object is typed `ModelJson<M>`: the model's properties without methods, with the related models as `ModelJson` too. Values like dates aren't converted.

##### Examples

```js
Expand Down Expand Up @@ -635,9 +637,9 @@ See [this section](/api/model/overview.html#model-data-lifecycle) for more infor

```js
class Person extends Model {
$parseDatabaseJson(json) {
$parseDatabaseJson(json, options) {
// Remember to call the super class's implementation.
json = super.$parseDatabaseJson(json);
json = super.$parseDatabaseJson(json, options);
// Do your conversion here.
return json;
}
Expand All @@ -654,11 +656,14 @@ There are a couple of requirements for the implementation:

2. This function must be able to handle any subset of the model's properties coming in.You cannot assume that some column is present in the `json` object as it depends on the select statement. There can also be additional columns because of joins, aliases etc. This method must also be prepared for null values in _any_ property of the `json` object.

The default implementation applies the [columnNameMappers](/api/model/static-properties.html#static-columnnamemappers) and then parses the [jsonAttributes](/api/model/static-properties.html#static-jsonattributes) as [jsonColumnFormat](/api/model/static-properties.html#static-jsoncolumnformat) says.

##### Arguments

| Argument | Type | Description |
| -------- | ------ | -------------------------------- |
| json | Object | The JSON POJO in database format |
| options | Object | Optional. Passed by queries to parse the [jsonAttributes](/api/model/static-properties.html#static-jsonattributes) depending on the driver. Overrides pass it on to `super`. |

##### Return value

Expand Down Expand Up @@ -802,13 +807,14 @@ Validates the JSON before setting values.
modelInstance.$setDatabaseJson(json);
```

Sets the values from a JSON object in database format.
Sets the values from a JSON object in database format, converted by [\$parseDatabaseJson](/api/model/instance-methods.html#parsedatabasejson). Called by hand, the source of `json` is unknown: unless [jsonColumnFormat](/api/model/static-properties.html#static-jsoncolumnformat) says otherwise, strings in [jsonAttributes](/api/model/static-properties.html#static-jsonattributes) that are valid JSON are parsed, and other strings are kept.

##### Arguments

| Argument | Type | Description |
| -------- | ------ | -------------------------------- |
| json | Object | The JSON POJO in database format |
| options | Object | Optional. Passed by queries to parse the [jsonAttributes](/api/model/static-properties.html#static-jsonattributes) depending on the driver. Overrides pass it on to `super`. |

##### Return value

Expand Down Expand Up @@ -846,6 +852,15 @@ modelInstance.$setRelated(relation, relatedModels);

Sets related models to a corresponding property in the object.

The foreign keys follow the relation:

- `BelongsToOneRelation`: the foreign key of the object is set to the key of the related model, e.g. `post.$setRelated('author', user)` also sets `post.authorId` to `user.id`. Clearing the relation with `null` or `[]`, or setting a related model without a key, sets the foreign key to `null`.
- `BelongsToManyRelation`: the array of keys of the object is set to the keys of the related models, e.g. `post.$setRelated('tags', tags)` sets `post.tagIds` to the ids of `tags`, and to `[]` when the relation is cleared. Related models without a key are left out of the array.
- `HasOneRelation` and `HasManyRelation`: the foreign keys of the related models are set to the key of the object, e.g. `user.$setRelated('posts', posts)` sets `authorId` of each post to `user.id`. The foreign keys of the models that are no longer related, because the relation is cleared or they are replaced, are set to `null`, unless they already point to another object. If the object has no key yet, the foreign keys of the related models are set to `null`.
- `ManyToManyRelation` and `HasOneThroughRelation`: nothing is set, as the keys live in the join table.

Foreign keys set on the models before are overwritten. Only `$setRelated()` and `$appendRelated()` sync the keys: [fromJson](/api/model/static-methods.html#static-fromjson), [\$setJson](/api/model/instance-methods.html#setjson), queries and direct assignments leave them as they are.

##### Arguments

| Argument | Type | Description |
Expand Down Expand Up @@ -877,7 +892,7 @@ console.log(person.children[0]);
modelInstance.$appendRelated(relation, relatedModels);
```

Appends related models to a corresponding property in the object.
Appends related models to a corresponding property in the object. The foreign keys of the appended models are synced like with [\$setRelated](/api/model/instance-methods.html#setrelated), and the models already in the relation keep theirs. For `BelongsToManyRelation`, the keys of the appended models are added to the array of the object, also when the relation isn't loaded. For `BelongsToOneRelation` and `HasOneRelation`, the related model is replaced like with `$setRelated()`.

##### Arguments

Expand Down
Loading
Loading