@vantreeseba/drizzle-graphql 15.1.0 → 15.1.2

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 CHANGED
@@ -335,7 +335,9 @@ const { schema } = buildSchema(db, {
335
335
  send it at all — the SDL itself says the column is server-owned. It stays readable on the
336
336
  object type.
337
337
  - Every insert stamps it, including rows created by a nested `create`.
338
- - Updates never write it, so an update cannot hand a row to another owner.
338
+ - Updates never write it, so an update cannot hand a row to another owner. Neither does the
339
+ conflict branch of an upsert: it is left out of the default overwrite and of the columns
340
+ `onConflict.update` can name.
339
341
 
340
342
  Together the two close the loop: `contextValues` decides which tenant a new row belongs to, and
341
343
  `scope` decides which rows a request can see and change afterwards.
@@ -984,6 +986,9 @@ input StockItemIdLocationIdKey {
984
986
  composes with column filters, relation filters, `OR` / `AND` / `NOT`
985
987
  - Composite primary keys get a field like any other unique constraint
986
988
  - Single-column constraints do not: `eq` on the column already names the row
989
+ - Partial unique indexes (`uniqueIndex().on(…).where(…)`) do not either: they are unique
990
+ only among the rows the predicate matches, so with soft-deleted rows included
991
+ (`deleted: INCLUDE`) the key can match more than one
987
992
  - A field name a column or relation already holds is skipped, the same way a relation
988
993
  yields to a same-named column
989
994
  - It compiles to exactly the equalities you would have written by hand; the guarantee is in
@@ -1042,6 +1047,11 @@ The pattern operators are `like` / `notLike` / `ilike` / `notIlike`, the safe
1042
1047
  `startsWith` / `endsWith` / `contains` and their `i`-prefixed forms, plus the `insensitive`
1043
1048
  flag that modifies them.
1044
1049
 
1050
+ An operand of `null` leaves its operator out, as though it had not been sent. `false` is an
1051
+ ordinary value: `active: { eq: false }` matches the rows that are false, `ne: false` the rows
1052
+ that are true, and `has: false` the arrays holding a `false`. Only the flag operators —
1053
+ `isNull`, `isNotNull` and `isEmpty` — read `false` as "add no condition".
1054
+
1045
1055
  The omissions are the cases where the operator cannot ask anything a caller means:
1046
1056
 
1047
1057
  - **Pattern matching a timestamp, a boolean or an enum member** compares whatever the value
@@ -1479,7 +1489,12 @@ field, and passing it back as `after` resumes strictly after that row.
1479
1489
  in `asc`), MySQL and SQLite smallest (first in `asc`). The predicate matches either way,
1480
1490
  so rows with `NULL` in an ordered column are paged through, not skipped, and `nulls` is
1481
1491
  part of the ordering a cursor is tied to
1482
- - Dates and bigints round-trip losslessly through the cursor
1492
+ - Dates and bigints round-trip losslessly through the cursor, at the column's own precision:
1493
+ a PostgreSQL `timestamp` / `timestamptz` (microseconds unless declared `precision: 3` or
1494
+ less) or a MySQL `datetime` / `timestamp` with `fsp` above 3 holds more than the
1495
+ millisecond JS `Date` that `mode: 'date'` reads it into, so the cursor carries the
1496
+ database's own text of the value instead, and rows a microsecond apart are still distinct
1497
+ pages
1483
1498
  - A cursor is only valid under the ordering it was issued for — reusing it with a
1484
1499
  different `orderBy` (or a malformed/corrupted cursor) returns a `GraphQLError`
1485
1500
  - `after` cannot be combined with `distinct`, with `matchFilterOrder`, or with an `orderBy`
@@ -1556,9 +1571,17 @@ mutation {
1556
1571
  - Columns the request did not supply are never overwritten — a partial upsert does not null
1557
1572
  out the rest of the row. Listing an unsupplied column in `update` is an error rather than
1558
1573
  a silent no-op
1574
+ - That holds per row: a batch whose rows supply different columns is split into one
1575
+ statement per run of consecutive rows that write the same columns, run in one transaction,
1576
+ so a row that left a column out keeps it. The rows come back in the order they were sent
1559
1577
  - `target` must match one of the table's unique constraints exactly; anything else is
1560
1578
  rejected with the list of valid targets, instead of the database's opaque "no unique or
1561
1579
  exclusion constraint matching" error
1580
+ - `target` can be a partial unique index — the `WHERE deleted_at IS NULL` companion of
1581
+ [soft delete](#soft-delete), say. Its predicate goes into `ON CONFLICT (…) WHERE …`, so a
1582
+ conflict is detected only against the rows the index covers
1583
+ - `contextValues` columns are never overwritten on conflict — see
1584
+ [columns the server owns](#columns-the-server-owns)
1562
1585
  - `action: NOTHING` inserts nothing and returns nothing for the conflicting row. An `UPDATE`
1563
1586
  with no columns left to write degrades to the same thing
1564
1587
  - `values` is the same `Create<Type>Input` the insert mutations take, so turning `insert`
@@ -1593,7 +1616,9 @@ Dialect differences:
1593
1616
  - **PostgreSQL** and **SQLite** — the full surface above. A table with no primary key and no
1594
1617
  unique constraint has nothing to conflict on, so it gets no upsert mutations at all
1595
1618
  - **MySQL** — `ON DUPLICATE KEY UPDATE` fires on whichever unique key was violated and takes
1596
- no predicate, so `<Type>OnConflict` there has only `action` and `update`. `action: NOTHING`
1619
+ no predicate, so `<Type>OnConflict` there has only `action` and `update`. Because the
1620
+ conflict can be on any unique key, the default overwrite leaves out the primary key as well
1621
+ — copying a new row's key over the conflicting row's would move it. `action: NOTHING`
1597
1622
  becomes `INSERT IGNORE`, and the mutations return `MutationReturn` like every other MySQL
1598
1623
  mutation
1599
1624
 
@@ -1891,6 +1916,10 @@ Two details worth stating plainly:
1891
1916
  - **A rejection is the default because truncation lies.** Silently cutting a page down to
1892
1917
  `maxLimit` tells a paginating client it has reached the end when it has not. `clampToMax:
1893
1918
  true` opts into that when you would rather never fail a query.
1919
+ - **A negative `limit` or `offset` is always rejected** with `DRIZZLE_INVALID_INPUT_VALUE`,
1920
+ whether or not `limits` is configured — on root lists, single queries, relation fields and
1921
+ `resolveSelection`. SQLite reads `LIMIT -1` as "no limit", so a negative value would
1922
+ otherwise walk straight past `maxLimit`; `0` stays valid and returns no rows.
1894
1923
 
1895
1924
  The policy applies to root list queries and to to-many relation fields, on both the eager and
1896
1925
  the lazy relation paths. A relation takes the policy of the table it *reads*, not its parent's:
@@ -2114,7 +2143,8 @@ to match on prose:
2114
2143
  `DrizzleErrorContext`.
2115
2144
 
2116
2145
  A value the request could not supply is `DRIZZLE_INVALID_INPUT_VALUE` — a date that does not
2117
- parse, a `BigInt` argument that is not an integer, a malformed JSON column value — and a write
2146
+ parse, a `BigInt` argument that is not an integer, a malformed JSON column value, a negative
2147
+ `limit` or `offset` — and a write
2118
2148
  naming something that is not a column of its table is `DRIZZLE_UNKNOWN_COLUMN`. The mirror
2119
2149
  image, a stored value the scalar transporting it cannot represent, is
2120
2150
  `DRIZZLE_UNREPRESENTABLE_VALUE`; it says the data is out of range, not that the request was
@@ -2565,8 +2595,11 @@ It understands `values` (one object or a list), `where` (`eq` for one row, `inAr
2565
2595
  many, an equality on every column of a composite key), and the per-entry `where` inside a
2566
2596
  batch update's `updates`. Check `complete` before treating `rows` as the affected set: it
2567
2597
  is `false` when the arguments do not pin the rows down — an insert whose key the database
2568
- generates, a filter on something other than the primary key, an unbounded update, or a
2569
- table with no primary key at all.
2598
+ generates, a filter on something other than the primary key, an unbounded update, a
2599
+ table with no primary key at all, or an upsert whose `onConflict` targets another unique key
2600
+ or can skip a conflicting row (`action: NOTHING`, a `where`). On MySQL, where an upsert has
2601
+ no `target` and conflicts on whichever unique key it hits, `complete` assumes the primary
2602
+ key: do not rely on it for a table with other unique keys.
2570
2603
 
2571
2604
  > [!NOTE]
2572
2605
  > `extensions.drizzle` is stable API. The point of publishing it is that consumers stop