create-velox-app 0.4.6 → 0.4.9
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/dist/index.js.map +1 -1
- package/dist/templates/compiler.d.ts.map +1 -1
- package/dist/templates/compiler.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +9 -1
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/placeholders.d.ts +9 -0
- package/dist/templates/placeholders.d.ts.map +1 -1
- package/dist/templates/placeholders.js +31 -5
- package/dist/templates/placeholders.js.map +1 -1
- package/dist/templates/shared/web-base.d.ts +4 -2
- package/dist/templates/shared/web-base.d.ts.map +1 -1
- package/dist/templates/shared/web-base.js +15 -6
- package/dist/templates/shared/web-base.js.map +1 -1
- package/dist/templates/trpc.d.ts +15 -0
- package/dist/templates/trpc.d.ts.map +1 -0
- package/dist/templates/trpc.js +89 -0
- package/dist/templates/trpc.js.map +1 -0
- package/dist/templates/types.d.ts +1 -1
- package/dist/templates/types.d.ts.map +1 -1
- package/dist/templates/types.js +6 -0
- package/dist/templates/types.js.map +1 -1
- package/package.json +2 -2
- package/src/templates/source/api/config/auth.ts +7 -0
- package/src/templates/source/api/index.auth.ts +13 -2
- package/src/templates/source/api/index.default.ts +6 -1
- package/src/templates/source/api/index.trpc.ts +64 -0
- package/src/templates/source/api/package.auth.json +1 -0
- package/src/templates/source/api/package.default.json +1 -0
- package/src/templates/source/api/prisma.config.ts +1 -0
- package/src/templates/source/api/procedures/auth.ts +14 -8
- package/src/templates/source/api/procedures/health.ts +1 -1
- package/src/templates/source/api/procedures/users.auth.ts +24 -68
- package/src/templates/source/api/procedures/users.default.ts +28 -58
- package/src/templates/source/api/schemas/user.ts +9 -4
- package/src/templates/source/api/tsconfig.json +3 -2
- package/src/templates/source/web/App.module.css +54 -2
- package/src/templates/source/web/api.ts +42 -0
- package/src/templates/source/web/main.tsx +63 -13
- package/src/templates/source/web/package.json +10 -8
- package/src/templates/source/web/routes/__root.tsx +42 -3
- package/src/templates/source/web/routes/about.tsx +8 -2
- package/src/templates/source/web/routes/index.auth.tsx +25 -71
- package/src/templates/source/web/routes/index.default.tsx +12 -32
- package/src/templates/source/web/routes/users.tsx +85 -0
- package/src/templates/source/web/tsconfig.json +2 -1
- package/src/templates/source/web/vite.config.ts +4 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Entry Point - tRPC Hybrid Template
|
|
3
|
+
*
|
|
4
|
+
* VeloxTS hybrid API architecture:
|
|
5
|
+
* - tRPC at /trpc for type-safe frontend communication
|
|
6
|
+
* - REST at /api for external consumers
|
|
7
|
+
*
|
|
8
|
+
* Both APIs generated from the same procedure definitions.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import 'dotenv/config';
|
|
12
|
+
|
|
13
|
+
import { databasePlugin, serve, veloxApp } from '@veloxts/velox';
|
|
14
|
+
|
|
15
|
+
import { config } from './config/app.js';
|
|
16
|
+
import { prisma } from './config/database.js';
|
|
17
|
+
import { healthProcedures } from './procedures/health.js';
|
|
18
|
+
import { userProcedures } from './procedures/users.js';
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Application Bootstrap
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
const app = await veloxApp({
|
|
25
|
+
port: config.port,
|
|
26
|
+
host: config.host,
|
|
27
|
+
logger: config.logger,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await app.register(databasePlugin({ client: prisma }));
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// API Registration
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Serve procedures as both REST and tRPC endpoints
|
|
38
|
+
*
|
|
39
|
+
* - REST: /api/users, /api/health
|
|
40
|
+
* - tRPC: /trpc/users.getUser, /trpc/health.check
|
|
41
|
+
*/
|
|
42
|
+
const router = await serve(app, [healthProcedures, userProcedures], {
|
|
43
|
+
api: config.apiPrefix,
|
|
44
|
+
rpc: '/trpc',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Type Exports for Frontend
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* AppRouter type for frontend type safety
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* import type { AppRouter } from '../../api/src';
|
|
57
|
+
* import { createVeloxHooks } from '@veloxts/client/react';
|
|
58
|
+
*
|
|
59
|
+
* export const api = createVeloxHooks<AppRouter>();
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export type AppRouter = typeof router;
|
|
63
|
+
|
|
64
|
+
await app.start();
|
|
@@ -15,11 +15,11 @@ import {
|
|
|
15
15
|
AuthError,
|
|
16
16
|
authenticated,
|
|
17
17
|
createAuthRateLimiter,
|
|
18
|
+
defineProcedures,
|
|
18
19
|
hashPassword,
|
|
19
20
|
jwtManager,
|
|
20
|
-
verifyPassword,
|
|
21
|
-
defineProcedures,
|
|
22
21
|
procedure,
|
|
22
|
+
verifyPassword,
|
|
23
23
|
z,
|
|
24
24
|
} from '@veloxts/velox';
|
|
25
25
|
|
|
@@ -54,8 +54,14 @@ const rateLimiter = createAuthRateLimiter({
|
|
|
54
54
|
// ============================================================================
|
|
55
55
|
|
|
56
56
|
const COMMON_PASSWORDS = new Set([
|
|
57
|
-
'password',
|
|
58
|
-
'
|
|
57
|
+
'password',
|
|
58
|
+
'password123',
|
|
59
|
+
'12345678',
|
|
60
|
+
'123456789',
|
|
61
|
+
'qwerty123',
|
|
62
|
+
'letmein',
|
|
63
|
+
'welcome',
|
|
64
|
+
'admin123',
|
|
59
65
|
]);
|
|
60
66
|
|
|
61
67
|
// ============================================================================
|
|
@@ -127,7 +133,7 @@ const DUMMY_HASH = '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.uy7dPSSXB5G6Uy
|
|
|
127
133
|
// ============================================================================
|
|
128
134
|
|
|
129
135
|
export const authProcedures = defineProcedures('auth', {
|
|
130
|
-
|
|
136
|
+
createAccount: procedure()
|
|
131
137
|
.rest({ method: 'POST', path: '/auth/register' })
|
|
132
138
|
.use(rateLimiter.register())
|
|
133
139
|
.input(RegisterInput)
|
|
@@ -165,7 +171,7 @@ export const authProcedures = defineProcedures('auth', {
|
|
|
165
171
|
});
|
|
166
172
|
}),
|
|
167
173
|
|
|
168
|
-
|
|
174
|
+
createSession: procedure()
|
|
169
175
|
.rest({ method: 'POST', path: '/auth/login' })
|
|
170
176
|
.use(
|
|
171
177
|
rateLimiter.login((ctx) => {
|
|
@@ -198,7 +204,7 @@ export const authProcedures = defineProcedures('auth', {
|
|
|
198
204
|
});
|
|
199
205
|
}),
|
|
200
206
|
|
|
201
|
-
|
|
207
|
+
createRefresh: procedure()
|
|
202
208
|
.rest({ method: 'POST', path: '/auth/refresh' })
|
|
203
209
|
.use(rateLimiter.refresh())
|
|
204
210
|
.input(RefreshInput)
|
|
@@ -247,7 +253,7 @@ export const authProcedures = defineProcedures('auth', {
|
|
|
247
253
|
}
|
|
248
254
|
}),
|
|
249
255
|
|
|
250
|
-
|
|
256
|
+
deleteSession: procedure()
|
|
251
257
|
.rest({ method: 'POST', path: '/auth/logout' })
|
|
252
258
|
.guard(authenticated)
|
|
253
259
|
.output(LogoutResponse)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Health Check Procedures
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { defineProcedures, procedure, VELOX_VERSION, z } from '@veloxts/velox';
|
|
6
6
|
|
|
7
7
|
export const healthProcedures = defineProcedures('health', {
|
|
8
8
|
getHealth: procedure()
|
|
@@ -2,62 +2,25 @@
|
|
|
2
2
|
* User Procedures
|
|
3
3
|
*
|
|
4
4
|
* CRUD procedures for user management with authentication guards.
|
|
5
|
+
* Clean implementation without boilerplate:
|
|
6
|
+
* - No manual DbUser/DbClient interfaces needed
|
|
7
|
+
* - No toUserResponse() transformation needed
|
|
8
|
+
* - Output schema automatically serializes Date → string via withTimestamps()
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import {
|
|
8
12
|
AuthError,
|
|
9
13
|
authenticated,
|
|
10
|
-
hasRole,
|
|
11
14
|
defineProcedures,
|
|
12
15
|
GuardError,
|
|
13
|
-
|
|
16
|
+
hasRole,
|
|
17
|
+
NotFoundError,
|
|
14
18
|
paginationInputSchema,
|
|
19
|
+
procedure,
|
|
15
20
|
z,
|
|
16
21
|
} from '@veloxts/velox';
|
|
17
22
|
|
|
18
|
-
import {
|
|
19
|
-
CreateUserInput,
|
|
20
|
-
UpdateUserInput,
|
|
21
|
-
type User,
|
|
22
|
-
UserSchema,
|
|
23
|
-
} from '../schemas/user.js';
|
|
24
|
-
|
|
25
|
-
// ============================================================================
|
|
26
|
-
// Database Types
|
|
27
|
-
// ============================================================================
|
|
28
|
-
|
|
29
|
-
interface DbUser {
|
|
30
|
-
id: string;
|
|
31
|
-
name: string;
|
|
32
|
-
email: string;
|
|
33
|
-
createdAt: Date;
|
|
34
|
-
updatedAt: Date;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface DbClient {
|
|
38
|
-
user: {
|
|
39
|
-
findUnique: (args: { where: { id: string } }) => Promise<DbUser | null>;
|
|
40
|
-
findMany: (args?: { skip?: number; take?: number }) => Promise<DbUser[]>;
|
|
41
|
-
create: (args: { data: { name: string; email: string } }) => Promise<DbUser>;
|
|
42
|
-
update: (args: { where: { id: string }; data: { name?: string; email?: string } }) => Promise<DbUser>;
|
|
43
|
-
delete: (args: { where: { id: string } }) => Promise<DbUser>;
|
|
44
|
-
count: () => Promise<number>;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function getDb(ctx: { db: unknown }): DbClient {
|
|
49
|
-
return ctx.db as DbClient;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function toUserResponse(dbUser: DbUser): User {
|
|
53
|
-
return {
|
|
54
|
-
id: dbUser.id,
|
|
55
|
-
name: dbUser.name,
|
|
56
|
-
email: dbUser.email,
|
|
57
|
-
createdAt: dbUser.createdAt instanceof Date ? dbUser.createdAt.toISOString() : dbUser.createdAt,
|
|
58
|
-
updatedAt: dbUser.updatedAt instanceof Date ? dbUser.updatedAt.toISOString() : dbUser.updatedAt,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
23
|
+
import { CreateUserInput, UpdateUserInput, UserSchema } from '../schemas/user.js';
|
|
61
24
|
|
|
62
25
|
// ============================================================================
|
|
63
26
|
// User Procedures
|
|
@@ -66,11 +29,14 @@ function toUserResponse(dbUser: DbUser): User {
|
|
|
66
29
|
export const userProcedures = defineProcedures('users', {
|
|
67
30
|
getUser: procedure()
|
|
68
31
|
.input(z.object({ id: z.string().uuid() }))
|
|
69
|
-
.output(UserSchema
|
|
32
|
+
.output(UserSchema)
|
|
70
33
|
.query(async ({ input, ctx }) => {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
34
|
+
const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
35
|
+
if (!user) {
|
|
36
|
+
throw new NotFoundError(`User with id '${input.id}' not found`);
|
|
37
|
+
}
|
|
38
|
+
// Return Prisma object directly - output schema handles Date serialization
|
|
39
|
+
return user;
|
|
74
40
|
}),
|
|
75
41
|
|
|
76
42
|
listUsers: procedure()
|
|
@@ -86,20 +52,17 @@ export const userProcedures = defineProcedures('users', {
|
|
|
86
52
|
})
|
|
87
53
|
)
|
|
88
54
|
.query(async ({ input, ctx }) => {
|
|
89
|
-
const db = getDb(ctx);
|
|
90
55
|
const page = input?.page ?? 1;
|
|
91
56
|
const limit = input?.limit ?? 10;
|
|
92
57
|
const skip = (page - 1) * limit;
|
|
93
58
|
|
|
94
|
-
const [
|
|
95
|
-
db.user.findMany({ skip, take: limit }),
|
|
96
|
-
db.user.count(),
|
|
59
|
+
const [users, total] = await Promise.all([
|
|
60
|
+
ctx.db.user.findMany({ skip, take: limit }),
|
|
61
|
+
ctx.db.user.count(),
|
|
97
62
|
]);
|
|
98
63
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
meta: { page, limit, total },
|
|
102
|
-
};
|
|
64
|
+
// Return Prisma objects directly - output schema serializes dates
|
|
65
|
+
return { data: users, meta: { page, limit, total } };
|
|
103
66
|
}),
|
|
104
67
|
|
|
105
68
|
createUser: procedure()
|
|
@@ -107,9 +70,7 @@ export const userProcedures = defineProcedures('users', {
|
|
|
107
70
|
.input(CreateUserInput)
|
|
108
71
|
.output(UserSchema)
|
|
109
72
|
.mutation(async ({ input, ctx }) => {
|
|
110
|
-
|
|
111
|
-
const user = await db.user.create({ data: input });
|
|
112
|
-
return toUserResponse(user);
|
|
73
|
+
return ctx.db.user.create({ data: input });
|
|
113
74
|
}),
|
|
114
75
|
|
|
115
76
|
updateUser: procedure()
|
|
@@ -117,7 +78,6 @@ export const userProcedures = defineProcedures('users', {
|
|
|
117
78
|
.input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
|
|
118
79
|
.output(UserSchema)
|
|
119
80
|
.mutation(async ({ input, ctx }) => {
|
|
120
|
-
const db = getDb(ctx);
|
|
121
81
|
const { id, ...data } = input;
|
|
122
82
|
|
|
123
83
|
if (!ctx.user) {
|
|
@@ -131,8 +91,7 @@ export const userProcedures = defineProcedures('users', {
|
|
|
131
91
|
throw new GuardError('ownership', 'You can only update your own profile', 403);
|
|
132
92
|
}
|
|
133
93
|
|
|
134
|
-
|
|
135
|
-
return toUserResponse(updated);
|
|
94
|
+
return ctx.db.user.update({ where: { id }, data });
|
|
136
95
|
}),
|
|
137
96
|
|
|
138
97
|
patchUser: procedure()
|
|
@@ -140,7 +99,6 @@ export const userProcedures = defineProcedures('users', {
|
|
|
140
99
|
.input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
|
|
141
100
|
.output(UserSchema)
|
|
142
101
|
.mutation(async ({ input, ctx }) => {
|
|
143
|
-
const db = getDb(ctx);
|
|
144
102
|
const { id, ...data } = input;
|
|
145
103
|
|
|
146
104
|
if (!ctx.user) {
|
|
@@ -154,8 +112,7 @@ export const userProcedures = defineProcedures('users', {
|
|
|
154
112
|
throw new GuardError('ownership', 'You can only update your own profile', 403);
|
|
155
113
|
}
|
|
156
114
|
|
|
157
|
-
|
|
158
|
-
return toUserResponse(updated);
|
|
115
|
+
return ctx.db.user.update({ where: { id }, data });
|
|
159
116
|
}),
|
|
160
117
|
|
|
161
118
|
deleteUser: procedure()
|
|
@@ -163,8 +120,7 @@ export const userProcedures = defineProcedures('users', {
|
|
|
163
120
|
.input(z.object({ id: z.string().uuid() }))
|
|
164
121
|
.output(z.object({ success: z.boolean() }))
|
|
165
122
|
.mutation(async ({ input, ctx }) => {
|
|
166
|
-
|
|
167
|
-
await db.user.delete({ where: { id: input.id } });
|
|
123
|
+
await ctx.db.user.delete({ where: { id: input.id } });
|
|
168
124
|
return { success: true };
|
|
169
125
|
}),
|
|
170
126
|
});
|
|
@@ -1,53 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User Procedures
|
|
3
|
+
*
|
|
4
|
+
* Clean implementation without boilerplate:
|
|
5
|
+
* - No manual DbUser/DbClient interfaces needed
|
|
6
|
+
* - No toUserResponse() transformation needed
|
|
7
|
+
* - Output schema automatically serializes Date → string via withTimestamps()
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
defineProcedures,
|
|
12
|
+
NotFoundError,
|
|
13
|
+
paginationInputSchema,
|
|
14
|
+
procedure,
|
|
15
|
+
z,
|
|
16
|
+
} from '@veloxts/velox';
|
|
6
17
|
|
|
7
18
|
import { CreateUserInput, UpdateUserInput, UserSchema } from '../schemas/user.js';
|
|
8
19
|
|
|
9
|
-
// Database types
|
|
10
|
-
interface DbUser {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
email: string;
|
|
14
|
-
createdAt: Date;
|
|
15
|
-
updatedAt: Date;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface DbClient {
|
|
19
|
-
user: {
|
|
20
|
-
findUnique: (args: { where: { id: string } }) => Promise<DbUser | null>;
|
|
21
|
-
findMany: (args?: { skip?: number; take?: number }) => Promise<DbUser[]>;
|
|
22
|
-
create: (args: { data: { name: string; email: string } }) => Promise<DbUser>;
|
|
23
|
-
update: (args: { where: { id: string }; data: { name?: string; email?: string } }) => Promise<DbUser>;
|
|
24
|
-
delete: (args: { where: { id: string } }) => Promise<DbUser>;
|
|
25
|
-
count: () => Promise<number>;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getDb(ctx: { db: unknown }): DbClient {
|
|
30
|
-
return ctx.db as DbClient;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function toUserResponse(dbUser: DbUser) {
|
|
34
|
-
return {
|
|
35
|
-
id: dbUser.id,
|
|
36
|
-
name: dbUser.name,
|
|
37
|
-
email: dbUser.email,
|
|
38
|
-
createdAt: dbUser.createdAt.toISOString(),
|
|
39
|
-
updatedAt: dbUser.updatedAt.toISOString(),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
20
|
export const userProcedures = defineProcedures('users', {
|
|
44
21
|
getUser: procedure()
|
|
45
22
|
.input(z.object({ id: z.string().uuid() }))
|
|
46
|
-
.output(UserSchema
|
|
23
|
+
.output(UserSchema)
|
|
47
24
|
.query(async ({ input, ctx }) => {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
25
|
+
const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
26
|
+
if (!user) {
|
|
27
|
+
throw new NotFoundError(`User with id '${input.id}' not found`);
|
|
28
|
+
}
|
|
29
|
+
// Return Prisma object directly - output schema handles Date serialization
|
|
30
|
+
return user;
|
|
51
31
|
}),
|
|
52
32
|
|
|
53
33
|
listUsers: procedure()
|
|
@@ -63,57 +43,47 @@ export const userProcedures = defineProcedures('users', {
|
|
|
63
43
|
})
|
|
64
44
|
)
|
|
65
45
|
.query(async ({ input, ctx }) => {
|
|
66
|
-
const db = getDb(ctx);
|
|
67
46
|
const page = input?.page ?? 1;
|
|
68
47
|
const limit = input?.limit ?? 10;
|
|
69
48
|
const skip = (page - 1) * limit;
|
|
70
49
|
|
|
71
|
-
const [
|
|
72
|
-
db.user.findMany({ skip, take: limit }),
|
|
73
|
-
db.user.count(),
|
|
50
|
+
const [users, total] = await Promise.all([
|
|
51
|
+
ctx.db.user.findMany({ skip, take: limit }),
|
|
52
|
+
ctx.db.user.count(),
|
|
74
53
|
]);
|
|
75
54
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
meta: { page, limit, total },
|
|
79
|
-
};
|
|
55
|
+
// Return Prisma objects directly - output schema serializes dates
|
|
56
|
+
return { data: users, meta: { page, limit, total } };
|
|
80
57
|
}),
|
|
81
58
|
|
|
82
59
|
createUser: procedure()
|
|
83
60
|
.input(CreateUserInput)
|
|
84
61
|
.output(UserSchema)
|
|
85
62
|
.mutation(async ({ input, ctx }) => {
|
|
86
|
-
|
|
87
|
-
const user = await db.user.create({ data: input });
|
|
88
|
-
return toUserResponse(user);
|
|
63
|
+
return ctx.db.user.create({ data: input });
|
|
89
64
|
}),
|
|
90
65
|
|
|
91
66
|
updateUser: procedure()
|
|
92
67
|
.input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
|
|
93
68
|
.output(UserSchema)
|
|
94
69
|
.mutation(async ({ input, ctx }) => {
|
|
95
|
-
const db = getDb(ctx);
|
|
96
70
|
const { id, ...data } = input;
|
|
97
|
-
|
|
98
|
-
return toUserResponse(user);
|
|
71
|
+
return ctx.db.user.update({ where: { id }, data });
|
|
99
72
|
}),
|
|
100
73
|
|
|
101
74
|
patchUser: procedure()
|
|
102
75
|
.input(z.object({ id: z.string().uuid() }).merge(UpdateUserInput))
|
|
103
76
|
.output(UserSchema)
|
|
104
77
|
.mutation(async ({ input, ctx }) => {
|
|
105
|
-
const db = getDb(ctx);
|
|
106
78
|
const { id, ...data } = input;
|
|
107
|
-
|
|
108
|
-
return toUserResponse(user);
|
|
79
|
+
return ctx.db.user.update({ where: { id }, data });
|
|
109
80
|
}),
|
|
110
81
|
|
|
111
82
|
deleteUser: procedure()
|
|
112
83
|
.input(z.object({ id: z.string().uuid() }))
|
|
113
84
|
.output(z.object({ success: z.boolean() }))
|
|
114
85
|
.mutation(async ({ input, ctx }) => {
|
|
115
|
-
|
|
116
|
-
await db.user.delete({ where: { id: input.id } });
|
|
86
|
+
await ctx.db.user.delete({ where: { id: input.id } });
|
|
117
87
|
return { success: true };
|
|
118
88
|
}),
|
|
119
89
|
});
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* User Schemas
|
|
3
|
+
*
|
|
4
|
+
* Uses withTimestamps() for automatic Date → string serialization.
|
|
5
|
+
* No manual transformation needed in procedure handlers.
|
|
3
6
|
*/
|
|
4
7
|
|
|
5
|
-
import { createIdSchema, emailSchema, z } from '@veloxts/velox';
|
|
8
|
+
import { createIdSchema, emailSchema, withTimestamps, z } from '@veloxts/velox';
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
// Business fields only - timestamps added separately
|
|
11
|
+
const UserFields = z.object({
|
|
8
12
|
id: createIdSchema('uuid'),
|
|
9
13
|
name: z.string().min(1).max(100),
|
|
10
14
|
email: emailSchema,
|
|
11
|
-
createdAt: z.coerce.date().transform((d) => d.toISOString()),
|
|
12
|
-
updatedAt: z.coerce.date().transform((d) => d.toISOString()),
|
|
13
15
|
});
|
|
14
16
|
|
|
17
|
+
// Complete schema with automatic Date → string serialization
|
|
18
|
+
export const UserSchema = withTimestamps(UserFields);
|
|
19
|
+
|
|
15
20
|
export type User = z.infer<typeof UserSchema>;
|
|
16
21
|
|
|
17
22
|
export const CreateUserInput = z.object({
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
"compilerOptions": {
|
|
5
5
|
"rootDir": "./src",
|
|
6
6
|
"outDir": "./dist",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"composite": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true
|
|
9
10
|
},
|
|
10
11
|
"include": ["src/**/*"],
|
|
11
12
|
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
|
@@ -99,7 +99,9 @@
|
|
|
99
99
|
border: 1px solid #222;
|
|
100
100
|
border-radius: 12px;
|
|
101
101
|
padding: 1.5rem;
|
|
102
|
-
transition:
|
|
102
|
+
transition:
|
|
103
|
+
border-color 0.2s,
|
|
104
|
+
transform 0.2s;
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
.card:hover {
|
|
@@ -217,7 +219,9 @@
|
|
|
217
219
|
font-size: 1rem;
|
|
218
220
|
font-weight: 600;
|
|
219
221
|
cursor: pointer;
|
|
220
|
-
transition:
|
|
222
|
+
transition:
|
|
223
|
+
opacity 0.2s,
|
|
224
|
+
transform 0.1s;
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
.button:hover {
|
|
@@ -247,6 +251,54 @@
|
|
|
247
251
|
margin-top: 0.5rem;
|
|
248
252
|
}
|
|
249
253
|
|
|
254
|
+
/* Table */
|
|
255
|
+
.tableContainer {
|
|
256
|
+
background: #111;
|
|
257
|
+
border: 1px solid #222;
|
|
258
|
+
border-radius: 12px;
|
|
259
|
+
overflow: hidden;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.table {
|
|
263
|
+
width: 100%;
|
|
264
|
+
border-collapse: collapse;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.table th,
|
|
268
|
+
.table td {
|
|
269
|
+
padding: 1rem 1.5rem;
|
|
270
|
+
text-align: left;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.table th {
|
|
274
|
+
background: #0a0a0a;
|
|
275
|
+
color: #888;
|
|
276
|
+
font-weight: 600;
|
|
277
|
+
font-size: 0.85rem;
|
|
278
|
+
text-transform: uppercase;
|
|
279
|
+
letter-spacing: 0.05em;
|
|
280
|
+
border-bottom: 1px solid #222;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.table td {
|
|
284
|
+
color: #fff;
|
|
285
|
+
border-bottom: 1px solid #1a1a1a;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.table tbody tr:hover {
|
|
289
|
+
background: #1a1a1a;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.table tbody tr:last-child td {
|
|
293
|
+
border-bottom: none;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.emptyState {
|
|
297
|
+
text-align: center;
|
|
298
|
+
color: #666;
|
|
299
|
+
padding: 2rem !important;
|
|
300
|
+
}
|
|
301
|
+
|
|
250
302
|
/* Responsive */
|
|
251
303
|
@media (max-width: 768px) {
|
|
252
304
|
.nav {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VeloxTS API Hooks
|
|
3
|
+
*
|
|
4
|
+
* This file creates typed hooks for accessing your backend procedures.
|
|
5
|
+
* Import `api` in your components for full autocomplete support.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { api } from '@/api';
|
|
10
|
+
* import { useQueryClient } from '@veloxts/client/react';
|
|
11
|
+
*
|
|
12
|
+
* function UserProfile({ userId }: { userId: string }) {
|
|
13
|
+
* const queryClient = useQueryClient();
|
|
14
|
+
*
|
|
15
|
+
* const { data: user } = api.users.getUser.useQuery({ id: userId });
|
|
16
|
+
* const { mutate } = api.users.updateUser.useMutation({
|
|
17
|
+
* onSuccess: () => api.users.getUser.invalidate({ id: userId }, queryClient),
|
|
18
|
+
* });
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { createVeloxHooks } from '@veloxts/client/react';
|
|
24
|
+
|
|
25
|
+
import type { AppRouter } from '../../api/src/index.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Type-safe API hooks with full autocomplete
|
|
29
|
+
*
|
|
30
|
+
* Hooks (call inside components):
|
|
31
|
+
* - api.namespace.procedure.useQuery(input, options)
|
|
32
|
+
* - api.namespace.procedure.useMutation(options)
|
|
33
|
+
* - api.namespace.procedure.useSuspenseQuery(input, options)
|
|
34
|
+
*
|
|
35
|
+
* Cache utilities (input first, queryClient last):
|
|
36
|
+
* - api.namespace.procedure.getQueryKey(input)
|
|
37
|
+
* - api.namespace.procedure.invalidate(input, queryClient)
|
|
38
|
+
* - api.namespace.procedure.prefetch(input, queryClient)
|
|
39
|
+
* - api.namespace.procedure.getData(input, queryClient)
|
|
40
|
+
* - api.namespace.procedure.setData(input, data, queryClient)
|
|
41
|
+
*/
|
|
42
|
+
export const api = createVeloxHooks<AppRouter>();
|