@vantreeseba/drizzle-graphql 15.1.4 → 15.1.6

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
@@ -301,7 +301,7 @@ narrow the scope, never widen it. It reaches every path that touches the table:
301
301
  | `orderBy` through a to-one — `comments(orderBy: { post: { title: … } })` | an out-of-scope post sorts as `NULL` |
302
302
  | `updatePosts`, `updatePostsMany`, `deletePosts` | an out-of-scope row is simply not matched |
303
303
  | `upsertPosts` (conflict branch) | a conflicting row outside the scope is left alone, not taken over |
304
- | nested `connect` / `disconnect` / `set` | only in-scope rows can be attached or detached |
304
+ | nested `connect` / `disconnect` / `set` | only in-scope rows can be attached or detached; a many-to-many `set` keeps the links to out-of-scope rows |
305
305
 
306
306
  Two things it deliberately does not do:
307
307
 
@@ -347,10 +347,14 @@ const { schema } = buildSchema(db, {
347
347
  - The column is **removed from `CreatePostsInput` and `UpdatePostsInput`**, so a client cannot
348
348
  send it at all — the SDL itself says the column is server-owned. It stays readable on the
349
349
  object type.
350
- - Every insert stamps it, including rows created by a nested `create`.
350
+ - Every insert stamps it, including rows created by a nested `create` and junction rows a
351
+ many-to-many `connect` / `set` inserts.
351
352
  - Updates never write it, so an update cannot hand a row to another owner. Neither does the
352
353
  conflict branch of an upsert: it is left out of the default overwrite and of the columns
353
354
  `onConflict.update` can name.
355
+ - A relation whose foreign key is such a column gets no
356
+ [nested-write](#nested-writes) field from either side: every nested operation on it would
357
+ write that column. The build notes which relations it left out.
354
358
 
355
359
  Together the two close the loop: `contextValues` decides which tenant a new row belongs to, and
356
360
  `scope` decides which rows a request can see and change afterwards.
@@ -493,8 +497,8 @@ What a declared table gets:
493
497
  | relation filters — `users(where: { posts: { some: {} } })`, `none`, `every`, `.through()` junctions | a marked post neither matches nor blocks `none`, with the same default as the relation field (see below) |
494
498
  | `orderBy` through a to-one | a marked target the relation field hides sorts as `NULL` |
495
499
  | `updatePosts`, `updatePostsMany`, a second `deletePosts` | a marked row is simply not matched |
496
- | nested `connect` / `disconnect` / `set` | a marked row cannot be attached or detached |
497
- | `CreatePostsInput`, `UpdatePostsInput` | the marker column is **removed** — only the mutations write it |
500
+ | nested `connect` / `disconnect` / `set` | a marked row cannot be attached or detached; a many-to-many `set` keeps the links to marked rows |
501
+ | `CreatePostsInput`, `UpdatePostsInput`, nested `create` payloads | the marker column is **removed** — only the mutations write it |
498
502
 
499
503
  Every read path also gains a `deleted` argument for the cases the default hides:
500
504
 
@@ -1371,7 +1375,10 @@ can count or summarise related rows without fetching them:
1371
1375
  All parents in a selection are aggregated with a single
1372
1376
  `SELECT <fk>, … WHERE <fk> IN (…) GROUP BY <fk>` per request, so `postsAggregate` on a list of
1373
1377
  users is one extra query, not one per user. Differently-aliased selections with different
1374
- `where` arguments are batched separately.
1378
+ `where` arguments are batched separately. A batch whose parent keys would bind more than
1379
+ 10,000 parameters (10,000 parents on a single-column key, 5,000 on a two-column one) is split
1380
+ into several such queries, which keeps it within SQLite's 32,766- and Postgres/MySQL's
1381
+ 65,535-parameter limits; the batched relation fields split the same way.
1375
1382
 
1376
1383
  ## Distinct
1377
1384
 
@@ -1600,8 +1607,9 @@ mutation {
1600
1607
  out the rest of the row. Listing an unsupplied column in `update` is an error rather than
1601
1608
  a silent no-op
1602
1609
  - That holds per row: a batch whose rows supply different columns is split into one
1603
- statement per run of consecutive rows that write the same columns, run in one transaction,
1604
- so a row that left a column out keeps it. The rows come back in the order they were sent
1610
+ statement per run of consecutive rows that write the same columns, run in one transaction
1611
+ (on a synchronous SQLite driver such as better-sqlite3 too), so a row that left a column out
1612
+ keeps it. The rows come back in the order they were sent
1605
1613
  - `target` must match one of the table's unique constraints exactly; anything else is
1606
1614
  rejected with the list of valid targets, instead of the database's opaque "no unique or
1607
1615
  exclusion constraint matching" error
@@ -1692,9 +1700,10 @@ mutation {
1692
1700
 
1693
1701
  - `connect`, `disconnect` and `set` take the target's own `Filters` input — a list of them
1694
1702
  on a to-many relation, a single one that must match exactly one row on a to-one — so they
1695
- select rows the same way a query does. A to-one `disconnect` is just `disconnect: true`,
1696
- since there is only one row to detach. A filter that matches everything is rejected rather than
1697
- attaching the whole table
1703
+ select rows the same way a query does. A to-one `connect` that matches no row, or several,
1704
+ fails with `DRIZZLE_NESTED_WRITE_INVALID` before anything is detached. A to-one
1705
+ `disconnect` is just `disconnect: true`, since there is only one row to detach. A filter
1706
+ that matches everything is rejected rather than attaching the whole table
1698
1707
  - `set` replaces the relation wholesale — everything attached is detached first, then the
1699
1708
  filters attach. It runs before `create`, so a row created in the same write survives it
1700
1709
  - A to-one relation takes one operation at a time (`connect` **or** `disconnect` **or**
@@ -1716,14 +1725,42 @@ mutation {
1716
1725
  — so a failure anywhere leaves nothing behind
1717
1726
  - A `set` that carries only relation fields is a valid update: it changes what the row is
1718
1727
  attached to without rewriting the row
1728
+ - When the parent write produces no row — an update whose `where` matched nothing, an
1729
+ insert or upsert a conflict turned into a no-op — whatever a parent-side `create` inserted
1730
+ for it is rolled back too, so no orphan is left. In a batch only that entry is undone
1731
+ - The returned rows reflect the nested write: on a self-referencing relation
1732
+ (`reports: { connect: … }` on the row itself), where a child-side statement rewrites the
1733
+ parent row, the parent rows are read back after it
1734
+ - A relation joining on more than one column per side gets no nested-write field: setting
1735
+ one key would leave the rest of the join (a tenant column, say) unmatched and unwritten
1736
+
1737
+ ### A relation's own `where`
1738
+
1739
+ A relation declared with a `where` (`publishedPosts: r.many.posts({ …, where: { published: true } })`)
1740
+ is written through the same way it is read: only the rows it shows.
1741
+
1742
+ - `disconnect`, `set`, and the detaching a to-one attach does, reach only rows that satisfy
1743
+ the `where` — `publishedPosts: { set: [] }` leaves the drafts attached
1744
+ - `connect` and `set` attach only rows that satisfy it, so the relation shows what was
1745
+ attached. A to-one `connect` must match exactly one such row
1746
+ - There is no `create`: the inserted row is attached whether or not it satisfies the
1747
+ `where`, and the relation could come back without it. Create it with its own mutation, or
1748
+ through a relation with no `where`
1749
+ - A many-to-many `set` unlinks only the links to rows the `where` shows
1750
+ - A relation that inherits its `where` from the other side (declared without `from` / `to`)
1751
+ filters the parent rather than the target. Writing through it on a parent row it is
1752
+ hidden for fails with `DRIZZLE_NESTED_WRITE_INVALID`
1719
1753
 
1720
1754
  ### The target table's own rules
1721
1755
 
1722
1756
  A nested write is a write to another table, so it follows that table's rules, not just the
1723
1757
  root field's:
1724
1758
 
1725
- - **`scope`** narrows the rows `connect` / `disconnect` / `set` can reach, and
1726
- **`contextValues`** stamp the rows a nested `create` inserts
1759
+ - **`scope`** and **`softDelete`** narrow the rows `connect` / `disconnect` / `set` can
1760
+ reach — a many-to-many `set` keeps the links to rows it cannot reach — and
1761
+ **`contextValues`** stamp the rows a nested `create` inserts. A relation keyed on a
1762
+ `contextValues` column is not offered at all, and the payload of a nested `create` leaves
1763
+ out the context columns and the soft-delete marker, as the target's own create input does
1727
1764
  - **`features`**: each nested input is generated only when the table it writes has that
1728
1765
  write turned on. Whatever is left out is noted on the console at build time, and a
1729
1766
  relation with nothing left loses its field altogether
@@ -1767,7 +1804,8 @@ mutation {
1767
1804
  re-connecting is a no-op rather than a unique-constraint error
1768
1805
  - `disconnect` deletes only the links, never the rows they pointed at
1769
1806
  - `set: []` unlinks everything and links nothing back, which is how a many-to-many relation
1770
- is cleared
1807
+ is cleared. "Everything" is everything the request could have unlinked: links to rows
1808
+ outside the target's `scope`, soft-deleted, or hidden by the relation's own `where` stay
1771
1809
  - There is no `create`: inserting a row *and* linking it is a write to two tables, and the
1772
1810
  link is what the relation field is for. Create the row with its own mutation, then
1773
1811
  `connect` it
@@ -2452,7 +2490,15 @@ Generated schemas resolve nested relations without N+1 query explosions:
2452
2490
  `ROW_NUMBER() OVER (PARTITION BY …)` window function — still one query, not one per
2453
2491
  parent. A relation joined on several columns (`from: [a, b], to: [x, y]`) is batched,
2454
2492
  grouped and partitioned by all of them, matching the parents with
2455
- `(a = … AND b = …) OR …` instead of `IN (…)`.
2493
+ `(a = … AND b = …) OR …` instead of `IN (…)`, nested as a balanced tree so a batch of
2494
+ thousands of parents stays within SQLite's expression-depth limit. A batch over 10,000 key
2495
+ parameters runs as several queries, each parent read whole by one of them. The parent's join values are bound as the
2496
+ column holds them, not in their GraphQL output form, so a relation joined on a timestamp,
2497
+ `date`, `bigint` or decimal column works on this path and in `<relation>Aggregate` too. A
2498
+ parent row this library remapped keeps its original values for that; a parent your own
2499
+ resolver built in GraphQL form (an ISO string for a timestamp, say) has them mapped back the
2500
+ way an input value would be, which cuts a `'string'`-mode microsecond timestamp to
2501
+ milliseconds — return the row as drizzle read it to keep the full precision.
2456
2502
 
2457
2503
  ```Typescript
2458
2504
  // entities.fieldResolvers is keyed by table name, then relation name