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.
Files changed (113) hide show
  1. package/README.md +2 -43
  2. package/dist/cli.js +23 -13
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +26 -8
  5. package/dist/index.js.map +1 -1
  6. package/dist/templates/auth.d.ts.map +1 -1
  7. package/dist/templates/auth.js +24 -0
  8. package/dist/templates/auth.js.map +1 -1
  9. package/dist/templates/fullstack.d.ts +15 -0
  10. package/dist/templates/fullstack.d.ts.map +1 -0
  11. package/dist/templates/fullstack.js +110 -0
  12. package/dist/templates/fullstack.js.map +1 -0
  13. package/dist/templates/index.d.ts +1 -1
  14. package/dist/templates/index.d.ts.map +1 -1
  15. package/dist/templates/index.js +27 -5
  16. package/dist/templates/index.js.map +1 -1
  17. package/dist/templates/placeholders.d.ts +6 -1
  18. package/dist/templates/placeholders.d.ts.map +1 -1
  19. package/dist/templates/placeholders.js +15 -5
  20. package/dist/templates/placeholders.js.map +1 -1
  21. package/dist/templates/rsc.d.ts +15 -0
  22. package/dist/templates/rsc.d.ts.map +1 -0
  23. package/dist/templates/rsc.js +192 -0
  24. package/dist/templates/rsc.js.map +1 -0
  25. package/dist/templates/shared/root.d.ts +1 -0
  26. package/dist/templates/shared/root.d.ts.map +1 -1
  27. package/dist/templates/shared/root.js +4 -0
  28. package/dist/templates/shared/root.js.map +1 -1
  29. package/dist/templates/spa.d.ts +12 -0
  30. package/dist/templates/spa.d.ts.map +1 -0
  31. package/dist/templates/spa.js +101 -0
  32. package/dist/templates/spa.js.map +1 -0
  33. package/dist/templates/trpc.d.ts.map +1 -1
  34. package/dist/templates/trpc.js +16 -0
  35. package/dist/templates/trpc.js.map +1 -1
  36. package/dist/templates/types.d.ts +14 -1
  37. package/dist/templates/types.d.ts.map +1 -1
  38. package/dist/templates/types.js +35 -10
  39. package/dist/templates/types.js.map +1 -1
  40. package/package.json +3 -3
  41. package/src/templates/source/api/config/auth.ts +2 -2
  42. package/src/templates/source/api/config/database.ts +44 -10
  43. package/src/templates/source/api/index.auth.ts +10 -16
  44. package/src/templates/source/api/index.default.ts +10 -9
  45. package/src/templates/source/api/index.trpc.ts +9 -28
  46. package/src/templates/source/api/package.auth.json +7 -6
  47. package/src/templates/source/api/package.default.json +5 -4
  48. package/src/templates/source/api/prisma/schema.auth.prisma +3 -2
  49. package/src/templates/source/api/prisma/schema.default.prisma +3 -2
  50. package/src/templates/source/api/prisma.config.ts +7 -1
  51. package/src/templates/source/api/procedures/auth.ts +38 -66
  52. package/src/templates/source/api/procedures/health.ts +4 -9
  53. package/src/templates/source/api/procedures/users.auth.ts +7 -11
  54. package/src/templates/source/api/procedures/users.default.ts +7 -11
  55. package/src/templates/source/api/router.auth.ts +31 -0
  56. package/src/templates/source/api/router.default.ts +29 -0
  57. package/src/templates/source/api/router.trpc.ts +37 -0
  58. package/src/templates/source/api/router.types.auth.ts +88 -0
  59. package/src/templates/source/api/router.types.default.ts +73 -0
  60. package/src/templates/source/api/router.types.trpc.ts +73 -0
  61. package/src/templates/source/api/routes.auth.ts +66 -0
  62. package/src/templates/source/api/routes.default.ts +53 -0
  63. package/src/templates/source/api/schemas/auth.ts +79 -0
  64. package/src/templates/source/api/schemas/health.ts +21 -0
  65. package/src/templates/source/api/schemas/user.ts +62 -12
  66. package/src/templates/source/api/utils/auth.ts +157 -0
  67. package/src/templates/source/root/.cursorrules +187 -0
  68. package/src/templates/source/root/CLAUDE.auth.md +264 -0
  69. package/src/templates/source/root/CLAUDE.default.md +185 -0
  70. package/src/templates/source/root/package.json +7 -1
  71. package/src/templates/source/rsc/CLAUDE.md +104 -0
  72. package/src/templates/source/rsc/app/actions/posts.ts +93 -0
  73. package/src/templates/source/rsc/app/actions/users.ts +83 -0
  74. package/src/templates/source/rsc/app/layouts/dashboard.tsx +127 -0
  75. package/src/templates/source/rsc/app/layouts/marketing.tsx +25 -0
  76. package/src/templates/source/rsc/app/layouts/minimal.tsx +30 -0
  77. package/src/templates/source/rsc/app/layouts/root.tsx +241 -0
  78. package/src/templates/source/rsc/app/pages/(dashboard)/profile.tsx +71 -0
  79. package/src/templates/source/rsc/app/pages/(dashboard)/settings.tsx +104 -0
  80. package/src/templates/source/rsc/app/pages/(marketing)/about.tsx +52 -0
  81. package/src/templates/source/rsc/app/pages/_not-found.tsx +149 -0
  82. package/src/templates/source/rsc/app/pages/docs/[...slug].tsx +211 -0
  83. package/src/templates/source/rsc/app/pages/index.tsx +50 -0
  84. package/src/templates/source/rsc/app/pages/print.tsx +80 -0
  85. package/src/templates/source/rsc/app/pages/users/[id]/posts/[postId].tsx +89 -0
  86. package/src/templates/source/rsc/app/pages/users/[id]/posts/index.tsx +76 -0
  87. package/src/templates/source/rsc/app/pages/users/[id]/posts/new.tsx +79 -0
  88. package/src/templates/source/rsc/app/pages/users/[id].tsx +64 -0
  89. package/src/templates/source/rsc/app/pages/users/_layout.tsx +104 -0
  90. package/src/templates/source/rsc/app/pages/users.tsx +44 -0
  91. package/src/templates/source/rsc/app.config.ts +12 -0
  92. package/src/templates/source/rsc/env.example +6 -0
  93. package/src/templates/source/rsc/gitignore +34 -0
  94. package/src/templates/source/rsc/package.json +41 -0
  95. package/src/templates/source/rsc/prisma/schema.prisma +34 -0
  96. package/src/templates/source/rsc/prisma.config.ts +22 -0
  97. package/src/templates/source/rsc/public/favicon.svg +4 -0
  98. package/src/templates/source/rsc/src/api/database.ts +72 -0
  99. package/src/templates/source/rsc/src/api/handler.ts +53 -0
  100. package/src/templates/source/rsc/src/api/procedures/health.ts +48 -0
  101. package/src/templates/source/rsc/src/api/procedures/posts.ts +151 -0
  102. package/src/templates/source/rsc/src/api/procedures/users.ts +87 -0
  103. package/src/templates/source/rsc/src/api/schemas/post.ts +53 -0
  104. package/src/templates/source/rsc/src/api/schemas/user.ts +38 -0
  105. package/src/templates/source/rsc/src/entry.client.tsx +28 -0
  106. package/src/templates/source/rsc/src/entry.server.tsx +304 -0
  107. package/src/templates/source/rsc/tsconfig.json +24 -0
  108. package/src/templates/source/web/App.module.css +1 -1
  109. package/src/templates/source/web/api.ts +8 -1
  110. package/src/templates/source/web/main.tsx +4 -4
  111. package/src/templates/source/web/package.json +6 -6
  112. package/src/templates/source/web/routes/__root.tsx +2 -2
  113. 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,6 @@
1
+ # Database
2
+ DATABASE_URL="file:./dev.db"
3
+
4
+ # Server
5
+ PORT=__API_PORT__
6
+ NODE_ENV=development
@@ -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
+ });