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.
Files changed (47) hide show
  1. package/dist/index.js.map +1 -1
  2. package/dist/templates/compiler.d.ts.map +1 -1
  3. package/dist/templates/compiler.js.map +1 -1
  4. package/dist/templates/index.d.ts.map +1 -1
  5. package/dist/templates/index.js +9 -1
  6. package/dist/templates/index.js.map +1 -1
  7. package/dist/templates/placeholders.d.ts +9 -0
  8. package/dist/templates/placeholders.d.ts.map +1 -1
  9. package/dist/templates/placeholders.js +31 -5
  10. package/dist/templates/placeholders.js.map +1 -1
  11. package/dist/templates/shared/web-base.d.ts +4 -2
  12. package/dist/templates/shared/web-base.d.ts.map +1 -1
  13. package/dist/templates/shared/web-base.js +15 -6
  14. package/dist/templates/shared/web-base.js.map +1 -1
  15. package/dist/templates/trpc.d.ts +15 -0
  16. package/dist/templates/trpc.d.ts.map +1 -0
  17. package/dist/templates/trpc.js +89 -0
  18. package/dist/templates/trpc.js.map +1 -0
  19. package/dist/templates/types.d.ts +1 -1
  20. package/dist/templates/types.d.ts.map +1 -1
  21. package/dist/templates/types.js +6 -0
  22. package/dist/templates/types.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/templates/source/api/config/auth.ts +7 -0
  25. package/src/templates/source/api/index.auth.ts +13 -2
  26. package/src/templates/source/api/index.default.ts +6 -1
  27. package/src/templates/source/api/index.trpc.ts +64 -0
  28. package/src/templates/source/api/package.auth.json +1 -0
  29. package/src/templates/source/api/package.default.json +1 -0
  30. package/src/templates/source/api/prisma.config.ts +1 -0
  31. package/src/templates/source/api/procedures/auth.ts +14 -8
  32. package/src/templates/source/api/procedures/health.ts +1 -1
  33. package/src/templates/source/api/procedures/users.auth.ts +24 -68
  34. package/src/templates/source/api/procedures/users.default.ts +28 -58
  35. package/src/templates/source/api/schemas/user.ts +9 -4
  36. package/src/templates/source/api/tsconfig.json +3 -2
  37. package/src/templates/source/web/App.module.css +54 -2
  38. package/src/templates/source/web/api.ts +42 -0
  39. package/src/templates/source/web/main.tsx +63 -13
  40. package/src/templates/source/web/package.json +10 -8
  41. package/src/templates/source/web/routes/__root.tsx +42 -3
  42. package/src/templates/source/web/routes/about.tsx +8 -2
  43. package/src/templates/source/web/routes/index.auth.tsx +25 -71
  44. package/src/templates/source/web/routes/index.default.tsx +12 -32
  45. package/src/templates/source/web/routes/users.tsx +85 -0
  46. package/src/templates/source/web/tsconfig.json +2 -1
  47. 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();
@@ -23,6 +23,7 @@
23
23
  "bcrypt": "5.1.1",
24
24
  "better-sqlite3": "12.5.0",
25
25
  "dotenv": "17.2.3",
26
+ "file-uri-to-path": "2.0.0",
26
27
  "zod": "3.24.4"
27
28
  },
28
29
  "devDependencies": {
@@ -22,6 +22,7 @@
22
22
  "@veloxts/velox": "__VELOXTS_VERSION__",
23
23
  "better-sqlite3": "12.5.0",
24
24
  "dotenv": "17.2.3",
25
+ "file-uri-to-path": "2.0.0",
25
26
  "zod": "3.24.4"
26
27
  },
27
28
  "devDependencies": {
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import 'dotenv/config';
8
+
8
9
  import { defineConfig } from 'prisma/config';
9
10
 
10
11
  export default defineConfig({
@@ -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', 'password123', '12345678', '123456789',
58
- 'qwerty123', 'letmein', 'welcome', 'admin123',
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
- register: procedure()
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
- login: procedure()
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
- refresh: procedure()
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
- logout: procedure()
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 { VELOX_VERSION, defineProcedures, procedure, z } from '@veloxts/velox';
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
- procedure,
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.nullable())
32
+ .output(UserSchema)
70
33
  .query(async ({ input, ctx }) => {
71
- const db = getDb(ctx);
72
- const user = await db.user.findUnique({ where: { id: input.id } });
73
- return user ? toUserResponse(user) : null;
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 [dbUsers, total] = await Promise.all([
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
- return {
100
- data: dbUsers.map(toUserResponse),
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
- const db = getDb(ctx);
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
- const updated = await db.user.update({ where: { id }, data });
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
- const updated = await db.user.update({ where: { id }, data });
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
- const db = getDb(ctx);
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 { defineProcedures, procedure, paginationInputSchema, z } from '@veloxts/velox';
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.nullable())
23
+ .output(UserSchema)
47
24
  .query(async ({ input, ctx }) => {
48
- const db = getDb(ctx);
49
- const user = await db.user.findUnique({ where: { id: input.id } });
50
- return user ? toUserResponse(user) : null;
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 [dbUsers, total] = await Promise.all([
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
- return {
77
- data: dbUsers.map(toUserResponse),
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
- const db = getDb(ctx);
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
- const user = await db.user.update({ where: { id }, data });
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
- const user = await db.user.update({ where: { id }, data });
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
- const db = getDb(ctx);
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
- export const UserSchema = z.object({
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
- "declaration": false,
8
- "declarationMap": false
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: border-color 0.2s, transform 0.2s;
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: opacity 0.2s, transform 0.1s;
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>();