@drizzle-graphql-suite/schema 0.8.3 → 0.9.1

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
@@ -1,495 +1,11 @@
1
- # @drizzle-graphql-suite/schema
1
+ # Deprecated
2
2
 
3
- > Part of [`drizzle-graphql-suite`](https://github.com/annexare/drizzle-graphql-suite).
4
- > See also: [`client`](https://github.com/annexare/drizzle-graphql-suite/tree/main/packages/client) | [`query`](https://github.com/annexare/drizzle-graphql-suite/tree/main/packages/query)
3
+ This package has been renamed to `@graphql-suite/schema`.
5
4
 
6
- Auto-generates a complete GraphQL schema with CRUD operations, relation-level filtering, and hooks from Drizzle PostgreSQL schemas.
7
-
8
- ## Installation
9
-
10
- ```bash
11
- bun add @drizzle-graphql-suite/schema
12
- ```
13
-
14
- ```bash
15
- npm install @drizzle-graphql-suite/schema
16
- ```
17
-
18
- Or install the full suite:
5
+ Please update your dependencies:
19
6
 
20
7
  ```bash
21
- bun add drizzle-graphql-suite
22
- ```
23
-
24
- ```bash
25
- npm install drizzle-graphql-suite
26
- ```
27
-
28
- ## Motivation
29
-
30
- Inspired by [`drizzle-graphql`](https://github.com/drizzle-team/drizzle-graphql), this package is a purpose-built replacement focused on PostgreSQL. Key improvements:
31
-
32
- - **Small generated schema** — the generated schema stays compact even when supporting self-relations and deeply nested relations, thanks to configurable depth limiting (`limitRelationDepth`, `limitSelfRelationDepth`), per-relation pruning (`pruneRelations`), and per-table control (`tables.exclude`, per-table `queries`/`mutations` toggles) — up to 90% schema size reduction when tuned
33
- - **Native PostgreSQL JSON/JSONB support** — `json` and `jsonb` columns map to a custom `JSON` GraphQL scalar, so structured data passes through without manual type wiring
34
- - **Relation-level filtering** with EXISTS subqueries (`some`/`every`/`none` quantifiers)
35
- - **Runtime permissions** — `withPermissions()` builds filtered schemas per role, fully reflected in introspection
36
- - **Row-level security helpers** — `withRowSecurity()` + `mergeHooks()` for composable auth
37
- - **Per-operation hooks system** (before/after/resolve) for auth, audit, and custom logic
38
- - **Count queries** with full filter support
39
- - **`buildEntities()`** for composable schema building (avoids redundant schema validation)
40
- - **Configurable query/mutation suffixes** for naming customization
41
- - **Self-relation depth limiting** — separate from general depth, prevents exponential type growth
42
- - **Relation pruning** — `false`, `'leaf'`, or `{ only: [...] }` per relation
43
- - **`buildSchemaFromDrizzle()`** — no database connection needed (for codegen/introspection)
44
- - **Code generation** — `generateSDL`, `generateTypes`, `generateEntityDefs`
45
- - **Architecture** — TypeScript source, PostgreSQL-only, `SchemaBuilder` class, type caching, lazy thunks for circular relations
46
- - **Bug fixes** — relation filter join conditions (Drizzle v0.44+), operator map replacements, `catch (e: unknown)` narrowing
47
-
48
- ## API Reference
49
-
50
- ### `buildSchema(db, config?)`
51
-
52
- Builds a complete `GraphQLSchema` with all CRUD operations from a Drizzle database instance. Returns `{ schema, entities, withPermissions }`.
53
-
54
- ```ts
55
- import { buildSchema } from 'drizzle-graphql-suite/schema'
56
- import { createYoga } from 'graphql-yoga'
57
- import { db } from './db'
58
-
59
- const { schema, entities, withPermissions } = buildSchema(db, {
60
- limitRelationDepth: 3,
61
- tables: { exclude: ['session'] },
62
- })
63
-
64
- const yoga = createYoga({ schema })
65
- ```
66
-
67
- See [Runtime Permissions](#runtime-permissions) for `withPermissions` usage.
68
-
69
- #### Framework Integration
70
-
71
- **Next.js App Router**
72
-
73
- ```ts
74
- // app/api/graphql/route.ts
75
- import { createYoga } from 'graphql-yoga'
76
- import { schema } from '@/lib/schema' // from buildSchema() above
77
-
78
- const { handleRequest } = createYoga({
79
- schema,
80
- graphqlEndpoint: '/api/graphql',
81
- fetchAPI: { Response },
82
- })
83
-
84
- export { handleRequest as GET, handleRequest as POST }
85
- ```
86
-
87
- **ElysiaJS**
88
-
89
- ```ts
90
- // server.ts
91
- import { Elysia } from 'elysia'
92
- import { yoga } from '@elysiajs/graphql-yoga'
93
- import { schema } from './schema' // from buildSchema() above
94
-
95
- new Elysia()
96
- .use(yoga({ schema }))
97
- .listen(3000)
98
- ```
99
-
100
- ### `buildEntities(db, config?)`
101
-
102
- Returns `GeneratedEntities` only — queries, mutations, inputs, and types — without constructing a `GraphQLSchema`. Use this when composing into a larger schema (e.g., Pothos) to avoid redundant schema validation.
103
-
104
- ```ts
105
- import { buildEntities } from 'drizzle-graphql-suite/schema'
106
-
107
- const entities = buildEntities(db, { mutations: false })
108
- // entities.queries, entities.mutations, entities.inputs, entities.types
109
- ```
110
-
111
- ### `buildSchemaFromDrizzle(drizzleSchema, config?)`
112
-
113
- Builds a schema directly from Drizzle schema exports — no database connection or `.env` required. Resolvers are stubs. Intended for schema introspection and code generation.
114
-
115
- ```ts
116
- import { buildSchemaFromDrizzle } from 'drizzle-graphql-suite/schema'
117
- import * as schema from './db/schema'
118
-
119
- const { schema: graphqlSchema } = buildSchemaFromDrizzle(schema)
120
- ```
121
-
122
- ## Configuration
123
-
124
- `BuildSchemaConfig` controls all aspects of schema generation:
125
-
126
- ### `mutations`
127
-
128
- Enable or disable mutation generation globally.
129
-
130
- - **Type**: `boolean`
131
- - **Default**: `true`
132
-
133
- ### `limitRelationDepth`
134
-
135
- Maximum depth of nested relation fields on queries. Set to `0` to omit relations, `undefined` for no limit.
136
-
137
- - **Type**: `number | undefined`
138
- - **Default**: `3`
139
-
140
- ### `limitSelfRelationDepth`
141
-
142
- Maximum occurrences of the same table via direct self-relations (e.g., `asset.template → asset`). At `1`, self-relation fields are omitted entirely. At `2`, one level of expansion is allowed. Cross-table paths that revisit a table reset the counter and use `limitRelationDepth` instead.
143
-
144
- - **Type**: `number`
145
- - **Default**: `1`
146
-
147
- ### `suffixes`
148
-
149
- Customize query field name suffixes for list and single queries.
150
-
151
- - **Type**: `{ list?: string; single?: string }`
152
- - **Default**: `{ list: '', single: 'Single' }`
153
-
154
- ### `tables`
155
-
156
- Per-table schema control:
157
-
158
- ```ts
159
- {
160
- tables: {
161
- // Remove tables entirely (relations to them are silently skipped)
162
- exclude: ['session', 'verification'],
163
- // Per-table operation overrides
164
- config: {
165
- auditLog: { queries: true, mutations: false },
166
- user: { mutations: false },
167
- },
168
- },
169
- }
170
- ```
171
-
172
- - `exclude` — `string[]` — tables removed from the schema entirely
173
- - `config` — `Record<string, TableOperations>` — per-table `queries` and `mutations` booleans
174
-
175
- ### `pruneRelations`
176
-
177
- Fine-grained per-relation pruning. Keys are `tableName.relationName`:
178
-
179
- ```ts
180
- {
181
- pruneRelations: {
182
- // Omit this relation field entirely
183
- 'asset.childAssets': false,
184
- // Expand with scalar columns only (no nested relations)
185
- 'user.posts': 'leaf',
186
- // Expand with only the listed child relations
187
- 'post.comments': { only: ['author'] },
188
- },
189
- }
190
- ```
191
-
192
- - `false` — relation field omitted entirely from parent type
193
- - `'leaf'` — relation expands with scalar columns only
194
- - `{ only: string[] }` — relation expands with only the listed child relation fields
195
-
196
- ### `hooks`
197
-
198
- Per-table, per-operation hooks. See [Hooks System](#hooks-system).
199
-
200
- ### `debug`
201
-
202
- Enable diagnostic logging for schema size and relation tree.
203
-
204
- - **Type**: `boolean | { schemaSize?: boolean; relationTree?: boolean }`
205
- - **Default**: `undefined`
206
-
207
- ## Runtime Permissions
208
-
209
- Build filtered `GraphQLSchema` variants per role or user — introspection fully reflects what each role can see and do.
210
-
211
- ```ts
212
- import { buildSchema, permissive, restricted, readOnly } from 'drizzle-graphql-suite/schema'
213
-
214
- const { schema, withPermissions } = buildSchema(db)
215
-
216
- // Full schema (admin)
217
- const adminSchema = schema
218
-
219
- // Permissive: everything allowed except audit (excluded) and users (read-only)
220
- const maintainerSchema = withPermissions(
221
- permissive('maintainer', { audit: false, users: readOnly() }),
222
- )
223
-
224
- // Restricted: nothing allowed except posts and comments (queries only)
225
- const userSchema = withPermissions(
226
- restricted('user', { posts: { query: true }, comments: { query: true } }),
227
- )
228
-
229
- // Restricted with nothing granted — only Query { _empty: Boolean }
230
- const anonSchema = withPermissions(restricted('anon'))
231
- ```
232
-
233
- Schemas are cached by `id` — calling `withPermissions` with the same `id` returns the same `GraphQLSchema` instance.
234
-
235
- ### Permission Helpers
236
-
237
- | Helper | Description |
238
- |--------|-------------|
239
- | `permissive(id, tables?)` | All tables allowed by default; overrides deny |
240
- | `restricted(id, tables?)` | Nothing allowed by default; overrides grant |
241
- | `readOnly()` | Shorthand for `{ query: true, insert: false, update: false, delete: false }` |
242
-
243
- ### Table Access
244
-
245
- Each table can be set to `true` (all operations), `false` (excluded entirely), or a `TableAccess` object:
246
-
247
- ```ts
248
- type TableAccess = {
249
- query?: boolean // list + single + count
250
- insert?: boolean // insert + insertSingle
251
- update?: boolean
252
- delete?: boolean
253
- }
254
- ```
255
-
256
- In **permissive** mode, omitted fields default to `true`. In **restricted** mode, omitted fields default to `false`.
257
-
258
- ### Introspection Behavior
259
-
260
- - `false` (excluded table) — removed from everywhere: no entry points, no relation fields on other types, no filter fields
261
- - `readOnly()` — table types exist (accessible via relations), but only query entry points; no mutations
262
- - Granular control — e.g. `{ query: true, insert: true, delete: false }` removes only `deleteFrom{Table}` mutation
263
-
264
- ## Row-Level Security
265
-
266
- Generate hooks that inject WHERE clauses for row-level filtering. Compose with other hooks using `mergeHooks`.
267
-
268
- ```ts
269
- import { buildSchema, withRowSecurity, mergeHooks } from 'drizzle-graphql-suite/schema'
270
-
271
- const { schema } = buildSchema(db, {
272
- hooks: mergeHooks(
273
- withRowSecurity({
274
- posts: (context) => ({ authorId: { eq: context.user.id } }),
275
- }),
276
- myOtherHooks,
277
- ),
278
- })
279
- ```
280
-
281
- ### `withRowSecurity(rules)`
282
-
283
- Generates a `HooksConfig` with `before` hooks on `query`, `querySingle`, `count`, `update`, and `delete` operations. Each rule is a function that receives the GraphQL context and returns a WHERE filter object.
284
-
285
- ### `mergeHooks(...configs)`
286
-
287
- Deep-merges multiple `HooksConfig` objects:
288
-
289
- - **`before` hooks** — chained sequentially; each receives the previous hook's modified args
290
- - **`after` hooks** — chained sequentially; each receives the previous hook's result
291
- - **`resolve` hooks** — last one wins (cannot be composed)
292
-
293
- ## Hooks System
294
-
295
- Hooks intercept operations for auth, validation, audit logging, or custom resolution.
296
-
297
- ### Hook Types
298
-
299
- Each operation supports either **before/after** hooks or a **resolve** hook (not both):
300
-
301
- | Hook | Timing | Use Case |
302
- |------|--------|----------|
303
- | `before` | Before default resolver | Auth checks, argument transformation, pass data to `after` |
304
- | `after` | After default resolver | Audit logging, result transformation |
305
- | `resolve` | Replaces default resolver | Full control, custom data sources |
306
-
307
- ### Operations
308
-
309
- | Operation | Type | Description |
310
- |-----------|------|-------------|
311
- | `query` | Read | List query |
312
- | `querySingle` | Read | Single record query |
313
- | `count` | Read | Count query |
314
- | `insert` | Write | Batch insert |
315
- | `insertSingle` | Write | Single record insert |
316
- | `update` | Write | Update mutation |
317
- | `delete` | Write | Delete mutation |
318
-
319
- ### Example
320
-
321
- ```ts
322
- buildSchema(db, {
323
- hooks: {
324
- user: {
325
- query: {
326
- before: async ({ args, context }) => {
327
- if (!context.user) throw new Error('Unauthorized')
328
- // Optionally modify args or pass data to after hook
329
- return { args, data: { startTime: Date.now() } }
330
- },
331
- after: async ({ result, beforeData }) => {
332
- console.log(`Query took ${Date.now() - beforeData.startTime}ms`)
333
- return result
334
- },
335
- },
336
- delete: {
337
- resolve: async ({ args, context, defaultResolve }) => {
338
- // Soft delete instead of hard delete
339
- return defaultResolve({ ...args, set: { deletedAt: new Date() } })
340
- },
341
- },
342
- },
343
- },
344
- })
345
- ```
346
-
347
- ## Relation Filtering
348
-
349
- Filter by related rows using EXISTS subqueries with `some`, `every`, and `none` quantifiers.
350
-
351
- ```graphql
352
- query {
353
- users(where: {
354
- posts: {
355
- some: { published: { eq: true } }
356
- none: { flagged: { eq: true } }
357
- }
358
- }) {
359
- id
360
- name
361
- posts {
362
- title
363
- }
364
- }
365
- }
366
- ```
367
-
368
- - **`some`** — at least one related row matches
369
- - **`every`** — all related rows match
370
- - **`none`** — no related rows match
371
-
372
- For one-to-one relations, filters apply directly (no quantifiers needed):
373
-
374
- ```graphql
375
- query {
376
- posts(where: {
377
- author: { role: { eq: "admin" } }
378
- }) {
379
- title
380
- }
381
- }
382
- ```
383
-
384
- ## Generated Operations
385
-
386
- | Pattern | Example (`user` table) | Type |
387
- |---------|----------------------|------|
388
- | `{table}` | `user` | Single query |
389
- | `{table}{listSuffix}` | `users` | List query |
390
- | `{table}Count` | `userCount` | Count query |
391
- | `insertInto{Table}` | `insertIntoUser` | Batch insert |
392
- | `insertInto{Table}Single` | `insertIntoUserSingle` | Single insert |
393
- | `update{Table}` | `updateUser` | Update |
394
- | `deleteFrom{Table}` | `deleteFromUser` | Delete |
395
-
396
- ## Column Type Mapping
397
-
398
- | Drizzle Type | GraphQL Type |
399
- |-------------|--------------|
400
- | `boolean` | `Boolean` |
401
- | `text`, `varchar`, `char` | `String` |
402
- | `integer`, `smallint`, `serial`, `smallserial`, `bigserial` | `Int` |
403
- | `real`, `doublePrecision`, `numeric` | `Float` |
404
- | `bigint` | `String` |
405
- | `date`, `timestamp`, `time` | `String` |
406
- | `json`, `jsonb` | `JSON` (custom scalar) |
407
- | `bytea` | `[Int!]` |
408
- | `vector` | `[Float!]` |
409
- | `geometry` | `PgGeometryObject { x, y }` |
410
- | `enum` | Generated `GraphQLEnumType` |
411
-
412
- ## Code Generation
413
-
414
- > **When to use:** Only when the client is in a separate repository that cannot import the Drizzle schema directly. For same-repo setups, [`createDrizzleClient`](https://github.com/annexare/drizzle-graphql-suite/tree/main/packages/client) infers all types automatically — no codegen needed.
415
-
416
- Three code generation functions for producing static artifacts from a GraphQL schema:
417
-
418
- ### `generateSDL(schema)`
419
-
420
- Generates the GraphQL Schema Definition Language string.
421
-
422
- ```ts
423
- import { buildSchemaFromDrizzle, generateSDL } from 'drizzle-graphql-suite/schema'
424
- import * as drizzleSchema from './db/schema'
425
- import { writeFileSync } from 'node:fs'
426
-
427
- const { schema } = buildSchemaFromDrizzle(drizzleSchema)
428
- writeFileSync('schema.graphql', generateSDL(schema))
429
- ```
430
-
431
- ### `generateTypes(schema, options?)`
432
-
433
- Generates TypeScript types: wire format types (Date → string), filter types, insert/update input types, and orderBy types. Optionally imports Drizzle types for precise wire format derivation.
434
-
435
- ```ts
436
- import { generateTypes } from 'drizzle-graphql-suite/schema'
437
-
438
- const types = generateTypes(schema, {
439
- drizzle: {
440
- importPath: '@myapp/db/schema',
441
- typeNames: { userProfile: 'UserProfile' },
442
- },
443
- })
444
- writeFileSync('generated/types.ts', types)
445
- ```
446
-
447
- ### `generateEntityDefs(schema, options?)`
448
-
449
- Generates a runtime schema descriptor object and `EntityDefs` type for the client package. Use this instead of `createDrizzleClient` when the client is in a separate repo and can't import the Drizzle schema.
450
-
451
- ```ts
452
- import { generateEntityDefs } from 'drizzle-graphql-suite/schema'
453
-
454
- const entityDefs = generateEntityDefs(schema, {
455
- drizzle: { importPath: '@myapp/db/schema' },
456
- })
457
- writeFileSync('generated/entity-defs.ts', entityDefs)
458
- ```
459
-
460
- ### Full Codegen Script
461
-
462
- ```ts
463
- import { buildSchemaFromDrizzle, generateSDL, generateTypes, generateEntityDefs } from 'drizzle-graphql-suite/schema'
464
- import * as drizzleSchema from './db/schema'
465
- import { writeFileSync, mkdirSync } from 'node:fs'
466
-
467
- const { schema } = buildSchemaFromDrizzle(drizzleSchema)
468
-
469
- mkdirSync('generated', { recursive: true })
470
- writeFileSync('generated/schema.graphql', generateSDL(schema))
471
- writeFileSync('generated/types.ts', generateTypes(schema, {
472
- drizzle: { importPath: '@myapp/db/schema' },
473
- }))
474
- writeFileSync('generated/entity-defs.ts', generateEntityDefs(schema, {
475
- drizzle: { importPath: '@myapp/db/schema' },
476
- }))
477
- ```
478
-
479
- ## `GeneratedEntities` Type
480
-
481
- The return type from `buildEntities()` and `buildSchema()`:
482
-
483
- ```ts
484
- type GeneratedEntities = {
485
- queries: Record<string, GraphQLFieldConfig<any, any>>
486
- mutations: Record<string, GraphQLFieldConfig<any, any>>
487
- inputs: Record<string, GraphQLInputObjectType>
488
- types: Record<string, GraphQLObjectType>
489
- }
8
+ bun add @graphql-suite/schema
490
9
  ```
491
10
 
492
- - **`queries`** — all generated query field configs, spreadable into a parent schema
493
- - **`mutations`** — all generated mutation field configs
494
- - **`inputs`** — filter, insert, update, and orderBy input types by name
495
- - **`types`** — output object types by table name
11
+ Documentation: https://graphql-suite.annexare.com
package/index.d.ts CHANGED
@@ -1,32 +1 @@
1
- import type { PgDatabase } from 'drizzle-orm/pg-core';
2
- import type { GraphQLSchema } from 'graphql';
3
- import type { BuildSchemaConfig, GeneratedEntities, PermissionConfig } from './types';
4
- export { GraphQLJSON } from './graphql/scalars';
5
- export declare const buildSchema: (db: PgDatabase<any, any, any>, config?: BuildSchemaConfig) => {
6
- schema: GraphQLSchema;
7
- entities: GeneratedEntities;
8
- withPermissions: (permissions: PermissionConfig) => GraphQLSchema;
9
- clearPermissionCache: (id?: string) => void;
10
- };
11
- export declare const buildEntities: (db: PgDatabase<any, any, any>, config?: BuildSchemaConfig) => GeneratedEntities;
12
- /**
13
- * Build a GraphQL schema directly from Drizzle schema exports — no database
14
- * connection or `.env` required. Creates a lightweight mock db instance that
15
- * satisfies `SchemaBuilder`'s metadata needs (table definitions, relations,
16
- * table name mapping) without an actual connection.
17
- *
18
- * Resolver functions on the returned entities are stubs — this is intended
19
- * for schema introspection and code generation, not query execution.
20
- */
21
- export declare const buildSchemaFromDrizzle: (drizzleSchema: Record<string, unknown>, config?: BuildSchemaConfig) => {
22
- schema: GraphQLSchema;
23
- entities: GeneratedEntities;
24
- withPermissions: (permissions: PermissionConfig) => GraphQLSchema;
25
- clearPermissionCache: (id?: string) => void;
26
- };
27
- export type { CodegenOptions } from './codegen';
28
- export { generateEntityDefs, generateSDL, generateTypes } from './codegen';
29
- export { permissive, readOnly, restricted } from './permissions';
30
- export { mergeHooks, withRowSecurity } from './row-security';
31
- export { SchemaBuilder } from './schema-builder';
32
- export * from './types';
1
+ export * from '@graphql-suite/schema'