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,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for authentication that don't require database access.
|
|
5
|
+
* These are safe to import from procedures without pulling in server-only code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Role Parsing
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
const ALLOWED_ROLES = ['user', 'admin', 'moderator', 'editor'] as const;
|
|
13
|
+
|
|
14
|
+
export function parseUserRoles(rolesJson: string | null): string[] {
|
|
15
|
+
if (!rolesJson) return ['user'];
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const parsed: unknown = JSON.parse(rolesJson);
|
|
19
|
+
|
|
20
|
+
if (!Array.isArray(parsed)) {
|
|
21
|
+
return ['user'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const validRoles = parsed
|
|
25
|
+
.filter((role): role is string => typeof role === 'string')
|
|
26
|
+
.filter((role) => ALLOWED_ROLES.includes(role as (typeof ALLOWED_ROLES)[number]));
|
|
27
|
+
|
|
28
|
+
return validRoles.length > 0 ? validRoles : ['user'];
|
|
29
|
+
} catch {
|
|
30
|
+
return ['user'];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Token Revocation Store
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* In-memory token revocation store.
|
|
40
|
+
*
|
|
41
|
+
* PRODUCTION NOTE: Replace with Redis or database-backed store for:
|
|
42
|
+
* - Persistence across server restarts
|
|
43
|
+
* - Horizontal scaling (multiple server instances)
|
|
44
|
+
*/
|
|
45
|
+
class InMemoryTokenStore {
|
|
46
|
+
private revokedTokens: Map<string, number> = new Map();
|
|
47
|
+
private usedRefreshTokens: Map<string, string> = new Map();
|
|
48
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
49
|
+
|
|
50
|
+
constructor() {
|
|
51
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 5 * 60 * 1000);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
revoke(jti: string, expiresInMs: number = 7 * 24 * 60 * 60 * 1000): void {
|
|
55
|
+
this.revokedTokens.set(jti, Date.now() + expiresInMs);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
isRevoked(jti: string): boolean {
|
|
59
|
+
const expiry = this.revokedTokens.get(jti);
|
|
60
|
+
if (!expiry) return false;
|
|
61
|
+
if (Date.now() > expiry) {
|
|
62
|
+
this.revokedTokens.delete(jti);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
markRefreshTokenUsed(jti: string, userId: string): void {
|
|
69
|
+
this.usedRefreshTokens.set(jti, userId);
|
|
70
|
+
setTimeout(() => this.usedRefreshTokens.delete(jti), 7 * 24 * 60 * 60 * 1000);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
isRefreshTokenUsed(jti: string): string | undefined {
|
|
74
|
+
return this.usedRefreshTokens.get(jti);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
revokeAllUserTokens(userId: string): void {
|
|
78
|
+
console.warn(
|
|
79
|
+
`[Security] Token reuse detected for user ${userId}. ` +
|
|
80
|
+
'All tokens should be revoked. Implement proper user->token mapping for production.'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private cleanup(): void {
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
for (const [jti, expiry] of this.revokedTokens.entries()) {
|
|
87
|
+
if (now > expiry) {
|
|
88
|
+
this.revokedTokens.delete(jti);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
destroy(): void {
|
|
94
|
+
if (this.cleanupInterval) {
|
|
95
|
+
clearInterval(this.cleanupInterval);
|
|
96
|
+
this.cleanupInterval = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const tokenStore = new InMemoryTokenStore();
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// JWT Configuration Helper
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Gets required JWT secrets from environment variables.
|
|
109
|
+
* Throws a clear error in production if secrets are not configured.
|
|
110
|
+
*/
|
|
111
|
+
export function getJwtSecrets(): { jwtSecret: string; refreshSecret: string } {
|
|
112
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
113
|
+
const refreshSecret = process.env.JWT_REFRESH_SECRET;
|
|
114
|
+
|
|
115
|
+
const isDevelopment = process.env.NODE_ENV !== 'production';
|
|
116
|
+
|
|
117
|
+
if (!jwtSecret || !refreshSecret) {
|
|
118
|
+
if (isDevelopment) {
|
|
119
|
+
console.warn(
|
|
120
|
+
'\n' +
|
|
121
|
+
'='.repeat(70) +
|
|
122
|
+
'\n' +
|
|
123
|
+
' WARNING: JWT secrets not configured!\n' +
|
|
124
|
+
' Using temporary development secrets. DO NOT USE IN PRODUCTION!\n' +
|
|
125
|
+
'\n' +
|
|
126
|
+
' To configure secrets, add to .env:\n' +
|
|
127
|
+
' JWT_SECRET=<generate with: openssl rand -base64 64>\n' +
|
|
128
|
+
' JWT_REFRESH_SECRET=<generate with: openssl rand -base64 64>\n' +
|
|
129
|
+
'='.repeat(70) +
|
|
130
|
+
'\n'
|
|
131
|
+
);
|
|
132
|
+
return {
|
|
133
|
+
jwtSecret:
|
|
134
|
+
jwtSecret || `dev-only-jwt-secret-${Math.random().toString(36).substring(2).repeat(4)}`,
|
|
135
|
+
refreshSecret:
|
|
136
|
+
refreshSecret ||
|
|
137
|
+
`dev-only-refresh-secret-${Math.random().toString(36).substring(2).repeat(4)}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throw new Error(
|
|
142
|
+
'\n' +
|
|
143
|
+
'CRITICAL: JWT secrets are required but not configured.\n' +
|
|
144
|
+
'\n' +
|
|
145
|
+
'Required environment variables:\n' +
|
|
146
|
+
' - JWT_SECRET: Secret for signing access tokens (64+ characters)\n' +
|
|
147
|
+
' - JWT_REFRESH_SECRET: Secret for signing refresh tokens (64+ characters)\n' +
|
|
148
|
+
'\n' +
|
|
149
|
+
'Generate secure secrets with:\n' +
|
|
150
|
+
' openssl rand -base64 64\n' +
|
|
151
|
+
'\n' +
|
|
152
|
+
'Add them to your environment or .env file before starting the server.\n'
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { jwtSecret, refreshSecret };
|
|
157
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# VeloxTS Project - Cursor AI Rules
|
|
2
|
+
|
|
3
|
+
This is a VeloxTS full-stack TypeScript application.
|
|
4
|
+
|
|
5
|
+
## Project Type
|
|
6
|
+
|
|
7
|
+
- **Framework**: VeloxTS (Laravel-inspired TypeScript framework)
|
|
8
|
+
- **Backend**: Fastify + VeloxTS procedures (apps/api)
|
|
9
|
+
- **Frontend**: React + Vite + TanStack Router (apps/web)
|
|
10
|
+
- **Database**: Prisma with SQLite
|
|
11
|
+
- **Validation**: Zod schemas
|
|
12
|
+
|
|
13
|
+
## Architecture Rules
|
|
14
|
+
|
|
15
|
+
### Backend (apps/api)
|
|
16
|
+
|
|
17
|
+
Procedures in `src/procedures/` define API endpoints. Naming conventions determine HTTP methods:
|
|
18
|
+
|
|
19
|
+
- `get*`, `find*` → GET (single resource)
|
|
20
|
+
- `list*` → GET (collection)
|
|
21
|
+
- `create*`, `add*` → POST
|
|
22
|
+
- `update*`, `edit*` → PUT
|
|
23
|
+
- `patch*` → PATCH
|
|
24
|
+
- `delete*`, `remove*` → DELETE
|
|
25
|
+
|
|
26
|
+
Schemas in `src/schemas/` use Zod for validation.
|
|
27
|
+
|
|
28
|
+
Context available in procedures:
|
|
29
|
+
- `ctx.db` - Prisma client
|
|
30
|
+
- `ctx.request` - Fastify request
|
|
31
|
+
- `ctx.reply` - Fastify reply
|
|
32
|
+
/* @if auth */
|
|
33
|
+
- `ctx.user` - Authenticated user (if logged in)
|
|
34
|
+
/* @endif auth */
|
|
35
|
+
|
|
36
|
+
### Frontend (apps/web)
|
|
37
|
+
|
|
38
|
+
Routes in `src/routes/` use TanStack Router file-based routing.
|
|
39
|
+
API calls use `@veloxts/client` hooks: `useQuery`, `useMutation`.
|
|
40
|
+
Types flow from backend automatically - no codegen needed.
|
|
41
|
+
|
|
42
|
+
## Code Style
|
|
43
|
+
|
|
44
|
+
### TypeScript Strictness
|
|
45
|
+
|
|
46
|
+
CRITICAL - Never violate these rules:
|
|
47
|
+
- NEVER use `any` type
|
|
48
|
+
- NEVER use `as any` assertions
|
|
49
|
+
- NEVER use `@ts-ignore` or `@ts-expect-error`
|
|
50
|
+
- Use `unknown` with type guards instead
|
|
51
|
+
- Use proper generics and type inference
|
|
52
|
+
|
|
53
|
+
### Procedure Pattern
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { defineProcedures, procedure, z } from '@veloxts/velox';
|
|
57
|
+
|
|
58
|
+
export const entityProcedures = defineProcedures('entities', {
|
|
59
|
+
getEntity: procedure()
|
|
60
|
+
.input(z.object({ id: z.string().uuid() }))
|
|
61
|
+
.output(EntitySchema)
|
|
62
|
+
.query(async ({ input, ctx }) => {
|
|
63
|
+
return ctx.db.entity.findUnique({ where: { id: input.id } });
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
createEntity: procedure()
|
|
67
|
+
.input(CreateEntitySchema)
|
|
68
|
+
.output(EntitySchema)
|
|
69
|
+
.mutation(async ({ input, ctx }) => {
|
|
70
|
+
return ctx.db.entity.create({ data: input });
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Schema Pattern
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { z } from '@veloxts/velox';
|
|
79
|
+
|
|
80
|
+
export const EntitySchema = z.object({
|
|
81
|
+
id: z.string().uuid(),
|
|
82
|
+
name: z.string().min(1).max(255),
|
|
83
|
+
createdAt: z.date(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export type Entity = z.infer<typeof EntitySchema>;
|
|
87
|
+
|
|
88
|
+
export const CreateEntitySchema = EntitySchema.omit({
|
|
89
|
+
id: true,
|
|
90
|
+
createdAt: true,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export type CreateEntity = z.infer<typeof CreateEntitySchema>;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
/* @if auth */
|
|
97
|
+
### Protected Procedures
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { authenticated, hasRole } from '@veloxts/auth';
|
|
101
|
+
|
|
102
|
+
// Require authentication
|
|
103
|
+
const getProfile = procedure()
|
|
104
|
+
.guard(authenticated)
|
|
105
|
+
.query(({ ctx }) => ctx.user);
|
|
106
|
+
|
|
107
|
+
// Require role
|
|
108
|
+
const adminOnly = procedure()
|
|
109
|
+
.guard(hasRole('admin'))
|
|
110
|
+
.mutation(({ ctx, input }) => { /* ... */ });
|
|
111
|
+
```
|
|
112
|
+
/* @endif auth */
|
|
113
|
+
|
|
114
|
+
## CLI Commands
|
|
115
|
+
|
|
116
|
+
Common commands for development:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Development
|
|
120
|
+
__RUN_CMD__ dev # Start dev server with HMR
|
|
121
|
+
__RUN_CMD__ velox dev --verbose # With timing info
|
|
122
|
+
|
|
123
|
+
# Code generation
|
|
124
|
+
__RUN_CMD__ velox make resource Post --crud # Full CRUD resource
|
|
125
|
+
__RUN_CMD__ velox make procedure Users # Just procedure
|
|
126
|
+
__RUN_CMD__ velox make schema Post # Just schema
|
|
127
|
+
|
|
128
|
+
# Database
|
|
129
|
+
__RUN_CMD__ velox migrate run # Run migrations
|
|
130
|
+
__RUN_CMD__ velox migrate status # Check status
|
|
131
|
+
__RUN_CMD__ velox db seed # Run seeders
|
|
132
|
+
__RUN_CMD__ db:push # Push schema (Prisma)
|
|
133
|
+
__RUN_CMD__ db:studio # Open Prisma Studio
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## File Naming Conventions
|
|
137
|
+
|
|
138
|
+
- Procedures: `src/procedures/{entity}.ts` (plural, kebab-case for multi-word)
|
|
139
|
+
- Schemas: `src/schemas/{entity}.ts` (singular, kebab-case)
|
|
140
|
+
- Routes: `src/routes/{path}.tsx`
|
|
141
|
+
- Components: `src/components/{Name}.tsx` (PascalCase)
|
|
142
|
+
|
|
143
|
+
## Import Conventions
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// VeloxTS imports (use umbrella package)
|
|
147
|
+
import { defineProcedures, procedure, z } from '@veloxts/velox';
|
|
148
|
+
/* @if auth */
|
|
149
|
+
import { authenticated, hasRole } from '@veloxts/auth';
|
|
150
|
+
/* @endif auth */
|
|
151
|
+
|
|
152
|
+
// Database client from config
|
|
153
|
+
import { db } from '../config/database';
|
|
154
|
+
|
|
155
|
+
// Local schemas
|
|
156
|
+
import { UserSchema, CreateUserSchema } from '../schemas/user';
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Error Handling
|
|
160
|
+
|
|
161
|
+
Use structured errors with codes:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { VeloxError } from '@veloxts/core';
|
|
165
|
+
|
|
166
|
+
// Not found
|
|
167
|
+
throw VeloxError.notFound('User', userId);
|
|
168
|
+
|
|
169
|
+
// Validation
|
|
170
|
+
throw VeloxError.validation('Invalid email format');
|
|
171
|
+
|
|
172
|
+
// Unauthorized
|
|
173
|
+
throw VeloxError.unauthorized('Login required');
|
|
174
|
+
|
|
175
|
+
// Forbidden
|
|
176
|
+
throw VeloxError.forbidden('Cannot access this resource');
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## JSON Output
|
|
180
|
+
|
|
181
|
+
All CLI commands support `--json` for AI tooling:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
velox migrate status --json
|
|
185
|
+
velox make resource Post --json --dry-run
|
|
186
|
+
velox procedures list --json
|
|
187
|
+
```
|
|
@@ -136,6 +136,85 @@ JWT_REFRESH_SECRET=<64+ chars> # Generate: openssl rand -base64 64
|
|
|
136
136
|
| `patchUser` | PATCH | `/users/:id` |
|
|
137
137
|
| `deleteUser` | DELETE | `/users/:id` |
|
|
138
138
|
|
|
139
|
+
## Guards and Policies
|
|
140
|
+
|
|
141
|
+
### Using Guards
|
|
142
|
+
|
|
143
|
+
Guards protect procedures from unauthorized access:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { authenticated, hasRole, hasPermission, allOf, anyOf } from '@veloxts/auth';
|
|
147
|
+
|
|
148
|
+
// Require authentication
|
|
149
|
+
const getProfile = procedure()
|
|
150
|
+
.guard(authenticated)
|
|
151
|
+
.query(({ ctx }) => ctx.user);
|
|
152
|
+
|
|
153
|
+
// Require specific role
|
|
154
|
+
const adminDashboard = procedure()
|
|
155
|
+
.guard(hasRole('admin'))
|
|
156
|
+
.query(({ ctx }) => { /* ... */ });
|
|
157
|
+
|
|
158
|
+
// Require permission
|
|
159
|
+
const deletePost = procedure()
|
|
160
|
+
.guard(hasPermission('posts.delete'))
|
|
161
|
+
.mutation(({ ctx, input }) => { /* ... */ });
|
|
162
|
+
|
|
163
|
+
// Combine guards (AND logic)
|
|
164
|
+
const adminWithPermission = procedure()
|
|
165
|
+
.guard(allOf([hasRole('admin'), hasPermission('users.manage')]))
|
|
166
|
+
.mutation(({ ctx, input }) => { /* ... */ });
|
|
167
|
+
|
|
168
|
+
// Any of guards (OR logic)
|
|
169
|
+
const moderatorOrAdmin = procedure()
|
|
170
|
+
.guard(anyOf([hasRole('admin'), hasRole('moderator')]))
|
|
171
|
+
.mutation(({ ctx, input }) => { /* ... */ });
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Available Guards
|
|
175
|
+
|
|
176
|
+
| Guard | Description |
|
|
177
|
+
|-------|-------------|
|
|
178
|
+
| `authenticated` | Requires logged-in user |
|
|
179
|
+
| `emailVerified` | Requires verified email |
|
|
180
|
+
| `hasRole(role)` | Checks user role |
|
|
181
|
+
| `hasPermission(perm)` | Checks user permission |
|
|
182
|
+
| `hasAnyPermission(perms)` | Any permission matches |
|
|
183
|
+
| `allOf(guards)` | All guards must pass |
|
|
184
|
+
| `anyOf(guards)` | Any guard must pass |
|
|
185
|
+
| `not(guard)` | Inverts guard result |
|
|
186
|
+
|
|
187
|
+
### Resource Policies
|
|
188
|
+
|
|
189
|
+
Define authorization rules for resources:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { definePolicy, registerPolicy, authorize } from '@veloxts/auth';
|
|
193
|
+
|
|
194
|
+
// Define policy for Post resource
|
|
195
|
+
const PostPolicy = definePolicy<User, Post>({
|
|
196
|
+
view: () => true,
|
|
197
|
+
create: (user) => user.emailVerified,
|
|
198
|
+
update: (user, post) => user.id === post.authorId,
|
|
199
|
+
delete: (user, post) => user.id === post.authorId || user.role === 'admin',
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Register the policy
|
|
203
|
+
registerPolicy('Post', PostPolicy);
|
|
204
|
+
|
|
205
|
+
// Use in procedures
|
|
206
|
+
const deletePost = procedure()
|
|
207
|
+
.guard(authenticated)
|
|
208
|
+
.mutation(async ({ ctx, input }) => {
|
|
209
|
+
const post = await ctx.db.post.findUnique({ where: { id: input.id } });
|
|
210
|
+
|
|
211
|
+
// Throws 403 if unauthorized
|
|
212
|
+
await authorize(ctx.user, 'delete', 'Post', post);
|
|
213
|
+
|
|
214
|
+
return ctx.db.post.delete({ where: { id: input.id } });
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
139
218
|
## Database
|
|
140
219
|
|
|
141
220
|
After schema changes:
|
|
@@ -146,3 +225,188 @@ __RUN_CMD__ db:generate # Regenerate client
|
|
|
146
225
|
```
|
|
147
226
|
|
|
148
227
|
Access via context: `ctx.db.user.findMany()`
|
|
228
|
+
|
|
229
|
+
## Code Generation
|
|
230
|
+
|
|
231
|
+
### Available Generators
|
|
232
|
+
|
|
233
|
+
| Generator | Alias | Description |
|
|
234
|
+
|-----------|-------|-------------|
|
|
235
|
+
| `procedure` | `p` | API procedure with queries/mutations |
|
|
236
|
+
| `schema` | `s` | Zod validation schema |
|
|
237
|
+
| `model` | `m` | Prisma model definition |
|
|
238
|
+
| `migration` | `mig` | Database migration file |
|
|
239
|
+
| `test` | `t` | Unit/integration test file |
|
|
240
|
+
| `resource` | `r` | Complete CRUD resource (all above) |
|
|
241
|
+
| `seeder` | `seed` | Database seeder |
|
|
242
|
+
| `factory` | `f` | Test data factory |
|
|
243
|
+
|
|
244
|
+
### Usage Examples
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Generate a complete CRUD resource
|
|
248
|
+
__RUN_CMD__ velox make resource Post --crud
|
|
249
|
+
|
|
250
|
+
# Generate just a procedure
|
|
251
|
+
__RUN_CMD__ velox make procedure Users --crud
|
|
252
|
+
|
|
253
|
+
# Generate with soft-delete support
|
|
254
|
+
__RUN_CMD__ velox m r Comment --soft-delete
|
|
255
|
+
|
|
256
|
+
# Preview without writing files
|
|
257
|
+
__RUN_CMD__ velox make --dry-run resource Post
|
|
258
|
+
|
|
259
|
+
# JSON output for scripting
|
|
260
|
+
__RUN_CMD__ velox make resource Post --json
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Generator Options
|
|
264
|
+
|
|
265
|
+
**Common Options:**
|
|
266
|
+
- `--dry-run, -d` - Preview changes without writing
|
|
267
|
+
- `--force, -f` - Overwrite existing files
|
|
268
|
+
- `--json` - Output results as JSON
|
|
269
|
+
|
|
270
|
+
**Resource/Procedure Options:**
|
|
271
|
+
- `--crud, -c` - Generate full CRUD operations
|
|
272
|
+
- `--paginated, -P` - Include pagination for list
|
|
273
|
+
- `--soft-delete, -s` - Add soft delete support
|
|
274
|
+
- `--timestamps, -t` - Include timestamps (default: true)
|
|
275
|
+
|
|
276
|
+
## Migration Runner
|
|
277
|
+
|
|
278
|
+
### Commands
|
|
279
|
+
|
|
280
|
+
| Command | Description |
|
|
281
|
+
|---------|-------------|
|
|
282
|
+
| `velox migrate status` | Show migration status |
|
|
283
|
+
| `velox migrate run` | Run pending migrations |
|
|
284
|
+
| `velox migrate rollback` | Rollback last migration |
|
|
285
|
+
| `velox migrate fresh` | Drop all tables and re-run |
|
|
286
|
+
| `velox migrate reset` | Rollback all then re-run |
|
|
287
|
+
|
|
288
|
+
### Usage
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# Check status
|
|
292
|
+
__RUN_CMD__ velox migrate status
|
|
293
|
+
|
|
294
|
+
# Run pending migrations
|
|
295
|
+
__RUN_CMD__ velox migrate run
|
|
296
|
+
|
|
297
|
+
# Development mode (creates migration from schema diff)
|
|
298
|
+
__RUN_CMD__ velox migrate run --dev
|
|
299
|
+
|
|
300
|
+
# Rollback last migration
|
|
301
|
+
__RUN_CMD__ velox migrate rollback
|
|
302
|
+
|
|
303
|
+
# Fresh database
|
|
304
|
+
__RUN_CMD__ velox migrate fresh
|
|
305
|
+
|
|
306
|
+
# JSON output
|
|
307
|
+
__RUN_CMD__ velox migrate status --json
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Database Seeding
|
|
311
|
+
|
|
312
|
+
### Commands
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# Run all seeders
|
|
316
|
+
__RUN_CMD__ velox db seed
|
|
317
|
+
|
|
318
|
+
# Run specific seeder
|
|
319
|
+
__RUN_CMD__ velox db seed UserSeeder
|
|
320
|
+
|
|
321
|
+
# Fresh seed (truncate first)
|
|
322
|
+
__RUN_CMD__ velox db seed --fresh
|
|
323
|
+
|
|
324
|
+
# Preview
|
|
325
|
+
__RUN_CMD__ velox db seed --dry-run
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Seeder Example
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
// apps/api/src/database/seeders/UserSeeder.ts
|
|
332
|
+
import type { Seeder } from '@veloxts/cli';
|
|
333
|
+
|
|
334
|
+
export const UserSeeder: Seeder = {
|
|
335
|
+
name: 'UserSeeder',
|
|
336
|
+
dependencies: [],
|
|
337
|
+
|
|
338
|
+
async run(db) {
|
|
339
|
+
await db.user.createMany({
|
|
340
|
+
data: [
|
|
341
|
+
{ email: 'admin@example.com', name: 'Admin' },
|
|
342
|
+
{ email: 'user@example.com', name: 'User' },
|
|
343
|
+
],
|
|
344
|
+
});
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Error Handling
|
|
350
|
+
|
|
351
|
+
VeloxTS uses structured error codes for AI tooling:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
// Error format: VeloxError[E1001]: Message
|
|
355
|
+
// E1xxx - Core errors
|
|
356
|
+
// E2xxx - Generator errors
|
|
357
|
+
// E3xxx - Seeding errors
|
|
358
|
+
// E4xxx - Migration errors
|
|
359
|
+
// E5xxx - Dev server errors
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Common Patterns
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import { VeloxError } from '@veloxts/core';
|
|
366
|
+
|
|
367
|
+
const getUser = procedure()
|
|
368
|
+
.input(z.object({ id: z.string().uuid() }))
|
|
369
|
+
.query(async ({ ctx, input }) => {
|
|
370
|
+
const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
371
|
+
|
|
372
|
+
if (!user) {
|
|
373
|
+
throw VeloxError.notFound('User', input.id);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return user;
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Development Workflow
|
|
381
|
+
|
|
382
|
+
### Hot Module Replacement (HMR)
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
# Default: HMR enabled
|
|
386
|
+
__RUN_CMD__ velox dev
|
|
387
|
+
|
|
388
|
+
# With verbose timing
|
|
389
|
+
__RUN_CMD__ velox dev --verbose
|
|
390
|
+
|
|
391
|
+
# Disable HMR
|
|
392
|
+
__RUN_CMD__ velox dev --no-hmr
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### CLI JSON Output
|
|
396
|
+
|
|
397
|
+
All CLI commands support `--json` for scripting:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
velox migrate status --json
|
|
401
|
+
velox db seed --json --dry-run
|
|
402
|
+
velox procedures list --json
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Recommended Flow
|
|
406
|
+
|
|
407
|
+
1. Define Zod schemas in `apps/api/src/schemas/`
|
|
408
|
+
2. Generate resource: `velox make resource Post --crud`
|
|
409
|
+
3. Customize generated procedures as needed
|
|
410
|
+
4. Run migrations: `velox migrate run --dev`
|
|
411
|
+
5. Seed data: `velox db seed --fresh`
|
|
412
|
+
6. Test endpoints with Thunder Client or curl
|