@vantreeseba/drizzle-graphql 4.2.0 → 5.1.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/README.md +60 -4
- package/dist/README.md +60 -4
- package/dist/index.cjs +655 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -5
- package/dist/index.d.ts +92 -5
- package/dist/index.js +668 -138
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,9 +100,10 @@ const { schema } = buildSchema(db, {
|
|
|
100
100
|
relationAggregates: false, // <relation>Aggregate fields on object types
|
|
101
101
|
distinct: false, // the `distinct` argument on list queries
|
|
102
102
|
insert: false, // create<Table> / create<Table>Single mutations
|
|
103
|
-
update: false, // update<Table> mutations
|
|
104
|
-
delete: false, // delete<Table> mutations
|
|
103
|
+
update: false, // update<Table> / update<Table>Single mutations
|
|
104
|
+
delete: false, // delete<Table> / delete<Table>Single mutations
|
|
105
105
|
upsert: true, // upsert<Table> / upsert<Table>Single mutations (off by default)
|
|
106
|
+
requireWhere: true, // make `where` non-null on plural update/delete (off by default)
|
|
106
107
|
},
|
|
107
108
|
})
|
|
108
109
|
```
|
|
@@ -204,8 +205,10 @@ A **to-many** relation takes a `some` / `none` / `every` wrapper
|
|
|
204
205
|
- `some: {}` means "at least one related row exists"; `none: {}` means "none exist"
|
|
205
206
|
- Several modes may be given at once and are `AND`ed together
|
|
206
207
|
- A relation whose name collides with a column name is skipped — the column keeps the field
|
|
207
|
-
- Many-to-many relations declared with `.through()` are
|
|
208
|
-
|
|
208
|
+
- Many-to-many relations declared with `.through()` are filterable too: the `EXISTS`
|
|
209
|
+
subquery joins the junction table to the target, so
|
|
210
|
+
`users(where: { roles: { some: { name: { eq: "admin" } } } })` works the same as a direct
|
|
211
|
+
to-many relation
|
|
209
212
|
|
|
210
213
|
## Aggregate queries
|
|
211
214
|
|
|
@@ -465,6 +468,37 @@ The build-wide `conflictDoNothing` option is deprecated in favour of this: it ap
|
|
|
465
468
|
`create*` mutation with no way for a request to opt out. `onConflict: { action: NOTHING }` is
|
|
466
469
|
the per-request replacement.
|
|
467
470
|
|
|
471
|
+
## Single-row update & delete
|
|
472
|
+
|
|
473
|
+
Alongside the plural `update<Table>` / `delete<Table>` mutations, every table gets an
|
|
474
|
+
`update<Table>Single` / `delete<Table>Single` variant that targets exactly one row:
|
|
475
|
+
|
|
476
|
+
```graphql
|
|
477
|
+
mutation {
|
|
478
|
+
updateUsersSingle(where: { id: { eq: 1 } }, set: { name: "Dan" }) {
|
|
479
|
+
id
|
|
480
|
+
name
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
- `where` is **non-null** and must contain at least one filter — `where: {}` is rejected at
|
|
486
|
+
resolve time, so a Single write can never become an unbounded one
|
|
487
|
+
- The return type is the single (nullable) row type, not a list: the affected row comes back
|
|
488
|
+
directly, and **no match returns `null`** instead of an empty list
|
|
489
|
+
- If `where` matches **more than one row**, the mutation throws a `GraphQLError` and writes
|
|
490
|
+
nothing — the match is checked (`LIMIT 2`) before the write runs
|
|
491
|
+
- **MySQL** cannot return the affected row (no `RETURNING`), so its Single variants keep the
|
|
492
|
+
single-match and non-empty-`where` guarantees but return `MutationReturn` like every other
|
|
493
|
+
MySQL mutation
|
|
494
|
+
- The variants are generated under `features.update` / `features.delete`, share the plural
|
|
495
|
+
mutations' input types, and appear in `entities.mutations` for custom schemas
|
|
496
|
+
|
|
497
|
+
The plural mutations still accept a missing `where` (a full-table write) by default. To rule
|
|
498
|
+
that out at the type level, `features: { requireWhere: true }` makes `where` non-null on
|
|
499
|
+
`update<Table>` / `delete<Table>` and rejects a `where` with no filters, exactly like the
|
|
500
|
+
Single variants. It defaults to `false` for backwards compatibility.
|
|
501
|
+
|
|
468
502
|
## Query cost
|
|
469
503
|
|
|
470
504
|
Generated fields carry a `complexity` hint in their GraphQL extensions, ready for
|
|
@@ -623,6 +657,28 @@ Generated schemas resolve nested relations without N+1 query explosions:
|
|
|
623
657
|
> **SQLite 3.25+**. Relations without pagination, and all other query/mutation paths,
|
|
624
658
|
> have no such requirement.
|
|
625
659
|
|
|
660
|
+
### Relation nullability
|
|
661
|
+
|
|
662
|
+
A **to-many** relation field is always `[Target!]!`. A **to-one** relation field is
|
|
663
|
+
nullable by default, but honors the relation's declared optionality: declaring
|
|
664
|
+
`optional: false` on a Drizzle `one` relation — the assertion that the related row always
|
|
665
|
+
exists, i.e. a `NOT NULL` foreign key — emits the field as `Target!`:
|
|
666
|
+
|
|
667
|
+
```Typescript
|
|
668
|
+
const relations = buildRelations({ Users, Posts }, {
|
|
669
|
+
Posts: {
|
|
670
|
+
// posts.author_id is NOT NULL → author: Users!
|
|
671
|
+
author: r.one.Users({ from: r.Posts.authorId, to: r.Users.id, optional: false }),
|
|
672
|
+
},
|
|
673
|
+
})
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Only the explicit `optional: false` declaration is honored — required-ness is never
|
|
677
|
+
inferred from column nullability, since a `NOT NULL` `from` column does not guarantee a
|
|
678
|
+
related row exists when the constraint lives on the other side of the join. Note that on a
|
|
679
|
+
required relation, a `where` argument that filters out the related row (or a dangling
|
|
680
|
+
foreign key) resolves to `null` and therefore surfaces as a GraphQL non-null error.
|
|
681
|
+
|
|
626
682
|
### Overriding a relation's resolver without overfetching
|
|
627
683
|
|
|
628
684
|
By default the eager `with:` pre-fetch is driven purely by the GraphQL selection set:
|
package/dist/README.md
CHANGED
|
@@ -100,9 +100,10 @@ const { schema } = buildSchema(db, {
|
|
|
100
100
|
relationAggregates: false, // <relation>Aggregate fields on object types
|
|
101
101
|
distinct: false, // the `distinct` argument on list queries
|
|
102
102
|
insert: false, // create<Table> / create<Table>Single mutations
|
|
103
|
-
update: false, // update<Table> mutations
|
|
104
|
-
delete: false, // delete<Table> mutations
|
|
103
|
+
update: false, // update<Table> / update<Table>Single mutations
|
|
104
|
+
delete: false, // delete<Table> / delete<Table>Single mutations
|
|
105
105
|
upsert: true, // upsert<Table> / upsert<Table>Single mutations (off by default)
|
|
106
|
+
requireWhere: true, // make `where` non-null on plural update/delete (off by default)
|
|
106
107
|
},
|
|
107
108
|
})
|
|
108
109
|
```
|
|
@@ -204,8 +205,10 @@ A **to-many** relation takes a `some` / `none` / `every` wrapper
|
|
|
204
205
|
- `some: {}` means "at least one related row exists"; `none: {}` means "none exist"
|
|
205
206
|
- Several modes may be given at once and are `AND`ed together
|
|
206
207
|
- A relation whose name collides with a column name is skipped — the column keeps the field
|
|
207
|
-
- Many-to-many relations declared with `.through()` are
|
|
208
|
-
|
|
208
|
+
- Many-to-many relations declared with `.through()` are filterable too: the `EXISTS`
|
|
209
|
+
subquery joins the junction table to the target, so
|
|
210
|
+
`users(where: { roles: { some: { name: { eq: "admin" } } } })` works the same as a direct
|
|
211
|
+
to-many relation
|
|
209
212
|
|
|
210
213
|
## Aggregate queries
|
|
211
214
|
|
|
@@ -465,6 +468,37 @@ The build-wide `conflictDoNothing` option is deprecated in favour of this: it ap
|
|
|
465
468
|
`create*` mutation with no way for a request to opt out. `onConflict: { action: NOTHING }` is
|
|
466
469
|
the per-request replacement.
|
|
467
470
|
|
|
471
|
+
## Single-row update & delete
|
|
472
|
+
|
|
473
|
+
Alongside the plural `update<Table>` / `delete<Table>` mutations, every table gets an
|
|
474
|
+
`update<Table>Single` / `delete<Table>Single` variant that targets exactly one row:
|
|
475
|
+
|
|
476
|
+
```graphql
|
|
477
|
+
mutation {
|
|
478
|
+
updateUsersSingle(where: { id: { eq: 1 } }, set: { name: "Dan" }) {
|
|
479
|
+
id
|
|
480
|
+
name
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
- `where` is **non-null** and must contain at least one filter — `where: {}` is rejected at
|
|
486
|
+
resolve time, so a Single write can never become an unbounded one
|
|
487
|
+
- The return type is the single (nullable) row type, not a list: the affected row comes back
|
|
488
|
+
directly, and **no match returns `null`** instead of an empty list
|
|
489
|
+
- If `where` matches **more than one row**, the mutation throws a `GraphQLError` and writes
|
|
490
|
+
nothing — the match is checked (`LIMIT 2`) before the write runs
|
|
491
|
+
- **MySQL** cannot return the affected row (no `RETURNING`), so its Single variants keep the
|
|
492
|
+
single-match and non-empty-`where` guarantees but return `MutationReturn` like every other
|
|
493
|
+
MySQL mutation
|
|
494
|
+
- The variants are generated under `features.update` / `features.delete`, share the plural
|
|
495
|
+
mutations' input types, and appear in `entities.mutations` for custom schemas
|
|
496
|
+
|
|
497
|
+
The plural mutations still accept a missing `where` (a full-table write) by default. To rule
|
|
498
|
+
that out at the type level, `features: { requireWhere: true }` makes `where` non-null on
|
|
499
|
+
`update<Table>` / `delete<Table>` and rejects a `where` with no filters, exactly like the
|
|
500
|
+
Single variants. It defaults to `false` for backwards compatibility.
|
|
501
|
+
|
|
468
502
|
## Query cost
|
|
469
503
|
|
|
470
504
|
Generated fields carry a `complexity` hint in their GraphQL extensions, ready for
|
|
@@ -623,6 +657,28 @@ Generated schemas resolve nested relations without N+1 query explosions:
|
|
|
623
657
|
> **SQLite 3.25+**. Relations without pagination, and all other query/mutation paths,
|
|
624
658
|
> have no such requirement.
|
|
625
659
|
|
|
660
|
+
### Relation nullability
|
|
661
|
+
|
|
662
|
+
A **to-many** relation field is always `[Target!]!`. A **to-one** relation field is
|
|
663
|
+
nullable by default, but honors the relation's declared optionality: declaring
|
|
664
|
+
`optional: false` on a Drizzle `one` relation — the assertion that the related row always
|
|
665
|
+
exists, i.e. a `NOT NULL` foreign key — emits the field as `Target!`:
|
|
666
|
+
|
|
667
|
+
```Typescript
|
|
668
|
+
const relations = buildRelations({ Users, Posts }, {
|
|
669
|
+
Posts: {
|
|
670
|
+
// posts.author_id is NOT NULL → author: Users!
|
|
671
|
+
author: r.one.Users({ from: r.Posts.authorId, to: r.Users.id, optional: false }),
|
|
672
|
+
},
|
|
673
|
+
})
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Only the explicit `optional: false` declaration is honored — required-ness is never
|
|
677
|
+
inferred from column nullability, since a `NOT NULL` `from` column does not guarantee a
|
|
678
|
+
related row exists when the constraint lives on the other side of the join. Note that on a
|
|
679
|
+
required relation, a `where` argument that filters out the related row (or a dangling
|
|
680
|
+
foreign key) resolves to `null` and therefore surfaces as a GraphQL non-null error.
|
|
681
|
+
|
|
626
682
|
### Overriding a relation's resolver without overfetching
|
|
627
683
|
|
|
628
684
|
By default the eager `with:` pre-fetch is driven purely by the GraphQL selection set:
|