create-velox-app 0.6.31 → 0.6.52

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 (74) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/GUIDE.md +230 -0
  3. package/dist/cli.js +1 -0
  4. package/dist/index.js +14 -4
  5. package/dist/templates/auth.js +10 -0
  6. package/dist/templates/index.js +30 -1
  7. package/dist/templates/placeholders.js +0 -3
  8. package/dist/templates/rsc-auth.d.ts +12 -0
  9. package/dist/templates/rsc-auth.js +208 -0
  10. package/dist/templates/rsc.js +40 -1
  11. package/dist/templates/shared/css-generator.d.ts +26 -0
  12. package/dist/templates/shared/css-generator.js +553 -0
  13. package/dist/templates/shared/index.d.ts +3 -0
  14. package/dist/templates/shared/index.js +3 -0
  15. package/dist/templates/shared/rsc-styles.d.ts +54 -0
  16. package/dist/templates/shared/rsc-styles.js +68 -0
  17. package/dist/templates/shared/theme.d.ts +133 -0
  18. package/dist/templates/shared/theme.js +141 -0
  19. package/dist/templates/spa.js +10 -0
  20. package/dist/templates/trpc.js +10 -0
  21. package/dist/templates/types.d.ts +2 -1
  22. package/dist/templates/types.js +6 -0
  23. package/package.json +6 -3
  24. package/src/templates/source/api/config/database.ts +13 -32
  25. package/src/templates/source/api/docker-compose.yml +21 -0
  26. package/src/templates/source/root/CLAUDE.auth.md +6 -0
  27. package/src/templates/source/root/CLAUDE.default.md +6 -0
  28. package/src/templates/source/rsc/CLAUDE.md +56 -2
  29. package/src/templates/source/rsc/app/actions/posts.ts +1 -1
  30. package/src/templates/source/rsc/app/actions/users.ts +111 -20
  31. package/src/templates/source/rsc/app/layouts/dashboard.tsx +21 -16
  32. package/src/templates/source/rsc/app/layouts/marketing.tsx +34 -0
  33. package/src/templates/source/rsc/app/layouts/minimal-content.tsx +21 -0
  34. package/src/templates/source/rsc/app/layouts/minimal.tsx +86 -5
  35. package/src/templates/source/rsc/app/layouts/root.tsx +148 -44
  36. package/src/templates/source/rsc/docker-compose.yml +21 -0
  37. package/src/templates/source/rsc/package.json +3 -3
  38. package/src/templates/source/rsc/src/api/database.ts +13 -32
  39. package/src/templates/source/rsc/src/api/handler.ts +1 -1
  40. package/src/templates/source/rsc/src/entry.client.tsx +65 -18
  41. package/src/templates/source/rsc-auth/CLAUDE.md +230 -0
  42. package/src/templates/source/rsc-auth/app/actions/auth.ts +112 -0
  43. package/src/templates/source/rsc-auth/app/actions/users.ts +289 -0
  44. package/src/templates/source/rsc-auth/app/layouts/dashboard.tsx +132 -0
  45. package/src/templates/source/rsc-auth/app/layouts/marketing.tsx +59 -0
  46. package/src/templates/source/rsc-auth/app/layouts/minimal-content.tsx +21 -0
  47. package/src/templates/source/rsc-auth/app/layouts/minimal.tsx +111 -0
  48. package/src/templates/source/rsc-auth/app/layouts/root.tsx +355 -0
  49. package/src/templates/source/rsc-auth/app/pages/_not-found.tsx +15 -0
  50. package/src/templates/source/rsc-auth/app/pages/auth/login.tsx +198 -0
  51. package/src/templates/source/rsc-auth/app/pages/auth/register.tsx +225 -0
  52. package/src/templates/source/rsc-auth/app/pages/dashboard/index.tsx +267 -0
  53. package/src/templates/source/rsc-auth/app/pages/index.tsx +83 -0
  54. package/src/templates/source/rsc-auth/app/pages/users.tsx +47 -0
  55. package/src/templates/source/rsc-auth/app.config.ts +12 -0
  56. package/src/templates/source/rsc-auth/docker-compose.yml +21 -0
  57. package/src/templates/source/rsc-auth/env.example +11 -0
  58. package/src/templates/source/rsc-auth/gitignore +34 -0
  59. package/src/templates/source/rsc-auth/package.json +44 -0
  60. package/src/templates/source/rsc-auth/prisma/schema.prisma +23 -0
  61. package/src/templates/source/rsc-auth/prisma.config.ts +22 -0
  62. package/src/templates/source/rsc-auth/public/favicon.svg +4 -0
  63. package/src/templates/source/rsc-auth/src/api/database.ts +129 -0
  64. package/src/templates/source/rsc-auth/src/api/handler.ts +85 -0
  65. package/src/templates/source/rsc-auth/src/api/procedures/auth.ts +262 -0
  66. package/src/templates/source/rsc-auth/src/api/procedures/health.ts +48 -0
  67. package/src/templates/source/rsc-auth/src/api/procedures/users.ts +87 -0
  68. package/src/templates/source/rsc-auth/src/api/schemas/auth.ts +79 -0
  69. package/src/templates/source/rsc-auth/src/api/schemas/user.ts +38 -0
  70. package/src/templates/source/rsc-auth/src/api/utils/auth.ts +157 -0
  71. package/src/templates/source/rsc-auth/src/entry.client.tsx +63 -0
  72. package/src/templates/source/rsc-auth/src/entry.server.tsx +262 -0
  73. package/src/templates/source/rsc-auth/tsconfig.json +24 -0
  74. package/src/templates/source/shared/scripts/check-client-imports.sh +75 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * VeloxTS Design System
3
+ *
4
+ * Dark mode theme constants extracted from the SPA template.
5
+ * This is the single source of truth for all RSC template styling.
6
+ */
7
+ /**
8
+ * Color palette - Dark mode with cyan accents
9
+ */
10
+ export declare const colors: {
11
+ readonly background: "#0a0a0a";
12
+ readonly backgroundAlt: "#111";
13
+ readonly surface: "#111";
14
+ readonly surfaceHover: "#1a1a1a";
15
+ readonly surfaceAlt: "#1a1a1a";
16
+ readonly border: "#222";
17
+ readonly borderHover: "#333";
18
+ readonly borderFocus: "#00d9ff";
19
+ readonly text: "#ededed";
20
+ readonly textMuted: "#888";
21
+ readonly textDimmed: "#666";
22
+ readonly textInverse: "#000";
23
+ readonly accent: "#00d9ff";
24
+ readonly accentHover: "rgba(0, 217, 255, 0.8)";
25
+ readonly accentDimmed: "rgba(0, 217, 255, 0.6)";
26
+ readonly success: "#00d9ff";
27
+ readonly error: "#ff4444";
28
+ readonly errorBg: "#2a1111";
29
+ readonly errorText: "#ff6666";
30
+ readonly warning: "#ffaa00";
31
+ readonly warningBg: "#2a2211";
32
+ readonly info: "#00d9ff";
33
+ readonly selection: "#00d9ff";
34
+ readonly selectionText: "#000";
35
+ readonly scrollbarTrack: "#111";
36
+ readonly scrollbarThumb: "#333";
37
+ readonly scrollbarThumbHover: "#444";
38
+ readonly codeBg: "#1a1a1a";
39
+ };
40
+ /**
41
+ * Typography
42
+ */
43
+ export declare const typography: {
44
+ readonly fontFamily: {
45
+ readonly sans: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"Open Sans\", \"Helvetica Neue\", sans-serif";
46
+ readonly mono: "\"SF Mono\", \"Fira Code\", \"Fira Mono\", Menlo, Monaco, \"Courier New\", monospace";
47
+ };
48
+ readonly fontSize: {
49
+ readonly xs: "0.625rem";
50
+ readonly sm: "0.75rem";
51
+ readonly base: "0.875rem";
52
+ readonly md: "0.9rem";
53
+ readonly lg: "1rem";
54
+ readonly xl: "1.25rem";
55
+ readonly '2xl': "1.5rem";
56
+ readonly '3xl': "2rem";
57
+ readonly '4xl': "2.5rem";
58
+ readonly '5xl': "3rem";
59
+ };
60
+ readonly fontWeight: {
61
+ readonly normal: "400";
62
+ readonly medium: "500";
63
+ readonly semibold: "600";
64
+ readonly bold: "700";
65
+ };
66
+ readonly lineHeight: {
67
+ readonly tight: "1.2";
68
+ readonly normal: "1.6";
69
+ readonly relaxed: "1.8";
70
+ };
71
+ };
72
+ /**
73
+ * Spacing scale (based on 0.25rem = 4px)
74
+ */
75
+ export declare const spacing: {
76
+ readonly 0: "0";
77
+ readonly 1: "0.25rem";
78
+ readonly 2: "0.5rem";
79
+ readonly 3: "0.75rem";
80
+ readonly 4: "1rem";
81
+ readonly 5: "1.25rem";
82
+ readonly 6: "1.5rem";
83
+ readonly 8: "2rem";
84
+ readonly 10: "2.5rem";
85
+ readonly 12: "3rem";
86
+ readonly 16: "4rem";
87
+ readonly 20: "5rem";
88
+ };
89
+ /**
90
+ * Layout constants
91
+ */
92
+ export declare const layout: {
93
+ readonly maxWidth: "1200px";
94
+ readonly maxWidthNarrow: "1000px";
95
+ readonly maxWidthWide: "1400px";
96
+ readonly navHeight: "64px";
97
+ readonly sidebarWidth: "240px";
98
+ readonly borderRadius: {
99
+ readonly sm: "4px";
100
+ readonly md: "6px";
101
+ readonly lg: "8px";
102
+ readonly xl: "12px";
103
+ };
104
+ readonly boxShadow: {
105
+ readonly sm: "0 1px 2px rgba(0, 0, 0, 0.3)";
106
+ readonly md: "0 2px 8px rgba(0, 0, 0, 0.4)";
107
+ readonly lg: "0 4px 16px rgba(0, 0, 0, 0.5)";
108
+ };
109
+ };
110
+ /**
111
+ * Transitions
112
+ */
113
+ export declare const transitions: {
114
+ readonly fast: "0.1s";
115
+ readonly normal: "0.2s";
116
+ readonly slow: "0.3s";
117
+ readonly ease: "ease";
118
+ readonly easeIn: "ease-in";
119
+ readonly easeOut: "ease-out";
120
+ readonly easeInOut: "ease-in-out";
121
+ };
122
+ /**
123
+ * Z-index layers
124
+ */
125
+ export declare const zIndex: {
126
+ readonly base: 0;
127
+ readonly dropdown: 10;
128
+ readonly sticky: 100;
129
+ readonly fixed: 200;
130
+ readonly modal: 300;
131
+ readonly popover: 400;
132
+ readonly tooltip: 500;
133
+ };
@@ -0,0 +1,141 @@
1
+ /**
2
+ * VeloxTS Design System
3
+ *
4
+ * Dark mode theme constants extracted from the SPA template.
5
+ * This is the single source of truth for all RSC template styling.
6
+ */
7
+ /**
8
+ * Color palette - Dark mode with cyan accents
9
+ */
10
+ export const colors = {
11
+ // Backgrounds
12
+ background: '#0a0a0a',
13
+ backgroundAlt: '#111',
14
+ surface: '#111',
15
+ surfaceHover: '#1a1a1a',
16
+ surfaceAlt: '#1a1a1a',
17
+ // Borders
18
+ border: '#222',
19
+ borderHover: '#333',
20
+ borderFocus: '#00d9ff',
21
+ // Text
22
+ text: '#ededed',
23
+ textMuted: '#888',
24
+ textDimmed: '#666',
25
+ textInverse: '#000',
26
+ // Accent & Brand
27
+ accent: '#00d9ff',
28
+ accentHover: 'rgba(0, 217, 255, 0.8)',
29
+ accentDimmed: 'rgba(0, 217, 255, 0.6)',
30
+ // Semantic Colors
31
+ success: '#00d9ff',
32
+ error: '#ff4444',
33
+ errorBg: '#2a1111',
34
+ errorText: '#ff6666',
35
+ warning: '#ffaa00',
36
+ warningBg: '#2a2211',
37
+ info: '#00d9ff',
38
+ // Selection
39
+ selection: '#00d9ff',
40
+ selectionText: '#000',
41
+ // Scrollbar
42
+ scrollbarTrack: '#111',
43
+ scrollbarThumb: '#333',
44
+ scrollbarThumbHover: '#444',
45
+ // Code blocks
46
+ codeBg: '#1a1a1a',
47
+ };
48
+ /**
49
+ * Typography
50
+ */
51
+ export const typography = {
52
+ fontFamily: {
53
+ sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif',
54
+ mono: '"SF Mono", "Fira Code", "Fira Mono", Menlo, Monaco, "Courier New", monospace',
55
+ },
56
+ fontSize: {
57
+ xs: '0.625rem', // 10px
58
+ sm: '0.75rem', // 12px
59
+ base: '0.875rem', // 14px
60
+ md: '0.9rem', // 14.4px
61
+ lg: '1rem', // 16px
62
+ xl: '1.25rem', // 20px
63
+ '2xl': '1.5rem', // 24px
64
+ '3xl': '2rem', // 32px
65
+ '4xl': '2.5rem', // 40px
66
+ '5xl': '3rem', // 48px
67
+ },
68
+ fontWeight: {
69
+ normal: '400',
70
+ medium: '500',
71
+ semibold: '600',
72
+ bold: '700',
73
+ },
74
+ lineHeight: {
75
+ tight: '1.2',
76
+ normal: '1.6',
77
+ relaxed: '1.8',
78
+ },
79
+ };
80
+ /**
81
+ * Spacing scale (based on 0.25rem = 4px)
82
+ */
83
+ export const spacing = {
84
+ 0: '0',
85
+ 1: '0.25rem', // 4px
86
+ 2: '0.5rem', // 8px
87
+ 3: '0.75rem', // 12px
88
+ 4: '1rem', // 16px
89
+ 5: '1.25rem', // 20px
90
+ 6: '1.5rem', // 24px
91
+ 8: '2rem', // 32px
92
+ 10: '2.5rem', // 40px
93
+ 12: '3rem', // 48px
94
+ 16: '4rem', // 64px
95
+ 20: '5rem', // 80px
96
+ };
97
+ /**
98
+ * Layout constants
99
+ */
100
+ export const layout = {
101
+ maxWidth: '1200px',
102
+ maxWidthNarrow: '1000px',
103
+ maxWidthWide: '1400px',
104
+ navHeight: '64px',
105
+ sidebarWidth: '240px',
106
+ borderRadius: {
107
+ sm: '4px',
108
+ md: '6px',
109
+ lg: '8px',
110
+ xl: '12px',
111
+ },
112
+ boxShadow: {
113
+ sm: '0 1px 2px rgba(0, 0, 0, 0.3)',
114
+ md: '0 2px 8px rgba(0, 0, 0, 0.4)',
115
+ lg: '0 4px 16px rgba(0, 0, 0, 0.5)',
116
+ },
117
+ };
118
+ /**
119
+ * Transitions
120
+ */
121
+ export const transitions = {
122
+ fast: '0.1s',
123
+ normal: '0.2s',
124
+ slow: '0.3s',
125
+ ease: 'ease',
126
+ easeIn: 'ease-in',
127
+ easeOut: 'ease-out',
128
+ easeInOut: 'ease-in-out',
129
+ };
130
+ /**
131
+ * Z-index layers
132
+ */
133
+ export const zIndex = {
134
+ base: 0,
135
+ dropdown: 10,
136
+ sticky: 100,
137
+ fixed: 200,
138
+ modal: 300,
139
+ popover: 400,
140
+ tooltip: 500,
141
+ };
@@ -65,6 +65,9 @@ function generateRoutes() {
65
65
  function generateApiTypesDts() {
66
66
  return compileTemplate('api/types.d.ts', DEFAULT_CONFIG);
67
67
  }
68
+ function generateDockerCompose(config) {
69
+ return compileTemplate('api/docker-compose.yml', config);
70
+ }
68
71
  // ============================================================================
69
72
  // SPA Template Generator
70
73
  // ============================================================================
@@ -92,6 +95,13 @@ export function generateSpaTemplate(config) {
92
95
  { path: 'apps/api/src/routes.ts', content: generateRoutes() },
93
96
  { path: 'apps/api/src/types.d.ts', content: generateApiTypesDts() },
94
97
  ];
98
+ // Add docker-compose for PostgreSQL
99
+ if (config.database === 'postgresql') {
100
+ files.push({
101
+ path: 'apps/api/docker-compose.yml',
102
+ content: generateDockerCompose(config),
103
+ });
104
+ }
95
105
  // Add root workspace files
96
106
  const rootFiles = generateRootFiles(config, false);
97
107
  // Add web package files
@@ -73,6 +73,9 @@ function generateRoutes() {
73
73
  function generateApiTypesDts() {
74
74
  return compileTemplate('api/types.d.ts', DEFAULT_CONFIG);
75
75
  }
76
+ function generateDockerCompose(config) {
77
+ return compileTemplate('api/docker-compose.yml', config);
78
+ }
76
79
  // ============================================================================
77
80
  // tRPC Template Generator
78
81
  // ============================================================================
@@ -100,6 +103,13 @@ export function generateTrpcTemplate(config) {
100
103
  { path: 'apps/api/src/routes.ts', content: generateRoutes() },
101
104
  { path: 'apps/api/src/types.d.ts', content: generateApiTypesDts() },
102
105
  ];
106
+ // Add docker-compose for PostgreSQL
107
+ if (config.database === 'postgresql') {
108
+ files.push({
109
+ path: 'apps/api/docker-compose.yml',
110
+ content: generateDockerCompose(config),
111
+ });
112
+ }
103
113
  // Add root workspace files (use false for isAuthTemplate)
104
114
  const rootFiles = generateRootFiles(config, false);
105
115
  // Add web package files (use false for isAuthTemplate)
@@ -10,8 +10,9 @@
10
10
  * - `auth` - SPA + API with JWT authentication
11
11
  * - `trpc` - SPA + API with tRPC integration
12
12
  * - `rsc` (alias: `fullstack`) - React Server Components with Vinxi
13
+ * - `rsc-auth` - RSC with JWT authentication and validated() server actions
13
14
  */
14
- export type TemplateType = 'spa' | 'auth' | 'trpc' | 'rsc';
15
+ export type TemplateType = 'spa' | 'auth' | 'trpc' | 'rsc' | 'rsc-auth';
15
16
  /**
16
17
  * Template aliases for backward compatibility
17
18
  */
@@ -92,6 +92,12 @@ export const TEMPLATE_METADATA = {
92
92
  description: 'React Server Components with Vinxi + embedded Fastify',
93
93
  hint: 'Unified server/client with file-based routing and streaming',
94
94
  },
95
+ 'rsc-auth': {
96
+ type: 'rsc-auth',
97
+ label: 'RSC + Auth',
98
+ description: 'RSC with JWT authentication and validated() server actions',
99
+ hint: 'Full-stack auth with rate limiting, CSRF, and role-based access',
100
+ },
95
101
  };
96
102
  /**
97
103
  * Get all available template types
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-velox-app",
3
- "version": "0.6.31",
3
+ "version": "0.6.52",
4
4
  "description": "Project scaffolder for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,8 +29,9 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "25.0.3",
32
+ "@vitest/coverage-v8": "4.0.16",
32
33
  "typescript": "5.9.3",
33
- "vitest": "4.0.15"
34
+ "vitest": "4.0.16"
34
35
  },
35
36
  "keywords": [
36
37
  "velox",
@@ -59,6 +60,8 @@
59
60
  "clean": "rm -rf dist tsconfig.tsbuildinfo",
60
61
  "test": "vitest run",
61
62
  "test:watch": "vitest",
62
- "smoke-test": "./scripts/smoke-test.sh"
63
+ "test:coverage": "vitest run --coverage",
64
+ "smoke-test": "./scripts/smoke-test.sh",
65
+ "verify-publish": "./scripts/verify-publish.sh"
63
66
  }
64
67
  }
@@ -20,7 +20,6 @@ import type { PrismaBetterSqlite3 as PrismaBetterSqlite3Type } from '@prisma/ada
20
20
  import type { PrismaPg as PrismaPgType } from '@prisma/adapter-pg';
21
21
  /* @endif postgresql */
22
22
  import type { PrismaClient as PrismaClientType } from '@prisma/client';
23
- import type { Pool as PoolType } from 'pg';
24
23
 
25
24
  const require = createRequire(import.meta.url);
26
25
  /* @if sqlite */
@@ -32,9 +31,6 @@ const { PrismaBetterSqlite3 } = require('@prisma/adapter-better-sqlite3') as {
32
31
  const { PrismaPg } = require('@prisma/adapter-pg') as {
33
32
  PrismaPg: typeof PrismaPgType;
34
33
  };
35
- const { Pool } = require('pg') as {
36
- Pool: typeof PoolType;
37
- };
38
34
  /* @endif postgresql */
39
35
  const { PrismaClient } = require('@prisma/client') as {
40
36
  PrismaClient: typeof PrismaClientType;
@@ -44,10 +40,6 @@ declare global {
44
40
  // Allow global `var` declarations for hot reload in development
45
41
  // eslint-disable-next-line no-var
46
42
  var __db: PrismaClient | undefined;
47
- /* @if postgresql */
48
- // eslint-disable-next-line no-var
49
- var __pool: InstanceType<typeof PoolType> | undefined;
50
- /* @endif postgresql */
51
43
  }
52
44
 
53
45
  /* @if sqlite */
@@ -72,32 +64,24 @@ function createPrismaClient(): PrismaClient {
72
64
  /* @endif sqlite */
73
65
  /* @if postgresql */
74
66
  /**
75
- * Create a PostgreSQL connection pool.
76
- * Uses connection pooling for better performance in production.
67
+ * Create a Prisma client instance using the PostgreSQL adapter.
68
+ *
69
+ * Prisma 7 Breaking Change:
70
+ * - PrismaPg now takes connectionString directly (not a Pool instance)
71
+ * - Pool management is handled internally by the adapter
77
72
  */
78
- function createPool(): InstanceType<typeof PoolType> {
79
- const databaseUrl = process.env.DATABASE_URL;
73
+ function createPrismaClient(): PrismaClient {
74
+ const connectionString = process.env.DATABASE_URL;
80
75
 
81
- if (!databaseUrl) {
76
+ if (!connectionString) {
82
77
  throw new Error(
83
78
  '[VeloxTS] DATABASE_URL environment variable is not set. ' +
84
79
  'Ensure .env file exists with DATABASE_URL defined.'
85
80
  );
86
81
  }
87
82
 
88
- return new Pool({
89
- connectionString: databaseUrl,
90
- max: 10, // Maximum connections in pool
91
- });
92
- }
93
-
94
- /**
95
- * Create a Prisma client instance using the PostgreSQL adapter.
96
- * Uses connection pooling for efficient database access.
97
- */
98
- function createPrismaClient(pool: InstanceType<typeof PoolType>): PrismaClient {
99
- // Prisma 7 requires driver adapters for direct connections
100
- const adapter = new PrismaPg(pool);
83
+ // Prisma 7: Pass connectionString directly to PrismaPg (not a Pool)
84
+ const adapter = new PrismaPg({ connectionString });
101
85
  return new PrismaClient({ adapter });
102
86
  }
103
87
  /* @endif postgresql */
@@ -111,19 +95,16 @@ if (process.env.NODE_ENV !== 'production') {
111
95
  }
112
96
  /* @endif sqlite */
113
97
  /* @if postgresql */
114
- // Use global singletons for hot reload in development
115
- const pool = globalThis.__pool ?? createPool();
116
- export const db = globalThis.__db ?? createPrismaClient(pool);
98
+ // Use global singleton for hot reload in development
99
+ export const db = globalThis.__db ?? createPrismaClient();
117
100
 
118
101
  if (process.env.NODE_ENV !== 'production') {
119
- globalThis.__pool = pool;
120
102
  globalThis.__db = db;
121
103
  }
122
104
 
123
- // Graceful shutdown - close pool on process exit
105
+ // Graceful shutdown - disconnect Prisma on process exit
124
106
  const shutdown = async () => {
125
107
  await db.$disconnect();
126
- await pool.end();
127
108
  process.exit(0);
128
109
  };
129
110
 
@@ -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:
@@ -69,6 +69,12 @@ export const postProcedures = procedures('posts', {
69
69
 
70
70
  Then register in `src/procedures/index.ts` and add to collections in `src/index.ts`.
71
71
 
72
+ ## Prisma 7 Configuration
73
+
74
+ This project uses Prisma 7 which has breaking changes:
75
+ - Database URL is configured in `prisma.config.ts`, NOT in `schema.prisma`
76
+ - NEVER add `url` property to the datasource block in `schema.prisma`
77
+
72
78
  ### Frontend Development (apps/web)
73
79
 
74
80
  **Creating a new route:**
@@ -68,6 +68,12 @@ export const postProcedures = procedures('posts', {
68
68
 
69
69
  Then register in `src/procedures/index.ts` and add to collections in `src/index.ts`.
70
70
 
71
+ ## Prisma 7 Configuration
72
+
73
+ This project uses Prisma 7 which has breaking changes:
74
+ - Database URL is configured in `prisma.config.ts`, NOT in `schema.prisma`
75
+ - NEVER add `url` property to the datasource block in `schema.prisma`
76
+
71
77
  ### Frontend Development (apps/web)
72
78
 
73
79
  **Creating a new route:**
@@ -44,15 +44,69 @@ __PROJECT_NAME__/
44
44
 
45
45
  ### Server Actions
46
46
  - Defined in `app/actions/` with `'use server'` directive
47
- - Type-safe with Zod validation via `createAction()`
47
+ - Type-safe with Zod validation
48
48
  - Can be called directly from client components
49
49
 
50
+ ### Validated Actions (Recommended)
51
+ Use `validated()` for secure server actions with built-in protection:
52
+ ```typescript
53
+ // app/actions/users.ts
54
+ 'use server';
55
+ import { validated, validatedMutation, validatedQuery } from '@veloxts/web/server';
56
+ import { z } from 'zod';
57
+
58
+ // Public query (no auth required)
59
+ export const searchUsers = validatedQuery(
60
+ z.object({ query: z.string().optional() }),
61
+ async (input) => {
62
+ return db.user.findMany({ where: { name: { contains: input.query } } });
63
+ }
64
+ );
65
+
66
+ // Protected mutation (requires auth by default)
67
+ export const updateUser = validatedMutation(
68
+ z.object({ id: z.string(), name: z.string() }),
69
+ async (input, ctx) => {
70
+ // ctx.user is typed and available
71
+ return db.user.update({ where: { id: input.id }, data: input });
72
+ }
73
+ );
74
+
75
+ // Custom security options
76
+ export const createUser = validated(
77
+ CreateUserSchema,
78
+ async (input) => { /* ... */ },
79
+ {
80
+ rateLimit: { maxRequests: 10, windowMs: 60_000 },
81
+ maxInputSize: 10 * 1024,
82
+ }
83
+ );
84
+
85
+ // With role-based authorization
86
+ export const deleteUser = validated(
87
+ DeleteUserSchema,
88
+ async (input) => { /* ... */ },
89
+ {
90
+ requireAuth: true,
91
+ requireRoles: ['admin'],
92
+ }
93
+ );
94
+ ```
95
+
96
+ Security features:
97
+ - **Input validation** - Zod schema validation
98
+ - **Input sanitization** - Prototype pollution prevention
99
+ - **Input size limits** - DoS protection (default 1MB)
100
+ - **Rate limiting** - Sliding window per IP
101
+ - **Authentication** - Optional via `requireAuth: true`
102
+ - **Authorization** - Custom callbacks via `authorize`
103
+
50
104
  ### Procedure Bridge Pattern
51
105
  Server actions can bridge to API procedures for code reuse:
52
106
  ```typescript
53
107
  // app/actions/posts.ts
54
108
  'use server';
55
- import { action } from '@veloxts/web';
109
+ import { action } from '@veloxts/web/server';
56
110
  import { postProcedures } from '@/api/procedures/posts';
57
111
 
58
112
  export const createPost = action.fromProcedure(
@@ -35,7 +35,7 @@
35
35
  * ```
36
36
  */
37
37
 
38
- import { action } from '@veloxts/web';
38
+ import { action } from '@veloxts/web/server';
39
39
 
40
40
  import { postProcedures } from '@/api/procedures/posts';
41
41