@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,146 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
# Production overrides for docker-compose.yml
|
|
4
|
+
# Usage: docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
|
|
5
|
+
|
|
6
|
+
services:
|
|
7
|
+
postgres:
|
|
8
|
+
environment:
|
|
9
|
+
POSTGRES_DB: ${POSTGRES_DB}
|
|
10
|
+
POSTGRES_USER: ${POSTGRES_USER}
|
|
11
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
12
|
+
volumes:
|
|
13
|
+
- postgres_prod_data:/var/lib/postgresql/data
|
|
14
|
+
deploy:
|
|
15
|
+
resources:
|
|
16
|
+
limits:
|
|
17
|
+
memory: 1G
|
|
18
|
+
cpus: '0.5'
|
|
19
|
+
reservations:
|
|
20
|
+
memory: 512M
|
|
21
|
+
cpus: '0.25'
|
|
22
|
+
|
|
23
|
+
redis:
|
|
24
|
+
volumes:
|
|
25
|
+
- redis_prod_data:/data
|
|
26
|
+
deploy:
|
|
27
|
+
resources:
|
|
28
|
+
limits:
|
|
29
|
+
memory: 256M
|
|
30
|
+
cpus: '0.25'
|
|
31
|
+
reservations:
|
|
32
|
+
memory: 128M
|
|
33
|
+
cpus: '0.1'
|
|
34
|
+
|
|
35
|
+
api:
|
|
36
|
+
environment:
|
|
37
|
+
NODE_ENV: production
|
|
38
|
+
LOG_LEVEL: info
|
|
39
|
+
RATE_LIMIT_WINDOW_MS: 900000
|
|
40
|
+
RATE_LIMIT_MAX_REQUESTS: 100
|
|
41
|
+
deploy:
|
|
42
|
+
replicas: 2
|
|
43
|
+
resources:
|
|
44
|
+
limits:
|
|
45
|
+
memory: 512M
|
|
46
|
+
cpus: '0.5'
|
|
47
|
+
reservations:
|
|
48
|
+
memory: 256M
|
|
49
|
+
cpus: '0.25'
|
|
50
|
+
restart_policy:
|
|
51
|
+
condition: on-failure
|
|
52
|
+
delay: 5s
|
|
53
|
+
max_attempts: 3
|
|
54
|
+
window: 120s
|
|
55
|
+
|
|
56
|
+
web:
|
|
57
|
+
deploy:
|
|
58
|
+
replicas: 2
|
|
59
|
+
resources:
|
|
60
|
+
limits:
|
|
61
|
+
memory: 128M
|
|
62
|
+
cpus: '0.25'
|
|
63
|
+
reservations:
|
|
64
|
+
memory: 64M
|
|
65
|
+
cpus: '0.1'
|
|
66
|
+
restart_policy:
|
|
67
|
+
condition: on-failure
|
|
68
|
+
delay: 5s
|
|
69
|
+
max_attempts: 3
|
|
70
|
+
window: 120s
|
|
71
|
+
|
|
72
|
+
# Load balancer for production
|
|
73
|
+
nginx:
|
|
74
|
+
image: nginx:alpine
|
|
75
|
+
container_name: ${PROJECT_NAME:-idealyst}-nginx
|
|
76
|
+
ports:
|
|
77
|
+
- "80:80"
|
|
78
|
+
- "443:443"
|
|
79
|
+
volumes:
|
|
80
|
+
- ./docker/nginx/prod.conf:/etc/nginx/nginx.conf:ro
|
|
81
|
+
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
|
|
82
|
+
- ./logs/nginx:/var/log/nginx
|
|
83
|
+
depends_on:
|
|
84
|
+
- api
|
|
85
|
+
- web
|
|
86
|
+
restart: unless-stopped
|
|
87
|
+
deploy:
|
|
88
|
+
resources:
|
|
89
|
+
limits:
|
|
90
|
+
memory: 128M
|
|
91
|
+
cpus: '0.25'
|
|
92
|
+
reservations:
|
|
93
|
+
memory: 64M
|
|
94
|
+
cpus: '0.1'
|
|
95
|
+
networks:
|
|
96
|
+
- idealyst-network
|
|
97
|
+
|
|
98
|
+
# Monitoring with Prometheus (optional)
|
|
99
|
+
prometheus:
|
|
100
|
+
image: prom/prometheus:latest
|
|
101
|
+
container_name: ${PROJECT_NAME:-idealyst}-prometheus
|
|
102
|
+
ports:
|
|
103
|
+
- "9090:9090"
|
|
104
|
+
volumes:
|
|
105
|
+
- ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
|
106
|
+
- prometheus_data:/prometheus
|
|
107
|
+
command:
|
|
108
|
+
- '--config.file=/etc/prometheus/prometheus.yml'
|
|
109
|
+
- '--storage.tsdb.path=/prometheus'
|
|
110
|
+
- '--web.console.libraries=/etc/prometheus/console_libraries'
|
|
111
|
+
- '--web.console.templates=/etc/prometheus/consoles'
|
|
112
|
+
- '--storage.tsdb.retention.time=200h'
|
|
113
|
+
- '--web.enable-lifecycle'
|
|
114
|
+
restart: unless-stopped
|
|
115
|
+
networks:
|
|
116
|
+
- idealyst-network
|
|
117
|
+
profiles:
|
|
118
|
+
- monitoring
|
|
119
|
+
|
|
120
|
+
# Log aggregation with Grafana (optional)
|
|
121
|
+
grafana:
|
|
122
|
+
image: grafana/grafana:latest
|
|
123
|
+
container_name: ${PROJECT_NAME:-idealyst}-grafana
|
|
124
|
+
ports:
|
|
125
|
+
- "3002:3000"
|
|
126
|
+
volumes:
|
|
127
|
+
- grafana_data:/var/lib/grafana
|
|
128
|
+
- ./docker/grafana/provisioning:/etc/grafana/provisioning
|
|
129
|
+
environment:
|
|
130
|
+
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
|
|
131
|
+
GF_USERS_ALLOW_SIGN_UP: false
|
|
132
|
+
restart: unless-stopped
|
|
133
|
+
networks:
|
|
134
|
+
- idealyst-network
|
|
135
|
+
profiles:
|
|
136
|
+
- monitoring
|
|
137
|
+
|
|
138
|
+
volumes:
|
|
139
|
+
postgres_prod_data:
|
|
140
|
+
redis_prod_data:
|
|
141
|
+
prometheus_data:
|
|
142
|
+
grafana_data:
|
|
143
|
+
|
|
144
|
+
networks:
|
|
145
|
+
idealyst-network:
|
|
146
|
+
driver: bridge
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
# PostgreSQL Database
|
|
5
|
+
postgres:
|
|
6
|
+
image: postgres:15-alpine
|
|
7
|
+
container_name: ${PROJECT_NAME:-idealyst}-postgres
|
|
8
|
+
environment:
|
|
9
|
+
POSTGRES_DB: ${POSTGRES_DB:-idealyst_db}
|
|
10
|
+
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
|
11
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
|
12
|
+
ports:
|
|
13
|
+
- "${POSTGRES_PORT:-5432}:5432"
|
|
14
|
+
volumes:
|
|
15
|
+
- postgres_data:/var/lib/postgresql/data
|
|
16
|
+
- ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
|
|
17
|
+
healthcheck:
|
|
18
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
|
|
19
|
+
interval: 30s
|
|
20
|
+
timeout: 10s
|
|
21
|
+
retries: 5
|
|
22
|
+
networks:
|
|
23
|
+
- idealyst-network
|
|
24
|
+
|
|
25
|
+
# Redis Cache
|
|
26
|
+
redis:
|
|
27
|
+
image: redis:7-alpine
|
|
28
|
+
container_name: ${PROJECT_NAME:-idealyst}-redis
|
|
29
|
+
ports:
|
|
30
|
+
- "${REDIS_PORT:-6379}:6379"
|
|
31
|
+
volumes:
|
|
32
|
+
- redis_data:/data
|
|
33
|
+
healthcheck:
|
|
34
|
+
test: ["CMD", "redis-cli", "ping"]
|
|
35
|
+
interval: 30s
|
|
36
|
+
timeout: 10s
|
|
37
|
+
retries: 5
|
|
38
|
+
networks:
|
|
39
|
+
- idealyst-network
|
|
40
|
+
|
|
41
|
+
# API Service
|
|
42
|
+
api:
|
|
43
|
+
build:
|
|
44
|
+
context: .
|
|
45
|
+
dockerfile: Dockerfile
|
|
46
|
+
target: api-runner
|
|
47
|
+
container_name: ${PROJECT_NAME:-idealyst}-api
|
|
48
|
+
environment:
|
|
49
|
+
NODE_ENV: ${NODE_ENV:-production}
|
|
50
|
+
PORT: 3000
|
|
51
|
+
DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-idealyst_db}
|
|
52
|
+
REDIS_URL: redis://redis:6379
|
|
53
|
+
ports:
|
|
54
|
+
- "${API_PORT:-3000}:3000"
|
|
55
|
+
depends_on:
|
|
56
|
+
postgres:
|
|
57
|
+
condition: service_healthy
|
|
58
|
+
redis:
|
|
59
|
+
condition: service_healthy
|
|
60
|
+
volumes:
|
|
61
|
+
- ./uploads:/app/uploads
|
|
62
|
+
restart: unless-stopped
|
|
63
|
+
healthcheck:
|
|
64
|
+
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
|
65
|
+
interval: 30s
|
|
66
|
+
timeout: 10s
|
|
67
|
+
retries: 5
|
|
68
|
+
networks:
|
|
69
|
+
- idealyst-network
|
|
70
|
+
|
|
71
|
+
# Web Application
|
|
72
|
+
web:
|
|
73
|
+
build:
|
|
74
|
+
context: .
|
|
75
|
+
dockerfile: Dockerfile
|
|
76
|
+
target: web-runner
|
|
77
|
+
container_name: ${PROJECT_NAME:-idealyst}-web
|
|
78
|
+
ports:
|
|
79
|
+
- "${WEB_PORT:-80}:80"
|
|
80
|
+
depends_on:
|
|
81
|
+
- api
|
|
82
|
+
restart: unless-stopped
|
|
83
|
+
healthcheck:
|
|
84
|
+
test: ["CMD", "curl", "-f", "http://localhost"]
|
|
85
|
+
interval: 30s
|
|
86
|
+
timeout: 10s
|
|
87
|
+
retries: 5
|
|
88
|
+
networks:
|
|
89
|
+
- idealyst-network
|
|
90
|
+
|
|
91
|
+
# Development Service (for local development)
|
|
92
|
+
dev:
|
|
93
|
+
build:
|
|
94
|
+
context: .
|
|
95
|
+
dockerfile: Dockerfile
|
|
96
|
+
target: dev
|
|
97
|
+
container_name: ${PROJECT_NAME:-idealyst}-dev
|
|
98
|
+
environment:
|
|
99
|
+
NODE_ENV: development
|
|
100
|
+
ports:
|
|
101
|
+
- "3000:3000" # API dev server
|
|
102
|
+
- "5173:5173" # Vite dev server
|
|
103
|
+
- "8080:8080" # Additional dev server
|
|
104
|
+
- "19006:19006" # Expo dev tools
|
|
105
|
+
volumes:
|
|
106
|
+
- .:/app
|
|
107
|
+
- /app/node_modules
|
|
108
|
+
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
109
|
+
depends_on:
|
|
110
|
+
postgres:
|
|
111
|
+
condition: service_healthy
|
|
112
|
+
redis:
|
|
113
|
+
condition: service_healthy
|
|
114
|
+
networks:
|
|
115
|
+
- idealyst-network
|
|
116
|
+
tty: true
|
|
117
|
+
stdin_open: true
|
|
118
|
+
|
|
119
|
+
# Test Runner
|
|
120
|
+
test:
|
|
121
|
+
build:
|
|
122
|
+
context: .
|
|
123
|
+
dockerfile: Dockerfile
|
|
124
|
+
target: test-runner
|
|
125
|
+
container_name: ${PROJECT_NAME:-idealyst}-test
|
|
126
|
+
environment:
|
|
127
|
+
NODE_ENV: test
|
|
128
|
+
CI: true
|
|
129
|
+
volumes:
|
|
130
|
+
- .:/app
|
|
131
|
+
- /app/node_modules
|
|
132
|
+
networks:
|
|
133
|
+
- idealyst-network
|
|
134
|
+
profiles:
|
|
135
|
+
- testing
|
|
136
|
+
|
|
137
|
+
volumes:
|
|
138
|
+
postgres_data:
|
|
139
|
+
redis_data:
|
|
140
|
+
|
|
141
|
+
networks:
|
|
142
|
+
idealyst-network:
|
|
143
|
+
driver: bridge
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** @type {import('jest').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
projects: [
|
|
4
|
+
'<rootDir>/packages/*/jest.config.js',
|
|
5
|
+
],
|
|
6
|
+
collectCoverage: true,
|
|
7
|
+
collectCoverageFrom: [
|
|
8
|
+
'packages/*/src/**/*.{ts,tsx}',
|
|
9
|
+
'!packages/*/src/**/*.d.ts',
|
|
10
|
+
'!packages/*/src/**/index.ts',
|
|
11
|
+
],
|
|
12
|
+
coverageDirectory: '<rootDir>/coverage',
|
|
13
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
14
|
+
testTimeout: 30000,
|
|
15
|
+
// Ignore template files in node_modules
|
|
16
|
+
testPathIgnorePatterns: [
|
|
17
|
+
'/node_modules/',
|
|
18
|
+
'/templates/',
|
|
19
|
+
],
|
|
20
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{packageName}}",
|
|
3
|
+
"version": "{{version}}",
|
|
4
|
+
"description": "{{description}}",
|
|
5
|
+
"private": true,
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"packages/*"
|
|
8
|
+
],
|
|
9
|
+
"packageManager": "yarn@4.1.0",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "concurrently \"yarn api:dev\" \"yarn web:dev\" \"yarn mobile:start\"",
|
|
12
|
+
"build": "yarn build:packages && yarn api:build && yarn web:build",
|
|
13
|
+
"build:packages": "yarn workspace @{{workspaceScope}}/database build && yarn workspace @{{workspaceScope}}/shared build",
|
|
14
|
+
"api:dev": "yarn workspace @{{workspaceScope}}/api dev",
|
|
15
|
+
"api:build": "yarn workspace @{{workspaceScope}}/api build",
|
|
16
|
+
"web:dev": "yarn workspace @{{workspaceScope}}/web dev",
|
|
17
|
+
"web:build": "yarn workspace @{{workspaceScope}}/web build",
|
|
18
|
+
"mobile:start": "yarn workspace @{{workspaceScope}}/mobile start",
|
|
19
|
+
"mobile:android": "yarn workspace @{{workspaceScope}}/mobile android",
|
|
20
|
+
"mobile:ios": "yarn workspace @{{workspaceScope}}/mobile ios",
|
|
21
|
+
"db:generate": "yarn workspace @{{workspaceScope}}/database db:generate",
|
|
22
|
+
"db:push": "yarn workspace @{{workspaceScope}}/database db:push",
|
|
23
|
+
"db:migrate": "yarn workspace @{{workspaceScope}}/database db:migrate",
|
|
24
|
+
"db:studio": "yarn workspace @{{workspaceScope}}/database db:studio",
|
|
25
|
+
"test": "yarn workspaces foreach --include '@/*' run test",
|
|
26
|
+
"test:watch": "yarn workspaces foreach --include '@/*' run test:watch",
|
|
27
|
+
"test:coverage": "yarn workspaces foreach --include '@/*' run test:coverage",
|
|
28
|
+
"test:ci": "yarn workspaces foreach --include '@/*' --parallel run test --passWithNoTests",
|
|
29
|
+
"type-check": "yarn workspaces run type-check"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@babel/core": "^7.28.0",
|
|
33
|
+
"@babel/preset-env": "^7.28.0",
|
|
34
|
+
"@babel/preset-react": "^7.27.1",
|
|
35
|
+
"@babel/preset-typescript": "^7.27.1",
|
|
36
|
+
"@idealyst/mcp-server": "^{{idealystVersion}}",
|
|
37
|
+
"@mobilenext/mobile-mcp": "^0.0.34",
|
|
38
|
+
"@playwright/mcp": "^0.0.45",
|
|
39
|
+
"@types/jest": "^29.5.12",
|
|
40
|
+
"concurrently": "^8.2.2",
|
|
41
|
+
"jest": "^29.7.0",
|
|
42
|
+
"ts-jest": "^29.1.2",
|
|
43
|
+
"typescript": "^5.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# {{projectName}} API
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
A simplified tRPC API with automatic CRUD generation for Prisma models.
|
|
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';
|
|
13
|
+
|
|
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
|
|
29
|
+
- **Express.js** - Web framework for Node.js
|
|
30
|
+
- **TypeScript** - Type-safe JavaScript
|
|
31
|
+
- **Prisma** - Next-generation ORM for database access
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
1. **Setup environment variables:**
|
|
36
|
+
```bash
|
|
37
|
+
cp .env.example .env
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
2. **Install dependencies:**
|
|
41
|
+
```bash
|
|
42
|
+
yarn install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
3. **Setup database:**
|
|
46
|
+
```bash
|
|
47
|
+
# Generate Prisma client
|
|
48
|
+
yarn prisma:generate
|
|
49
|
+
|
|
50
|
+
# Run migrations
|
|
51
|
+
yarn prisma:migrate
|
|
52
|
+
|
|
53
|
+
# Seed database with sample data
|
|
54
|
+
yarn prisma:seed
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
4. **Start development server:**
|
|
58
|
+
```bash
|
|
59
|
+
yarn dev
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The API will be available at `http://localhost:3000`
|
|
63
|
+
|
|
64
|
+
## Creating CRUD APIs
|
|
65
|
+
|
|
66
|
+
This template provides a simple way to create type-safe APIs with automatic CRUD operations for your Prisma models.
|
|
67
|
+
|
|
68
|
+
### 1. Define Your Prisma Model
|
|
69
|
+
|
|
70
|
+
Add models to `packages/database/schema.prisma`:
|
|
71
|
+
|
|
72
|
+
```prisma
|
|
73
|
+
model User {
|
|
74
|
+
id String @id @default(cuid())
|
|
75
|
+
email String @unique
|
|
76
|
+
name String
|
|
77
|
+
createdAt DateTime @default(now())
|
|
78
|
+
updatedAt DateTime @updatedAt
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. Create a Router
|
|
83
|
+
|
|
84
|
+
Create `src/routers/user.ts`:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { z } from 'zod';
|
|
88
|
+
import { createCrudRouter } from '../lib/crud.js';
|
|
89
|
+
|
|
90
|
+
const createUserSchema = z.object({
|
|
91
|
+
email: z.string().email(),
|
|
92
|
+
name: z.string().min(1),
|
|
93
|
+
});
|
|
94
|
+
|
|
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
|
+
);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. Add to Main Router
|
|
108
|
+
|
|
109
|
+
Update `src/router/index.ts`:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { userRouter } from '../routers/user.js';
|
|
113
|
+
|
|
114
|
+
export const appRouter = router({
|
|
115
|
+
// ... existing routes
|
|
116
|
+
users: userRouter,
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 4. Use in Frontend
|
|
121
|
+
|
|
122
|
+
```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'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Update user
|
|
134
|
+
const updateUser = trpc.users.update.useMutation();
|
|
135
|
+
await updateUser.mutateAsync({
|
|
136
|
+
id: 'user-id',
|
|
137
|
+
data: { name: 'Jane Doe' }
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Delete user
|
|
141
|
+
const deleteUser = trpc.users.delete.useMutation();
|
|
142
|
+
await deleteUser.mutateAsync({ id: 'user-id' });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Generated CRUD Operations
|
|
146
|
+
|
|
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
|
|
155
|
+
|
|
156
|
+
## Available Scripts
|
|
157
|
+
|
|
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
|
|
164
|
+
|
|
165
|
+
## Project Structure
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
src/
|
|
169
|
+
├── router/
|
|
170
|
+
│ └── index.ts # Main router definition
|
|
171
|
+
├── routers/
|
|
172
|
+
│ ├── test.ts # Example CRUD router for Test model
|
|
173
|
+
│ └── user.example.ts # Example CRUD router for User model
|
|
174
|
+
├── 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
|
|
180
|
+
```
|
|
181
|
+
|
|
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
|
|
234
|
+
|
|
235
|
+
This simplified approach removes controller complexity while maintaining full type safety and providing powerful CRUD operations for all your Prisma models.
|
|
236
|
+
|
|
237
|
+
## Environment Variables
|
|
238
|
+
|
|
239
|
+
Create a `.env` file with:
|
|
240
|
+
|
|
241
|
+
```env
|
|
242
|
+
# Database
|
|
243
|
+
DATABASE_URL="file:./dev.db"
|
|
244
|
+
|
|
245
|
+
# API
|
|
246
|
+
PORT=3000
|
|
247
|
+
NODE_ENV=development
|
|
248
|
+
|
|
249
|
+
# Add your environment variables here
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Testing
|
|
253
|
+
|
|
254
|
+
The project includes Jest for testing:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { createContext } from '../src/context.js';
|
|
258
|
+
import { appRouter } from '../src/router/index.js';
|
|
259
|
+
|
|
260
|
+
describe('API Tests', () => {
|
|
261
|
+
test('should get test records', async () => {
|
|
262
|
+
const ctx = createContext({} as any);
|
|
263
|
+
const caller = appRouter.createCaller(ctx);
|
|
264
|
+
|
|
265
|
+
const tests = await caller.test.getAll({});
|
|
266
|
+
expect(tests).toBeDefined();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Run tests with:
|
|
272
|
+
```bash
|
|
273
|
+
yarn test
|
|
274
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import { appRouter } from '../src/router/index.js';
|
|
3
|
+
|
|
4
|
+
describe('API Router', () => {
|
|
5
|
+
it('should have a valid router configuration', () => {
|
|
6
|
+
expect(appRouter).toBeDefined();
|
|
7
|
+
expect(typeof appRouter).toBe('object');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should export the expected router structure', () => {
|
|
11
|
+
// Test that the router has the expected structure
|
|
12
|
+
expect(appRouter._def).toBeDefined();
|
|
13
|
+
expect(appRouter._def.router).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('Sample API Test', () => {
|
|
18
|
+
it('should pass a basic test', () => {
|
|
19
|
+
expect(1 + 1).toBe(2);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should handle async operations', async () => {
|
|
23
|
+
const result = await Promise.resolve('test');
|
|
24
|
+
expect(result).toBe('test');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @type {import('jest').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
roots: ['<rootDir>/src', '<rootDir>/__tests__'],
|
|
6
|
+
testMatch: [
|
|
7
|
+
'**/__tests__/**/*.{ts,tsx,js}',
|
|
8
|
+
'**/*.{test,spec}.{ts,tsx,js}'
|
|
9
|
+
],
|
|
10
|
+
transform: {
|
|
11
|
+
'^.+\\.tsx?$': 'ts-jest',
|
|
12
|
+
},
|
|
13
|
+
collectCoverageFrom: [
|
|
14
|
+
'src/**/*.{ts,tsx}',
|
|
15
|
+
'!src/**/*.d.ts',
|
|
16
|
+
'!src/**/index.ts',
|
|
17
|
+
],
|
|
18
|
+
coverageDirectory: 'coverage',
|
|
19
|
+
coverageReporters: ['text', 'lcov'],
|
|
20
|
+
testTimeout: 10000,
|
|
21
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
|
22
|
+
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
|
23
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Global test setup for API tests
|
|
2
|
+
// This file runs before all tests
|
|
3
|
+
|
|
4
|
+
// Mock environment variables for testing
|
|
5
|
+
process.env.NODE_ENV = 'test';
|
|
6
|
+
process.env.DATABASE_URL = 'file:./test.db';
|
|
7
|
+
|
|
8
|
+
// Increase timeout for async operations
|
|
9
|
+
jest.setTimeout(10000);
|