@vantreeseba/drizzle-graphql 6.0.0 → 7.0.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 +110 -3
- package/dist/README.md +110 -3
- package/dist/index.cjs +820 -384
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -1
- package/dist/index.d.ts +60 -1
- package/dist/index.js +726 -280
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,8 @@ const { schema } = buildSchema(db, {
|
|
|
104
104
|
delete: false, // delete<Table> / delete<Table>Single mutations
|
|
105
105
|
upsert: true, // upsert<Table> / upsert<Table>Single mutations (off by default)
|
|
106
106
|
nestedWrites: true, // relation fields on the create/update inputs (off by default)
|
|
107
|
+
fieldUpdateOperations: true, // increment/push operations on the update input (off by default)
|
|
108
|
+
countMutations: true, // update<Table>Count / delete<Table>Count mutations (off by default)
|
|
107
109
|
requireWhere: true, // make `where` non-null on plural update/delete (off by default)
|
|
108
110
|
},
|
|
109
111
|
})
|
|
@@ -111,8 +113,8 @@ const { schema } = buildSchema(db, {
|
|
|
111
113
|
|
|
112
114
|
- Any flag left out keeps its default of `true`, so `{ features: { delete: false } }`
|
|
113
115
|
changes nothing else
|
|
114
|
-
- `upsert` and `
|
|
115
|
-
|
|
116
|
+
- `upsert`, `nestedWrites`, `fieldUpdateOperations` and `countMutations` are the
|
|
117
|
+
exceptions: all four default to `false`, so they only exist if you ask for them
|
|
116
118
|
- `groupBy` needs `aggregates`: it reuses those output types, so turning `aggregates` off
|
|
117
119
|
turns the group-by queries off with it
|
|
118
120
|
- Turning off `insert` or `update` also drops the input type that only that mutation
|
|
@@ -467,7 +469,10 @@ enum. Rows sharing the same combination of those columns collapse to one:
|
|
|
467
469
|
- `where` is applied **before** rows are collapsed; `limit` and `offset` are applied
|
|
468
470
|
**after**, so `limit: 10` returns ten distinct rows
|
|
469
471
|
- Several columns are treated as one combined key, not as independent ones
|
|
470
|
-
- `distinct` is available on
|
|
472
|
+
- `distinct` is also available on to-many relation fields, where it means one row per
|
|
473
|
+
distinct value **per parent** — the window is partitioned by the foreign key as well as
|
|
474
|
+
the requested columns
|
|
475
|
+
- `distinct` is not on single queries — those return one row either way
|
|
471
476
|
|
|
472
477
|
It runs as an extra `row_number() over (partition by …)` query that picks the surviving
|
|
473
478
|
rows' primary keys, after which the main query is narrowed to them — so it needs the same
|
|
@@ -536,6 +541,32 @@ field, and passing it back as `after` resumes strictly after that row.
|
|
|
536
541
|
- If a table has a real column named `cursor`, the column wins and the meta field is
|
|
537
542
|
skipped
|
|
538
543
|
|
|
544
|
+
To-many relation fields take `after` and `distinct` too, with the same meaning they have on
|
|
545
|
+
a root list of the same table — a relation field is a list of the target table, so it gets
|
|
546
|
+
that table's whole pagination surface:
|
|
547
|
+
|
|
548
|
+
```graphql
|
|
549
|
+
{
|
|
550
|
+
users {
|
|
551
|
+
id
|
|
552
|
+
posts(orderBy: { createdAt: { direction: desc, priority: 1 } }, limit: 10, after: "…") {
|
|
553
|
+
id
|
|
554
|
+
cursor
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Drizzle's `with:` clause cannot express either one — keyset needs a predicate over the
|
|
561
|
+
request's own total order, `distinct` needs a window pass, and a `cursor` has to be computed
|
|
562
|
+
from the raw row. So a relation field that is passed `after` or `distinct`, or that selects
|
|
563
|
+
`cursor`, drops out of the eager fetch and resolves through the request-scoped batch loader
|
|
564
|
+
instead, which implements all three. That is still **one query per relation** across every
|
|
565
|
+
parent in the batch (`distinct` adds its key-selection pass, as on a root list) — never one
|
|
566
|
+
per parent. Nothing about the arguments changes; only how many round-trips the request
|
|
567
|
+
costs.
|
|
568
|
+
|
|
569
|
+
|
|
539
570
|
## Upsert
|
|
540
571
|
|
|
541
572
|
`features.upsert` adds a pair of mutations per table that insert rows, or update the ones
|
|
@@ -660,6 +691,71 @@ and interleaves awaited statements inside a transaction, which a synchronous SQL
|
|
|
660
691
|
cannot do — `buildSchema` rejects both combinations rather than generating something that
|
|
661
692
|
half-works.
|
|
662
693
|
|
|
694
|
+
## Atomic column updates
|
|
695
|
+
|
|
696
|
+
`Update<Table>Input` sets columns to values, so "increment the view count" is a read followed
|
|
697
|
+
by a write of the number the read returned — two round-trips, and a lost update whenever two
|
|
698
|
+
clients read the same number. `features.fieldUpdateOperations` replaces qualifying columns'
|
|
699
|
+
update fields with an operations input, so the arithmetic happens in the database:
|
|
700
|
+
|
|
701
|
+
```graphql
|
|
702
|
+
mutation {
|
|
703
|
+
updatePostsSingle(where: { id: { eq: 1 } }, set: { views: { increment: 1 } }) {
|
|
704
|
+
views
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
compiles to `SET views = views + 1`. The flag changes the update inputs only; insert inputs
|
|
710
|
+
are untouched.
|
|
711
|
+
|
|
712
|
+
- **Numeric columns** (`Int`, `Float`, `BigInt`, `Decimal`) take
|
|
713
|
+
`set` / `increment` / `decrement` / `multiply` / `divide`
|
|
714
|
+
- **Array columns** take `set` / `push`, where `push` appends to the existing array
|
|
715
|
+
- Exactly one operation per column. Passing two, or `{}`, is a `GraphQLError` naming the
|
|
716
|
+
column — graphql-js has no `@oneOf` on the version range this library supports, so it is
|
|
717
|
+
enforced at resolve time rather than by the type
|
|
718
|
+
- `set` is the explicit spelling of the plain assignment the field had before the flag, so
|
|
719
|
+
every existing update has a one-key rewrite: `{ views: 5 }` → `{ views: { set: 5 } }`
|
|
720
|
+
- The input types are named for the **scalar**, not the column — every `Int` column in the
|
|
721
|
+
schema shares one `IntFieldUpdate` — and a column claimed by a `scalarOverrides` entry
|
|
722
|
+
keeps its plain field, since the override decides what that column accepts
|
|
723
|
+
|
|
724
|
+
Two things follow the database's own semantics rather than being guarded:
|
|
725
|
+
|
|
726
|
+
- `increment`/`multiply` on an integer column can overflow, and `divide` on one **rounds**
|
|
727
|
+
the way the dialect rounds (PostgreSQL truncates toward zero; use a `Float` or `Decimal`
|
|
728
|
+
column if you need the fraction)
|
|
729
|
+
- `divide: 0` raises the driver's division-by-zero error, surfaced as a `GraphQLError`
|
|
730
|
+
|
|
731
|
+
## Row counts instead of rows
|
|
732
|
+
|
|
733
|
+
The plural `update<Table>` / `delete<Table>` mutations always `RETURNING *`, which is wasted
|
|
734
|
+
work — and wasted bandwidth — when a client only wants to know how many rows were touched.
|
|
735
|
+
`features.countMutations` adds a count-returning variant of each:
|
|
736
|
+
|
|
737
|
+
```graphql
|
|
738
|
+
mutation {
|
|
739
|
+
deletePostsCount(where: { status: { eq: "spam" } })
|
|
740
|
+
updatePostsCount(where: { draft: { eq: true } }, set: { draft: false })
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
- Both return `Int!` — the number of rows the write affected — and add no new output type
|
|
745
|
+
to the schema
|
|
746
|
+
- Arguments are exactly the plural mutation's, so a client swaps one field name for the
|
|
747
|
+
other; `features.requireWhere` applies to them the same way
|
|
748
|
+
- They follow the feature switch of the write they mirror: `updateCount` needs
|
|
749
|
+
`features.update`, `deleteCount` needs `features.delete`
|
|
750
|
+
- **MySQL** gets them too, and they are arguably most useful there: MySQL's other mutations
|
|
751
|
+
return only `MutationReturn { isSuccess }`, so a row count is strictly more information
|
|
752
|
+
- `update<Table>Count` **rejects** nested writes with a `GraphQLError` — a nested write
|
|
753
|
+
needs the parent rows this mutation deliberately does not read back. Use the plural
|
|
754
|
+
mutation for those
|
|
755
|
+
- The count is read from whichever field the driver reports it in (`rowCount`,
|
|
756
|
+
`affectedRows`, `rowsAffected`, `changes`, `count`); a driver that reports none is a
|
|
757
|
+
`GraphQLError` rather than a silent `0`
|
|
758
|
+
|
|
663
759
|
## Single-row update & delete
|
|
664
760
|
|
|
665
761
|
Alongside the plural `update<Table>` / `delete<Table>` mutations, every table gets an
|
|
@@ -686,6 +782,17 @@ mutation {
|
|
|
686
782
|
- The variants are generated under `features.update` / `features.delete`, share the plural
|
|
687
783
|
mutations' input types, and appear in `entities.mutations` for custom schemas
|
|
688
784
|
|
|
785
|
+
### Mutation return shapes
|
|
786
|
+
|
|
787
|
+
Every mutation that returns rows returns them non-null, with two exceptions that are
|
|
788
|
+
deliberate rather than accidental:
|
|
789
|
+
|
|
790
|
+
| Mutation | Returns | Why |
|
|
791
|
+
| --- | --- | --- |
|
|
792
|
+
| `create<Table>Single` | `Table!`, or `Table` under `conflictDoNothing` | An insert of one row returns that row. It can only fail to produce one when the `conflictDoNothing` build option swallows a conflict, so it is nullable exactly when that option is set |
|
|
793
|
+
| `update<Table>Many` | `[Table]!` | Each entry's updated rows **in entry order**. An entry whose `where` matched nothing contributes `null` in its slot, so the response lines up with the request positionally — the nullable element is the whole point |
|
|
794
|
+
| everything else | `[Table!]!` / `Table!` / `Table` | — |
|
|
795
|
+
|
|
689
796
|
The plural mutations still accept a missing `where` (a full-table write) by default. To rule
|
|
690
797
|
that out at the type level, `features: { requireWhere: true }` makes `where` non-null on
|
|
691
798
|
`update<Table>` / `delete<Table>` and rejects a `where` with no filters, exactly like the
|
package/dist/README.md
CHANGED
|
@@ -104,6 +104,8 @@ const { schema } = buildSchema(db, {
|
|
|
104
104
|
delete: false, // delete<Table> / delete<Table>Single mutations
|
|
105
105
|
upsert: true, // upsert<Table> / upsert<Table>Single mutations (off by default)
|
|
106
106
|
nestedWrites: true, // relation fields on the create/update inputs (off by default)
|
|
107
|
+
fieldUpdateOperations: true, // increment/push operations on the update input (off by default)
|
|
108
|
+
countMutations: true, // update<Table>Count / delete<Table>Count mutations (off by default)
|
|
107
109
|
requireWhere: true, // make `where` non-null on plural update/delete (off by default)
|
|
108
110
|
},
|
|
109
111
|
})
|
|
@@ -111,8 +113,8 @@ const { schema } = buildSchema(db, {
|
|
|
111
113
|
|
|
112
114
|
- Any flag left out keeps its default of `true`, so `{ features: { delete: false } }`
|
|
113
115
|
changes nothing else
|
|
114
|
-
- `upsert` and `
|
|
115
|
-
|
|
116
|
+
- `upsert`, `nestedWrites`, `fieldUpdateOperations` and `countMutations` are the
|
|
117
|
+
exceptions: all four default to `false`, so they only exist if you ask for them
|
|
116
118
|
- `groupBy` needs `aggregates`: it reuses those output types, so turning `aggregates` off
|
|
117
119
|
turns the group-by queries off with it
|
|
118
120
|
- Turning off `insert` or `update` also drops the input type that only that mutation
|
|
@@ -467,7 +469,10 @@ enum. Rows sharing the same combination of those columns collapse to one:
|
|
|
467
469
|
- `where` is applied **before** rows are collapsed; `limit` and `offset` are applied
|
|
468
470
|
**after**, so `limit: 10` returns ten distinct rows
|
|
469
471
|
- Several columns are treated as one combined key, not as independent ones
|
|
470
|
-
- `distinct` is available on
|
|
472
|
+
- `distinct` is also available on to-many relation fields, where it means one row per
|
|
473
|
+
distinct value **per parent** — the window is partitioned by the foreign key as well as
|
|
474
|
+
the requested columns
|
|
475
|
+
- `distinct` is not on single queries — those return one row either way
|
|
471
476
|
|
|
472
477
|
It runs as an extra `row_number() over (partition by …)` query that picks the surviving
|
|
473
478
|
rows' primary keys, after which the main query is narrowed to them — so it needs the same
|
|
@@ -536,6 +541,32 @@ field, and passing it back as `after` resumes strictly after that row.
|
|
|
536
541
|
- If a table has a real column named `cursor`, the column wins and the meta field is
|
|
537
542
|
skipped
|
|
538
543
|
|
|
544
|
+
To-many relation fields take `after` and `distinct` too, with the same meaning they have on
|
|
545
|
+
a root list of the same table — a relation field is a list of the target table, so it gets
|
|
546
|
+
that table's whole pagination surface:
|
|
547
|
+
|
|
548
|
+
```graphql
|
|
549
|
+
{
|
|
550
|
+
users {
|
|
551
|
+
id
|
|
552
|
+
posts(orderBy: { createdAt: { direction: desc, priority: 1 } }, limit: 10, after: "…") {
|
|
553
|
+
id
|
|
554
|
+
cursor
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Drizzle's `with:` clause cannot express either one — keyset needs a predicate over the
|
|
561
|
+
request's own total order, `distinct` needs a window pass, and a `cursor` has to be computed
|
|
562
|
+
from the raw row. So a relation field that is passed `after` or `distinct`, or that selects
|
|
563
|
+
`cursor`, drops out of the eager fetch and resolves through the request-scoped batch loader
|
|
564
|
+
instead, which implements all three. That is still **one query per relation** across every
|
|
565
|
+
parent in the batch (`distinct` adds its key-selection pass, as on a root list) — never one
|
|
566
|
+
per parent. Nothing about the arguments changes; only how many round-trips the request
|
|
567
|
+
costs.
|
|
568
|
+
|
|
569
|
+
|
|
539
570
|
## Upsert
|
|
540
571
|
|
|
541
572
|
`features.upsert` adds a pair of mutations per table that insert rows, or update the ones
|
|
@@ -660,6 +691,71 @@ and interleaves awaited statements inside a transaction, which a synchronous SQL
|
|
|
660
691
|
cannot do — `buildSchema` rejects both combinations rather than generating something that
|
|
661
692
|
half-works.
|
|
662
693
|
|
|
694
|
+
## Atomic column updates
|
|
695
|
+
|
|
696
|
+
`Update<Table>Input` sets columns to values, so "increment the view count" is a read followed
|
|
697
|
+
by a write of the number the read returned — two round-trips, and a lost update whenever two
|
|
698
|
+
clients read the same number. `features.fieldUpdateOperations` replaces qualifying columns'
|
|
699
|
+
update fields with an operations input, so the arithmetic happens in the database:
|
|
700
|
+
|
|
701
|
+
```graphql
|
|
702
|
+
mutation {
|
|
703
|
+
updatePostsSingle(where: { id: { eq: 1 } }, set: { views: { increment: 1 } }) {
|
|
704
|
+
views
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
compiles to `SET views = views + 1`. The flag changes the update inputs only; insert inputs
|
|
710
|
+
are untouched.
|
|
711
|
+
|
|
712
|
+
- **Numeric columns** (`Int`, `Float`, `BigInt`, `Decimal`) take
|
|
713
|
+
`set` / `increment` / `decrement` / `multiply` / `divide`
|
|
714
|
+
- **Array columns** take `set` / `push`, where `push` appends to the existing array
|
|
715
|
+
- Exactly one operation per column. Passing two, or `{}`, is a `GraphQLError` naming the
|
|
716
|
+
column — graphql-js has no `@oneOf` on the version range this library supports, so it is
|
|
717
|
+
enforced at resolve time rather than by the type
|
|
718
|
+
- `set` is the explicit spelling of the plain assignment the field had before the flag, so
|
|
719
|
+
every existing update has a one-key rewrite: `{ views: 5 }` → `{ views: { set: 5 } }`
|
|
720
|
+
- The input types are named for the **scalar**, not the column — every `Int` column in the
|
|
721
|
+
schema shares one `IntFieldUpdate` — and a column claimed by a `scalarOverrides` entry
|
|
722
|
+
keeps its plain field, since the override decides what that column accepts
|
|
723
|
+
|
|
724
|
+
Two things follow the database's own semantics rather than being guarded:
|
|
725
|
+
|
|
726
|
+
- `increment`/`multiply` on an integer column can overflow, and `divide` on one **rounds**
|
|
727
|
+
the way the dialect rounds (PostgreSQL truncates toward zero; use a `Float` or `Decimal`
|
|
728
|
+
column if you need the fraction)
|
|
729
|
+
- `divide: 0` raises the driver's division-by-zero error, surfaced as a `GraphQLError`
|
|
730
|
+
|
|
731
|
+
## Row counts instead of rows
|
|
732
|
+
|
|
733
|
+
The plural `update<Table>` / `delete<Table>` mutations always `RETURNING *`, which is wasted
|
|
734
|
+
work — and wasted bandwidth — when a client only wants to know how many rows were touched.
|
|
735
|
+
`features.countMutations` adds a count-returning variant of each:
|
|
736
|
+
|
|
737
|
+
```graphql
|
|
738
|
+
mutation {
|
|
739
|
+
deletePostsCount(where: { status: { eq: "spam" } })
|
|
740
|
+
updatePostsCount(where: { draft: { eq: true } }, set: { draft: false })
|
|
741
|
+
}
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
- Both return `Int!` — the number of rows the write affected — and add no new output type
|
|
745
|
+
to the schema
|
|
746
|
+
- Arguments are exactly the plural mutation's, so a client swaps one field name for the
|
|
747
|
+
other; `features.requireWhere` applies to them the same way
|
|
748
|
+
- They follow the feature switch of the write they mirror: `updateCount` needs
|
|
749
|
+
`features.update`, `deleteCount` needs `features.delete`
|
|
750
|
+
- **MySQL** gets them too, and they are arguably most useful there: MySQL's other mutations
|
|
751
|
+
return only `MutationReturn { isSuccess }`, so a row count is strictly more information
|
|
752
|
+
- `update<Table>Count` **rejects** nested writes with a `GraphQLError` — a nested write
|
|
753
|
+
needs the parent rows this mutation deliberately does not read back. Use the plural
|
|
754
|
+
mutation for those
|
|
755
|
+
- The count is read from whichever field the driver reports it in (`rowCount`,
|
|
756
|
+
`affectedRows`, `rowsAffected`, `changes`, `count`); a driver that reports none is a
|
|
757
|
+
`GraphQLError` rather than a silent `0`
|
|
758
|
+
|
|
663
759
|
## Single-row update & delete
|
|
664
760
|
|
|
665
761
|
Alongside the plural `update<Table>` / `delete<Table>` mutations, every table gets an
|
|
@@ -686,6 +782,17 @@ mutation {
|
|
|
686
782
|
- The variants are generated under `features.update` / `features.delete`, share the plural
|
|
687
783
|
mutations' input types, and appear in `entities.mutations` for custom schemas
|
|
688
784
|
|
|
785
|
+
### Mutation return shapes
|
|
786
|
+
|
|
787
|
+
Every mutation that returns rows returns them non-null, with two exceptions that are
|
|
788
|
+
deliberate rather than accidental:
|
|
789
|
+
|
|
790
|
+
| Mutation | Returns | Why |
|
|
791
|
+
| --- | --- | --- |
|
|
792
|
+
| `create<Table>Single` | `Table!`, or `Table` under `conflictDoNothing` | An insert of one row returns that row. It can only fail to produce one when the `conflictDoNothing` build option swallows a conflict, so it is nullable exactly when that option is set |
|
|
793
|
+
| `update<Table>Many` | `[Table]!` | Each entry's updated rows **in entry order**. An entry whose `where` matched nothing contributes `null` in its slot, so the response lines up with the request positionally — the nullable element is the whole point |
|
|
794
|
+
| everything else | `[Table!]!` / `Table!` / `Table` | — |
|
|
795
|
+
|
|
689
796
|
The plural mutations still accept a missing `where` (a full-table write) by default. To rule
|
|
690
797
|
that out at the type level, `features: { requireWhere: true }` makes `where` non-null on
|
|
691
798
|
`update<Table>` / `delete<Table>` and rejects a `where` with no filters, exactly like the
|