@vantreeseba/drizzle-graphql 7.3.0 → 7.5.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 +161 -0
- package/dist/README.md +161 -0
- package/dist/index.cjs +614 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +270 -23
- package/dist/index.d.ts +270 -23
- package/dist/index.js +606 -159
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,6 +85,39 @@ Automatically create GraphQL schema or customizable schema config fields from Dr
|
|
|
85
85
|
})
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
## Naming the generated types and fields
|
|
89
|
+
|
|
90
|
+
By default every name comes from the table's key in the Drizzle schema: `tasks` gives type
|
|
91
|
+
`Tasks`, queries `tasks` / `tasksSingle`, mutations `createTasks` / `createTasksSingle`.
|
|
92
|
+
Most schemas use plural table keys and want singular type names, so that pairing is shipped
|
|
93
|
+
as a preset:
|
|
94
|
+
|
|
95
|
+
```Typescript
|
|
96
|
+
const { schema } = buildSchema(db, { typeNameMapper: 'singularize' })
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`tasks` then gives type `Task`, queries `tasks` (list) and `task` (single), mutations
|
|
100
|
+
`createTasks` (array) and `createTask` (single), `updateTask`, `deleteTask`. Irregular
|
|
101
|
+
plurals go through `pluralize`, so `people` gives `Person` / `people` / `person` and
|
|
102
|
+
`addresses` gives `Address` rather than the `addresss` a naive suffix would produce. A table
|
|
103
|
+
key that is already singular is pluralized for the list side, and a capitalized key is
|
|
104
|
+
uncapitalized for the field side, so `tasks`, `task` and `Tasks` all land on the same names.
|
|
105
|
+
|
|
106
|
+
For anything else, pass your own function — it is asked per table and may return `undefined`
|
|
107
|
+
to leave that table with the default naming. The preset is exported as `singularizeMapper`
|
|
108
|
+
so it can be wrapped:
|
|
109
|
+
|
|
110
|
+
```Typescript
|
|
111
|
+
import { buildSchema, singularizeMapper } from 'drizzle-graphql'
|
|
112
|
+
|
|
113
|
+
const { schema } = buildSchema(db, {
|
|
114
|
+
typeNameMapper: (table) => (table === 'auditLog' ? undefined : singularizeMapper(table)),
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
With a mapper in play the singular/plural forms are what keep the list and single fields
|
|
119
|
+
apart, so `suffixes` may be empty on both sides. Without one, they must still differ.
|
|
120
|
+
|
|
88
121
|
## Choosing what gets generated
|
|
89
122
|
|
|
90
123
|
Every generated operation is on by default. `features` turns individual ones off, which
|
|
@@ -124,6 +157,35 @@ const { schema } = buildSchema(db, {
|
|
|
124
157
|
`mutations: false`
|
|
125
158
|
- List and single queries are always generated, so `Query` is never empty
|
|
126
159
|
|
|
160
|
+
### Per-table operations
|
|
161
|
+
|
|
162
|
+
The choice is rarely build-wide: a schema usually has a handful of tables that must not be
|
|
163
|
+
deleted through the API, a handful whose writes belong to a hand-written resolver that
|
|
164
|
+
enforces something, and a majority that want everything. Any flag can be a predicate
|
|
165
|
+
instead of a boolean, asked once per table with the table's key in the Drizzle schema:
|
|
166
|
+
|
|
167
|
+
```Typescript
|
|
168
|
+
const { schema } = buildSchema(db, {
|
|
169
|
+
features: {
|
|
170
|
+
delete: (table) => table !== 'auditLog',
|
|
171
|
+
update: (table) => !ownedByCustomResolver.has(table),
|
|
172
|
+
upsert: (table) => table === 'settings',
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- The table is still fully readable — this decides which operations it offers, not whether
|
|
178
|
+
it is in the schema at all. For that, use `exclude` below
|
|
179
|
+
- `nestedWrites` is the one flag that stays build-wide: its plans are computed once over
|
|
180
|
+
the whole relation graph, and a nested write reaches a second table by definition
|
|
181
|
+
- `Mutation` is omitted when *no* table generates a mutation, and kept when one still does
|
|
182
|
+
- Some operations are built out of others, and the generator knows which. A table that
|
|
183
|
+
asks for `upsert` while its `insert` or `update` is off — an upsert is a second write
|
|
184
|
+
path past the operation you turned off — warns on the console at build time, naming the
|
|
185
|
+
tables, rather than leaving that to be discovered later. So does asking for `updateMany`
|
|
186
|
+
without `update`, `groupBy` without `aggregates`, `countMutations` with neither plural
|
|
187
|
+
write, or `nestedWrites` alongside a table whose own writes are off
|
|
188
|
+
|
|
127
189
|
## Excluding tables and columns
|
|
128
190
|
|
|
129
191
|
`features` decides which *kinds* of operation exist. `exclude` decides which tables and
|
|
@@ -1425,6 +1487,105 @@ request-scoped batch loader, so the field still works even before you override i
|
|
|
1425
1487
|
and relation names are the Drizzle schema keys (e.g. `Users`, `posts`), matching the keys
|
|
1426
1488
|
of `entities.fieldResolvers`.
|
|
1427
1489
|
|
|
1490
|
+
## Writing your own resolver over a generated type
|
|
1491
|
+
|
|
1492
|
+
When you replace or wrap a generated resolver — to add a permission check that needs the
|
|
1493
|
+
row, to run a write inside your own transaction, to enforce a rule the schema cannot
|
|
1494
|
+
express — you become responsible for returning a row shaped like the type you replaced.
|
|
1495
|
+
That means translating the GraphQL selection set into the relational query builder's
|
|
1496
|
+
`with:` tree: relation field resolvers only fill in relations the parent did not already
|
|
1497
|
+
provide.
|
|
1498
|
+
|
|
1499
|
+
Two exports do that for you, alongside the already-exported `extractFilters` and
|
|
1500
|
+
`extractOrderBy`:
|
|
1501
|
+
|
|
1502
|
+
```Typescript
|
|
1503
|
+
import { resolveSelection, selectionToWith } from '@vantreeseba/drizzle-graphql'
|
|
1504
|
+
|
|
1505
|
+
// The whole read: selected columns, relation arguments, eager loading, cursor
|
|
1506
|
+
// pagination, and the transport remapping of the result — against any executor.
|
|
1507
|
+
const rows = await resolveSelection(info, {
|
|
1508
|
+
db,
|
|
1509
|
+
table: 'Users',
|
|
1510
|
+
where: { id: { eq: userId } },
|
|
1511
|
+
executor: tx, // defaults to `db`; pass a transaction to see its own writes
|
|
1512
|
+
})
|
|
1513
|
+
|
|
1514
|
+
// Or just the `with:` tree, if you are building the query yourself.
|
|
1515
|
+
const withTree = selectionToWith(info, { db, table: 'Users' })
|
|
1516
|
+
const row = await tx.query.Users.findFirst({ where: { id: userId }, with: withTree })
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1519
|
+
`selectionToWith` resolves fragments and inline fragments against `info.fragments`,
|
|
1520
|
+
intersects the selection against the relations config (so scalars and any fields you added
|
|
1521
|
+
yourself drop out instead of becoming invalid `with:` keys), follows aliases back to the
|
|
1522
|
+
relation they select, compiles each relation's `where` / `orderBy` / `limit` / `offset`
|
|
1523
|
+
into its nested entry, and leaves `<relation>Aggregate` selections out — they are not
|
|
1524
|
+
`with:` entries. It returns `undefined` when the selection asks for no relations.
|
|
1525
|
+
|
|
1526
|
+
`resolveSelection` additionally accepts `single`, `orderBy`, `limit`, `offset` and `after`,
|
|
1527
|
+
and returns the rows already remapped to their GraphQL transport forms.
|
|
1528
|
+
|
|
1529
|
+
Both take a `typeNameMapper` option. If the schema was built with one, pass the same
|
|
1530
|
+
function here — the translation matches selections against GraphQL type names, so a build
|
|
1531
|
+
that renames its types has to say so.
|
|
1532
|
+
|
|
1533
|
+
## Identifying generated fields
|
|
1534
|
+
|
|
1535
|
+
Every generated field and object type publishes what it is under `extensions.drizzle`, so
|
|
1536
|
+
wrappers — `graphql-middleware`, envelop, `graphql-shield`, an audit log, a cache
|
|
1537
|
+
invalidator — can ask the schema instead of parsing field names. Names are configurable
|
|
1538
|
+
(`prefixes`, `suffixes`, `typeNameMapper`), which makes name parsing ambiguous by
|
|
1539
|
+
construction; this is not.
|
|
1540
|
+
|
|
1541
|
+
```Typescript
|
|
1542
|
+
import { drizzleExtension, identifyRows } from '@vantreeseba/drizzle-graphql'
|
|
1543
|
+
|
|
1544
|
+
const field = info.parentType.getFields()[info.fieldName]
|
|
1545
|
+
const meta = drizzleExtension(field)
|
|
1546
|
+
// {
|
|
1547
|
+
// table: 'Users', // the Drizzle schema key
|
|
1548
|
+
// kind: 'mutation', // 'query' | 'mutation' | 'relation' | 'aggregate' | 'type'
|
|
1549
|
+
// operation: 'update', // 'select' | 'insert' | 'update' | 'updateMany' | 'upsert'
|
|
1550
|
+
// // | 'delete' | 'aggregate' | 'groupBy' | 'relation'
|
|
1551
|
+
// // | 'relationAggregate'
|
|
1552
|
+
// single: true, // one row, or a list
|
|
1553
|
+
// targetArg: 'where', // 'where' | 'values' | 'updates' — where the rows live
|
|
1554
|
+
// primaryKey: ['id'], // key columns of `table`, in schema order
|
|
1555
|
+
// }
|
|
1556
|
+
|
|
1557
|
+
if (meta?.kind === 'mutation') await audit(meta.table, meta.operation, args[meta.targetArg])
|
|
1558
|
+
```
|
|
1559
|
+
|
|
1560
|
+
`drizzleExtension` returns `undefined` for anything this library did not generate, which is
|
|
1561
|
+
the check to make before treating a field as one of ours. Relation fields (and
|
|
1562
|
+
`<relation>Aggregate` fields) additionally carry `relation` and `parentTable`, with `table`
|
|
1563
|
+
naming the relation's **target** — so `Users.posts` reads as
|
|
1564
|
+
`{ table: 'Posts', parentTable: 'Users', relation: 'posts' }`. Object types carry
|
|
1565
|
+
`{ table, kind: 'type', primaryKey }`, which is what a stitching layer needs to map a type
|
|
1566
|
+
back to its source.
|
|
1567
|
+
|
|
1568
|
+
Because the row identity of a mutation lives in a different argument for every operation,
|
|
1569
|
+
`identifyRows` reads whichever one applies and hands back primary-key values:
|
|
1570
|
+
|
|
1571
|
+
```Typescript
|
|
1572
|
+
const target = identifyRows(field, args)
|
|
1573
|
+
// { table: 'Users', primaryKey: ['id'], rows: [{ id: 3 }], complete: true }
|
|
1574
|
+
|
|
1575
|
+
if (target?.complete) await invalidate(target.table, target.rows)
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
It understands `values` (one object or a list), `where` (`eq` for one row, `inArray` for
|
|
1579
|
+
many, an equality on every column of a composite key), and the per-entry `where` inside a
|
|
1580
|
+
batch update's `updates`. Check `complete` before treating `rows` as the affected set: it
|
|
1581
|
+
is `false` when the arguments do not pin the rows down — an insert whose key the database
|
|
1582
|
+
generates, a filter on something other than the primary key, an unbounded update, or a
|
|
1583
|
+
table with no primary key at all.
|
|
1584
|
+
|
|
1585
|
+
> [!NOTE]
|
|
1586
|
+
> `extensions.drizzle` is stable API. The point of publishing it is that consumers stop
|
|
1587
|
+
> parsing names, which only holds if the shape does not move: fields are added to it, never
|
|
1588
|
+
> removed or repurposed.
|
|
1428
1589
|
## Configuration reference
|
|
1429
1590
|
|
|
1430
1591
|
Every key of the second argument to `buildSchema(db, config?)`. Each links to the section
|
package/dist/README.md
CHANGED
|
@@ -85,6 +85,39 @@ Automatically create GraphQL schema or customizable schema config fields from Dr
|
|
|
85
85
|
})
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
## Naming the generated types and fields
|
|
89
|
+
|
|
90
|
+
By default every name comes from the table's key in the Drizzle schema: `tasks` gives type
|
|
91
|
+
`Tasks`, queries `tasks` / `tasksSingle`, mutations `createTasks` / `createTasksSingle`.
|
|
92
|
+
Most schemas use plural table keys and want singular type names, so that pairing is shipped
|
|
93
|
+
as a preset:
|
|
94
|
+
|
|
95
|
+
```Typescript
|
|
96
|
+
const { schema } = buildSchema(db, { typeNameMapper: 'singularize' })
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`tasks` then gives type `Task`, queries `tasks` (list) and `task` (single), mutations
|
|
100
|
+
`createTasks` (array) and `createTask` (single), `updateTask`, `deleteTask`. Irregular
|
|
101
|
+
plurals go through `pluralize`, so `people` gives `Person` / `people` / `person` and
|
|
102
|
+
`addresses` gives `Address` rather than the `addresss` a naive suffix would produce. A table
|
|
103
|
+
key that is already singular is pluralized for the list side, and a capitalized key is
|
|
104
|
+
uncapitalized for the field side, so `tasks`, `task` and `Tasks` all land on the same names.
|
|
105
|
+
|
|
106
|
+
For anything else, pass your own function — it is asked per table and may return `undefined`
|
|
107
|
+
to leave that table with the default naming. The preset is exported as `singularizeMapper`
|
|
108
|
+
so it can be wrapped:
|
|
109
|
+
|
|
110
|
+
```Typescript
|
|
111
|
+
import { buildSchema, singularizeMapper } from 'drizzle-graphql'
|
|
112
|
+
|
|
113
|
+
const { schema } = buildSchema(db, {
|
|
114
|
+
typeNameMapper: (table) => (table === 'auditLog' ? undefined : singularizeMapper(table)),
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
With a mapper in play the singular/plural forms are what keep the list and single fields
|
|
119
|
+
apart, so `suffixes` may be empty on both sides. Without one, they must still differ.
|
|
120
|
+
|
|
88
121
|
## Choosing what gets generated
|
|
89
122
|
|
|
90
123
|
Every generated operation is on by default. `features` turns individual ones off, which
|
|
@@ -124,6 +157,35 @@ const { schema } = buildSchema(db, {
|
|
|
124
157
|
`mutations: false`
|
|
125
158
|
- List and single queries are always generated, so `Query` is never empty
|
|
126
159
|
|
|
160
|
+
### Per-table operations
|
|
161
|
+
|
|
162
|
+
The choice is rarely build-wide: a schema usually has a handful of tables that must not be
|
|
163
|
+
deleted through the API, a handful whose writes belong to a hand-written resolver that
|
|
164
|
+
enforces something, and a majority that want everything. Any flag can be a predicate
|
|
165
|
+
instead of a boolean, asked once per table with the table's key in the Drizzle schema:
|
|
166
|
+
|
|
167
|
+
```Typescript
|
|
168
|
+
const { schema } = buildSchema(db, {
|
|
169
|
+
features: {
|
|
170
|
+
delete: (table) => table !== 'auditLog',
|
|
171
|
+
update: (table) => !ownedByCustomResolver.has(table),
|
|
172
|
+
upsert: (table) => table === 'settings',
|
|
173
|
+
},
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- The table is still fully readable — this decides which operations it offers, not whether
|
|
178
|
+
it is in the schema at all. For that, use `exclude` below
|
|
179
|
+
- `nestedWrites` is the one flag that stays build-wide: its plans are computed once over
|
|
180
|
+
the whole relation graph, and a nested write reaches a second table by definition
|
|
181
|
+
- `Mutation` is omitted when *no* table generates a mutation, and kept when one still does
|
|
182
|
+
- Some operations are built out of others, and the generator knows which. A table that
|
|
183
|
+
asks for `upsert` while its `insert` or `update` is off — an upsert is a second write
|
|
184
|
+
path past the operation you turned off — warns on the console at build time, naming the
|
|
185
|
+
tables, rather than leaving that to be discovered later. So does asking for `updateMany`
|
|
186
|
+
without `update`, `groupBy` without `aggregates`, `countMutations` with neither plural
|
|
187
|
+
write, or `nestedWrites` alongside a table whose own writes are off
|
|
188
|
+
|
|
127
189
|
## Excluding tables and columns
|
|
128
190
|
|
|
129
191
|
`features` decides which *kinds* of operation exist. `exclude` decides which tables and
|
|
@@ -1425,6 +1487,105 @@ request-scoped batch loader, so the field still works even before you override i
|
|
|
1425
1487
|
and relation names are the Drizzle schema keys (e.g. `Users`, `posts`), matching the keys
|
|
1426
1488
|
of `entities.fieldResolvers`.
|
|
1427
1489
|
|
|
1490
|
+
## Writing your own resolver over a generated type
|
|
1491
|
+
|
|
1492
|
+
When you replace or wrap a generated resolver — to add a permission check that needs the
|
|
1493
|
+
row, to run a write inside your own transaction, to enforce a rule the schema cannot
|
|
1494
|
+
express — you become responsible for returning a row shaped like the type you replaced.
|
|
1495
|
+
That means translating the GraphQL selection set into the relational query builder's
|
|
1496
|
+
`with:` tree: relation field resolvers only fill in relations the parent did not already
|
|
1497
|
+
provide.
|
|
1498
|
+
|
|
1499
|
+
Two exports do that for you, alongside the already-exported `extractFilters` and
|
|
1500
|
+
`extractOrderBy`:
|
|
1501
|
+
|
|
1502
|
+
```Typescript
|
|
1503
|
+
import { resolveSelection, selectionToWith } from '@vantreeseba/drizzle-graphql'
|
|
1504
|
+
|
|
1505
|
+
// The whole read: selected columns, relation arguments, eager loading, cursor
|
|
1506
|
+
// pagination, and the transport remapping of the result — against any executor.
|
|
1507
|
+
const rows = await resolveSelection(info, {
|
|
1508
|
+
db,
|
|
1509
|
+
table: 'Users',
|
|
1510
|
+
where: { id: { eq: userId } },
|
|
1511
|
+
executor: tx, // defaults to `db`; pass a transaction to see its own writes
|
|
1512
|
+
})
|
|
1513
|
+
|
|
1514
|
+
// Or just the `with:` tree, if you are building the query yourself.
|
|
1515
|
+
const withTree = selectionToWith(info, { db, table: 'Users' })
|
|
1516
|
+
const row = await tx.query.Users.findFirst({ where: { id: userId }, with: withTree })
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1519
|
+
`selectionToWith` resolves fragments and inline fragments against `info.fragments`,
|
|
1520
|
+
intersects the selection against the relations config (so scalars and any fields you added
|
|
1521
|
+
yourself drop out instead of becoming invalid `with:` keys), follows aliases back to the
|
|
1522
|
+
relation they select, compiles each relation's `where` / `orderBy` / `limit` / `offset`
|
|
1523
|
+
into its nested entry, and leaves `<relation>Aggregate` selections out — they are not
|
|
1524
|
+
`with:` entries. It returns `undefined` when the selection asks for no relations.
|
|
1525
|
+
|
|
1526
|
+
`resolveSelection` additionally accepts `single`, `orderBy`, `limit`, `offset` and `after`,
|
|
1527
|
+
and returns the rows already remapped to their GraphQL transport forms.
|
|
1528
|
+
|
|
1529
|
+
Both take a `typeNameMapper` option. If the schema was built with one, pass the same
|
|
1530
|
+
function here — the translation matches selections against GraphQL type names, so a build
|
|
1531
|
+
that renames its types has to say so.
|
|
1532
|
+
|
|
1533
|
+
## Identifying generated fields
|
|
1534
|
+
|
|
1535
|
+
Every generated field and object type publishes what it is under `extensions.drizzle`, so
|
|
1536
|
+
wrappers — `graphql-middleware`, envelop, `graphql-shield`, an audit log, a cache
|
|
1537
|
+
invalidator — can ask the schema instead of parsing field names. Names are configurable
|
|
1538
|
+
(`prefixes`, `suffixes`, `typeNameMapper`), which makes name parsing ambiguous by
|
|
1539
|
+
construction; this is not.
|
|
1540
|
+
|
|
1541
|
+
```Typescript
|
|
1542
|
+
import { drizzleExtension, identifyRows } from '@vantreeseba/drizzle-graphql'
|
|
1543
|
+
|
|
1544
|
+
const field = info.parentType.getFields()[info.fieldName]
|
|
1545
|
+
const meta = drizzleExtension(field)
|
|
1546
|
+
// {
|
|
1547
|
+
// table: 'Users', // the Drizzle schema key
|
|
1548
|
+
// kind: 'mutation', // 'query' | 'mutation' | 'relation' | 'aggregate' | 'type'
|
|
1549
|
+
// operation: 'update', // 'select' | 'insert' | 'update' | 'updateMany' | 'upsert'
|
|
1550
|
+
// // | 'delete' | 'aggregate' | 'groupBy' | 'relation'
|
|
1551
|
+
// // | 'relationAggregate'
|
|
1552
|
+
// single: true, // one row, or a list
|
|
1553
|
+
// targetArg: 'where', // 'where' | 'values' | 'updates' — where the rows live
|
|
1554
|
+
// primaryKey: ['id'], // key columns of `table`, in schema order
|
|
1555
|
+
// }
|
|
1556
|
+
|
|
1557
|
+
if (meta?.kind === 'mutation') await audit(meta.table, meta.operation, args[meta.targetArg])
|
|
1558
|
+
```
|
|
1559
|
+
|
|
1560
|
+
`drizzleExtension` returns `undefined` for anything this library did not generate, which is
|
|
1561
|
+
the check to make before treating a field as one of ours. Relation fields (and
|
|
1562
|
+
`<relation>Aggregate` fields) additionally carry `relation` and `parentTable`, with `table`
|
|
1563
|
+
naming the relation's **target** — so `Users.posts` reads as
|
|
1564
|
+
`{ table: 'Posts', parentTable: 'Users', relation: 'posts' }`. Object types carry
|
|
1565
|
+
`{ table, kind: 'type', primaryKey }`, which is what a stitching layer needs to map a type
|
|
1566
|
+
back to its source.
|
|
1567
|
+
|
|
1568
|
+
Because the row identity of a mutation lives in a different argument for every operation,
|
|
1569
|
+
`identifyRows` reads whichever one applies and hands back primary-key values:
|
|
1570
|
+
|
|
1571
|
+
```Typescript
|
|
1572
|
+
const target = identifyRows(field, args)
|
|
1573
|
+
// { table: 'Users', primaryKey: ['id'], rows: [{ id: 3 }], complete: true }
|
|
1574
|
+
|
|
1575
|
+
if (target?.complete) await invalidate(target.table, target.rows)
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
It understands `values` (one object or a list), `where` (`eq` for one row, `inArray` for
|
|
1579
|
+
many, an equality on every column of a composite key), and the per-entry `where` inside a
|
|
1580
|
+
batch update's `updates`. Check `complete` before treating `rows` as the affected set: it
|
|
1581
|
+
is `false` when the arguments do not pin the rows down — an insert whose key the database
|
|
1582
|
+
generates, a filter on something other than the primary key, an unbounded update, or a
|
|
1583
|
+
table with no primary key at all.
|
|
1584
|
+
|
|
1585
|
+
> [!NOTE]
|
|
1586
|
+
> `extensions.drizzle` is stable API. The point of publishing it is that consumers stop
|
|
1587
|
+
> parsing names, which only holds if the shape does not move: fields are added to it, never
|
|
1588
|
+
> removed or repurposed.
|
|
1428
1589
|
## Configuration reference
|
|
1429
1590
|
|
|
1430
1591
|
Every key of the second argument to `buildSchema(db, config?)`. Each links to the section
|