@idealyst/cli 1.0.91 → 1.0.93

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.
Files changed (71) hide show
  1. package/dist/generators/init.js +8 -13
  2. package/dist/generators/init.js.map +1 -1
  3. package/dist/generators/utils.js +3 -7
  4. package/dist/generators/utils.js.map +1 -1
  5. package/dist/template/.devcontainer/gitignore.template +2 -0
  6. package/dist/template/gitignore.template +56 -0
  7. package/dist/template/mcp.json.template +8 -0
  8. package/dist/template/packages/api/README.md +400 -164
  9. package/dist/template/packages/api/gitignore.template +35 -0
  10. package/dist/template/packages/api/package.json +11 -1
  11. package/dist/template/packages/api/src/context.ts +35 -2
  12. package/dist/template/packages/api/src/graphql/builder.ts +75 -0
  13. package/dist/template/packages/api/src/graphql/generated.ts +64 -0
  14. package/dist/template/packages/api/src/graphql/index.ts +75 -0
  15. package/dist/template/packages/api/src/graphql/types/index.ts +44 -0
  16. package/dist/template/packages/api/src/graphql/types/test.ts +245 -0
  17. package/dist/template/packages/api/src/index.ts +20 -3
  18. package/dist/template/packages/api/src/lib/database.ts +1 -1
  19. package/dist/template/packages/api/src/routers/test.ts +140 -38
  20. package/dist/template/packages/api/src/server.ts +23 -5
  21. package/dist/template/packages/api/tsconfig.json +1 -0
  22. package/dist/template/packages/database/gitignore.template +41 -0
  23. package/dist/template/packages/mobile/babel.config.js +1 -2
  24. package/dist/template/packages/mobile/gitignore.template +73 -0
  25. package/dist/template/packages/mobile/package.json +10 -2
  26. package/dist/template/packages/shared/gitignore.template +35 -0
  27. package/dist/template/packages/shared/package.json +6 -0
  28. package/dist/template/packages/shared/src/components/App.tsx +13 -2
  29. package/dist/template/packages/shared/src/components/HelloWorld.tsx +333 -106
  30. package/dist/template/packages/shared/src/graphql/client.ts +34 -0
  31. package/dist/template/packages/shared/src/index.ts +8 -0
  32. package/dist/template/packages/web/gitignore.template +35 -0
  33. package/dist/template/packages/web/vite.config.ts +2 -2
  34. package/dist/template/yarnrc.yml.template +4 -0
  35. package/package.json +1 -1
  36. package/template/packages/api/README.md +400 -164
  37. package/template/packages/api/package.json +11 -1
  38. package/template/packages/api/src/context.ts +35 -2
  39. package/template/packages/api/src/graphql/builder.ts +75 -0
  40. package/template/packages/api/src/graphql/generated.ts +64 -0
  41. package/template/packages/api/src/graphql/index.ts +75 -0
  42. package/template/packages/api/src/graphql/types/index.ts +44 -0
  43. package/template/packages/api/src/graphql/types/test.ts +245 -0
  44. package/template/packages/api/src/index.ts +20 -3
  45. package/template/packages/api/src/lib/database.ts +1 -1
  46. package/template/packages/api/src/routers/test.ts +140 -38
  47. package/template/packages/api/src/server.ts +23 -5
  48. package/template/packages/api/tsconfig.json +1 -0
  49. package/template/packages/mobile/babel.config.js +1 -2
  50. package/template/packages/mobile/package.json +10 -2
  51. package/template/packages/shared/package.json +6 -0
  52. package/template/packages/shared/src/components/App.tsx +13 -2
  53. package/template/packages/shared/src/components/HelloWorld.tsx +333 -106
  54. package/template/packages/shared/src/graphql/client.ts +34 -0
  55. package/template/packages/shared/src/index.ts +8 -0
  56. package/template/packages/web/vite.config.ts +2 -2
  57. package/dist/template/packages/api/src/lib/crud.ts +0 -150
  58. package/dist/template/packages/api/src/routers/user.example.ts +0 -83
  59. package/dist/template/packages/mobile/src/App-with-trpc-and-shared.tsx +0 -8
  60. package/dist/template/packages/mobile/src/App-with-trpc.tsx +0 -30
  61. package/dist/template/packages/web/src/App-with-trpc-and-shared.tsx +0 -14
  62. package/dist/template/packages/web/src/App-with-trpc.tsx +0 -32
  63. package/template/packages/api/src/lib/crud.ts +0 -150
  64. package/template/packages/api/src/routers/user.example.ts +0 -83
  65. package/template/packages/mobile/src/App-with-trpc-and-shared.tsx +0 -8
  66. package/template/packages/mobile/src/App-with-trpc.tsx +0 -30
  67. package/template/packages/web/src/App-with-trpc-and-shared.tsx +0 -14
  68. package/template/packages/web/src/App-with-trpc.tsx +0 -32
  69. /package/dist/template/{.dockerignore → dockerignore.template} +0 -0
  70. /package/dist/template/{.env.example → env.example.template} +0 -0
  71. /package/dist/template/packages/api/{.env.example → env.example.template} +0 -0
@@ -2,33 +2,45 @@
2
2
 
3
3
  {{description}}
4
4
 
5
- A simplified tRPC API with automatic CRUD generation for Prisma models.
5
+ A dual-API backend with both **tRPC** (for internal TypeScript clients) and **GraphQL** (for public APIs and mobile apps).
6
6
 
7
- This API project is built with:
8
- - **tRPC** - End-to-end```typescript
9
- import { z } from 'zod';
10
- import { router, publicProcedure } from '../trpc.js';
11
- import { createCrudRouter } from '../lib/crud.js';
12
- import { prisma } from '../lib/database.js';
7
+ ## Tech Stack
13
8
 
14
- const baseCrudRouter = createCrudRouter('user', createUserSchema);
15
-
16
- export const userRouter = router({
17
- ...baseCrudRouter,
18
-
19
- // Add custom procedures
20
- getByEmail: publicProcedure
21
- .input(z.object({ email: z.string().email() }))
22
- .query(async ({ input }) => {
23
- return await prisma.user.findUnique({
24
- where: { email: input.email }
25
- });
26
- }),
27
- });
28
- ```Zod** - TypeScript-first schema validation
9
+ - **tRPC** - End-to-end type-safe APIs for TypeScript clients
10
+ - **GraphQL + Pothos** - Code-first GraphQL schema with Prisma integration
11
+ - **GraphQL Yoga** - Modern, performant GraphQL server
12
+ - **Zod** - TypeScript-first schema validation
29
13
  - **Express.js** - Web framework for Node.js
30
- - **TypeScript** - Type-safe JavaScript
31
14
  - **Prisma** - Next-generation ORM for database access
15
+ - **TypeScript** - Type-safe JavaScript
16
+
17
+ ## API Architecture
18
+
19
+ ```
20
+ ┌─────────────────────────────────────────────────┐
21
+ │ Single Express Server │
22
+ │ (port 3000) │
23
+ ├─────────────────────────────────────────────────┤
24
+ │ │
25
+ │ GET /health → Health check │
26
+ │ POST /trpc/* → tRPC procedures │
27
+ │ POST /graphql → GraphQL queries │
28
+ │ GET /graphql → GraphiQL Playground │
29
+ │ │
30
+ └─────────────────────────────────────────────────┘
31
+ ```
32
+
33
+ ### When to Use Each
34
+
35
+ | Use Case | Recommended API |
36
+ |----------|----------------|
37
+ | Internal web app (TypeScript) | tRPC |
38
+ | Mobile apps (React Native, iOS, Android) | GraphQL |
39
+ | Third-party integrations | GraphQL |
40
+ | Admin dashboards | tRPC |
41
+ | Auth flows (login, register) | tRPC |
42
+ | Public data queries | GraphQL |
43
+ | File uploads | tRPC |
32
44
 
33
45
  ## Quick Start
34
46
 
@@ -44,14 +56,14 @@ export const userRouter = router({
44
56
 
45
57
  3. **Setup database:**
46
58
  ```bash
47
- # Generate Prisma client
48
- yarn prisma:generate
49
-
59
+ # Generate Prisma client and Pothos types
60
+ yarn generate
61
+
50
62
  # Run migrations
51
- yarn prisma:migrate
52
-
63
+ cd ../database && yarn db:migrate
64
+
53
65
  # Seed database with sample data
54
- yarn prisma:seed
66
+ yarn db:seed
55
67
  ```
56
68
 
57
69
  4. **Start development server:**
@@ -59,216 +71,440 @@ export const userRouter = router({
59
71
  yarn dev
60
72
  ```
61
73
 
62
- The API will be available at `http://localhost:3000`
74
+ The API will be available at:
75
+ - **tRPC**: `http://localhost:3000/trpc`
76
+ - **GraphQL**: `http://localhost:3000/graphql`
77
+ - **GraphiQL Playground**: `http://localhost:3000/graphql` (in development)
78
+
79
+ ## Test API - tRPC vs GraphQL Comparison
80
+
81
+ The template includes a `Test` model with equivalent implementations in both tRPC and GraphQL, demonstrating how the same functionality can be exposed through both APIs.
82
+
83
+ ### API Equivalence Table
84
+
85
+ | Operation | tRPC | GraphQL |
86
+ |-----------|------|---------|
87
+ | Get all | `trpc.test.getAll({ skip, take })` | `query { tests(skip, take) { ... } }` |
88
+ | Get by ID | `trpc.test.getById({ id })` | `query { test(id) { ... } }` |
89
+ | Create | `trpc.test.create({ name, message })` | `mutation { createTest(input) { ... } }` |
90
+ | Update | `trpc.test.update({ id, data })` | `mutation { updateTest(id, input) { ... } }` |
91
+ | Delete | `trpc.test.delete({ id })` | `mutation { deleteTest(id) { ... } }` |
92
+ | Count | `trpc.test.count({})` | `query { testCount }` |
93
+
94
+ ### tRPC Usage (TypeScript Client)
95
+
96
+ ```typescript
97
+ import { trpc } from './utils/trpc';
98
+
99
+ // Get all tests
100
+ const { data: tests } = trpc.test.getAll.useQuery({ take: 10 });
101
+
102
+ // Get test by ID
103
+ const { data: test } = trpc.test.getById.useQuery({ id: 'test-id' });
104
+
105
+ // Create a new test
106
+ const createTest = trpc.test.create.useMutation();
107
+ await createTest.mutateAsync({
108
+ name: 'My Test',
109
+ message: 'Hello World',
110
+ status: 'active',
111
+ });
112
+
113
+ // Update a test
114
+ const updateTest = trpc.test.update.useMutation();
115
+ await updateTest.mutateAsync({
116
+ id: 'test-id',
117
+ data: { name: 'Updated Name' },
118
+ });
119
+
120
+ // Delete a test
121
+ const deleteTest = trpc.test.delete.useMutation();
122
+ await deleteTest.mutateAsync({ id: 'test-id' });
123
+
124
+ // Get count
125
+ const { data: count } = trpc.test.count.useQuery({});
126
+ ```
127
+
128
+ ### GraphQL Usage
129
+
130
+ Visit `http://localhost:3000/graphql` to access the GraphiQL playground.
131
+
132
+ ```graphql
133
+ # Get all tests (equivalent to trpc.test.getAll)
134
+ query GetTests {
135
+ tests(take: 10) {
136
+ id
137
+ name
138
+ message
139
+ status
140
+ createdAt
141
+ }
142
+ }
63
143
 
64
- ## Creating CRUD APIs
144
+ # Get test by ID (equivalent to trpc.test.getById)
145
+ query GetTest($id: String!) {
146
+ test(id: $id) {
147
+ id
148
+ name
149
+ message
150
+ status
151
+ }
152
+ }
65
153
 
66
- This template provides a simple way to create type-safe APIs with automatic CRUD operations for your Prisma models.
154
+ # Create a new test (equivalent to trpc.test.create)
155
+ mutation CreateTest {
156
+ createTest(input: {
157
+ name: "My Test"
158
+ message: "Hello World"
159
+ status: "active"
160
+ }) {
161
+ id
162
+ name
163
+ message
164
+ }
165
+ }
67
166
 
68
- ### 1. Define Your Prisma Model
167
+ # Update a test (equivalent to trpc.test.update)
168
+ mutation UpdateTest($id: String!) {
169
+ updateTest(id: $id, input: { name: "Updated Name" }) {
170
+ id
171
+ name
172
+ }
173
+ }
69
174
 
70
- Add models to `packages/database/schema.prisma`:
175
+ # Delete a test (equivalent to trpc.test.delete)
176
+ mutation DeleteTest($id: String!) {
177
+ deleteTest(id: $id) {
178
+ id
179
+ }
180
+ }
181
+
182
+ # Get count (equivalent to trpc.test.count)
183
+ query GetTestCount {
184
+ testCount
185
+ }
186
+ ```
187
+
188
+ ### GraphQL with React/Apollo Client
189
+
190
+ ```typescript
191
+ import { useQuery, useMutation, gql } from '@apollo/client';
192
+
193
+ const GET_TESTS = gql`
194
+ query GetTests($take: Int) {
195
+ tests(take: $take) {
196
+ id
197
+ name
198
+ message
199
+ status
200
+ }
201
+ }
202
+ `;
203
+
204
+ const CREATE_TEST = gql`
205
+ mutation CreateTest($input: CreateTestInput!) {
206
+ createTest(input: $input) {
207
+ id
208
+ name
209
+ }
210
+ }
211
+ `;
212
+
213
+ // In your component
214
+ function TestList() {
215
+ const { data, loading } = useQuery(GET_TESTS, { variables: { take: 10 } });
216
+ const [createTest] = useMutation(CREATE_TEST);
217
+
218
+ const handleCreate = async () => {
219
+ await createTest({
220
+ variables: {
221
+ input: { name: 'New Test', message: 'Hello!' },
222
+ },
223
+ });
224
+ };
225
+
226
+ return (/* ... */);
227
+ }
228
+ ```
229
+
230
+ ## Adding New Models
231
+
232
+ ### Step 1: Define Prisma Model
233
+
234
+ Add to `packages/database/prisma/schema.prisma`:
71
235
 
72
236
  ```prisma
73
- model User {
237
+ model Product {
74
238
  id String @id @default(cuid())
75
- email String @unique
76
239
  name String
240
+ price Float
77
241
  createdAt DateTime @default(now())
78
242
  updatedAt DateTime @updatedAt
79
243
  }
80
244
  ```
81
245
 
82
- ### 2. Create a Router
246
+ ### Step 2: Create tRPC Router
83
247
 
84
- Create `src/routers/user.ts`:
248
+ Create `src/routers/product.ts`:
85
249
 
86
250
  ```typescript
87
251
  import { z } from 'zod';
88
252
  import { createCrudRouter } from '../lib/crud.js';
89
253
 
90
- const createUserSchema = z.object({
91
- email: z.string().email(),
254
+ const createProductSchema = z.object({
92
255
  name: z.string().min(1),
256
+ price: z.number().positive(),
93
257
  });
94
258
 
95
- const updateUserSchema = z.object({
96
- email: z.string().email().optional(),
97
- name: z.string().min(1).optional(),
98
- });
99
-
100
- export const userRouter = createCrudRouter(
101
- 'user',
102
- createUserSchema,
103
- updateUserSchema
104
- );
259
+ export const productRouter = createCrudRouter('Product', createProductSchema);
105
260
  ```
106
261
 
107
- ### 3. Add to Main Router
108
-
109
- Update `src/router/index.ts`:
262
+ Add to `src/router/index.ts`:
110
263
 
111
264
  ```typescript
112
- import { userRouter } from '../routers/user.js';
265
+ import { productRouter } from '../routers/product.js';
113
266
 
114
267
  export const appRouter = router({
115
- // ... existing routes
116
- users: userRouter,
268
+ test: testRouter,
269
+ product: productRouter, // Add here
117
270
  });
118
271
  ```
119
272
 
120
- ### 4. Use in Frontend
273
+ ### Step 3: Create GraphQL Type
274
+
275
+ Create `src/graphql/types/product.ts`:
121
276
 
122
277
  ```typescript
123
- // Get all users
124
- const { data: users } = trpc.users.getAll.useQuery();
125
-
126
- // Create user
127
- const createUser = trpc.users.create.useMutation();
128
- await createUser.mutateAsync({
129
- email: 'user@example.com',
130
- name: 'John Doe'
278
+ import { builder } from '../builder.js';
279
+ import { prisma } from '../../lib/database.js';
280
+
281
+ builder.prismaObject('Product', {
282
+ fields: (t) => ({
283
+ id: t.exposeID('id'),
284
+ name: t.exposeString('name'),
285
+ price: t.exposeFloat('price'),
286
+ createdAt: t.expose('createdAt', { type: 'DateTime' }),
287
+ }),
131
288
  });
132
289
 
133
- // Update user
134
- const updateUser = trpc.users.update.useMutation();
135
- await updateUser.mutateAsync({
136
- id: 'user-id',
137
- data: { name: 'Jane Doe' }
290
+ builder.queryFields((t) => ({
291
+ products: t.prismaField({
292
+ type: ['Product'],
293
+ args: {
294
+ skip: t.arg.int(),
295
+ take: t.arg.int(),
296
+ },
297
+ resolve: async (query, _root, args) => {
298
+ return prisma.product.findMany({
299
+ ...query,
300
+ skip: args.skip ?? undefined,
301
+ take: args.take ?? 10,
302
+ });
303
+ },
304
+ }),
305
+ }));
306
+
307
+ const CreateProductInput = builder.inputType('CreateProductInput', {
308
+ fields: (t) => ({
309
+ name: t.string({ required: true }),
310
+ price: t.float({ required: true }),
311
+ }),
138
312
  });
139
313
 
140
- // Delete user
141
- const deleteUser = trpc.users.delete.useMutation();
142
- await deleteUser.mutateAsync({ id: 'user-id' });
314
+ builder.mutationFields((t) => ({
315
+ createProduct: t.prismaField({
316
+ type: 'Product',
317
+ args: {
318
+ input: t.arg({ type: CreateProductInput, required: true }),
319
+ },
320
+ resolve: async (query, _root, args) => {
321
+ return prisma.product.create({
322
+ ...query,
323
+ data: args.input,
324
+ });
325
+ },
326
+ }),
327
+ }));
143
328
  ```
144
329
 
145
- ## Generated CRUD Operations
330
+ Register in `src/graphql/types/index.ts`:
146
331
 
147
- Each `createCrudRouter` call generates:
148
-
149
- - `getAll({ skip?, take?, orderBy? })` - List with pagination
150
- - `getById({ id })` - Get single record
151
- - `create(data)` - Create new record
152
- - `update({ id, data })` - Update existing record
153
- - `delete({ id })` - Delete record
154
- - `count({ where? })` - Count records
332
+ ```typescript
333
+ import './product.js';
334
+ ```
155
335
 
156
- ## Available Scripts
336
+ ### Step 4: Regenerate Types
157
337
 
158
- - `yarn dev` - Start development server with hot reload
159
- - `yarn build` - Build production bundle
160
- - `yarn start` - Start production server
161
- - `yarn test` - Run tests
162
- - `yarn lint` - Lint code
163
- - `yarn type-check` - Check TypeScript types
338
+ ```bash
339
+ yarn generate
340
+ ```
164
341
 
165
342
  ## Project Structure
166
343
 
167
344
  ```
168
345
  src/
346
+ ├── graphql/
347
+ │ ├── builder.ts # Pothos schema builder setup
348
+ │ ├── generated.ts # Auto-generated Prisma types
349
+ │ ├── index.ts # GraphQL Yoga server
350
+ │ └── types/
351
+ │ ├── index.ts # Type registration
352
+ │ ├── test.ts # Test model (active)
353
+ │ ├── user.ts # User model (example)
354
+ │ ├── post.ts # Post model (example)
355
+ │ └── comment.ts # Comment model (example)
169
356
  ├── router/
170
- │ └── index.ts # Main router definition
357
+ │ └── index.ts # Main tRPC router
171
358
  ├── routers/
172
- │ ├── test.ts # Example CRUD router for Test model
173
- │ └── user.example.ts # Example CRUD router for User model
359
+ │ ├── test.ts # Test tRPC CRUD router
360
+ │ └── user.example.ts # User tRPC example
174
361
  ├── lib/
175
- │ ├── crud.ts # CRUD router generator
176
- │ └── database.ts # Database connection
177
- ├── context.ts # tRPC context
178
- ├── trpc.ts # tRPC setup
179
- └── server.ts # Express server setup
362
+ │ ├── crud.ts # tRPC CRUD generator
363
+ │ └── database.ts # Prisma client
364
+ ├── context.ts # Shared context
365
+ ├── trpc.ts # tRPC setup
366
+ ├── index.ts # Exports
367
+ └── server.ts # Express server
180
368
  ```
181
369
 
182
- ## Advanced Usage
183
-
184
- ### Custom Procedures
185
-
186
- Extend generated routers with custom procedures:
187
-
188
- ```typescript
189
- import { router, publicProcedure } from '../trpc.js';
190
- import { createCrudRouter } from '../lib/crud.js';
191
-
192
- const baseCrudRouter = createCrudRouter('user', createUserSchema);
193
-
194
- export const userRouter = router({
195
- ...baseCrudRouter,
196
-
197
- // Add custom procedures
198
- getByEmail: publicProcedure
199
- .input(z.object({ email: z.string().email() }))
200
- .query(async ({ input }) => {
201
- return await db.user.findUnique({
202
- where: { email: input.email }
203
- });
204
- }),
205
- });
206
- ```
207
-
208
- ### Authentication
209
-
210
- For protected routes, you can add authentication middleware to the tRPC setup or create protected procedures:
211
-
212
- ```typescript
213
- import { protectedProcedure } from '../trpc.js';
214
-
215
- // Use protectedProcedure instead of publicProcedure in your CRUD router
216
- ```
217
-
218
- ## Development
219
-
220
- 1. **Add New Model**:
221
- - Add to Prisma schema
222
- - Run `yarn db:migrate`
223
- - Create router with `createCrudRouter`
224
- - Add to main router
225
-
226
- 2. **Test API**:
227
- - Start development: `yarn dev`
228
- - API available at `http://localhost:3000/trpc`
229
-
230
- 3. **Type Safety**:
231
- - All operations are fully type-safe
232
- - Frontend gets autocomplete and validation
233
- - Schemas ensure data integrity
370
+ ## Available Scripts
234
371
 
235
- This simplified approach removes controller complexity while maintaining full type safety and providing powerful CRUD operations for all your Prisma models.
372
+ | Script | Description |
373
+ |--------|-------------|
374
+ | `yarn dev` | Start development server with hot reload |
375
+ | `yarn build` | Build production bundle |
376
+ | `yarn start` | Start production server |
377
+ | `yarn generate` | Generate Prisma client and Pothos types |
378
+ | `yarn test` | Run tests |
379
+ | `yarn lint` | Lint code |
380
+ | `yarn type-check` | Check TypeScript types |
236
381
 
237
382
  ## Environment Variables
238
383
 
239
- Create a `.env` file with:
240
-
241
384
  ```env
242
385
  # Database
243
- DATABASE_URL="file:./dev.db"
386
+ DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
244
387
 
245
388
  # API
246
389
  PORT=3000
247
390
  NODE_ENV=development
248
-
249
- # Add your environment variables here
391
+ CORS_ORIGIN=http://localhost:5173
250
392
  ```
251
393
 
252
394
  ## Testing
253
395
 
254
- The project includes Jest for testing:
396
+ ### Testing tRPC
255
397
 
256
398
  ```typescript
257
399
  import { createContext } from '../src/context.js';
258
400
  import { appRouter } from '../src/router/index.js';
259
401
 
260
- describe('API Tests', () => {
261
- test('should get test records', async () => {
402
+ describe('tRPC Test API', () => {
403
+ test('should get all tests', async () => {
262
404
  const ctx = createContext({} as any);
263
405
  const caller = appRouter.createCaller(ctx);
264
-
265
- const tests = await caller.test.getAll({});
406
+
407
+ const tests = await caller.test.getAll({ take: 10 });
266
408
  expect(tests).toBeDefined();
267
409
  });
410
+
411
+ test('should create a test', async () => {
412
+ const ctx = createContext({} as any);
413
+ const caller = appRouter.createCaller(ctx);
414
+
415
+ const test = await caller.test.create({
416
+ name: 'Test Name',
417
+ message: 'Test Message',
418
+ });
419
+ expect(test.id).toBeDefined();
420
+ expect(test.name).toBe('Test Name');
421
+ });
268
422
  });
269
423
  ```
270
424
 
271
- Run tests with:
272
- ```bash
273
- yarn test
274
- ```
425
+ ### Testing GraphQL
426
+
427
+ ```typescript
428
+ import { schema } from '../src/graphql/index.js';
429
+ import { graphql } from 'graphql';
430
+
431
+ describe('GraphQL Test API', () => {
432
+ test('should query tests', async () => {
433
+ const query = `
434
+ query {
435
+ tests(take: 5) {
436
+ id
437
+ name
438
+ message
439
+ }
440
+ }
441
+ `;
442
+
443
+ const result = await graphql({ schema, source: query });
444
+ expect(result.errors).toBeUndefined();
445
+ expect(result.data?.tests).toBeDefined();
446
+ });
447
+
448
+ test('should create a test', async () => {
449
+ const mutation = `
450
+ mutation {
451
+ createTest(input: {
452
+ name: "Test Name"
453
+ message: "Test Message"
454
+ }) {
455
+ id
456
+ name
457
+ }
458
+ }
459
+ `;
460
+
461
+ const result = await graphql({ schema, source: mutation });
462
+ expect(result.errors).toBeUndefined();
463
+ expect(result.data?.createTest.name).toBe('Test Name');
464
+ });
465
+ });
466
+ ```
467
+
468
+ ## Advanced: Shared Service Layer
469
+
470
+ For complex business logic, use a shared service layer:
471
+
472
+ ```typescript
473
+ // src/services/test.service.ts
474
+ import { prisma } from '../lib/database.js';
475
+
476
+ export const testService = {
477
+ async getAll(options: { skip?: number; take?: number }) {
478
+ return prisma.test.findMany({
479
+ skip: options.skip,
480
+ take: options.take ?? 10,
481
+ orderBy: { createdAt: 'desc' },
482
+ });
483
+ },
484
+
485
+ async create(data: { name: string; message: string; status?: string }) {
486
+ // Add business logic, validation, etc.
487
+ return prisma.test.create({ data });
488
+ },
489
+ };
490
+
491
+ // Use in tRPC (src/routers/test.ts)
492
+ import { testService } from '../services/test.service.js';
493
+
494
+ export const testRouter = router({
495
+ getAll: publicProcedure
496
+ .input(z.object({ skip: z.number().optional(), take: z.number().optional() }))
497
+ .query(({ input }) => testService.getAll(input)),
498
+ });
499
+
500
+ // Use in GraphQL (src/graphql/types/test.ts)
501
+ builder.queryFields((t) => ({
502
+ tests: t.field({
503
+ type: [TestType],
504
+ args: { skip: t.arg.int(), take: t.arg.int() },
505
+ resolve: (_, args) => testService.getAll(args),
506
+ }),
507
+ }));
508
+ ```
509
+
510
+ This ensures consistent business logic across both APIs.
@@ -20,15 +20,23 @@
20
20
  "test:coverage": "jest --coverage",
21
21
  "lint": "eslint src --ext .ts,.tsx",
22
22
  "lint:fix": "eslint src --ext .ts,.tsx --fix",
23
- "type-check": "tsc --noEmit"
23
+ "type-check": "tsc --noEmit",
24
+ "generate": "prisma generate && yarn generate:pothos",
25
+ "generate:pothos": "tsx scripts/generate-pothos-types.ts"
24
26
  },
25
27
  "dependencies": {
26
28
  "@{{workspaceScope}}/database": "workspace:*",
27
29
  "@{{workspaceScope}}/shared": "workspace:*",
30
+ "@pothos/core": "^4.10.0",
31
+ "@pothos/plugin-prisma": "^4.12.0",
32
+ "@pothos/plugin-relay": "^4.6.2",
33
+ "@pothos/plugin-validation": "^4.2.0",
28
34
  "@trpc/server": "^11.5.1",
29
35
  "cors": "^2.8.5",
30
36
  "dotenv": "^16.3.1",
31
37
  "express": "^4.18.2",
38
+ "graphql": "^16.9.0",
39
+ "graphql-yoga": "^5.10.6",
32
40
  "zod": "^3.22.4"
33
41
  },
34
42
  "devDependencies": {
@@ -47,6 +55,8 @@
47
55
  "keywords": [
48
56
  "api",
49
57
  "trpc",
58
+ "graphql",
59
+ "pothos",
50
60
  "zod",
51
61
  "typescript",
52
62
  "express"