create-velox-app 0.4.14 → 0.6.23
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 +2 -43
- package/dist/cli.js +23 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.js +26 -8
- package/dist/index.js.map +1 -1
- package/dist/templates/auth.d.ts.map +1 -1
- package/dist/templates/auth.js +24 -0
- package/dist/templates/auth.js.map +1 -1
- package/dist/templates/fullstack.d.ts +15 -0
- package/dist/templates/fullstack.d.ts.map +1 -0
- package/dist/templates/fullstack.js +110 -0
- package/dist/templates/fullstack.js.map +1 -0
- package/dist/templates/index.d.ts +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +27 -5
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/placeholders.d.ts +6 -1
- package/dist/templates/placeholders.d.ts.map +1 -1
- package/dist/templates/placeholders.js +15 -5
- package/dist/templates/placeholders.js.map +1 -1
- package/dist/templates/rsc.d.ts +15 -0
- package/dist/templates/rsc.d.ts.map +1 -0
- package/dist/templates/rsc.js +192 -0
- package/dist/templates/rsc.js.map +1 -0
- package/dist/templates/shared/root.d.ts +1 -0
- package/dist/templates/shared/root.d.ts.map +1 -1
- package/dist/templates/shared/root.js +4 -0
- package/dist/templates/shared/root.js.map +1 -1
- package/dist/templates/spa.d.ts +12 -0
- package/dist/templates/spa.d.ts.map +1 -0
- package/dist/templates/spa.js +101 -0
- package/dist/templates/spa.js.map +1 -0
- package/dist/templates/trpc.d.ts.map +1 -1
- package/dist/templates/trpc.js +16 -0
- package/dist/templates/trpc.js.map +1 -1
- package/dist/templates/types.d.ts +14 -1
- package/dist/templates/types.d.ts.map +1 -1
- package/dist/templates/types.js +35 -10
- package/dist/templates/types.js.map +1 -1
- package/package.json +3 -3
- package/src/templates/source/api/config/auth.ts +2 -2
- package/src/templates/source/api/config/database.ts +44 -10
- package/src/templates/source/api/index.auth.ts +10 -16
- package/src/templates/source/api/index.default.ts +10 -9
- package/src/templates/source/api/index.trpc.ts +9 -28
- package/src/templates/source/api/package.auth.json +7 -6
- package/src/templates/source/api/package.default.json +5 -4
- package/src/templates/source/api/prisma/schema.auth.prisma +3 -2
- package/src/templates/source/api/prisma/schema.default.prisma +3 -2
- package/src/templates/source/api/prisma.config.ts +7 -1
- package/src/templates/source/api/procedures/auth.ts +38 -66
- package/src/templates/source/api/procedures/health.ts +4 -9
- package/src/templates/source/api/procedures/users.auth.ts +7 -11
- package/src/templates/source/api/procedures/users.default.ts +7 -11
- package/src/templates/source/api/router.auth.ts +31 -0
- package/src/templates/source/api/router.default.ts +29 -0
- package/src/templates/source/api/router.trpc.ts +37 -0
- package/src/templates/source/api/router.types.auth.ts +88 -0
- package/src/templates/source/api/router.types.default.ts +73 -0
- package/src/templates/source/api/router.types.trpc.ts +73 -0
- package/src/templates/source/api/routes.auth.ts +66 -0
- package/src/templates/source/api/routes.default.ts +53 -0
- package/src/templates/source/api/schemas/auth.ts +79 -0
- package/src/templates/source/api/schemas/health.ts +21 -0
- package/src/templates/source/api/schemas/user.ts +62 -12
- package/src/templates/source/api/utils/auth.ts +157 -0
- package/src/templates/source/root/.cursorrules +187 -0
- package/src/templates/source/root/CLAUDE.auth.md +264 -0
- package/src/templates/source/root/CLAUDE.default.md +185 -0
- package/src/templates/source/root/package.json +7 -1
- package/src/templates/source/rsc/CLAUDE.md +104 -0
- package/src/templates/source/rsc/app/actions/posts.ts +93 -0
- package/src/templates/source/rsc/app/actions/users.ts +83 -0
- package/src/templates/source/rsc/app/layouts/dashboard.tsx +127 -0
- package/src/templates/source/rsc/app/layouts/marketing.tsx +25 -0
- package/src/templates/source/rsc/app/layouts/minimal.tsx +30 -0
- package/src/templates/source/rsc/app/layouts/root.tsx +241 -0
- package/src/templates/source/rsc/app/pages/(dashboard)/profile.tsx +71 -0
- package/src/templates/source/rsc/app/pages/(dashboard)/settings.tsx +104 -0
- package/src/templates/source/rsc/app/pages/(marketing)/about.tsx +52 -0
- package/src/templates/source/rsc/app/pages/_not-found.tsx +149 -0
- package/src/templates/source/rsc/app/pages/docs/[...slug].tsx +211 -0
- package/src/templates/source/rsc/app/pages/index.tsx +50 -0
- package/src/templates/source/rsc/app/pages/print.tsx +80 -0
- package/src/templates/source/rsc/app/pages/users/[id]/posts/[postId].tsx +89 -0
- package/src/templates/source/rsc/app/pages/users/[id]/posts/index.tsx +76 -0
- package/src/templates/source/rsc/app/pages/users/[id]/posts/new.tsx +79 -0
- package/src/templates/source/rsc/app/pages/users/[id].tsx +64 -0
- package/src/templates/source/rsc/app/pages/users/_layout.tsx +104 -0
- package/src/templates/source/rsc/app/pages/users.tsx +44 -0
- package/src/templates/source/rsc/app.config.ts +12 -0
- package/src/templates/source/rsc/env.example +6 -0
- package/src/templates/source/rsc/gitignore +34 -0
- package/src/templates/source/rsc/package.json +41 -0
- package/src/templates/source/rsc/prisma/schema.prisma +34 -0
- package/src/templates/source/rsc/prisma.config.ts +22 -0
- package/src/templates/source/rsc/public/favicon.svg +4 -0
- package/src/templates/source/rsc/src/api/database.ts +72 -0
- package/src/templates/source/rsc/src/api/handler.ts +53 -0
- package/src/templates/source/rsc/src/api/procedures/health.ts +48 -0
- package/src/templates/source/rsc/src/api/procedures/posts.ts +151 -0
- package/src/templates/source/rsc/src/api/procedures/users.ts +87 -0
- package/src/templates/source/rsc/src/api/schemas/post.ts +53 -0
- package/src/templates/source/rsc/src/api/schemas/user.ts +38 -0
- package/src/templates/source/rsc/src/entry.client.tsx +28 -0
- package/src/templates/source/rsc/src/entry.server.tsx +304 -0
- package/src/templates/source/rsc/tsconfig.json +24 -0
- package/src/templates/source/web/App.module.css +1 -1
- package/src/templates/source/web/api.ts +8 -1
- package/src/templates/source/web/main.tsx +4 -4
- package/src/templates/source/web/package.json +6 -6
- package/src/templates/source/web/routes/__root.tsx +2 -2
- package/src/templates/source/web/routes/index.auth.tsx +3 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Users Segment Layout
|
|
3
|
+
*
|
|
4
|
+
* A directory-based layout that wraps all pages under /users/*.
|
|
5
|
+
* Demonstrates segment layouts (also called per-directory layouts).
|
|
6
|
+
*
|
|
7
|
+
* Layout inheritance chain for /users/[id]/posts:
|
|
8
|
+
* RootLayout -> UsersLayout -> Page
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ReactNode } from 'react';
|
|
12
|
+
|
|
13
|
+
interface UsersLayoutProps {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
params?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function UsersLayout({ children }: UsersLayoutProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="users-layout">
|
|
21
|
+
<style>{`
|
|
22
|
+
.users-layout {
|
|
23
|
+
min-height: 60vh;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.users-header {
|
|
27
|
+
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
|
28
|
+
color: white;
|
|
29
|
+
padding: 1.5rem 2rem;
|
|
30
|
+
border-radius: 8px 8px 0 0;
|
|
31
|
+
margin: -2rem -2rem 2rem -2rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.users-header h2 {
|
|
35
|
+
font-size: 0.875rem;
|
|
36
|
+
text-transform: uppercase;
|
|
37
|
+
opacity: 0.8;
|
|
38
|
+
margin-bottom: 0.25rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.users-header nav {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 1.5rem;
|
|
44
|
+
margin-top: 1rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.users-header a {
|
|
48
|
+
color: white;
|
|
49
|
+
text-decoration: none;
|
|
50
|
+
opacity: 0.8;
|
|
51
|
+
font-size: 0.875rem;
|
|
52
|
+
transition: opacity 0.2s;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.users-header a:hover {
|
|
56
|
+
opacity: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.users-breadcrumb {
|
|
60
|
+
font-size: 0.875rem;
|
|
61
|
+
color: rgba(255,255,255,0.7);
|
|
62
|
+
margin-top: 0.5rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.users-breadcrumb a {
|
|
66
|
+
color: rgba(255,255,255,0.7);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.users-content {
|
|
70
|
+
background: white;
|
|
71
|
+
border-radius: 0 0 8px 8px;
|
|
72
|
+
padding: 2rem;
|
|
73
|
+
margin: -2rem -2rem 0 -2rem;
|
|
74
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.segment-badge {
|
|
78
|
+
display: inline-block;
|
|
79
|
+
background: #e0e7ff;
|
|
80
|
+
color: #4338ca;
|
|
81
|
+
font-size: 0.625rem;
|
|
82
|
+
text-transform: uppercase;
|
|
83
|
+
padding: 0.25rem 0.5rem;
|
|
84
|
+
border-radius: 4px;
|
|
85
|
+
margin-bottom: 1rem;
|
|
86
|
+
}
|
|
87
|
+
`}</style>
|
|
88
|
+
|
|
89
|
+
<header className="users-header">
|
|
90
|
+
<h2>User Management</h2>
|
|
91
|
+
<nav>
|
|
92
|
+
<a href="/users">All Users</a>
|
|
93
|
+
<a href="/">Home</a>
|
|
94
|
+
<a href="/settings">Settings</a>
|
|
95
|
+
</nav>
|
|
96
|
+
</header>
|
|
97
|
+
|
|
98
|
+
<div className="users-content">
|
|
99
|
+
<span className="segment-badge">Users Section</span>
|
|
100
|
+
{children}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Users Page
|
|
3
|
+
*
|
|
4
|
+
* A React Server Component that runs on the server at request time.
|
|
5
|
+
* Demonstrates direct database access from RSC.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { db } from '../../src/api/database.js';
|
|
9
|
+
|
|
10
|
+
export default async function UsersPage() {
|
|
11
|
+
const users = await db.user.findMany({
|
|
12
|
+
orderBy: { createdAt: 'desc' },
|
|
13
|
+
take: 10,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="users-page">
|
|
18
|
+
<h1>Users</h1>
|
|
19
|
+
|
|
20
|
+
{users.length === 0 ? (
|
|
21
|
+
<p className="empty-state">
|
|
22
|
+
No users yet. Create one via the API at <code>POST /api/users</code>
|
|
23
|
+
</p>
|
|
24
|
+
) : (
|
|
25
|
+
<ul className="user-list">
|
|
26
|
+
{users.map((user) => (
|
|
27
|
+
<li key={user.id} className="user-card">
|
|
28
|
+
<a href={`/users/${user.id}`} className="user-link">
|
|
29
|
+
<span className="user-name">{user.name}</span>
|
|
30
|
+
<span className="user-email">{user.email}</span>
|
|
31
|
+
</a>
|
|
32
|
+
</li>
|
|
33
|
+
))}
|
|
34
|
+
</ul>
|
|
35
|
+
)}
|
|
36
|
+
|
|
37
|
+
<footer className="cta">
|
|
38
|
+
<p>
|
|
39
|
+
Edit <code>app/pages/users.tsx</code> to customize this page.
|
|
40
|
+
</p>
|
|
41
|
+
</footer>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vinxi Application Configuration
|
|
3
|
+
*
|
|
4
|
+
* VeloxTS full-stack application with React Server Components.
|
|
5
|
+
* All options have sensible defaults - only specify what you need to change.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineVeloxApp } from '@veloxts/web';
|
|
9
|
+
|
|
10
|
+
export default defineVeloxApp({
|
|
11
|
+
port: __API_PORT__,
|
|
12
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
.vinxi/
|
|
7
|
+
.output/
|
|
8
|
+
|
|
9
|
+
# Database
|
|
10
|
+
*.db
|
|
11
|
+
*.db-journal
|
|
12
|
+
|
|
13
|
+
# Environment
|
|
14
|
+
.env
|
|
15
|
+
.env.local
|
|
16
|
+
.env.*.local
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.vscode/
|
|
20
|
+
.idea/
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
|
|
24
|
+
# OS
|
|
25
|
+
.DS_Store
|
|
26
|
+
Thumbs.db
|
|
27
|
+
|
|
28
|
+
# Logs
|
|
29
|
+
*.log
|
|
30
|
+
npm-debug.log*
|
|
31
|
+
pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# TypeScript
|
|
34
|
+
*.tsbuildinfo
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vinxi dev --port __API_PORT__",
|
|
8
|
+
"build": "vinxi build",
|
|
9
|
+
"start": "vinxi start --port __API_PORT__",
|
|
10
|
+
"db:generate": "prisma generate",
|
|
11
|
+
"db:push": "prisma db push",
|
|
12
|
+
"db:migrate": "prisma migrate dev",
|
|
13
|
+
"db:studio": "prisma studio",
|
|
14
|
+
"type-check": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@prisma/adapter-better-sqlite3": "7.2.0",
|
|
18
|
+
"@prisma/client": "7.2.0",
|
|
19
|
+
"@prisma/client-runtime-utils": "7.2.0",
|
|
20
|
+
"better-sqlite3": "12.5.0",
|
|
21
|
+
"@veloxts/core": "__VELOXTS_VERSION__",
|
|
22
|
+
"@veloxts/orm": "__VELOXTS_VERSION__",
|
|
23
|
+
"@veloxts/router": "__VELOXTS_VERSION__",
|
|
24
|
+
"@veloxts/validation": "__VELOXTS_VERSION__",
|
|
25
|
+
"@veloxts/web": "__VELOXTS_VERSION__",
|
|
26
|
+
"@vinxi/server-functions": "0.5.1",
|
|
27
|
+
"dotenv": "17.2.3",
|
|
28
|
+
"react": "19.2.3",
|
|
29
|
+
"react-dom": "19.2.3",
|
|
30
|
+
"vinxi": "0.5.10",
|
|
31
|
+
"zod": "3.25.76"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "25.0.3",
|
|
35
|
+
"@types/react": "19.2.7",
|
|
36
|
+
"@types/react-dom": "19.2.3",
|
|
37
|
+
"prisma": "7.2.0",
|
|
38
|
+
"typescript": "5.9.3",
|
|
39
|
+
"vite": "7.3.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Prisma Schema for VeloxTS Full-Stack Application
|
|
2
|
+
|
|
3
|
+
generator client {
|
|
4
|
+
provider = "prisma-client-js"
|
|
5
|
+
output = "../node_modules/.prisma/client"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
datasource db {
|
|
9
|
+
provider = "sqlite"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
model User {
|
|
13
|
+
id String @id @default(uuid())
|
|
14
|
+
name String
|
|
15
|
+
email String @unique
|
|
16
|
+
createdAt DateTime @default(now())
|
|
17
|
+
updatedAt DateTime @updatedAt
|
|
18
|
+
posts Post[]
|
|
19
|
+
|
|
20
|
+
@@map("users")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
model Post {
|
|
24
|
+
id String @id @default(uuid())
|
|
25
|
+
title String
|
|
26
|
+
content String?
|
|
27
|
+
published Boolean @default(false)
|
|
28
|
+
createdAt DateTime @default(now())
|
|
29
|
+
updatedAt DateTime @updatedAt
|
|
30
|
+
userId String
|
|
31
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
32
|
+
|
|
33
|
+
@@map("posts")
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prisma Configuration (Prisma 7.x)
|
|
3
|
+
*
|
|
4
|
+
* Database URL is configured here instead of schema.prisma.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import 'dotenv/config';
|
|
8
|
+
|
|
9
|
+
import { defineConfig } from 'prisma/config';
|
|
10
|
+
|
|
11
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
12
|
+
|
|
13
|
+
if (!databaseUrl) {
|
|
14
|
+
throw new Error('DATABASE_URL environment variable is required');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
schema: './prisma/schema.prisma',
|
|
19
|
+
datasource: {
|
|
20
|
+
url: databaseUrl,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<rect width="32" height="32" rx="6" fill="#6366f1"/>
|
|
3
|
+
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" fill="white" font-family="system-ui" font-weight="bold" font-size="18">V</text>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Configuration
|
|
3
|
+
*
|
|
4
|
+
* Prisma client instance for database access.
|
|
5
|
+
* Uses Laravel-style `db` export for consistency.
|
|
6
|
+
*
|
|
7
|
+
* Note: Prisma 7 requires using driver adapters for direct database connections.
|
|
8
|
+
* We explicitly load dotenv here because Vite's SSR module evaluation
|
|
9
|
+
* doesn't always have access to .env variables.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import { dirname, resolve } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
// Type imports (erased at runtime, safe for ESM)
|
|
17
|
+
import type { PrismaBetterSqlite3 as PrismaBetterSqlite3Type } from '@prisma/adapter-better-sqlite3';
|
|
18
|
+
import type { PrismaClient as PrismaClientType } from '@prisma/client';
|
|
19
|
+
import dotenv from 'dotenv';
|
|
20
|
+
|
|
21
|
+
// Runtime imports using createRequire for Node.js v24+ CJS interop
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const { PrismaBetterSqlite3 } = require('@prisma/adapter-better-sqlite3') as {
|
|
24
|
+
PrismaBetterSqlite3: typeof PrismaBetterSqlite3Type;
|
|
25
|
+
};
|
|
26
|
+
const { PrismaClient } = require('@prisma/client') as {
|
|
27
|
+
PrismaClient: typeof PrismaClientType;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Get the project root directory (2 levels up from src/api/)
|
|
31
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const projectRoot = resolve(__dirname, '..', '..');
|
|
33
|
+
|
|
34
|
+
// Load .env file explicitly for Vite SSR compatibility
|
|
35
|
+
// Use project root for reliable path resolution
|
|
36
|
+
dotenv.config({ path: resolve(projectRoot, '.env') });
|
|
37
|
+
|
|
38
|
+
declare global {
|
|
39
|
+
// Allow global `var` declarations for hot reload in development
|
|
40
|
+
// eslint-disable-next-line no-var
|
|
41
|
+
var __db: PrismaClient | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create a Prisma client instance using the SQLite adapter.
|
|
46
|
+
*
|
|
47
|
+
* Prisma 7 Breaking Change:
|
|
48
|
+
* - `datasourceUrl` and `datasources` options removed from PrismaClient
|
|
49
|
+
* - Must use driver adapters for direct database connections
|
|
50
|
+
* - Validates that DATABASE_URL is set before creating the client
|
|
51
|
+
*/
|
|
52
|
+
function createPrismaClient(): PrismaClient {
|
|
53
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
54
|
+
|
|
55
|
+
if (!databaseUrl) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
'[VeloxTS] DATABASE_URL environment variable is not set. ' +
|
|
58
|
+
'Ensure .env file exists in project root with DATABASE_URL defined.'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Prisma 7 requires driver adapters for direct connections
|
|
63
|
+
const adapter = new PrismaBetterSqlite3({ url: databaseUrl });
|
|
64
|
+
return new PrismaClient({ adapter });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Use global singleton for hot reload in development
|
|
68
|
+
export const db = globalThis.__db ?? createPrismaClient();
|
|
69
|
+
|
|
70
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
71
|
+
globalThis.__db = db;
|
|
72
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Handler
|
|
3
|
+
*
|
|
4
|
+
* This creates the Fastify API handler that gets embedded in Vinxi.
|
|
5
|
+
* All routes under /api/* are handled by this Fastify instance.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT:
|
|
8
|
+
* - Vinxi HTTP routers expect h3 event handlers as default export
|
|
9
|
+
* - We use lazy initialization (factory function) to avoid Vite SSR module issues
|
|
10
|
+
* - createH3ApiHandler handles the Fastify app lifecycle and initialization
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { veloxApp } from '@veloxts/core';
|
|
14
|
+
import { databasePlugin } from '@veloxts/orm';
|
|
15
|
+
import { rest } from '@veloxts/router';
|
|
16
|
+
import { createH3ApiHandler } from '@veloxts/web';
|
|
17
|
+
|
|
18
|
+
import { db } from './database.js';
|
|
19
|
+
import { healthProcedures } from './procedures/health.js';
|
|
20
|
+
import { postProcedures } from './procedures/posts.js';
|
|
21
|
+
import { userProcedures } from './procedures/users.js';
|
|
22
|
+
|
|
23
|
+
// Export router type for frontend type safety
|
|
24
|
+
const router = { health: healthProcedures, users: userProcedures, posts: postProcedures };
|
|
25
|
+
export type AppRouter = typeof router;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Export the h3 event handler for Vinxi embedding.
|
|
29
|
+
*
|
|
30
|
+
* We use a factory function for lazy initialization to avoid
|
|
31
|
+
* Vite trying to evaluate the Fastify app during build time.
|
|
32
|
+
* The app is initialized on the first HTTP request.
|
|
33
|
+
*/
|
|
34
|
+
export default createH3ApiHandler({
|
|
35
|
+
app: async () => {
|
|
36
|
+
const app = await veloxApp();
|
|
37
|
+
|
|
38
|
+
// Register database plugin
|
|
39
|
+
await app.register(databasePlugin({ client: db }));
|
|
40
|
+
|
|
41
|
+
// Register REST routes from procedures
|
|
42
|
+
app.routes(
|
|
43
|
+
rest([healthProcedures, userProcedures, postProcedures], {
|
|
44
|
+
prefix: '', // No prefix - Vinxi handles /api/*
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Return the underlying Fastify instance for the h3 adapter
|
|
49
|
+
// The adapter will call ready() on it
|
|
50
|
+
return app.server;
|
|
51
|
+
},
|
|
52
|
+
basePath: '/api',
|
|
53
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Procedures
|
|
3
|
+
*
|
|
4
|
+
* API endpoints for application health monitoring.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineProcedures, procedure } from '@veloxts/router';
|
|
8
|
+
|
|
9
|
+
import { db } from '../database.js';
|
|
10
|
+
|
|
11
|
+
export const healthProcedures = defineProcedures('health', {
|
|
12
|
+
/**
|
|
13
|
+
* Basic health check
|
|
14
|
+
* GET /api/health
|
|
15
|
+
*/
|
|
16
|
+
getHealth: procedure()
|
|
17
|
+
.rest({ method: 'GET', path: '/health' })
|
|
18
|
+
.query(() => ({
|
|
19
|
+
status: 'healthy',
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
uptime: process.uptime(),
|
|
22
|
+
})),
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Readiness check (includes database)
|
|
26
|
+
* GET /api/health/ready
|
|
27
|
+
*/
|
|
28
|
+
getReady: procedure()
|
|
29
|
+
.rest({ method: 'GET', path: '/health/ready' })
|
|
30
|
+
.query(async () => {
|
|
31
|
+
try {
|
|
32
|
+
// Test database connection
|
|
33
|
+
await db.$queryRaw`SELECT 1`;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
status: 'ready',
|
|
37
|
+
database: 'connected',
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return {
|
|
42
|
+
status: 'not_ready',
|
|
43
|
+
database: 'disconnected',
|
|
44
|
+
timestamp: new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post Procedures
|
|
3
|
+
*
|
|
4
|
+
* Nested CRUD operations for posts under users.
|
|
5
|
+
* Uses .parent('users') to enable automatic path parameter merging.
|
|
6
|
+
* Routes: /api/users/:userId/posts/*
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { defineProcedures, procedure } from '@veloxts/router';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
|
|
12
|
+
import { db } from '../database.js';
|
|
13
|
+
import {
|
|
14
|
+
CreatePostSchema,
|
|
15
|
+
PostSchema,
|
|
16
|
+
PostWithUserSchema,
|
|
17
|
+
UpdatePostSchema,
|
|
18
|
+
} from '../schemas/post.js';
|
|
19
|
+
|
|
20
|
+
export const postProcedures = defineProcedures('posts', {
|
|
21
|
+
/**
|
|
22
|
+
* List all posts for a user
|
|
23
|
+
* GET /api/users/:userId/posts
|
|
24
|
+
*/
|
|
25
|
+
listPosts: procedure()
|
|
26
|
+
.parent('users')
|
|
27
|
+
.input(z.object({ userId: z.string().uuid() }))
|
|
28
|
+
.output(z.array(PostSchema))
|
|
29
|
+
.query(async ({ input }) => {
|
|
30
|
+
// Verify user exists
|
|
31
|
+
const user = await db.user.findUnique({
|
|
32
|
+
where: { id: input.userId },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!user) {
|
|
36
|
+
throw Object.assign(new Error('User not found'), { statusCode: 404 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return db.post.findMany({
|
|
40
|
+
where: { userId: input.userId },
|
|
41
|
+
orderBy: { createdAt: 'desc' },
|
|
42
|
+
});
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get a single post by ID
|
|
47
|
+
* GET /api/users/:userId/posts/:id
|
|
48
|
+
*/
|
|
49
|
+
getPost: procedure()
|
|
50
|
+
.parent('users')
|
|
51
|
+
.input(z.object({ userId: z.string().uuid(), id: z.string().uuid() }))
|
|
52
|
+
.output(PostWithUserSchema)
|
|
53
|
+
.query(async ({ input }) => {
|
|
54
|
+
const post = await db.post.findFirst({
|
|
55
|
+
where: {
|
|
56
|
+
id: input.id,
|
|
57
|
+
userId: input.userId,
|
|
58
|
+
},
|
|
59
|
+
include: {
|
|
60
|
+
user: {
|
|
61
|
+
select: { id: true, name: true },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!post) {
|
|
67
|
+
throw Object.assign(new Error('Post not found'), { statusCode: 404 });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return post;
|
|
71
|
+
}),
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Create a new post for a user
|
|
75
|
+
* POST /api/users/:userId/posts
|
|
76
|
+
*/
|
|
77
|
+
createPost: procedure()
|
|
78
|
+
.parent('users')
|
|
79
|
+
.input(CreatePostSchema.extend({ userId: z.string().uuid() }))
|
|
80
|
+
.output(PostSchema)
|
|
81
|
+
.mutation(async ({ input }) => {
|
|
82
|
+
const { userId, ...data } = input;
|
|
83
|
+
|
|
84
|
+
// Verify user exists
|
|
85
|
+
const user = await db.user.findUnique({
|
|
86
|
+
where: { id: userId },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!user) {
|
|
90
|
+
throw Object.assign(new Error('User not found'), { statusCode: 404 });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return db.post.create({
|
|
94
|
+
data: {
|
|
95
|
+
...data,
|
|
96
|
+
userId,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}),
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Update an existing post
|
|
103
|
+
* PUT /api/users/:userId/posts/:id
|
|
104
|
+
*/
|
|
105
|
+
updatePost: procedure()
|
|
106
|
+
.parent('users')
|
|
107
|
+
.input(UpdatePostSchema.extend({ userId: z.string().uuid(), id: z.string().uuid() }))
|
|
108
|
+
.output(PostSchema)
|
|
109
|
+
.mutation(async ({ input }) => {
|
|
110
|
+
const { id, userId, ...data } = input;
|
|
111
|
+
|
|
112
|
+
// Verify post belongs to user
|
|
113
|
+
const post = await db.post.findFirst({
|
|
114
|
+
where: { id, userId },
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (!post) {
|
|
118
|
+
throw Object.assign(new Error('Post not found'), { statusCode: 404 });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return db.post.update({
|
|
122
|
+
where: { id },
|
|
123
|
+
data,
|
|
124
|
+
});
|
|
125
|
+
}),
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Delete a post
|
|
129
|
+
* DELETE /api/users/:userId/posts/:id
|
|
130
|
+
*/
|
|
131
|
+
deletePost: procedure()
|
|
132
|
+
.parent('users')
|
|
133
|
+
.input(z.object({ userId: z.string().uuid(), id: z.string().uuid() }))
|
|
134
|
+
.output(z.object({ success: z.boolean() }))
|
|
135
|
+
.mutation(async ({ input }) => {
|
|
136
|
+
// Verify post belongs to user
|
|
137
|
+
const post = await db.post.findFirst({
|
|
138
|
+
where: { id: input.id, userId: input.userId },
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!post) {
|
|
142
|
+
throw Object.assign(new Error('Post not found'), { statusCode: 404 });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await db.post.delete({
|
|
146
|
+
where: { id: input.id },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return { success: true };
|
|
150
|
+
}),
|
|
151
|
+
});
|