create-velox-app 0.6.31 → 0.6.51
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/CHANGELOG.md +120 -0
- package/GUIDE.md +230 -0
- package/dist/cli.js +1 -0
- package/dist/index.js +14 -4
- package/dist/templates/auth.js +10 -0
- package/dist/templates/index.js +30 -1
- package/dist/templates/placeholders.js +0 -3
- package/dist/templates/rsc-auth.d.ts +12 -0
- package/dist/templates/rsc-auth.js +208 -0
- package/dist/templates/rsc.js +40 -1
- package/dist/templates/shared/css-generator.d.ts +26 -0
- package/dist/templates/shared/css-generator.js +553 -0
- package/dist/templates/shared/index.d.ts +3 -0
- package/dist/templates/shared/index.js +3 -0
- package/dist/templates/shared/rsc-styles.d.ts +54 -0
- package/dist/templates/shared/rsc-styles.js +68 -0
- package/dist/templates/shared/theme.d.ts +133 -0
- package/dist/templates/shared/theme.js +141 -0
- package/dist/templates/spa.js +10 -0
- package/dist/templates/trpc.js +10 -0
- package/dist/templates/types.d.ts +2 -1
- package/dist/templates/types.js +6 -0
- package/package.json +6 -3
- package/src/templates/source/api/config/database.ts +13 -32
- package/src/templates/source/api/docker-compose.yml +21 -0
- package/src/templates/source/root/CLAUDE.auth.md +6 -0
- package/src/templates/source/root/CLAUDE.default.md +6 -0
- package/src/templates/source/rsc/CLAUDE.md +56 -2
- package/src/templates/source/rsc/app/actions/posts.ts +1 -1
- package/src/templates/source/rsc/app/actions/users.ts +111 -20
- package/src/templates/source/rsc/app/layouts/dashboard.tsx +21 -16
- package/src/templates/source/rsc/app/layouts/marketing.tsx +34 -0
- package/src/templates/source/rsc/app/layouts/minimal-content.tsx +21 -0
- package/src/templates/source/rsc/app/layouts/minimal.tsx +86 -5
- package/src/templates/source/rsc/app/layouts/root.tsx +148 -44
- package/src/templates/source/rsc/docker-compose.yml +21 -0
- package/src/templates/source/rsc/package.json +3 -3
- package/src/templates/source/rsc/src/api/database.ts +13 -32
- package/src/templates/source/rsc/src/api/handler.ts +1 -1
- package/src/templates/source/rsc/src/entry.client.tsx +65 -18
- package/src/templates/source/rsc-auth/CLAUDE.md +230 -0
- package/src/templates/source/rsc-auth/app/actions/auth.ts +112 -0
- package/src/templates/source/rsc-auth/app/actions/users.ts +289 -0
- package/src/templates/source/rsc-auth/app/layouts/dashboard.tsx +132 -0
- package/src/templates/source/rsc-auth/app/layouts/marketing.tsx +59 -0
- package/src/templates/source/rsc-auth/app/layouts/minimal-content.tsx +21 -0
- package/src/templates/source/rsc-auth/app/layouts/minimal.tsx +111 -0
- package/src/templates/source/rsc-auth/app/layouts/root.tsx +355 -0
- package/src/templates/source/rsc-auth/app/pages/_not-found.tsx +15 -0
- package/src/templates/source/rsc-auth/app/pages/auth/login.tsx +198 -0
- package/src/templates/source/rsc-auth/app/pages/auth/register.tsx +225 -0
- package/src/templates/source/rsc-auth/app/pages/dashboard/index.tsx +267 -0
- package/src/templates/source/rsc-auth/app/pages/index.tsx +83 -0
- package/src/templates/source/rsc-auth/app/pages/users.tsx +47 -0
- package/src/templates/source/rsc-auth/app.config.ts +12 -0
- package/src/templates/source/rsc-auth/docker-compose.yml +21 -0
- package/src/templates/source/rsc-auth/env.example +11 -0
- package/src/templates/source/rsc-auth/gitignore +34 -0
- package/src/templates/source/rsc-auth/package.json +44 -0
- package/src/templates/source/rsc-auth/prisma/schema.prisma +23 -0
- package/src/templates/source/rsc-auth/prisma.config.ts +22 -0
- package/src/templates/source/rsc-auth/public/favicon.svg +4 -0
- package/src/templates/source/rsc-auth/src/api/database.ts +129 -0
- package/src/templates/source/rsc-auth/src/api/handler.ts +85 -0
- package/src/templates/source/rsc-auth/src/api/procedures/auth.ts +262 -0
- package/src/templates/source/rsc-auth/src/api/procedures/health.ts +48 -0
- package/src/templates/source/rsc-auth/src/api/procedures/users.ts +87 -0
- package/src/templates/source/rsc-auth/src/api/schemas/auth.ts +79 -0
- package/src/templates/source/rsc-auth/src/api/schemas/user.ts +38 -0
- package/src/templates/source/rsc-auth/src/api/utils/auth.ts +157 -0
- package/src/templates/source/rsc-auth/src/entry.client.tsx +63 -0
- package/src/templates/source/rsc-auth/src/entry.server.tsx +262 -0
- package/src/templates/source/rsc-auth/tsconfig.json +24 -0
- package/src/templates/source/shared/scripts/check-client-imports.sh +75 -0
|
@@ -21,19 +21,84 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
21
21
|
<title>VeloxTS App</title>
|
|
22
22
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
23
23
|
<style>{`
|
|
24
|
-
|
|
24
|
+
/* Global Reset & Dark Mode Base */
|
|
25
|
+
*,
|
|
26
|
+
*::before,
|
|
27
|
+
*::after {
|
|
25
28
|
box-sizing: border-box;
|
|
26
29
|
margin: 0;
|
|
27
30
|
padding: 0;
|
|
31
|
+
font: inherit;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
html {
|
|
35
|
+
font-size: 16px;
|
|
36
|
+
-webkit-font-smoothing: antialiased;
|
|
37
|
+
-moz-osx-font-smoothing: grayscale;
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
body {
|
|
31
|
-
|
|
41
|
+
background: #0a0a0a;
|
|
42
|
+
color: #ededed;
|
|
43
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
|
32
44
|
line-height: 1.6;
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
min-height: 100svh;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
h1, h2, h3, h4, h5, h6 {
|
|
49
|
+
text-wrap: balance;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
p, li, figcaption {
|
|
53
|
+
text-wrap: pretty;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
img, picture, svg, video, canvas {
|
|
57
|
+
max-width: 100%;
|
|
35
58
|
}
|
|
36
59
|
|
|
60
|
+
a {
|
|
61
|
+
color: #00d9ff;
|
|
62
|
+
text-decoration: none;
|
|
63
|
+
transition: opacity 0.2s;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
a:hover {
|
|
67
|
+
opacity: 0.8;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
code {
|
|
71
|
+
font-family: "SF Mono", "Fira Code", "Fira Mono", Menlo, Monaco, "Courier New", monospace;
|
|
72
|
+
background: #1a1a1a;
|
|
73
|
+
padding: 0.2em 0.4em;
|
|
74
|
+
border-radius: 4px;
|
|
75
|
+
font-size: 0.9em;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
::selection {
|
|
79
|
+
background: #00d9ff;
|
|
80
|
+
color: #000;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
::-webkit-scrollbar {
|
|
84
|
+
width: 8px;
|
|
85
|
+
height: 8px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
::-webkit-scrollbar-track {
|
|
89
|
+
background: #111;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
::-webkit-scrollbar-thumb {
|
|
93
|
+
background: #333;
|
|
94
|
+
border-radius: 4px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
::-webkit-scrollbar-thumb:hover {
|
|
98
|
+
background: #444;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Layout Structure */
|
|
37
102
|
.layout {
|
|
38
103
|
min-height: 100vh;
|
|
39
104
|
display: flex;
|
|
@@ -41,8 +106,9 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
41
106
|
}
|
|
42
107
|
|
|
43
108
|
.nav {
|
|
44
|
-
background: #
|
|
109
|
+
background: #111;
|
|
45
110
|
padding: 1rem 2rem;
|
|
111
|
+
border-bottom: 1px solid #222;
|
|
46
112
|
}
|
|
47
113
|
|
|
48
114
|
.nav-list {
|
|
@@ -51,17 +117,21 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
51
117
|
list-style: none;
|
|
52
118
|
max-width: 1200px;
|
|
53
119
|
margin: 0 auto;
|
|
120
|
+
align-items: center;
|
|
54
121
|
}
|
|
55
122
|
|
|
56
123
|
.nav-link {
|
|
57
|
-
color: #
|
|
124
|
+
color: #ededed;
|
|
58
125
|
text-decoration: none;
|
|
59
126
|
font-weight: 500;
|
|
60
127
|
transition: color 0.2s;
|
|
128
|
+
padding: 0.5rem 0.75rem;
|
|
129
|
+
border-radius: 4px;
|
|
61
130
|
}
|
|
62
131
|
|
|
63
132
|
.nav-link:hover {
|
|
64
|
-
color: #
|
|
133
|
+
color: #00d9ff;
|
|
134
|
+
background: #1a1a1a;
|
|
65
135
|
}
|
|
66
136
|
|
|
67
137
|
.main {
|
|
@@ -73,14 +143,15 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
73
143
|
}
|
|
74
144
|
|
|
75
145
|
.footer {
|
|
76
|
-
background: #
|
|
77
|
-
color: #
|
|
146
|
+
background: #111;
|
|
147
|
+
color: #888;
|
|
78
148
|
text-align: center;
|
|
79
149
|
padding: 1rem;
|
|
80
150
|
font-size: 0.875rem;
|
|
151
|
+
border-top: 1px solid #222;
|
|
81
152
|
}
|
|
82
153
|
|
|
83
|
-
/* Page
|
|
154
|
+
/* Home Page Hero */
|
|
84
155
|
.home-page .hero {
|
|
85
156
|
text-align: center;
|
|
86
157
|
padding: 3rem 0;
|
|
@@ -88,50 +159,64 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
88
159
|
|
|
89
160
|
.home-page h1 {
|
|
90
161
|
font-size: 2.5rem;
|
|
91
|
-
margin-bottom: 0.
|
|
162
|
+
margin-bottom: 0.75rem;
|
|
163
|
+
font-weight: 700;
|
|
92
164
|
}
|
|
93
165
|
|
|
94
166
|
.home-page .tagline {
|
|
95
|
-
color: #
|
|
167
|
+
color: #888;
|
|
96
168
|
font-size: 1.25rem;
|
|
97
169
|
}
|
|
98
170
|
|
|
171
|
+
/* Stats Cards */
|
|
99
172
|
.stats {
|
|
100
173
|
display: flex;
|
|
101
174
|
gap: 1rem;
|
|
102
175
|
justify-content: center;
|
|
103
176
|
margin: 2rem 0;
|
|
177
|
+
flex-wrap: wrap;
|
|
104
178
|
}
|
|
105
179
|
|
|
106
180
|
.stat-card {
|
|
107
|
-
background:
|
|
181
|
+
background: #111;
|
|
108
182
|
padding: 1.5rem 2rem;
|
|
109
183
|
border-radius: 8px;
|
|
110
|
-
|
|
184
|
+
border: 1px solid #222;
|
|
111
185
|
text-align: center;
|
|
186
|
+
min-width: 150px;
|
|
187
|
+
transition: border-color 0.2s;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.stat-card:hover {
|
|
191
|
+
border-color: #00d9ff;
|
|
112
192
|
}
|
|
113
193
|
|
|
114
194
|
.stat-value {
|
|
115
195
|
display: block;
|
|
116
196
|
font-size: 2rem;
|
|
117
197
|
font-weight: 700;
|
|
118
|
-
color: #
|
|
198
|
+
color: #00d9ff;
|
|
119
199
|
}
|
|
120
200
|
|
|
121
201
|
.stat-label {
|
|
122
|
-
color: #
|
|
202
|
+
color: #888;
|
|
123
203
|
font-size: 0.875rem;
|
|
204
|
+
margin-top: 0.5rem;
|
|
124
205
|
}
|
|
125
206
|
|
|
207
|
+
/* Features Section */
|
|
126
208
|
.features {
|
|
127
|
-
background:
|
|
209
|
+
background: #111;
|
|
128
210
|
padding: 2rem;
|
|
129
211
|
border-radius: 8px;
|
|
130
|
-
|
|
212
|
+
border: 1px solid #222;
|
|
213
|
+
margin-top: 2rem;
|
|
131
214
|
}
|
|
132
215
|
|
|
133
216
|
.features h2 {
|
|
134
|
-
margin-bottom:
|
|
217
|
+
margin-bottom: 1.5rem;
|
|
218
|
+
font-size: 1.5rem;
|
|
219
|
+
font-weight: 600;
|
|
135
220
|
}
|
|
136
221
|
|
|
137
222
|
.features ul {
|
|
@@ -139,8 +224,9 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
139
224
|
}
|
|
140
225
|
|
|
141
226
|
.features li {
|
|
142
|
-
padding: 0.
|
|
143
|
-
border-bottom: 1px solid #
|
|
227
|
+
padding: 0.75rem 0;
|
|
228
|
+
border-bottom: 1px solid #222;
|
|
229
|
+
color: #ededed;
|
|
144
230
|
}
|
|
145
231
|
|
|
146
232
|
.features li:last-child {
|
|
@@ -150,27 +236,23 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
150
236
|
.cta {
|
|
151
237
|
text-align: center;
|
|
152
238
|
margin-top: 2rem;
|
|
153
|
-
color: #
|
|
239
|
+
color: #888;
|
|
154
240
|
}
|
|
155
241
|
|
|
156
|
-
|
|
157
|
-
background: #f0f0f0;
|
|
158
|
-
padding: 0.2rem 0.4rem;
|
|
159
|
-
border-radius: 4px;
|
|
160
|
-
font-family: monospace;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/* Users page */
|
|
242
|
+
/* Users Page */
|
|
164
243
|
.users-page h1 {
|
|
165
|
-
margin-bottom:
|
|
244
|
+
margin-bottom: 1.5rem;
|
|
245
|
+
font-size: 2rem;
|
|
246
|
+
font-weight: 700;
|
|
166
247
|
}
|
|
167
248
|
|
|
168
249
|
.empty-state {
|
|
169
|
-
color: #
|
|
250
|
+
color: #888;
|
|
170
251
|
padding: 2rem;
|
|
171
252
|
text-align: center;
|
|
172
|
-
background:
|
|
253
|
+
background: #111;
|
|
173
254
|
border-radius: 8px;
|
|
255
|
+
border: 1px solid #222;
|
|
174
256
|
}
|
|
175
257
|
|
|
176
258
|
.user-list {
|
|
@@ -180,21 +262,48 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
180
262
|
}
|
|
181
263
|
|
|
182
264
|
.user-card {
|
|
183
|
-
background:
|
|
265
|
+
background: #111;
|
|
184
266
|
padding: 1rem;
|
|
185
267
|
border-radius: 8px;
|
|
186
|
-
|
|
268
|
+
border: 1px solid #222;
|
|
187
269
|
display: flex;
|
|
188
270
|
justify-content: space-between;
|
|
189
271
|
align-items: center;
|
|
272
|
+
transition: border-color 0.2s;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.user-card:hover {
|
|
276
|
+
border-color: #00d9ff;
|
|
190
277
|
}
|
|
191
278
|
|
|
192
279
|
.user-name {
|
|
193
280
|
font-weight: 600;
|
|
281
|
+
color: #ededed;
|
|
194
282
|
}
|
|
195
283
|
|
|
196
284
|
.user-email {
|
|
197
|
-
color: #
|
|
285
|
+
color: #888;
|
|
286
|
+
font-size: 0.875rem;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/* About Page */
|
|
290
|
+
.about-page h1 {
|
|
291
|
+
font-size: 2rem;
|
|
292
|
+
margin-bottom: 1.5rem;
|
|
293
|
+
font-weight: 700;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.about-page h2 {
|
|
297
|
+
font-size: 1.5rem;
|
|
298
|
+
margin-top: 2rem;
|
|
299
|
+
margin-bottom: 1rem;
|
|
300
|
+
font-weight: 600;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.about-page p {
|
|
304
|
+
color: #888;
|
|
305
|
+
margin-bottom: 1rem;
|
|
306
|
+
line-height: 1.8;
|
|
198
307
|
}
|
|
199
308
|
`}</style>
|
|
200
309
|
</head>
|
|
@@ -213,8 +322,8 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
213
322
|
</a>
|
|
214
323
|
</li>
|
|
215
324
|
<li>
|
|
216
|
-
<a href="/
|
|
217
|
-
|
|
325
|
+
<a href="/about" className="nav-link">
|
|
326
|
+
About
|
|
218
327
|
</a>
|
|
219
328
|
</li>
|
|
220
329
|
<li>
|
|
@@ -222,11 +331,6 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
222
331
|
Docs
|
|
223
332
|
</a>
|
|
224
333
|
</li>
|
|
225
|
-
<li>
|
|
226
|
-
<a href="/api/health" className="nav-link">
|
|
227
|
-
API
|
|
228
|
-
</a>
|
|
229
|
-
</li>
|
|
230
334
|
</ul>
|
|
231
335
|
</nav>
|
|
232
336
|
|
|
@@ -234,7 +338,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
234
338
|
|
|
235
339
|
<footer className="footer">Built with VeloxTS • React Server Components</footer>
|
|
236
340
|
</div>
|
|
237
|
-
<script src="/_build/entry.client.
|
|
341
|
+
<script src="/_build/src/entry.client.tsx" type="module" />
|
|
238
342
|
</body>
|
|
239
343
|
</html>
|
|
240
344
|
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16-alpine
|
|
4
|
+
container_name: __PROJECT_NAME__-postgres
|
|
5
|
+
restart: unless-stopped
|
|
6
|
+
environment:
|
|
7
|
+
POSTGRES_USER: ${DATABASE_USER:-user}
|
|
8
|
+
POSTGRES_PASSWORD: ${DATABASE_PASSWORD:-password}
|
|
9
|
+
POSTGRES_DB: ${DATABASE_NAME:-__PROJECT_NAME__}
|
|
10
|
+
ports:
|
|
11
|
+
- "${DATABASE_PORT:-5432}:5432"
|
|
12
|
+
volumes:
|
|
13
|
+
- postgres_data:/var/lib/postgresql/data
|
|
14
|
+
healthcheck:
|
|
15
|
+
test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USER:-user} -d ${DATABASE_NAME:-__PROJECT_NAME__}"]
|
|
16
|
+
interval: 10s
|
|
17
|
+
timeout: 5s
|
|
18
|
+
retries: 5
|
|
19
|
+
|
|
20
|
+
volumes:
|
|
21
|
+
postgres_data:
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"db:push": "prisma db push",
|
|
12
12
|
"db:migrate": "prisma migrate dev",
|
|
13
13
|
"db:studio": "prisma studio",
|
|
14
|
-
"type-check": "tsc --noEmit"
|
|
14
|
+
"type-check": "tsc --noEmit",
|
|
15
|
+
"lint:client": "./scripts/check-client-imports.sh"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
17
18
|
"@prisma/adapter-better-sqlite3": "7.2.0",
|
|
@@ -35,7 +36,6 @@
|
|
|
35
36
|
"@types/react": "19.2.7",
|
|
36
37
|
"@types/react-dom": "19.2.3",
|
|
37
38
|
"prisma": "7.2.0",
|
|
38
|
-
"typescript": "5.9.3"
|
|
39
|
-
"vite": "7.3.0"
|
|
39
|
+
"typescript": "5.9.3"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -23,7 +23,6 @@ import type { PrismaPg as PrismaPgType } from '@prisma/adapter-pg';
|
|
|
23
23
|
/* @endif postgresql */
|
|
24
24
|
import type { PrismaClient as PrismaClientType } from '@prisma/client';
|
|
25
25
|
import dotenv from 'dotenv';
|
|
26
|
-
import type { Pool as PoolType } from 'pg';
|
|
27
26
|
|
|
28
27
|
// Runtime imports using createRequire for Node.js v24+ CJS interop
|
|
29
28
|
const require = createRequire(import.meta.url);
|
|
@@ -36,9 +35,6 @@ const { PrismaBetterSqlite3 } = require('@prisma/adapter-better-sqlite3') as {
|
|
|
36
35
|
const { PrismaPg } = require('@prisma/adapter-pg') as {
|
|
37
36
|
PrismaPg: typeof PrismaPgType;
|
|
38
37
|
};
|
|
39
|
-
const { Pool } = require('pg') as {
|
|
40
|
-
Pool: typeof PoolType;
|
|
41
|
-
};
|
|
42
38
|
/* @endif postgresql */
|
|
43
39
|
const { PrismaClient } = require('@prisma/client') as {
|
|
44
40
|
PrismaClient: typeof PrismaClientType;
|
|
@@ -56,10 +52,6 @@ declare global {
|
|
|
56
52
|
// Allow global `var` declarations for hot reload in development
|
|
57
53
|
// eslint-disable-next-line no-var
|
|
58
54
|
var __db: PrismaClient | undefined;
|
|
59
|
-
/* @if postgresql */
|
|
60
|
-
// eslint-disable-next-line no-var
|
|
61
|
-
var __pool: InstanceType<typeof PoolType> | undefined;
|
|
62
|
-
/* @endif postgresql */
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
/* @if sqlite */
|
|
@@ -88,32 +80,24 @@ function createPrismaClient(): PrismaClient {
|
|
|
88
80
|
/* @endif sqlite */
|
|
89
81
|
/* @if postgresql */
|
|
90
82
|
/**
|
|
91
|
-
* Create a PostgreSQL
|
|
92
|
-
*
|
|
83
|
+
* Create a Prisma client instance using the PostgreSQL adapter.
|
|
84
|
+
*
|
|
85
|
+
* Prisma 7 Breaking Change:
|
|
86
|
+
* - PrismaPg now takes connectionString directly (not a Pool instance)
|
|
87
|
+
* - Pool management is handled internally by the adapter
|
|
93
88
|
*/
|
|
94
|
-
function
|
|
95
|
-
const
|
|
89
|
+
function createPrismaClient(): PrismaClient {
|
|
90
|
+
const connectionString = process.env.DATABASE_URL;
|
|
96
91
|
|
|
97
|
-
if (!
|
|
92
|
+
if (!connectionString) {
|
|
98
93
|
throw new Error(
|
|
99
94
|
'[VeloxTS] DATABASE_URL environment variable is not set. ' +
|
|
100
95
|
'Ensure .env file exists in project root with DATABASE_URL defined.'
|
|
101
96
|
);
|
|
102
97
|
}
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
max: 10, // Maximum connections in pool
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Create a Prisma client instance using the PostgreSQL adapter.
|
|
112
|
-
* Uses connection pooling for efficient database access.
|
|
113
|
-
*/
|
|
114
|
-
function createPrismaClient(pool: InstanceType<typeof PoolType>): PrismaClient {
|
|
115
|
-
// Prisma 7 requires driver adapters for direct connections
|
|
116
|
-
const adapter = new PrismaPg(pool);
|
|
99
|
+
// Prisma 7: Pass connectionString directly to PrismaPg (not a Pool)
|
|
100
|
+
const adapter = new PrismaPg({ connectionString });
|
|
117
101
|
return new PrismaClient({ adapter });
|
|
118
102
|
}
|
|
119
103
|
/* @endif postgresql */
|
|
@@ -127,19 +111,16 @@ if (process.env.NODE_ENV !== 'production') {
|
|
|
127
111
|
}
|
|
128
112
|
/* @endif sqlite */
|
|
129
113
|
/* @if postgresql */
|
|
130
|
-
// Use global
|
|
131
|
-
const
|
|
132
|
-
export const db = globalThis.__db ?? createPrismaClient(pool);
|
|
114
|
+
// Use global singleton for hot reload in development
|
|
115
|
+
export const db = globalThis.__db ?? createPrismaClient();
|
|
133
116
|
|
|
134
117
|
if (process.env.NODE_ENV !== 'production') {
|
|
135
|
-
globalThis.__pool = pool;
|
|
136
118
|
globalThis.__db = db;
|
|
137
119
|
}
|
|
138
120
|
|
|
139
|
-
// Graceful shutdown -
|
|
121
|
+
// Graceful shutdown - disconnect Prisma on process exit
|
|
140
122
|
const shutdown = async () => {
|
|
141
123
|
await db.$disconnect();
|
|
142
|
-
await pool.end();
|
|
143
124
|
process.exit(0);
|
|
144
125
|
};
|
|
145
126
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { veloxApp } from '@veloxts/core';
|
|
14
14
|
import { databasePlugin } from '@veloxts/orm';
|
|
15
15
|
import { rest } from '@veloxts/router';
|
|
16
|
-
import { createH3ApiHandler } from '@veloxts/web';
|
|
16
|
+
import { createH3ApiHandler } from '@veloxts/web/adapters';
|
|
17
17
|
|
|
18
18
|
import { db } from './database.js';
|
|
19
19
|
import { healthProcedures } from './procedures/health.js';
|
|
@@ -1,28 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client Entry Point
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Handles client-side hydration for pages marked with 'use client'.
|
|
5
|
+
* Server Components remain as static HTML without hydration.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: The hydration tree must match exactly what the server rendered.
|
|
8
|
+
* Server renders: <div id="root"><MinimalContent><Page /></MinimalContent></div>
|
|
9
|
+
* Client hydrates: <MinimalContent><Page /></MinimalContent>
|
|
10
|
+
*
|
|
11
|
+
* This template has no client components by default.
|
|
12
|
+
* To add interactive pages:
|
|
13
|
+
* 1. Create a page with 'use client' directive
|
|
14
|
+
* 2. Import it here
|
|
15
|
+
* 3. Add it to clientRoutes with its layout
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* import MyPage from '../app/pages/my-page.tsx';
|
|
19
|
+
* import MinimalContent from '../app/layouts/minimal-content.tsx';
|
|
20
|
+
*
|
|
21
|
+
* const clientRoutes: Record<string, ClientRoute> = {
|
|
22
|
+
* '/my-page': { Page: MyPage, Layout: MinimalContent },
|
|
23
|
+
* };
|
|
7
24
|
*/
|
|
8
25
|
|
|
9
|
-
import {
|
|
26
|
+
import { StrictMode } from 'react';
|
|
27
|
+
import { hydrateRoot } from 'react-dom/client';
|
|
28
|
+
|
|
29
|
+
// Import content layouts (shared between server and client)
|
|
30
|
+
import MinimalContent from '../app/layouts/minimal-content.tsx';
|
|
31
|
+
|
|
32
|
+
// Route mapping for client pages with their layouts
|
|
33
|
+
// The layout must match what the server rendered inside <div id="root">
|
|
34
|
+
interface ClientRoute {
|
|
35
|
+
Page: React.ComponentType;
|
|
36
|
+
Layout: React.ComponentType<{ children: React.ReactNode }>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const clientRoutes: Record<string, ClientRoute> = {
|
|
40
|
+
// No client pages by default in the RSC template
|
|
41
|
+
// Example: '/my-page': { Page: MyPage, Layout: MinimalContent },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Suppress unused variable warning - MinimalContent is imported for documentation
|
|
45
|
+
void MinimalContent;
|
|
10
46
|
|
|
11
47
|
const rootElement = document.getElementById('root');
|
|
48
|
+
const pathname = window.location.pathname;
|
|
12
49
|
|
|
13
|
-
if
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
// Only hydrate if this is a client page
|
|
51
|
+
const match = clientRoutes[pathname];
|
|
52
|
+
|
|
53
|
+
if (rootElement && match) {
|
|
54
|
+
const { Page, Layout } = match;
|
|
55
|
+
|
|
56
|
+
// Hydrate with the exact same tree structure as server rendered
|
|
57
|
+
hydrateRoot(
|
|
58
|
+
rootElement,
|
|
59
|
+
<StrictMode>
|
|
60
|
+
<Layout>
|
|
61
|
+
<Page />
|
|
62
|
+
</Layout>
|
|
63
|
+
</StrictMode>,
|
|
64
|
+
{
|
|
65
|
+
onRecoverableError: (error: unknown) => {
|
|
66
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
67
|
+
console.warn('[VeloxTS] Hydration warning:', error);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
}
|
|
27
71
|
);
|
|
72
|
+
} else {
|
|
73
|
+
// Server-only page, no hydration needed
|
|
74
|
+
console.debug('[VeloxTS] Server-rendered page, no client hydration needed');
|
|
28
75
|
}
|