pothos-drizzle-generator 0.1.21 → 0.1.23

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,220 +1,360 @@
1
- # pothos-drizzle-generator
1
+ # Pothos Drizzle Generator
2
2
 
3
- [![](https://img.shields.io/npm/l/pothos-drizzle-generator)](https://www.npmjs.com/package/pothos-drizzle-generator)
4
- [![](https://img.shields.io/npm/v/pothos-drizzle-generator)](https://www.npmjs.com/package/pothos-drizzle-generator)
5
- [![](https://img.shields.io/npm/dw/pothos-drizzle-generator)](https://www.npmjs.com/package/pothos-drizzle-generator)
6
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/node-libraries/pothos-drizzle-generator)
3
+ **Pothos Drizzle Generator** is a robust Pothos plugin designed to automatically generate a complete GraphQL schema (Queries & Mutations) directly from your Drizzle ORM schema definitions.
7
4
 
8
- A Pothos plugin that automatically generates GraphQL schemas based on Drizzle schema information.
5
+ By automating the creation of types, input objects, and resolvers for standard CRUD operations, this tool significantly reduces boilerplate code. It also provides granular control over permissions, complex filtering, and field visibility, ensuring your API remains secure and performant.
6
+
7
+ - Screenshot in ApolloExplorer
9
8
 
10
9
  ![](./documents/image.png)
11
10
 
12
- # sample
11
+ ## 🚀 Key Features
12
+
13
+ - **Automated CRUD Generation**: Instantly generates `findMany`, `findFirst`, `create`, `update`, and `delete` operations.
14
+ - **End-to-End Type Safety**: Ensures fully typed inputs and outputs that stay in sync with your Drizzle schema.
15
+ - **Deep Relational Filtering**: Apply filters, sorting, and pagination **not just to the main resource, but also to any nested relations** (e.g., "Find users and their _published_ posts").
16
+ - **Advanced Filtering**: Built-in support for complex queries, including `AND`, `OR`, `gt` (greater than), `contains`, and more.
17
+ - **Granular Access Control**: Configure visibility and permissions globally or on a per-model basis.
18
+ - **Smart Relations**: Seamlessly handles join tables and nested relationships.
19
+
20
+ ## 🔗 Sample Repository
21
+
22
+ Explore a working implementation in the sample repository:
23
+ [https://github.com/SoraKumo001/pothos-drizzle-generator-sample](https://github.com/SoraKumo001/pothos-drizzle-generator-sample)
24
+
25
+ ---
26
+
27
+ ## 📦 Getting Started
28
+
29
+ ### Requirements
30
+
31
+ Ensure your environment meets the following dependencies:
32
+
33
+ - **drizzle-orm**: `v1.0.0-beta.8`+
34
+ - **@pothos/core**: `v4.0.0`+
35
+ - **@pothos/plugin-drizzle**: `v0.16.0`+
36
+
37
+ ### Installation
38
+
39
+ Install the generator alongside the required Pothos and Drizzle packages:
40
+
41
+ ```bash
42
+ # npm
43
+ npm install pothos-drizzle-generator @pothos/core @pothos/plugin-drizzle drizzle-orm graphql
44
+
45
+ # pnpm
46
+ pnpm add pothos-drizzle-generator @pothos/core @pothos/plugin-drizzle drizzle-orm graphql
47
+
48
+ # yarn
49
+ yarn add pothos-drizzle-generator @pothos/core @pothos/plugin-drizzle drizzle-orm graphql
13
50
 
14
- https://github.com/SoraKumo001/pothos-drizzle-generator-sample
51
+ ```
52
+
53
+ ---
54
+
55
+ ## ⚡ Quick Start
56
+
57
+ Follow these steps to integrate the generator into your SchemaBuilder.
15
58
 
16
- # usage
59
+ ### 1. Setup & Initialization
17
60
 
18
- To use this service, you must have version `drizzle-orm@1.0.0-beta.2` or later.
61
+ Register the `PothosDrizzleGeneratorPlugin` and configure your Drizzle client.
19
62
 
20
63
  ```ts
21
64
  import "dotenv/config";
22
65
  import SchemaBuilder from "@pothos/core";
23
66
  import DrizzlePlugin from "@pothos/plugin-drizzle";
67
+ import PothosDrizzleGeneratorPlugin from "pothos-drizzle-generator";
24
68
  import { drizzle } from "drizzle-orm/node-postgres";
25
69
  import { getTableConfig } from "drizzle-orm/pg-core";
26
70
  import { relations } from "./db/relations";
27
- import PothosDrizzleGeneratorPlugin from "pothos-drizzle-generator";
28
71
 
72
+ // 1. Initialize Drizzle Client
29
73
  const db = drizzle({
30
74
  connection: process.env.DATABASE_URL!,
31
75
  relations,
32
76
  logger: true,
33
77
  });
34
78
 
79
+ // 2. Define Context & Types
35
80
  export interface PothosTypes {
36
81
  DrizzleRelations: typeof relations;
37
82
  Context: { userId?: string };
38
83
  }
39
84
 
85
+ // 3. Initialize Builder
40
86
  const builder = new SchemaBuilder<PothosTypes>({
41
87
  plugins: [
42
88
  DrizzlePlugin,
43
- PothosDrizzleGeneratorPlugin, // Set plugin
89
+ PothosDrizzleGeneratorPlugin, // Register the generator plugin
44
90
  ],
45
91
  drizzle: {
46
92
  client: () => db,
47
93
  relations,
48
94
  getTableConfig,
49
95
  },
96
+ // 4. Generator Configuration
97
+ pothosDrizzleGenerator: {
98
+ // Define your global and model-specific rules here
99
+ },
50
100
  });
51
101
 
102
+ // 5. Build Schema
52
103
  const schema = builder.toSchema();
53
104
  ```
54
105
 
55
- # Options
106
+ ---
107
+
108
+ ## ⚙️ Configuration Guide
109
+
110
+ The `pothosDrizzleGenerator` option offers a layered configuration approach, giving you full control over the generated schema.
111
+
112
+ Rules are applied in the following order:
56
113
 
57
- Settings defined in `all` are overridden by `models`.
114
+ 1. **Selection (`use`)**: Define which tables to process.
115
+ 2. **Global Defaults (`all`)**: Apply baseline rules to _every_ model.
116
+ 3. **Model Overrides (`models`)**: Apply specific rules to individual models, overriding defaults.
117
+
118
+ ### 1. Table Selection (`use`)
119
+
120
+ Control which tables are exposed in the GraphQL schema. This is useful for hiding internal tables or many-to-many join tables.
58
121
 
59
122
  ```ts
60
- const builder = new SchemaBuilder<PothosTypes>({
61
- plugins: [
62
- DrizzlePlugin,
63
- PothosDrizzleGeneratorPlugin, // Set plugin
64
- ],
65
- drizzle: {
66
- client: () => db,
67
- relations,
68
- getTableConfig,
69
- },
70
- pothosDrizzleGenerator: {
71
- // Specifying the Maximum Query Depth
72
- depthLimit: ({ ctx, modelName, operation }) => $limit$,
73
- // Specifying the model to use
74
- use: { include: [...$modelNames$], exclude: [...$modelNames$] },
75
- // Applies to all models
76
- all:{
77
- // Specifying fields to use in queries
78
- fields: ({ modelName }) => { include: [...$fields$], exclude: [...$fields$] },
79
- // Specifying the method of operation for the model
80
- operations: ({ modelName }) => { include: [...$operation$], exclude: [...$operation$] },
81
- // Runtime Permission Check
82
- executable: ({ ctx, modelName, operation }) => $permission$,
83
- // Specify the maximum value for the query's limit
84
- limit: ({ ctx, modelName, operation }) => $limit$,
85
- // Override the query's orderBy
86
- orderBy: ({ ctx, modelName, operation }) => $orderBy$,
87
- // Add query conditions
88
- where: ({ ctx, modelName, operation }) => $where$,
89
- // Specifying input fields
90
- inputFields: { include: [$fields$], exclude: [$fields$] },
91
- // Overwriting input data
92
- inputData: ({ ctx, modelName, operation }) => $inputData$,
93
- },
94
- // Apply to individual models
95
- models: {
96
- [$modelName$]: {
97
- // Specifying fields to use in queries
98
- fields: ({ modelName }) => { include: [...$fields$], exclude: [...$fields$] },
99
- // Specifying the method of operation for the model
100
- operations: ({ modelName }) => { include: [...$operation$], exclude: [...$operation$] },
101
- // Runtime Permission Check
102
- executable: ({ ctx, modelName, operation }) => $permission$,
103
- // Specify the maximum value for the query's limit
104
- limit: ({ ctx, modelName, operation }) => $limit$,
105
- // Override the query's orderBy
106
- orderBy: ({ ctx, modelName, operation }) => $orderBy$,
107
- // Add query conditions
108
- where: ({ ctx, modelName, operation }) => $where$,
109
- // Specifying input fields
110
- inputFields: { include: [$fields$], exclude: [$fields$] },
111
- // Overwriting input data
112
- inputData: ({ ctx, modelName, operation }) => $inputData$,
113
- },
123
+ pothosDrizzleGenerator: {
124
+ // Option A: Allowlist (Only generate these tables)
125
+ use: { include: ["users", "posts", "comments"] },
126
+
127
+ // Option B: Blocklist (Generate all EXCEPT these)
128
+ use: { exclude: ["users_to_groups", "audit_logs"] },
129
+ }
130
+
131
+ ```
132
+
133
+ ### 2. Global Defaults (`all`)
134
+
135
+ Use the `all` key to establish project-wide conventions, such as security policies, default query limits, or field visibility.
136
+
137
+ ```ts
138
+ pothosDrizzleGenerator: {
139
+ all: {
140
+ // Security: Require authentication for all write operations
141
+ executable: ({ ctx, operation }) => {
142
+ if (['create', 'update', 'delete'].includes(operation)) {
143
+ return !!ctx.userId; // Must be logged in
144
+ }
145
+ return true; // Read operations are public
114
146
  },
115
- },
116
- });
147
+ // Performance: Set a default limit for all queries
148
+ limit: () => 50,
149
+ }
150
+ }
151
+
152
+ ```
153
+
154
+ ### 3. Model Overrides (`models`)
155
+
156
+ Target specific tables by name to override global settings.
157
+
158
+ ```ts
159
+ pothosDrizzleGenerator: {
160
+ models: {
161
+ users: {
162
+ // Privacy: Users can only query their own record
163
+ where: ({ ctx }) => ({ id: { eq: ctx.userId } }),
164
+ // Security: Prevent user deletion via API
165
+ operations: () => ({ exclude: ["delete"] })
166
+ }
167
+ }
168
+ }
169
+
170
+ ```
171
+
172
+ ### 4. API Reference
173
+
174
+ The following callbacks can be used within both `all` and `models`.
175
+
176
+ | Property | Purpose | Arguments | Expected Return |
177
+ | ------------- | ---------------------------------------------------------- | ------------------------------- | -------------------------------- |
178
+ | `executable` | Authorization check. Return `false` to block execution. | `{ ctx, modelName, operation }` | `boolean` |
179
+ | `fields` | Control output field visibility. | `{ modelName }` | `{ include?: [], exclude?: [] }` |
180
+ | `inputFields` | Control input field visibility (for mutations). | `{ modelName }` | `{ include?: [], exclude?: [] }` |
181
+ | `operations` | Select which CRUD operations to generate. | `{ modelName }` | `{ include?: [], exclude?: [] }` |
182
+ | `where` | Apply mandatory filters (e.g., multi-tenancy). | `{ ctx, modelName, operation }` | `FilterObject` |
183
+ | `limit` | Set default max records for `findMany`. | `{ ctx, modelName, operation }` | `number` |
184
+ | `depthLimit` | Prevent deeply nested queries. | `{ ctx, modelName, operation }` | `number` |
185
+ | `orderBy` | Set default sort order. | `{ ctx, modelName, operation }` | `{ [col]: 'asc' \| 'desc' }` |
186
+ | `inputData` | Inject server-side values (e.g., `userId`) into mutations. | `{ ctx, modelName, operation }` | `Object` |
187
+
188
+ ### 5. Helper Functions
189
+
190
+ Import `isOperation` to simplify conditional logic within your callbacks.
191
+
192
+ ```ts
193
+ import { isOperation } from "pothos-drizzle-generator";
194
+
195
+ // Usage Example
196
+ executable: ({ ctx, operation }) => {
197
+ // Check if operation is a mutation (create/update/delete)
198
+ if (isOperation("mutation", operation)) {
199
+ return !!ctx.user;
200
+ }
201
+ return true;
202
+ },
203
+
117
204
  ```
118
205
 
119
- - example
206
+ **Available Operation Categories:**
207
+
208
+ - `OperationFind`: `findFirst`, `findMany`
209
+ - `OperationQuery`: `findFirst`, `findMany`, `count`
210
+ - `OperationCreate`: `createOne`, `createMany`
211
+ - `OperationUpdate`: `update`
212
+ - `OperationDelete`: `delete`
213
+ - `OperationMutation`: All write operations.
214
+ - `OperationAll`: Everything.
215
+
216
+ ---
217
+
218
+ ## 🛡️ Comprehensive Configuration Example
219
+
220
+ This example demonstrates a production-ready setup combining global security rules with specific model overrides.
120
221
 
121
222
  ```ts
223
+ import { isOperation } from "pothos-drizzle-generator";
224
+
122
225
  const builder = new SchemaBuilder<PothosTypes>({
123
- plugins: [
124
- DrizzlePlugin,
125
- PothosDrizzleGeneratorPlugin, // Set plugin
126
- ],
127
- drizzle: {
128
- client: () => db,
129
- relations,
130
- getTableConfig,
131
- },
226
+ // ... plugins setup
132
227
  pothosDrizzleGenerator: {
133
- // Tables not used
228
+ // 1. Exclude join tables from the schema
134
229
  use: { exclude: ["postsToCategories"] },
230
+
231
+ // 2. Global Defaults
135
232
  all: {
136
- // Maximum query depth
137
- depthLimit: () => 5,
138
- executable: ({ operation, ctx }) => {
139
- // Prohibit write operations if the user is not authenticated
140
- if (isOperation(OperationMutation, operation) && !ctx.get("user")) {
141
- return false;
142
- }
233
+ // Security: Read-only for guests, Writes for logged-in users
234
+ executable: ({ ctx, operation }) => {
235
+ if (isOperation("mutation", operation)) return !!ctx.user;
143
236
  return true;
144
237
  },
145
- inputFields: () => {
146
- // Exclude auto-generated fields
147
- return { exclude: ["createdAt", "updatedAt"] };
238
+ // Privacy: Hide sensitive fields everywhere
239
+ fields: () => ({ exclude: ["password", "secretKey"] }),
240
+ // Integrity: Protect system fields from manual input
241
+ inputFields: () => ({ exclude: ["createdAt", "updatedAt"] }),
242
+ // Logic: Filter out soft-deleted records (except when actually deleting)
243
+ where: ({ operation }) => {
244
+ if (operation !== "delete") return { deletedAt: { isNull: true } };
245
+ return {};
148
246
  },
247
+ // Performance: Default limits
248
+ limit: () => 50,
249
+ depthLimit: () => 5,
149
250
  },
251
+
252
+ // 3. Model Overrides
150
253
  models: {
254
+ users: {
255
+ // Privacy: Users see only themselves
256
+ where: ({ ctx }) => ({ id: { eq: ctx.user?.id } }),
257
+ limit: () => 1,
258
+ operations: () => ({ exclude: ["delete"] }),
259
+ },
151
260
  posts: {
152
- // Fields that cannot be overwritten
153
- // inputFields: () => ({ exclude: ["createdAt", "updatedAt"] }), // Defined in "all", so commented out
154
- // Set the current user's ID when writing data
155
- inputData: ({ ctx }) => {
156
- const user = ctx.get("user");
157
- if (!user) throw new Error("No permission");
158
- return { authorId: user.id };
159
- },
261
+ limit: () => 100,
262
+ // Automation: Attach current user as author
263
+ inputData: ({ ctx }) => ({ authorId: ctx.user?.id }),
264
+ // Logic: Public posts OR User's own posts
160
265
  where: ({ ctx, operation }) => {
161
- // When querying, only return published data or the user's own data
162
- if (isOperation(OperationQuery, operation)) {
266
+ if (isOperation("find", operation)) {
163
267
  return {
164
- OR: [
165
- { published: true },
166
- { authorId: { eq: ctx.get("user")?.id } },
167
- ],
268
+ OR: [{ published: true }, { authorId: { eq: ctx.user?.id } }],
168
269
  };
169
270
  }
170
- // When writing, only allow operations on the user's own data
171
- if (isOperation(OperationMutation, operation)) {
172
- return { authorId: ctx.get("user")?.id };
271
+ // Security: Only edit/delete own posts
272
+ if (isOperation(["update", "delete"], operation)) {
273
+ return { authorId: ctx.user?.id };
173
274
  }
174
275
  },
175
276
  },
277
+ audit_logs: {
278
+ // Security: Admin access only
279
+ executable: ({ ctx }) => !!ctx.user?.isAdmin,
280
+ },
176
281
  },
177
282
  },
178
283
  });
179
284
  ```
180
285
 
181
- # Current implementation status
182
-
183
- ## Operations
184
-
185
- - findMany
186
- - findFirst
187
- - count
188
- - create
189
- - update
190
- - delete
191
-
192
- ## Parameters
193
-
194
- - where
195
- - orderBy
196
- - offset
197
- - limit
198
-
199
- ## operators
200
-
201
- - AND
202
- - OR
203
- - NOT
204
- - eq
205
- - ne
206
- - gt
207
- - gte
208
- - lt
209
- - lte
210
- - like
211
- - notLike
212
- - ilike
213
- - notIlike
214
- - isNull
215
- - isNotNull,
216
- - in,
217
- - notIn
218
- - arrayContained
219
- - arrayOverlaps
220
- - arrayContains
286
+ ---
287
+
288
+ ## 💡 Generated Schema Capabilities
289
+
290
+ ### Optimized Data Retrieval (Solving N+1)
291
+
292
+ The generator's `findMany` operation is engineered for performance and flexibility.
293
+
294
+ - **Deep Filtering & Sorting**: You aren't limited to filtering the root node. You can apply specific `where` clauses, `limit`, and `orderBy` arguments **to any related field deep in the graph**.
295
+ - **Single Query Execution**: It consolidates fetching the main resource, related records, and counts into a **single, optimized SQL query**. This utilizes complex `JOIN` and `LATERAL` clauses to eliminate the N+1 problem.
296
+
297
+ **Example Query:**
298
+ _Fetching users and specifically only their 'published' posts._
299
+
300
+ ```graphql
301
+ query {
302
+ findManyUser {
303
+ id
304
+ name
305
+ # Filter related records directly
306
+ posts(where: { published: { eq: true } }, orderBy: { createdAt: desc }, limit: 5) {
307
+ title
308
+ createdAt
309
+ }
310
+ }
311
+ }
312
+ ```
313
+
314
+ ### Transactional Mutations
315
+
316
+ Write operations ensure data integrity through automatic transaction wrapping.
317
+
318
+ - **Atomic Operations**: When creating a record with related data (e.g., a Post with Categories), the entire process runs within a database transaction (`BEGIN` ... `COMMIT`).
319
+ - **Consistency**: If any part of the operation fails (e.g., inserting a relation), the entire action is rolled back, preventing orphaned data.
320
+
321
+ **Example Mutation:**
322
+
323
+ ```graphql
324
+ mutation {
325
+ createOnePost(
326
+ input: {
327
+ title: "My New Post"
328
+ content: "Hello World"
329
+ # Handles many-to-many relation automatically
330
+ categories: { set: [{ id: "cat-1" }, { id: "cat-2" }] }
331
+ }
332
+ ) {
333
+ id
334
+ categories {
335
+ name
336
+ }
337
+ }
338
+ }
339
+ ```
340
+
341
+ ---
342
+
343
+ ## 🔍 Supported Features Checklist
344
+
345
+ ### Operations
346
+
347
+ - **Queries**: `findMany`, `findFirst`, `count`
348
+ - **Mutations**: `create`, `update`, `delete`
349
+
350
+ ### Advanced Filtering (`where`)
351
+
352
+ - **Logical**: `AND`, `OR`, `NOT`
353
+ - **Comparators**: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`
354
+ - **Existence**: `isNull`, `isNotNull`
355
+ - **String Matching**: `like`, `notLike`, `ilike`, `notIlike`
356
+ - **Array Operations**: `arrayContained`, `arrayOverlaps`, `arrayContains`
357
+
358
+ ## License
359
+
360
+ MIT
@@ -172,7 +172,7 @@ class PothosDrizzleGenerator extends core_1.BasePlugin {
172
172
  limit: limit?.({ modelName, ctx, operation }),
173
173
  where: where?.({ modelName, ctx, operation }),
174
174
  orderBy: orderBy?.({ modelName, ctx, operation }),
175
- input: (0, operations_js_1.isOperation)(operations_js_1.OperationMutation, operation)
175
+ input: (0, operations_js_1.isOperation)("mutation", operation)
176
176
  ? inputData?.({ modelName, ctx, operation })
177
177
  : undefined,
178
178
  };
@@ -347,7 +347,7 @@ class PothosDrizzleGenerator extends core_1.BasePlugin {
347
347
  return client
348
348
  .insert(table)
349
349
  .values(combinedInputs)
350
- .then((v) => Array(v.rowCount ?? 0).fill({}));
350
+ .then((v) => Array(v.rowCount).fill({}));
351
351
  }
352
352
  return client.transaction(async (tx) => tx
353
353
  .insert(table)
@@ -400,7 +400,7 @@ class PothosDrizzleGenerator extends core_1.BasePlugin {
400
400
  .update(table)
401
401
  .set(combinedInput)
402
402
  .where(whereQuery)
403
- .then((v) => Array(v.rowCount ?? 0).fill({}));
403
+ .then((v) => Array(v.rowCount).fill({}));
404
404
  }
405
405
  return client.transaction(async (tx) => tx
406
406
  .update(table)
@@ -492,7 +492,7 @@ class PothosDrizzleGenerator extends core_1.BasePlugin {
492
492
  .getClient(ctx)
493
493
  .delete(table)
494
494
  .where(whereQuery)
495
- .then((v) => Array(v.rowCount ?? 0).fill({}));
495
+ .then((v) => Array(v.rowCount).fill({}));
496
496
  },
497
497
  }),
498
498
  }),