@idealyst/cli 1.0.90 → 1.0.91
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/package.json +2 -2
- package/template/.devcontainer/Dockerfile +26 -0
- package/template/.devcontainer/devcontainer.json +113 -0
- package/template/.devcontainer/docker-compose.yml +59 -0
- package/template/.devcontainer/figma-mcp.sh +32 -0
- package/template/.devcontainer/setup.sh +45 -0
- package/template/.dockerignore +151 -0
- package/template/.env.example +36 -0
- package/template/.env.production +56 -0
- package/template/DOCKER.md +0 -0
- package/template/Dockerfile +111 -0
- package/template/README.md +233 -0
- package/template/docker/nginx/prod.conf +238 -0
- package/template/docker/nginx.conf +131 -0
- package/template/docker/postgres/init.sql +41 -0
- package/template/docker/prometheus/prometheus.yml +52 -0
- package/template/docker-compose.prod.yml +146 -0
- package/template/docker-compose.yml +143 -0
- package/template/jest.config.js +20 -0
- package/template/package.json +45 -0
- package/template/packages/api/.env.example +6 -0
- package/template/packages/api/README.md +274 -0
- package/template/packages/api/__tests__/api.test.ts +26 -0
- package/template/packages/api/jest.config.js +23 -0
- package/template/packages/api/jest.setup.js +9 -0
- package/template/packages/api/package.json +56 -0
- package/template/packages/api/src/context.ts +19 -0
- package/template/packages/api/src/controllers/TestController.ts +0 -0
- package/template/packages/api/src/index.ts +9 -0
- package/template/packages/api/src/lib/crud.ts +150 -0
- package/template/packages/api/src/lib/database.ts +23 -0
- package/template/packages/api/src/router/index.ts +163 -0
- package/template/packages/api/src/routers/test.ts +59 -0
- package/template/packages/api/src/routers/user.example.ts +83 -0
- package/template/packages/api/src/server.ts +50 -0
- package/template/packages/api/src/trpc.ts +28 -0
- package/template/packages/api/tsconfig.json +43 -0
- package/template/packages/database/README.md +162 -0
- package/template/packages/database/package.json +49 -0
- package/template/packages/database/prisma/seed.ts +64 -0
- package/template/packages/database/schema.prisma +107 -0
- package/template/packages/database/src/index.ts +15 -0
- package/template/packages/database/src/validators.ts +10 -0
- package/template/packages/database/tsconfig.json +18 -0
- package/template/packages/mobile/README.md +86 -0
- package/template/packages/mobile/__tests__/App.test.tsx +156 -0
- package/template/packages/mobile/__tests__/components.test.tsx +300 -0
- package/template/packages/mobile/app.json +5 -0
- package/template/packages/mobile/babel.config.js +11 -0
- package/template/packages/mobile/index.js +6 -0
- package/template/packages/mobile/jest.config.js +21 -0
- package/template/packages/mobile/jest.setup.js +12 -0
- package/template/packages/mobile/metro.config.js +27 -0
- package/template/packages/mobile/package.json +52 -0
- package/template/packages/mobile/src/App-with-trpc-and-shared.tsx +8 -0
- package/template/packages/mobile/src/App-with-trpc.tsx +30 -0
- package/template/packages/mobile/src/App.tsx +8 -0
- package/template/packages/mobile/src/utils/trpc.ts +7 -0
- package/template/packages/mobile/tsconfig.json +28 -0
- package/template/packages/shared/README.md +135 -0
- package/template/packages/shared/__tests__/shared.test.ts +51 -0
- package/template/packages/shared/jest.config.js +22 -0
- package/template/packages/shared/package.json +62 -0
- package/template/packages/shared/src/components/App.tsx +46 -0
- package/template/packages/shared/src/components/HelloWorld.tsx +304 -0
- package/template/packages/shared/src/components/index.ts +1 -0
- package/template/packages/shared/src/index.ts +14 -0
- package/template/packages/shared/src/navigation/AppRouter.tsx +565 -0
- package/template/packages/shared/src/trpc/client.ts +44 -0
- package/template/packages/shared/tsconfig.json +22 -0
- package/template/packages/web/README.md +131 -0
- package/template/packages/web/__tests__/App.test.tsx +342 -0
- package/template/packages/web/__tests__/components.test.tsx +564 -0
- package/template/packages/web/index.html +13 -0
- package/template/packages/web/jest.config.js +27 -0
- package/template/packages/web/jest.setup.js +24 -0
- package/template/packages/web/package.json +69 -0
- package/template/packages/web/src/App-with-trpc-and-shared.tsx +14 -0
- package/template/packages/web/src/App-with-trpc.tsx +32 -0
- package/template/packages/web/src/App.tsx +14 -0
- package/template/packages/web/src/components/TestDemo.tsx +164 -0
- package/template/packages/web/src/main.tsx +25 -0
- package/template/packages/web/src/utils/trpc.ts +7 -0
- package/template/packages/web/tsconfig.json +26 -0
- package/template/packages/web/vite.config.ts +98 -0
- package/template/setup.sh +30 -0
- package/template/tsconfig.json +31 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{workspaceScope}}/api",
|
|
3
|
+
"version": "{{version}}",
|
|
4
|
+
"description": "{{description}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"import": "./src/index.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsx watch src/server.ts",
|
|
17
|
+
"start": "node dist/server.js",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:watch": "jest --watch",
|
|
20
|
+
"test:coverage": "jest --coverage",
|
|
21
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
22
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
23
|
+
"type-check": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@{{workspaceScope}}/database": "workspace:*",
|
|
27
|
+
"@{{workspaceScope}}/shared": "workspace:*",
|
|
28
|
+
"@trpc/server": "^11.5.1",
|
|
29
|
+
"cors": "^2.8.5",
|
|
30
|
+
"dotenv": "^16.3.1",
|
|
31
|
+
"express": "^4.18.2",
|
|
32
|
+
"zod": "^3.22.4"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/cors": "^2.8.17",
|
|
36
|
+
"@types/express": "^4.17.21",
|
|
37
|
+
"@types/jest": "^29.5.12",
|
|
38
|
+
"@types/node": "^20.10.4",
|
|
39
|
+
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
|
40
|
+
"@typescript-eslint/parser": "^6.13.1",
|
|
41
|
+
"eslint": "^8.54.0",
|
|
42
|
+
"jest": "^29.7.0",
|
|
43
|
+
"ts-jest": "^29.1.2",
|
|
44
|
+
"tsx": "^4.6.2",
|
|
45
|
+
"typescript": "^5.3.3"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"api",
|
|
49
|
+
"trpc",
|
|
50
|
+
"zod",
|
|
51
|
+
"typescript",
|
|
52
|
+
"express"
|
|
53
|
+
],
|
|
54
|
+
"author": "",
|
|
55
|
+
"license": "MIT"
|
|
56
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CreateExpressContextOptions } from '@trpc/server/adapters/express';
|
|
2
|
+
|
|
3
|
+
export interface Context {
|
|
4
|
+
req: CreateExpressContextOptions['req'];
|
|
5
|
+
res: CreateExpressContextOptions['res'];
|
|
6
|
+
// Add your dependencies here (e.g., database client, external services)
|
|
7
|
+
// Example: db: PrismaClient;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const createContext = ({ req, res }: CreateExpressContextOptions): Context => {
|
|
11
|
+
return {
|
|
12
|
+
req,
|
|
13
|
+
res,
|
|
14
|
+
// Initialize your dependencies here
|
|
15
|
+
// Example: db: prisma,
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type { CreateExpressContextOptions };
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Main exports for the API
|
|
2
|
+
export { appRouter } from './router/index.js';
|
|
3
|
+
export type { AppRouter } from './router/index.js';
|
|
4
|
+
|
|
5
|
+
// Export context type for client usage
|
|
6
|
+
export type { Context } from './context.js';
|
|
7
|
+
|
|
8
|
+
// Export CRUD utilities for creating routers
|
|
9
|
+
export { createCrudRouter } from './lib/crud.js';
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { Prisma } from '@{{workspaceScope}}/database/client';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { prisma } from '../lib/database.js';
|
|
4
|
+
import { publicProcedure, router } from '../trpc.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a standard CRUD router for any Prisma model
|
|
8
|
+
*
|
|
9
|
+
* @param modelName - The name of the Prisma model (e.g., 'user', 'post', 'test')
|
|
10
|
+
* @param createSchema - Zod schema for creating new records
|
|
11
|
+
* @param updateSchema - Zod schema for updating records (optional, defaults to createSchema.partial())
|
|
12
|
+
* @returns tRPC router with standard CRUD operations
|
|
13
|
+
*/
|
|
14
|
+
export function createCrudRouter<
|
|
15
|
+
TModelName extends Prisma.ModelName,
|
|
16
|
+
TCreateInput extends Record<string, any>,
|
|
17
|
+
TUpdateInput extends Record<string, any> = Partial<TCreateInput>
|
|
18
|
+
>(
|
|
19
|
+
modelName: TModelName,
|
|
20
|
+
createSchema: z.ZodSchema<TCreateInput>,
|
|
21
|
+
updateSchema?: z.ZodSchema<TUpdateInput>
|
|
22
|
+
) {
|
|
23
|
+
const model = (prisma as any)[modelName];
|
|
24
|
+
const updateSchemaToUse = updateSchema || createSchema.partial();
|
|
25
|
+
|
|
26
|
+
return router({
|
|
27
|
+
// Get all records
|
|
28
|
+
getAll: publicProcedure
|
|
29
|
+
.input(z.object({
|
|
30
|
+
skip: z.number().min(0).optional(),
|
|
31
|
+
take: z.number().min(1).max(100).optional(),
|
|
32
|
+
orderBy: z.record(z.enum(['asc', 'desc'])).optional(),
|
|
33
|
+
}))
|
|
34
|
+
.query(async ({ input }) => {
|
|
35
|
+
return await model.findMany({
|
|
36
|
+
skip: input.skip,
|
|
37
|
+
take: input.take || 10,
|
|
38
|
+
orderBy: input.orderBy,
|
|
39
|
+
});
|
|
40
|
+
}),
|
|
41
|
+
|
|
42
|
+
// Get record by ID
|
|
43
|
+
getById: publicProcedure
|
|
44
|
+
.input(z.object({ id: z.string() }))
|
|
45
|
+
.query(async ({ input }) => {
|
|
46
|
+
const record = await model.findUnique({
|
|
47
|
+
where: { id: input.id },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!record) {
|
|
51
|
+
throw new Error(`${modelName} not found`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return record;
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
// Create new record
|
|
58
|
+
create: publicProcedure
|
|
59
|
+
.input(createSchema)
|
|
60
|
+
.mutation(async ({ input }) => {
|
|
61
|
+
return await model.create({
|
|
62
|
+
data: input,
|
|
63
|
+
});
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
// Update record
|
|
67
|
+
update: publicProcedure
|
|
68
|
+
.input(z.object({
|
|
69
|
+
id: z.string(),
|
|
70
|
+
data: updateSchemaToUse,
|
|
71
|
+
}))
|
|
72
|
+
.mutation(async ({ input }) => {
|
|
73
|
+
const existingRecord = await model.findUnique({
|
|
74
|
+
where: { id: input.id },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (!existingRecord) {
|
|
78
|
+
throw new Error(`${modelName} not found`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return await model.update({
|
|
82
|
+
where: { id: input.id },
|
|
83
|
+
data: input.data,
|
|
84
|
+
});
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
// Delete record
|
|
88
|
+
delete: publicProcedure
|
|
89
|
+
.input(z.object({ id: z.string() }))
|
|
90
|
+
.mutation(async ({ input }) => {
|
|
91
|
+
const existingRecord = await model.findUnique({
|
|
92
|
+
where: { id: input.id },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
if (!existingRecord) {
|
|
96
|
+
throw new Error(`${modelName} not found`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return await model.delete({
|
|
100
|
+
where: { id: input.id },
|
|
101
|
+
});
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
// Get count
|
|
105
|
+
count: publicProcedure
|
|
106
|
+
.input(z.object({
|
|
107
|
+
where: z.record(z.any()).optional(),
|
|
108
|
+
}))
|
|
109
|
+
.query(async ({ input }) => {
|
|
110
|
+
return await model.count({
|
|
111
|
+
where: input.where,
|
|
112
|
+
});
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Example usage:
|
|
119
|
+
*
|
|
120
|
+
* ```typescript
|
|
121
|
+
* import { z } from 'zod';
|
|
122
|
+
* import { createCrudRouter } from '../lib/crud.js';
|
|
123
|
+
*
|
|
124
|
+
* // Define schemas for your model
|
|
125
|
+
* const createUserSchema = z.object({
|
|
126
|
+
* email: z.string().email(),
|
|
127
|
+
* name: z.string(),
|
|
128
|
+
* });
|
|
129
|
+
*
|
|
130
|
+
* const updateUserSchema = z.object({
|
|
131
|
+
* email: z.string().email().optional(),
|
|
132
|
+
* name: z.string().optional(),
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* // Create the CRUD router
|
|
136
|
+
* export const userRouter = createCrudRouter(
|
|
137
|
+
* 'user',
|
|
138
|
+
* createUserSchema,
|
|
139
|
+
* updateUserSchema
|
|
140
|
+
* );
|
|
141
|
+
* ```
|
|
142
|
+
*
|
|
143
|
+
* This will generate:
|
|
144
|
+
* - users.getAll() - Get all users with pagination
|
|
145
|
+
* - users.getById({ id }) - Get user by ID
|
|
146
|
+
* - users.create({ email, name }) - Create new user
|
|
147
|
+
* - users.update({ id, data: { email?, name? } }) - Update user
|
|
148
|
+
* - users.delete({ id }) - Delete user
|
|
149
|
+
* - users.count() - Get user count
|
|
150
|
+
*/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PrismaClient } from '@{{workspaceScope}}/database/client';
|
|
2
|
+
|
|
3
|
+
// Initialize Prisma Client
|
|
4
|
+
declare global {
|
|
5
|
+
// eslint-disable-next-line no-var
|
|
6
|
+
var __prisma: PrismaClient | undefined;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Prevent multiple instances of Prisma Client in development
|
|
10
|
+
export const prisma = globalThis.__prisma || new PrismaClient({
|
|
11
|
+
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
15
|
+
globalThis.__prisma = prisma;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Graceful shutdown
|
|
19
|
+
process.on('beforeExit', async () => {
|
|
20
|
+
await prisma.$disconnect();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export default prisma;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { router, publicProcedure } from '../trpc.js';
|
|
3
|
+
import { testRouter } from '../routers/test.js';
|
|
4
|
+
|
|
5
|
+
export const appRouter = router({
|
|
6
|
+
// Simple hello world procedure
|
|
7
|
+
hello: publicProcedure
|
|
8
|
+
.input(z.object({ name: z.string().optional() }))
|
|
9
|
+
.query(({ input }) => {
|
|
10
|
+
return {
|
|
11
|
+
greeting: `Hello ${input.name || 'World'}!`,
|
|
12
|
+
timestamp: new Date().toISOString(),
|
|
13
|
+
};
|
|
14
|
+
}),
|
|
15
|
+
|
|
16
|
+
// Health check procedure
|
|
17
|
+
health: publicProcedure.query(() => {
|
|
18
|
+
return {
|
|
19
|
+
status: 'OK',
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
};
|
|
23
|
+
}),
|
|
24
|
+
|
|
25
|
+
// Test CRUD endpoints - generated automatically from Prisma model
|
|
26
|
+
test: testRouter,
|
|
27
|
+
|
|
28
|
+
// Add your model routers here
|
|
29
|
+
// Example:
|
|
30
|
+
// users: userRouter,
|
|
31
|
+
// posts: postRouter,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Export type definition of API
|
|
35
|
+
export type AppRouter = typeof appRouter;
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
SIMPLIFIED CRUD API SYSTEM:
|
|
39
|
+
|
|
40
|
+
This API template uses a simplified approach with automatic CRUD generation for Prisma models.
|
|
41
|
+
|
|
42
|
+
## Quick Start:
|
|
43
|
+
|
|
44
|
+
1. **Define your Prisma model** in packages/database/schema.prisma
|
|
45
|
+
2. **Create Zod schemas** for validation
|
|
46
|
+
3. **Generate CRUD router** using createCrudRouter()
|
|
47
|
+
4. **Add to main router**
|
|
48
|
+
|
|
49
|
+
## Example - Adding a User model:
|
|
50
|
+
|
|
51
|
+
### 1. Define Prisma model:
|
|
52
|
+
```prisma
|
|
53
|
+
model User {
|
|
54
|
+
id String @id @default(cuid())
|
|
55
|
+
email String @unique
|
|
56
|
+
name String
|
|
57
|
+
createdAt DateTime @default(now())
|
|
58
|
+
updatedAt DateTime @updatedAt
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Create router file (src/routers/user.ts):
|
|
63
|
+
```typescript
|
|
64
|
+
import { z } from 'zod';
|
|
65
|
+
import { createCrudRouter } from '../lib/crud.js';
|
|
66
|
+
|
|
67
|
+
const createUserSchema = z.object({
|
|
68
|
+
email: z.string().email(),
|
|
69
|
+
name: z.string().min(1),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const updateUserSchema = z.object({
|
|
73
|
+
email: z.string().email().optional(),
|
|
74
|
+
name: z.string().min(1).optional(),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export const userRouter = createCrudRouter(
|
|
78
|
+
'user',
|
|
79
|
+
createUserSchema,
|
|
80
|
+
updateUserSchema
|
|
81
|
+
);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Add to main router:
|
|
85
|
+
```typescript
|
|
86
|
+
import { userRouter } from '../routers/user.js';
|
|
87
|
+
|
|
88
|
+
export const appRouter = router({
|
|
89
|
+
// ... other routes
|
|
90
|
+
users: userRouter,
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 4. Use in frontend:
|
|
95
|
+
```typescript
|
|
96
|
+
// Get all users
|
|
97
|
+
const { data: users } = trpc.users.getAll.useQuery();
|
|
98
|
+
|
|
99
|
+
// Create user
|
|
100
|
+
const createUser = trpc.users.create.useMutation();
|
|
101
|
+
await createUser.mutateAsync({
|
|
102
|
+
email: 'user@example.com',
|
|
103
|
+
name: 'John Doe'
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Update user
|
|
107
|
+
const updateUser = trpc.users.update.useMutation();
|
|
108
|
+
await updateUser.mutateAsync({
|
|
109
|
+
id: 'user-id',
|
|
110
|
+
data: { name: 'Jane Doe' }
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Available CRUD Operations:
|
|
115
|
+
|
|
116
|
+
Each generated router includes:
|
|
117
|
+
- `getAll({ skip?, take?, orderBy? })` - List with pagination
|
|
118
|
+
- `getById({ id })` - Get single record
|
|
119
|
+
- `create(data)` - Create new record
|
|
120
|
+
- `update({ id, data })` - Update existing record
|
|
121
|
+
- `delete({ id })` - Delete record
|
|
122
|
+
- `count({ where? })` - Count records
|
|
123
|
+
|
|
124
|
+
## Advanced Usage:
|
|
125
|
+
|
|
126
|
+
### Custom procedures:
|
|
127
|
+
You can extend generated routers with custom procedures:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { z } from 'zod';
|
|
131
|
+
import { router, publicProcedure } from '../trpc.js';
|
|
132
|
+
import { createCrudRouter } from '../lib/crud.js';
|
|
133
|
+
import { prisma } from '../lib/database.js';
|
|
134
|
+
|
|
135
|
+
const baseCrudRouter = createCrudRouter('user', createUserSchema);
|
|
136
|
+
|
|
137
|
+
export const userRouter = router({
|
|
138
|
+
...baseCrudRouter,
|
|
139
|
+
|
|
140
|
+
// Add custom procedures
|
|
141
|
+
getByEmail: publicProcedure
|
|
142
|
+
.input(z.object({ email: z.string().email() }))
|
|
143
|
+
.query(async ({ input }) => {
|
|
144
|
+
return await prisma.user.findUnique({
|
|
145
|
+
where: { email: input.email }
|
|
146
|
+
});
|
|
147
|
+
}),
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Authentication & Authorization:
|
|
152
|
+
Use middleware for protected procedures:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { protectedProcedure } from '../trpc.js';
|
|
156
|
+
|
|
157
|
+
// Replace publicProcedure with protectedProcedure in createCrudRouter
|
|
158
|
+
// or create a custom version for authenticated routes
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This simplified system removes the controller layer complexity while providing
|
|
162
|
+
type-safe, validated CRUD operations for all your Prisma models.
|
|
163
|
+
*/
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { createCrudRouter } from "../lib/crud.js";
|
|
3
|
+
|
|
4
|
+
// Define Zod schemas for the Test model based on the Prisma schema
|
|
5
|
+
const createTestSchema = z.object({
|
|
6
|
+
name: z.string().min(1, "Name is required"),
|
|
7
|
+
message: z.string().min(1, "Message is required"),
|
|
8
|
+
status: z.string().optional().default("active"),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const updateTestSchema = z.object({
|
|
12
|
+
name: z.string().min(1, "Name is required").optional(),
|
|
13
|
+
message: z.string().min(1, "Message is required").optional(),
|
|
14
|
+
status: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Create the CRUD router for the Test model
|
|
18
|
+
export const testRouter = createCrudRouter(
|
|
19
|
+
"Test",
|
|
20
|
+
createTestSchema,
|
|
21
|
+
updateTestSchema
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* This generates the following endpoints:
|
|
26
|
+
*
|
|
27
|
+
* - test.getAll({ skip?, take?, orderBy? }) - Get all test records with pagination
|
|
28
|
+
* - test.getById({ id }) - Get test record by ID
|
|
29
|
+
* - test.create({ name, message, status? }) - Create new test record
|
|
30
|
+
* - test.update({ id, data: { name?, message?, status? } }) - Update test record
|
|
31
|
+
* - test.delete({ id }) - Delete test record
|
|
32
|
+
* - test.count({ where? }) - Get test record count
|
|
33
|
+
*
|
|
34
|
+
* Example usage in frontend:
|
|
35
|
+
*
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // Get all tests
|
|
38
|
+
* const { data: tests } = trpc.test.getAll.useQuery({ take: 10 });
|
|
39
|
+
*
|
|
40
|
+
* // Create a new test
|
|
41
|
+
* const createTest = trpc.test.create.useMutation();
|
|
42
|
+
* await createTest.mutateAsync({
|
|
43
|
+
* name: 'My Test',
|
|
44
|
+
* message: 'This is a test message',
|
|
45
|
+
* status: 'active'
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* // Update a test
|
|
49
|
+
* const updateTest = trpc.test.update.useMutation();
|
|
50
|
+
* await updateTest.mutateAsync({
|
|
51
|
+
* id: 'test-id',
|
|
52
|
+
* data: { name: 'Updated Test' }
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // Delete a test
|
|
56
|
+
* const deleteTest = trpc.test.delete.useMutation();
|
|
57
|
+
* await deleteTest.mutateAsync({ id: 'test-id' });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createCrudRouter } from '../lib/crud.js';
|
|
3
|
+
|
|
4
|
+
// Define Zod schemas for the User model
|
|
5
|
+
const createUserSchema = z.object({
|
|
6
|
+
email: z.string().email('Invalid email format'),
|
|
7
|
+
name: z.string().min(1, 'Name is required'),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const updateUserSchema = z.object({
|
|
11
|
+
email: z.string().email('Invalid email format').optional(),
|
|
12
|
+
name: z.string().min(1, 'Name is required').optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Create the CRUD router for the User model
|
|
16
|
+
export const userRouter = createCrudRouter(
|
|
17
|
+
'user',
|
|
18
|
+
createUserSchema,
|
|
19
|
+
updateUserSchema
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* This generates the following endpoints:
|
|
24
|
+
*
|
|
25
|
+
* - users.getAll({ skip?, take?, orderBy? }) - Get all users with pagination
|
|
26
|
+
* - users.getById({ id }) - Get user by ID
|
|
27
|
+
* - users.create({ name, email }) - Create new user
|
|
28
|
+
* - users.update({ id, data: { name?, email? } }) - Update user
|
|
29
|
+
* - users.delete({ id }) - Delete user
|
|
30
|
+
* - users.count({ where? }) - Get user count
|
|
31
|
+
*
|
|
32
|
+
* To use this router:
|
|
33
|
+
*
|
|
34
|
+
* 1. First add a User model to your Prisma schema:
|
|
35
|
+
*
|
|
36
|
+
* ```prisma
|
|
37
|
+
* model User {
|
|
38
|
+
* id String @id @default(cuid())
|
|
39
|
+
* email String @unique
|
|
40
|
+
* name String
|
|
41
|
+
* createdAt DateTime @default(now())
|
|
42
|
+
* updatedAt DateTime @updatedAt
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* 2. Run `yarn db:migrate` to apply the schema changes
|
|
47
|
+
*
|
|
48
|
+
* 3. Uncomment the users router in src/router/index.ts:
|
|
49
|
+
*
|
|
50
|
+
* ```typescript
|
|
51
|
+
* import { userRouter } from '../routers/user.js';
|
|
52
|
+
*
|
|
53
|
+
* export const appRouter = router({
|
|
54
|
+
* // ... other routes
|
|
55
|
+
* users: userRouter,
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* 4. Use in your frontend:
|
|
60
|
+
*
|
|
61
|
+
* ```typescript
|
|
62
|
+
* // Get all users
|
|
63
|
+
* const { data: users } = trpc.users.getAll.useQuery({ take: 10 });
|
|
64
|
+
*
|
|
65
|
+
* // Create a new user
|
|
66
|
+
* const createUser = trpc.users.create.useMutation();
|
|
67
|
+
* await createUser.mutateAsync({
|
|
68
|
+
* name: 'John Doe',
|
|
69
|
+
* email: 'john@example.com'
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Update a user
|
|
73
|
+
* const updateUser = trpc.users.update.useMutation();
|
|
74
|
+
* await updateUser.mutateAsync({
|
|
75
|
+
* id: 'user-id',
|
|
76
|
+
* data: { name: 'Jane Doe' }
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* // Delete a user
|
|
80
|
+
* const deleteUser = trpc.users.delete.useMutation();
|
|
81
|
+
* await deleteUser.mutateAsync({ id: 'user-id' });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import { createExpressMiddleware } from '@trpc/server/adapters/express';
|
|
4
|
+
import { appRouter } from './router/index.js';
|
|
5
|
+
import { createContext } from './context.js';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
|
|
8
|
+
// Load environment variables
|
|
9
|
+
dotenv.config();
|
|
10
|
+
|
|
11
|
+
const app = express();
|
|
12
|
+
const PORT = process.env.PORT || 3000;
|
|
13
|
+
|
|
14
|
+
// CORS configuration
|
|
15
|
+
app.use(cors({
|
|
16
|
+
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
|
|
17
|
+
credentials: true,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Health check endpoint
|
|
21
|
+
app.get('/health', (req, res) => {
|
|
22
|
+
res.json({ status: 'OK', timestamp: new Date().toISOString() });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// tRPC middleware
|
|
26
|
+
app.use(
|
|
27
|
+
'/trpc',
|
|
28
|
+
createExpressMiddleware({
|
|
29
|
+
router: appRouter,
|
|
30
|
+
createContext,
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Default route
|
|
35
|
+
app.get('/', (req, res) => {
|
|
36
|
+
res.json({
|
|
37
|
+
message: 'Welcome to {{projectName}} API',
|
|
38
|
+
endpoints: {
|
|
39
|
+
health: '/health',
|
|
40
|
+
trpc: '/trpc',
|
|
41
|
+
playground: '/trpc-playground' // Available in development
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
app.listen(PORT, () => {
|
|
47
|
+
console.log(`🚀 Server running on http://localhost:${PORT}`);
|
|
48
|
+
console.log(`📡 tRPC API available at http://localhost:${PORT}/trpc`);
|
|
49
|
+
console.log(`🏥 Health check at http://localhost:${PORT}/health`);
|
|
50
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { initTRPC } from '@trpc/server';
|
|
2
|
+
import { type Context } from './context.js';
|
|
3
|
+
import { ZodError } from 'zod';
|
|
4
|
+
|
|
5
|
+
const t = initTRPC.context<Context>().create({
|
|
6
|
+
errorFormatter({ shape, error }) {
|
|
7
|
+
return {
|
|
8
|
+
...shape,
|
|
9
|
+
data: {
|
|
10
|
+
...shape.data,
|
|
11
|
+
zodError:
|
|
12
|
+
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Export reusable router and procedure helpers
|
|
19
|
+
export const router = t.router;
|
|
20
|
+
export const publicProcedure = t.procedure;
|
|
21
|
+
|
|
22
|
+
// You can create additional procedures with middleware here
|
|
23
|
+
// For example, a protected procedure that requires authentication:
|
|
24
|
+
// export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
|
|
25
|
+
// // Add your authentication logic here
|
|
26
|
+
// // Example: check for valid session/token
|
|
27
|
+
// return next({ ctx: { ...ctx, user: { id: 'user-id' } } });
|
|
28
|
+
// });
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"allowSyntheticDefaultImports": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"checkJs": false,
|
|
9
|
+
"jsx": "preserve",
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"rootDir": "./src",
|
|
15
|
+
"removeComments": false,
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noImplicitAny": true,
|
|
18
|
+
"strictNullChecks": true,
|
|
19
|
+
"strictFunctionTypes": true,
|
|
20
|
+
"noImplicitThis": true,
|
|
21
|
+
"useUnknownInCatchVariables": true,
|
|
22
|
+
"noImplicitReturns": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true,
|
|
24
|
+
"noUncheckedIndexedAccess": true,
|
|
25
|
+
"exactOptionalPropertyTypes": true,
|
|
26
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
27
|
+
"resolveJsonModule": true,
|
|
28
|
+
"isolatedModules": true,
|
|
29
|
+
"forceConsistentCasingInFileNames": true,
|
|
30
|
+
"skipLibCheck": true
|
|
31
|
+
},
|
|
32
|
+
"include": [
|
|
33
|
+
"src/**/*"
|
|
34
|
+
],
|
|
35
|
+
"exclude": [
|
|
36
|
+
"node_modules",
|
|
37
|
+
"dist",
|
|
38
|
+
"**/*.test.ts"
|
|
39
|
+
],
|
|
40
|
+
"ts-node": {
|
|
41
|
+
"esm": true
|
|
42
|
+
}
|
|
43
|
+
}
|