@vantreeseba/drizzle-graphql 1.0.2 → 2.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 +75 -0
- package/dist/README.md +75 -0
- package/dist/index.cjs +894 -581
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -18
- package/dist/index.d.ts +111 -18
- package/dist/index.js +922 -613
- package/dist/index.js.map +1 -1
- package/dist/package.json +7 -7
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -84,3 +84,78 @@ Automatically create GraphQL schema or customizable schema config fields from Dr
|
|
|
84
84
|
console.info('Server is running on http://localhost:4000/graphql')
|
|
85
85
|
})
|
|
86
86
|
```
|
|
87
|
+
|
|
88
|
+
## Relations & N+1 handling
|
|
89
|
+
|
|
90
|
+
Generated schemas resolve nested relations without N+1 query explosions:
|
|
91
|
+
|
|
92
|
+
- **Queries** — root queries (`entities.queries.*`) eagerly load every requested
|
|
93
|
+
relation in a single round-trip using Drizzle's relational query builder
|
|
94
|
+
(`with:`), including nested relations and per-relation `where` / `orderBy` /
|
|
95
|
+
`limit` / `offset` arguments.
|
|
96
|
+
- **Mutations (PostgreSQL & SQLite)** — after an insert or update, if the selection set
|
|
97
|
+
includes relation fields, the affected rows are re-fetched by primary key through one
|
|
98
|
+
relational query so their relations are eagerly loaded (single- and composite-column
|
|
99
|
+
primary keys are supported). `delete` mutations keep using the request-scoped batch
|
|
100
|
+
loader, since the rows no longer exist to re-fetch. MySQL mutations return only a
|
|
101
|
+
success flag (no row payload), so this step does not apply there.
|
|
102
|
+
- **Custom schemas** — each relation field also has a standalone resolver, exported as
|
|
103
|
+
`entities.fieldResolvers[TableName][relationName]`. When you wire generated types
|
|
104
|
+
into your own schema and your root resolver does **not** pre-fetch relations, these
|
|
105
|
+
resolvers batch all sibling loads in a request into a single `IN (…)` query
|
|
106
|
+
(request-scoped, keyed on the GraphQL `context`), preventing N+1. Per-parent
|
|
107
|
+
`limit` / `offset` on a to-many relation is applied across the whole batch using a
|
|
108
|
+
`ROW_NUMBER() OVER (PARTITION BY …)` window function — still one query, not one per
|
|
109
|
+
parent.
|
|
110
|
+
|
|
111
|
+
```Typescript
|
|
112
|
+
// entities.fieldResolvers is keyed by table name, then relation name
|
|
113
|
+
const usersPostsResolver = entities.fieldResolvers.Users.posts
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
> [!IMPORTANT]
|
|
117
|
+
> Per-parent paginated relations (a to-many relation field with `limit` or `offset`)
|
|
118
|
+
> rely on SQL window functions. These require **PostgreSQL**, **MySQL 8.0+**, or
|
|
119
|
+
> **SQLite 3.25+**. Relations without pagination, and all other query/mutation paths,
|
|
120
|
+
> have no such requirement.
|
|
121
|
+
|
|
122
|
+
### Overriding a relation's resolver without overfetching
|
|
123
|
+
|
|
124
|
+
By default the eager `with:` pre-fetch is driven purely by the GraphQL selection set:
|
|
125
|
+
any selected relation is fetched from the database, even if you intend to resolve it
|
|
126
|
+
yourself (from a cache, another service, a dataloader, …). To override a relation's
|
|
127
|
+
resolver, first opt it out of eager loading with `eagerLoadRelations` so the parent
|
|
128
|
+
query stops fetching it, then supply your resolver with the standard
|
|
129
|
+
[`@graphql-tools/schema`](https://the-guild.dev/graphql/tools) utilities:
|
|
130
|
+
|
|
131
|
+
```Typescript
|
|
132
|
+
import { addResolversToSchema } from '@graphql-tools/schema'
|
|
133
|
+
|
|
134
|
+
const { schema } = buildSchema(db, {
|
|
135
|
+
// Exclude Users.posts from the parent query's `with:` clause.
|
|
136
|
+
// Other relations keep eager-loading as usual.
|
|
137
|
+
eagerLoadRelations: (table, relation) => !(table === 'Users' && relation === 'posts'),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const finalSchema = addResolversToSchema({
|
|
141
|
+
schema,
|
|
142
|
+
resolvers: {
|
|
143
|
+
Users: {
|
|
144
|
+
posts: (parent, args, context) => context.postsLoader.load(parent.id),
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`eagerLoadRelations` accepts:
|
|
151
|
+
|
|
152
|
+
- `true` (default) — eager-load every relation.
|
|
153
|
+
- `false` — never eager-load; every relation resolves lazily through its
|
|
154
|
+
(request-batched) field resolver.
|
|
155
|
+
- `(tableName, relationName) => boolean` — decide per relation. Returning `false`
|
|
156
|
+
excludes that relation from `with:` (and from the mutation eager re-fetch).
|
|
157
|
+
|
|
158
|
+
Opting a relation out does **not** remove its field — it keeps resolving lazily via the
|
|
159
|
+
request-scoped batch loader, so the field still works even before you override it. Table
|
|
160
|
+
and relation names are the Drizzle schema keys (e.g. `Users`, `posts`), matching the keys
|
|
161
|
+
of `entities.fieldResolvers`.
|
package/dist/README.md
CHANGED
|
@@ -84,3 +84,78 @@ Automatically create GraphQL schema or customizable schema config fields from Dr
|
|
|
84
84
|
console.info('Server is running on http://localhost:4000/graphql')
|
|
85
85
|
})
|
|
86
86
|
```
|
|
87
|
+
|
|
88
|
+
## Relations & N+1 handling
|
|
89
|
+
|
|
90
|
+
Generated schemas resolve nested relations without N+1 query explosions:
|
|
91
|
+
|
|
92
|
+
- **Queries** — root queries (`entities.queries.*`) eagerly load every requested
|
|
93
|
+
relation in a single round-trip using Drizzle's relational query builder
|
|
94
|
+
(`with:`), including nested relations and per-relation `where` / `orderBy` /
|
|
95
|
+
`limit` / `offset` arguments.
|
|
96
|
+
- **Mutations (PostgreSQL & SQLite)** — after an insert or update, if the selection set
|
|
97
|
+
includes relation fields, the affected rows are re-fetched by primary key through one
|
|
98
|
+
relational query so their relations are eagerly loaded (single- and composite-column
|
|
99
|
+
primary keys are supported). `delete` mutations keep using the request-scoped batch
|
|
100
|
+
loader, since the rows no longer exist to re-fetch. MySQL mutations return only a
|
|
101
|
+
success flag (no row payload), so this step does not apply there.
|
|
102
|
+
- **Custom schemas** — each relation field also has a standalone resolver, exported as
|
|
103
|
+
`entities.fieldResolvers[TableName][relationName]`. When you wire generated types
|
|
104
|
+
into your own schema and your root resolver does **not** pre-fetch relations, these
|
|
105
|
+
resolvers batch all sibling loads in a request into a single `IN (…)` query
|
|
106
|
+
(request-scoped, keyed on the GraphQL `context`), preventing N+1. Per-parent
|
|
107
|
+
`limit` / `offset` on a to-many relation is applied across the whole batch using a
|
|
108
|
+
`ROW_NUMBER() OVER (PARTITION BY …)` window function — still one query, not one per
|
|
109
|
+
parent.
|
|
110
|
+
|
|
111
|
+
```Typescript
|
|
112
|
+
// entities.fieldResolvers is keyed by table name, then relation name
|
|
113
|
+
const usersPostsResolver = entities.fieldResolvers.Users.posts
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
> [!IMPORTANT]
|
|
117
|
+
> Per-parent paginated relations (a to-many relation field with `limit` or `offset`)
|
|
118
|
+
> rely on SQL window functions. These require **PostgreSQL**, **MySQL 8.0+**, or
|
|
119
|
+
> **SQLite 3.25+**. Relations without pagination, and all other query/mutation paths,
|
|
120
|
+
> have no such requirement.
|
|
121
|
+
|
|
122
|
+
### Overriding a relation's resolver without overfetching
|
|
123
|
+
|
|
124
|
+
By default the eager `with:` pre-fetch is driven purely by the GraphQL selection set:
|
|
125
|
+
any selected relation is fetched from the database, even if you intend to resolve it
|
|
126
|
+
yourself (from a cache, another service, a dataloader, …). To override a relation's
|
|
127
|
+
resolver, first opt it out of eager loading with `eagerLoadRelations` so the parent
|
|
128
|
+
query stops fetching it, then supply your resolver with the standard
|
|
129
|
+
[`@graphql-tools/schema`](https://the-guild.dev/graphql/tools) utilities:
|
|
130
|
+
|
|
131
|
+
```Typescript
|
|
132
|
+
import { addResolversToSchema } from '@graphql-tools/schema'
|
|
133
|
+
|
|
134
|
+
const { schema } = buildSchema(db, {
|
|
135
|
+
// Exclude Users.posts from the parent query's `with:` clause.
|
|
136
|
+
// Other relations keep eager-loading as usual.
|
|
137
|
+
eagerLoadRelations: (table, relation) => !(table === 'Users' && relation === 'posts'),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const finalSchema = addResolversToSchema({
|
|
141
|
+
schema,
|
|
142
|
+
resolvers: {
|
|
143
|
+
Users: {
|
|
144
|
+
posts: (parent, args, context) => context.postsLoader.load(parent.id),
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`eagerLoadRelations` accepts:
|
|
151
|
+
|
|
152
|
+
- `true` (default) — eager-load every relation.
|
|
153
|
+
- `false` — never eager-load; every relation resolves lazily through its
|
|
154
|
+
(request-batched) field resolver.
|
|
155
|
+
- `(tableName, relationName) => boolean` — decide per relation. Returning `false`
|
|
156
|
+
excludes that relation from `with:` (and from the mutation eager re-fetch).
|
|
157
|
+
|
|
158
|
+
Opting a relation out does **not** remove its field — it keeps resolving lazily via the
|
|
159
|
+
request-scoped batch loader, so the field still works even before you override it. Table
|
|
160
|
+
and relation names are the Drizzle schema keys (e.g. `Users`, `posts`), matching the keys
|
|
161
|
+
of `entities.fieldResolvers`.
|