@vantreeseba/drizzle-graphql 15.1.3 → 15.1.5

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
 
@@ -1361,6 +1365,9 @@ can count or summarise related rows without fetching them:
1361
1365
  and applies only to the related rows
1362
1366
  - A parent with no related rows gets `count: 0` and `null` for every other aggregate
1363
1367
  - To-one relations get no aggregate field — there is nothing to aggregate over
1368
+ - A relation joined on several columns (`from: [a, b], to: [x, y]`) is grouped by all of
1369
+ them, so each parent counts only the rows that match it on every column. A many-to-many
1370
+ (`.through()`) relation whose junction has a composite key gets no aggregate field
1364
1371
  - The field is skipped if its name would collide with a column or another relation
1365
1372
  - Set `features: { relationAggregates: false }` (globally or per table) to leave these
1366
1373
  fields out
@@ -1689,9 +1696,10 @@ mutation {
1689
1696
 
1690
1697
  - `connect`, `disconnect` and `set` take the target's own `Filters` input — a list of them
1691
1698
  on a to-many relation, a single one that must match exactly one row on a to-one — so they
1692
- select rows the same way a query does. A to-one `disconnect` is just `disconnect: true`,
1693
- since there is only one row to detach. A filter that matches everything is rejected rather than
1694
- attaching the whole table
1699
+ select rows the same way a query does. A to-one `connect` that matches no row, or several,
1700
+ fails with `DRIZZLE_NESTED_WRITE_INVALID` before anything is detached. A to-one
1701
+ `disconnect` is just `disconnect: true`, since there is only one row to detach. A filter
1702
+ that matches everything is rejected rather than attaching the whole table
1695
1703
  - `set` replaces the relation wholesale — everything attached is detached first, then the
1696
1704
  filters attach. It runs before `create`, so a row created in the same write survives it
1697
1705
  - A to-one relation takes one operation at a time (`connect` **or** `disconnect` **or**
@@ -1713,14 +1721,42 @@ mutation {
1713
1721
  — so a failure anywhere leaves nothing behind
1714
1722
  - A `set` that carries only relation fields is a valid update: it changes what the row is
1715
1723
  attached to without rewriting the row
1724
+ - When the parent write produces no row — an update whose `where` matched nothing, an
1725
+ insert or upsert a conflict turned into a no-op — whatever a parent-side `create` inserted
1726
+ for it is rolled back too, so no orphan is left. In a batch only that entry is undone
1727
+ - The returned rows reflect the nested write: on a self-referencing relation
1728
+ (`reports: { connect: … }` on the row itself), where a child-side statement rewrites the
1729
+ parent row, the parent rows are read back after it
1730
+ - A relation joining on more than one column per side gets no nested-write field: setting
1731
+ one key would leave the rest of the join (a tenant column, say) unmatched and unwritten
1732
+
1733
+ ### A relation's own `where`
1734
+
1735
+ A relation declared with a `where` (`publishedPosts: r.many.posts({ …, where: { published: true } })`)
1736
+ is written through the same way it is read: only the rows it shows.
1737
+
1738
+ - `disconnect`, `set`, and the detaching a to-one attach does, reach only rows that satisfy
1739
+ the `where` — `publishedPosts: { set: [] }` leaves the drafts attached
1740
+ - `connect` and `set` attach only rows that satisfy it, so the relation shows what was
1741
+ attached. A to-one `connect` must match exactly one such row
1742
+ - There is no `create`: the inserted row is attached whether or not it satisfies the
1743
+ `where`, and the relation could come back without it. Create it with its own mutation, or
1744
+ through a relation with no `where`
1745
+ - A many-to-many `set` unlinks only the links to rows the `where` shows
1746
+ - A relation that inherits its `where` from the other side (declared without `from` / `to`)
1747
+ filters the parent rather than the target. Writing through it on a parent row it is
1748
+ hidden for fails with `DRIZZLE_NESTED_WRITE_INVALID`
1716
1749
 
1717
1750
  ### The target table's own rules
1718
1751
 
1719
1752
  A nested write is a write to another table, so it follows that table's rules, not just the
1720
1753
  root field's:
1721
1754
 
1722
- - **`scope`** narrows the rows `connect` / `disconnect` / `set` can reach, and
1723
- **`contextValues`** stamp the rows a nested `create` inserts
1755
+ - **`scope`** and **`softDelete`** narrow the rows `connect` / `disconnect` / `set` can
1756
+ reach — a many-to-many `set` keeps the links to rows it cannot reach — and
1757
+ **`contextValues`** stamp the rows a nested `create` inserts. A relation keyed on a
1758
+ `contextValues` column is not offered at all, and the payload of a nested `create` leaves
1759
+ out the context columns and the soft-delete marker, as the target's own create input does
1724
1760
  - **`features`**: each nested input is generated only when the table it writes has that
1725
1761
  write turned on. Whatever is left out is noted on the console at build time, and a
1726
1762
  relation with nothing left loses its field altogether
@@ -1764,7 +1800,8 @@ mutation {
1764
1800
  re-connecting is a no-op rather than a unique-constraint error
1765
1801
  - `disconnect` deletes only the links, never the rows they pointed at
1766
1802
  - `set: []` unlinks everything and links nothing back, which is how a many-to-many relation
1767
- is cleared
1803
+ is cleared. "Everything" is everything the request could have unlinked: links to rows
1804
+ outside the target's `scope`, soft-deleted, or hidden by the relation's own `where` stay
1768
1805
  - There is no `create`: inserting a row *and* linking it is a write to two tables, and the
1769
1806
  link is what the relation field is for. Create the row with its own mutation, then
1770
1807
  `connect` it
@@ -2447,7 +2484,9 @@ Generated schemas resolve nested relations without N+1 query explosions:
2447
2484
  (request-scoped, keyed on the GraphQL `context`), preventing N+1. Per-parent
2448
2485
  `limit` / `offset` on a to-many relation is applied across the whole batch using a
2449
2486
  `ROW_NUMBER() OVER (PARTITION BY …)` window function — still one query, not one per
2450
- parent.
2487
+ parent. A relation joined on several columns (`from: [a, b], to: [x, y]`) is batched,
2488
+ grouped and partitioned by all of them, matching the parents with
2489
+ `(a = … AND b = …) OR …` instead of `IN (…)`.
2451
2490
 
2452
2491
  ```Typescript
2453
2492
  // entities.fieldResolvers is keyed by table name, then relation name