@idealyst/cli 1.0.24 → 1.0.26
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 +109 -109
- package/dist/index.js +233 -36
- package/dist/types/generators/api.d.ts +2 -0
- package/dist/types/generators/index.d.ts +1 -0
- package/dist/types/generators/utils.d.ts +18 -1
- package/dist/types/types.d.ts +2 -1
- package/package.json +1 -1
- package/templates/api/README.md +207 -0
- package/templates/api/env.example +12 -0
- package/templates/api/package.json +49 -0
- package/templates/api/prisma/schema.prisma +21 -0
- package/templates/api/src/context.ts +23 -0
- package/templates/api/src/controllers/UserController.ts +102 -0
- package/templates/api/src/index.ts +14 -0
- package/templates/api/src/lib/controller.ts +90 -0
- package/templates/api/src/lib/middleware.ts +170 -0
- package/templates/api/src/middleware/auth.ts +75 -0
- package/templates/api/src/middleware/common.ts +103 -0
- package/templates/api/src/router/index.ts +130 -0
- package/templates/api/src/server.ts +50 -0
- package/templates/api/src/trpc.ts +28 -0
- package/templates/api/tsconfig.json +44 -0
- package/templates/native/.yarnrc.yml +18 -18
- package/templates/native/App.tsx +23 -23
- package/templates/native/README.md +85 -85
- package/templates/native/app.json +4 -4
- package/templates/native/babel.config.js +9 -9
- package/templates/native/index.js +5 -5
- package/templates/native/metro.config.js +27 -27
- package/templates/native/package.json +9 -9
- package/templates/native/src/App-with-trpc.tsx +72 -0
- package/templates/native/src/utils/trpc.ts +127 -0
- package/templates/native/tsconfig.json +29 -29
- package/templates/shared/README.md +108 -108
- package/templates/shared/package.json +39 -39
- package/templates/shared/tsconfig.json +24 -24
- package/templates/web/README.md +89 -89
- package/templates/web/index.html +12 -12
- package/templates/web/package.json +55 -51
- package/templates/web/src/App-with-trpc.tsx +80 -0
- package/templates/web/src/App.tsx +14 -14
- package/templates/web/src/main.tsx +24 -24
- package/templates/web/src/utils/trpc.ts +93 -0
- package/templates/web/tsconfig.json +26 -26
- package/templates/web/vite.config.ts +68 -68
- package/templates/workspace/.yarnrc.yml +25 -25
- package/templates/workspace/README.md +79 -79
- package/templates/workspace/package.json +24 -24
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TemplateData } from '../types';
|
|
2
2
|
export declare function validateProjectName(name: string): boolean;
|
|
3
3
|
export declare function createPackageName(name: string): string;
|
|
4
|
-
export declare function updateWorkspacePackageJson(
|
|
4
|
+
export declare function updateWorkspacePackageJson(workspacePath: string, directory: string): Promise<void>;
|
|
5
5
|
export declare function copyTemplate(templatePath: string, destPath: string, data: TemplateData): Promise<void>;
|
|
6
6
|
export declare function processTemplateFiles(dir: string, data: TemplateData): Promise<void>;
|
|
7
7
|
export declare function processTemplateFile(filePath: string, data: TemplateData): Promise<void>;
|
|
@@ -10,10 +10,27 @@ export declare function runCommand(command: string, args: string[], options: {
|
|
|
10
10
|
cwd: string;
|
|
11
11
|
}): Promise<void>;
|
|
12
12
|
export declare function getTemplateData(projectName: string, description?: string, appName?: string): TemplateData;
|
|
13
|
+
/**
|
|
14
|
+
* Detects if we're in a workspace root directory
|
|
15
|
+
*/
|
|
16
|
+
export declare function isWorkspaceRoot(directory: string): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the correct project path for individual projects (native, web, shared).
|
|
19
|
+
* Individual projects can ONLY be created within an existing workspace.
|
|
20
|
+
* This enforces proper monorepo structure and prevents scattered individual projects.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveProjectPath(projectName: string, directory: string): Promise<{
|
|
23
|
+
projectPath: string;
|
|
24
|
+
workspacePath: string;
|
|
25
|
+
}>;
|
|
13
26
|
export declare function initializeReactNativeProject(projectName: string, directory: string, displayName?: string, skipInstall?: boolean): Promise<void>;
|
|
14
27
|
export declare function overlayIdealystFiles(templatePath: string, projectPath: string, data: TemplateData): Promise<void>;
|
|
15
28
|
export declare function mergePackageJsonDependencies(templatePath: string, projectPath: string): Promise<void>;
|
|
16
29
|
export declare function promptForProjectName(): Promise<string>;
|
|
17
30
|
export declare function promptForProjectType(): Promise<string>;
|
|
18
31
|
export declare function promptForAppName(projectName: string): Promise<string>;
|
|
32
|
+
export declare function promptForTrpcIntegration(): Promise<boolean>;
|
|
33
|
+
export declare function copyTrpcFiles(templatePath: string, projectPath: string, data: TemplateData): Promise<void>;
|
|
34
|
+
export declare function copyTrpcAppComponent(templatePath: string, projectPath: string, data: TemplateData): Promise<void>;
|
|
35
|
+
export declare function removeTrpcDependencies(projectPath: string): Promise<void>;
|
|
19
36
|
export declare function configureAndroidVectorIcons(projectPath: string): Promise<void>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export type ProjectType = 'native' | 'web' | 'shared' | 'workspace';
|
|
1
|
+
export type ProjectType = 'native' | 'web' | 'shared' | 'workspace' | 'api';
|
|
2
2
|
export interface GenerateProjectOptions {
|
|
3
3
|
name: string;
|
|
4
4
|
type: ProjectType;
|
|
5
5
|
directory: string;
|
|
6
6
|
skipInstall: boolean;
|
|
7
7
|
appName?: string;
|
|
8
|
+
withTrpc?: boolean;
|
|
8
9
|
}
|
|
9
10
|
export interface TemplateData {
|
|
10
11
|
projectName: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
This API project is built with:
|
|
6
|
+
- **tRPC** - End-to-end typesafe APIs
|
|
7
|
+
- **Prisma** - Modern database toolkit
|
|
8
|
+
- **Zod** - TypeScript-first schema validation
|
|
9
|
+
- **Express.js** - Web framework for Node.js
|
|
10
|
+
- **TypeScript** - Type-safe JavaScript
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
1. **Setup environment variables:**
|
|
15
|
+
```bash
|
|
16
|
+
cp env.example .env
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. **Install dependencies:**
|
|
20
|
+
```bash
|
|
21
|
+
yarn install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
3. **Setup database:**
|
|
25
|
+
```bash
|
|
26
|
+
# Generate Prisma client
|
|
27
|
+
yarn db:generate
|
|
28
|
+
|
|
29
|
+
# Push schema to database (for development)
|
|
30
|
+
yarn db:push
|
|
31
|
+
|
|
32
|
+
# Or run migrations (for production)
|
|
33
|
+
yarn db:migrate
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
4. **Start development server:**
|
|
37
|
+
```bash
|
|
38
|
+
yarn dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The API will be available at `http://localhost:3000`
|
|
42
|
+
|
|
43
|
+
## Available Scripts
|
|
44
|
+
|
|
45
|
+
- `yarn dev` - Start development server with hot reload
|
|
46
|
+
- `yarn build` - Build for production
|
|
47
|
+
- `yarn start` - Start production server
|
|
48
|
+
- `yarn db:generate` - Generate Prisma client
|
|
49
|
+
- `yarn db:push` - Push schema changes to database
|
|
50
|
+
- `yarn db:studio` - Open Prisma Studio (database GUI)
|
|
51
|
+
- `yarn db:migrate` - Run database migrations
|
|
52
|
+
- `yarn db:reset` - Reset database and run all migrations
|
|
53
|
+
- `yarn lint` - Run ESLint
|
|
54
|
+
- `yarn type-check` - Run TypeScript type checking
|
|
55
|
+
|
|
56
|
+
## API Endpoints
|
|
57
|
+
|
|
58
|
+
### tRPC Routes
|
|
59
|
+
|
|
60
|
+
All tRPC routes are available at `/trpc/[procedure]`
|
|
61
|
+
|
|
62
|
+
#### Example Routes
|
|
63
|
+
- `hello` - Simple greeting endpoint (accepts optional name parameter)
|
|
64
|
+
- `health` - API health check with timestamp
|
|
65
|
+
|
|
66
|
+
### REST Endpoints
|
|
67
|
+
- `GET /` - API information
|
|
68
|
+
- `GET /health` - Health check
|
|
69
|
+
|
|
70
|
+
## Database
|
|
71
|
+
|
|
72
|
+
This project uses SQLite by default for development. You can switch to PostgreSQL or MySQL by updating the `DATABASE_URL` in your `.env` file and the `provider` in `prisma/schema.prisma`.
|
|
73
|
+
|
|
74
|
+
### Database Schema
|
|
75
|
+
|
|
76
|
+
The schema starts empty - you can add your own models in `prisma/schema.prisma`. Example model structure is provided in comments.
|
|
77
|
+
|
|
78
|
+
## Development
|
|
79
|
+
|
|
80
|
+
### Adding New Routes
|
|
81
|
+
|
|
82
|
+
You can add routes in two ways:
|
|
83
|
+
|
|
84
|
+
#### 1. Simple tRPC Procedures (Traditional)
|
|
85
|
+
1. Create a new router file in `src/router/`
|
|
86
|
+
2. Define your procedures with Zod schemas for validation
|
|
87
|
+
3. Export the router and add it to `src/router/index.ts`
|
|
88
|
+
|
|
89
|
+
#### 2. Controller & Middleware System (Recommended)
|
|
90
|
+
This template includes a powerful controller and middleware system:
|
|
91
|
+
|
|
92
|
+
1. **Create a Controller:**
|
|
93
|
+
```typescript
|
|
94
|
+
// src/controllers/PostController.ts
|
|
95
|
+
import { z } from 'zod';
|
|
96
|
+
import { BaseController, controllerToRouter } from '../lib/controller.js';
|
|
97
|
+
import { requireAuth, requireAdmin } from '../middleware/auth.js';
|
|
98
|
+
import { logger, rateLimit } from '../middleware/common.js';
|
|
99
|
+
|
|
100
|
+
const createPostSchema = z.object({
|
|
101
|
+
title: z.string().min(1),
|
|
102
|
+
content: z.string(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export class PostController extends BaseController {
|
|
106
|
+
// Public endpoint
|
|
107
|
+
getAll = this.createQuery(
|
|
108
|
+
z.object({ published: z.boolean().optional() }),
|
|
109
|
+
async (input, ctx) => {
|
|
110
|
+
return ctx.prisma.post.findMany({
|
|
111
|
+
where: { published: input.published }
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// Protected endpoint with middleware
|
|
117
|
+
create = this.createMutationWithMiddleware(
|
|
118
|
+
createPostSchema,
|
|
119
|
+
[logger, rateLimit(5, 60000), requireAuth],
|
|
120
|
+
async (input, ctx) => {
|
|
121
|
+
return ctx.prisma.post.create({ data: input });
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Admin-only endpoint
|
|
126
|
+
delete = this.createMutationWithMiddleware(
|
|
127
|
+
z.object({ id: z.string() }),
|
|
128
|
+
[requireAuth, requireAdmin],
|
|
129
|
+
async (input, ctx) => {
|
|
130
|
+
return ctx.prisma.post.delete({ where: { id: input.id } });
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const postRouter = controllerToRouter({
|
|
136
|
+
getAll: new PostController({} as any).getAll,
|
|
137
|
+
create: new PostController({} as any).create,
|
|
138
|
+
delete: new PostController({} as any).delete,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
2. **Add to Main Router:**
|
|
143
|
+
```typescript
|
|
144
|
+
// src/router/index.ts
|
|
145
|
+
import { postRouter } from '../controllers/PostController.js';
|
|
146
|
+
|
|
147
|
+
export const appRouter = router({
|
|
148
|
+
posts: postRouter,
|
|
149
|
+
// ... other routes
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Available Middleware
|
|
154
|
+
|
|
155
|
+
#### Authentication
|
|
156
|
+
- `requireAuth` - Requires Bearer token authentication
|
|
157
|
+
- `requireRole(role)` - Requires specific user role
|
|
158
|
+
- `requireAdmin` - Requires admin role
|
|
159
|
+
|
|
160
|
+
#### Utility Middleware
|
|
161
|
+
- `logger` - Request/response logging with timing
|
|
162
|
+
- `rateLimit(maxRequests, windowMs)` - Rate limiting per IP
|
|
163
|
+
- `responseTime` - Adds X-Response-Time header
|
|
164
|
+
- `requestId` - Adds unique X-Request-ID header
|
|
165
|
+
- `errorHandler` - Centralized error handling
|
|
166
|
+
|
|
167
|
+
### Example tRPC Client Usage
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
|
171
|
+
import type { AppRouter } from './path/to/your/api';
|
|
172
|
+
|
|
173
|
+
const client = createTRPCProxyClient<AppRouter>({
|
|
174
|
+
links: [
|
|
175
|
+
httpBatchLink({
|
|
176
|
+
url: 'http://localhost:3000/trpc',
|
|
177
|
+
}),
|
|
178
|
+
],
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Use the client
|
|
182
|
+
const greeting = await client.hello.query({ name: 'John' });
|
|
183
|
+
const healthStatus = await client.health.query();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Environment Variables
|
|
187
|
+
|
|
188
|
+
Copy `env.example` to `.env` and configure:
|
|
189
|
+
|
|
190
|
+
- `DATABASE_URL` - Database connection string
|
|
191
|
+
- `PORT` - Server port (default: 3000)
|
|
192
|
+
- `NODE_ENV` - Environment (development/production)
|
|
193
|
+
- `CORS_ORIGIN` - CORS origin for client requests
|
|
194
|
+
|
|
195
|
+
## Deployment
|
|
196
|
+
|
|
197
|
+
1. Build the project: `yarn build`
|
|
198
|
+
2. Set up your production database
|
|
199
|
+
3. Run migrations: `yarn db:migrate`
|
|
200
|
+
4. Start the server: `yarn start`
|
|
201
|
+
|
|
202
|
+
## Learn More
|
|
203
|
+
|
|
204
|
+
- [tRPC Documentation](https://trpc.io/)
|
|
205
|
+
- [Prisma Documentation](https://www.prisma.io/docs/)
|
|
206
|
+
- [Zod Documentation](https://zod.dev/)
|
|
207
|
+
- [Express.js Documentation](https://expressjs.com/)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Database
|
|
2
|
+
DATABASE_URL="file:./dev.db"
|
|
3
|
+
|
|
4
|
+
# Server
|
|
5
|
+
PORT=3000
|
|
6
|
+
NODE_ENV=development
|
|
7
|
+
|
|
8
|
+
# CORS
|
|
9
|
+
CORS_ORIGIN="http://localhost:3000"
|
|
10
|
+
|
|
11
|
+
# Optional: Database for production (uncomment and configure as needed)
|
|
12
|
+
# DATABASE_URL="postgresql://username:password@localhost:5432/{{projectName}}"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"version": "{{version}}",
|
|
4
|
+
"description": "{{description}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsx watch src/server.ts",
|
|
10
|
+
"start": "node dist/server.js",
|
|
11
|
+
"db:generate": "prisma generate",
|
|
12
|
+
"db:push": "prisma db push",
|
|
13
|
+
"db:studio": "prisma studio",
|
|
14
|
+
"db:migrate": "prisma migrate dev",
|
|
15
|
+
"db:reset": "prisma migrate reset",
|
|
16
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
17
|
+
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
18
|
+
"type-check": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@prisma/client": "^5.7.1",
|
|
22
|
+
"@trpc/server": "^10.44.1",
|
|
23
|
+
"cors": "^2.8.5",
|
|
24
|
+
"dotenv": "^16.3.1",
|
|
25
|
+
"express": "^4.18.2",
|
|
26
|
+
"zod": "^3.22.4"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/cors": "^2.8.17",
|
|
30
|
+
"@types/express": "^4.17.21",
|
|
31
|
+
"@types/node": "^20.10.4",
|
|
32
|
+
"@typescript-eslint/eslint-plugin": "^6.13.1",
|
|
33
|
+
"@typescript-eslint/parser": "^6.13.1",
|
|
34
|
+
"eslint": "^8.54.0",
|
|
35
|
+
"prisma": "^5.7.1",
|
|
36
|
+
"tsx": "^4.6.2",
|
|
37
|
+
"typescript": "^5.3.3"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"api",
|
|
41
|
+
"trpc",
|
|
42
|
+
"prisma",
|
|
43
|
+
"zod",
|
|
44
|
+
"typescript",
|
|
45
|
+
"express"
|
|
46
|
+
],
|
|
47
|
+
"author": "",
|
|
48
|
+
"license": "MIT"
|
|
49
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client-js"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "sqlite"
|
|
10
|
+
url = env("DATABASE_URL")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Add your models here
|
|
14
|
+
// Example:
|
|
15
|
+
// model User {
|
|
16
|
+
// id String @id @default(cuid())
|
|
17
|
+
// email String @unique
|
|
18
|
+
// name String?
|
|
19
|
+
// createdAt DateTime @default(now())
|
|
20
|
+
// updatedAt DateTime @updatedAt
|
|
21
|
+
// }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client';
|
|
2
|
+
import type { CreateExpressContextOptions } from '@trpc/server/adapters/express';
|
|
3
|
+
|
|
4
|
+
// Create Prisma client
|
|
5
|
+
const prisma = new PrismaClient({
|
|
6
|
+
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export interface Context {
|
|
10
|
+
prisma: PrismaClient;
|
|
11
|
+
req: CreateExpressContextOptions['req'];
|
|
12
|
+
res: CreateExpressContextOptions['res'];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const createContext = ({ req, res }: CreateExpressContextOptions): Context => {
|
|
16
|
+
return {
|
|
17
|
+
prisma,
|
|
18
|
+
req,
|
|
19
|
+
res,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type { CreateExpressContextOptions };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseController, controllerToRouter } from '../lib/controller.js';
|
|
3
|
+
import { requireAuth, requireAdmin } from '../middleware/auth.js';
|
|
4
|
+
|
|
5
|
+
// Input schemas
|
|
6
|
+
const createUserSchema = z.object({
|
|
7
|
+
email: z.string().email(),
|
|
8
|
+
name: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const getUserSchema = z.object({
|
|
12
|
+
id: z.string(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const updateUserSchema = z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
name: z.string().optional(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// User controller class
|
|
21
|
+
export class UserController extends BaseController {
|
|
22
|
+
// Get all users (admin only)
|
|
23
|
+
getAll = this.createQueryWithMiddleware(
|
|
24
|
+
z.object({}),
|
|
25
|
+
[requireAuth, requireAdmin],
|
|
26
|
+
async (input, ctx) => {
|
|
27
|
+
// Simulate database query
|
|
28
|
+
return [
|
|
29
|
+
{ id: '1', email: 'user1@example.com', name: 'User 1' },
|
|
30
|
+
{ id: '2', email: 'user2@example.com', name: 'User 2' },
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Get user by ID (authenticated users)
|
|
36
|
+
getById = this.createQueryWithMiddleware(
|
|
37
|
+
getUserSchema,
|
|
38
|
+
[requireAuth],
|
|
39
|
+
async (input, ctx) => {
|
|
40
|
+
// In a real app, you'd query your database
|
|
41
|
+
const user = { id: input.id, email: 'user@example.com', name: 'John Doe' };
|
|
42
|
+
|
|
43
|
+
if (!user) {
|
|
44
|
+
throw new Error('User not found');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return user;
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Create user (public endpoint)
|
|
52
|
+
create = this.createMutation(
|
|
53
|
+
createUserSchema,
|
|
54
|
+
async (input, ctx) => {
|
|
55
|
+
// In a real app, you'd save to database
|
|
56
|
+
const newUser = {
|
|
57
|
+
id: Math.random().toString(36),
|
|
58
|
+
email: input.email,
|
|
59
|
+
name: input.name || null,
|
|
60
|
+
createdAt: new Date(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return newUser;
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Update user (authenticated users)
|
|
68
|
+
update = this.createMutationWithMiddleware(
|
|
69
|
+
updateUserSchema,
|
|
70
|
+
[requireAuth],
|
|
71
|
+
async (input, ctx) => {
|
|
72
|
+
// In a real app, you'd update the database
|
|
73
|
+
const updatedUser = {
|
|
74
|
+
id: input.id,
|
|
75
|
+
email: 'user@example.com', // Would come from database
|
|
76
|
+
name: input.name || 'Updated Name',
|
|
77
|
+
updatedAt: new Date(),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return updatedUser;
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Delete user (admin only)
|
|
85
|
+
delete = this.createMutationWithMiddleware(
|
|
86
|
+
z.object({ id: z.string() }),
|
|
87
|
+
[requireAuth, requireAdmin],
|
|
88
|
+
async (input, ctx) => {
|
|
89
|
+
// In a real app, you'd delete from database
|
|
90
|
+
return { success: true, deletedId: input.id };
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Export router - this would be used in your main router
|
|
96
|
+
export const userRouter = controllerToRouter({
|
|
97
|
+
getAll: new UserController({} as any).getAll,
|
|
98
|
+
getById: new UserController({} as any).getById,
|
|
99
|
+
create: new UserController({} as any).create,
|
|
100
|
+
update: new UserController({} as any).update,
|
|
101
|
+
delete: new UserController({} as any).delete,
|
|
102
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
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 middleware for potential external usage
|
|
9
|
+
export * from './middleware/auth.js';
|
|
10
|
+
export * from './middleware/common.js';
|
|
11
|
+
|
|
12
|
+
// Export controller base classes for extensions
|
|
13
|
+
export { BaseController, controllerToRouter } from './lib/controller.js';
|
|
14
|
+
export type { MiddlewareFn } from './lib/controller.js';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { Context } from '../context.js';
|
|
3
|
+
import { publicProcedure } from '../trpc.js';
|
|
4
|
+
|
|
5
|
+
// Base controller class
|
|
6
|
+
export abstract class BaseController {
|
|
7
|
+
protected ctx: Context;
|
|
8
|
+
|
|
9
|
+
constructor(ctx: Context) {
|
|
10
|
+
this.ctx = ctx;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Helper method to create a query procedure
|
|
14
|
+
protected createQuery<TInput, TOutput>(
|
|
15
|
+
inputSchema: z.ZodSchema<TInput>,
|
|
16
|
+
handler: (input: TInput, ctx: Context) => Promise<TOutput> | TOutput
|
|
17
|
+
) {
|
|
18
|
+
return publicProcedure
|
|
19
|
+
.input(inputSchema)
|
|
20
|
+
.query(async ({ input, ctx }) => {
|
|
21
|
+
return handler(input, ctx);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Helper method to create a mutation procedure
|
|
26
|
+
protected createMutation<TInput, TOutput>(
|
|
27
|
+
inputSchema: z.ZodSchema<TInput>,
|
|
28
|
+
handler: (input: TInput, ctx: Context) => Promise<TOutput> | TOutput
|
|
29
|
+
) {
|
|
30
|
+
return publicProcedure
|
|
31
|
+
.input(inputSchema)
|
|
32
|
+
.mutation(async ({ input, ctx }) => {
|
|
33
|
+
return handler(input, ctx);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper method to create a query with middleware
|
|
38
|
+
protected createQueryWithMiddleware<TInput, TOutput>(
|
|
39
|
+
inputSchema: z.ZodSchema<TInput>,
|
|
40
|
+
middleware: MiddlewareFn[],
|
|
41
|
+
handler: (input: TInput, ctx: Context) => Promise<TOutput> | TOutput
|
|
42
|
+
) {
|
|
43
|
+
let procedure = publicProcedure.input(inputSchema);
|
|
44
|
+
|
|
45
|
+
// Apply middleware
|
|
46
|
+
for (const mw of middleware) {
|
|
47
|
+
procedure = procedure.use(mw);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return procedure.query(async ({ input, ctx }) => {
|
|
51
|
+
return handler(input, ctx);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Helper method to create a mutation with middleware
|
|
56
|
+
protected createMutationWithMiddleware<TInput, TOutput>(
|
|
57
|
+
inputSchema: z.ZodSchema<TInput>,
|
|
58
|
+
middleware: MiddlewareFn[],
|
|
59
|
+
handler: (input: TInput, ctx: Context) => Promise<TOutput> | TOutput
|
|
60
|
+
) {
|
|
61
|
+
let procedure = publicProcedure.input(inputSchema);
|
|
62
|
+
|
|
63
|
+
// Apply middleware
|
|
64
|
+
for (const mw of middleware) {
|
|
65
|
+
procedure = procedure.use(mw);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return procedure.mutation(async ({ input, ctx }) => {
|
|
69
|
+
return handler(input, ctx);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Middleware function type compatible with tRPC
|
|
75
|
+
export type MiddlewareFn = (opts: {
|
|
76
|
+
ctx: Context;
|
|
77
|
+
next: () => Promise<{ ctx: Context }>;
|
|
78
|
+
}) => Promise<{ ctx: Context }>;
|
|
79
|
+
|
|
80
|
+
// Controller method decorator type
|
|
81
|
+
export interface ControllerMethod {
|
|
82
|
+
[key: string]: ReturnType<typeof publicProcedure.query> | ReturnType<typeof publicProcedure.mutation>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Helper function to convert controller to tRPC router object
|
|
86
|
+
export function controllerToRouter<T extends Record<string, any>>(
|
|
87
|
+
controllerMethods: T
|
|
88
|
+
): T {
|
|
89
|
+
return controllerMethods;
|
|
90
|
+
}
|