@veloxts/web 0.6.23 → 0.6.26

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 (2) hide show
  1. package/README.md +1 -540
  2. package/package.json +9 -9
package/README.md CHANGED
@@ -2,546 +2,7 @@
2
2
 
3
3
  > **Early Preview (v0.6.x)** - APIs are stabilizing but may still change. Use with caution in production.
4
4
 
5
- React Server Components integration for the VeloxTS framework using [Vinxi](https://vinxi.vercel.app/).
6
-
7
- ## Overview
8
-
9
- `@veloxts/web` enables full-stack React applications with VeloxTS by:
10
-
11
- - Embedding Fastify API routes inside Vinxi's HTTP infrastructure
12
- - Providing React Server Components with streaming support
13
- - Implementing Laravel-inspired file-based routing
14
- - Bridging server actions to tRPC procedures
15
- - Type-safe server actions with Zod validation
16
-
17
- ## Installation
18
-
19
- ```bash
20
- pnpm add @veloxts/web react@19 react-dom@19
21
- ```
22
-
23
- ## Quick Start
24
-
25
- ### 1. Create your Vinxi configuration
26
-
27
- ```typescript
28
- // app.config.ts
29
- import { defineVeloxApp } from '@veloxts/web';
30
-
31
- export default defineVeloxApp({
32
- port: 3030,
33
- apiHandler: './src/api.handler',
34
- serverEntry: './src/entry.server',
35
- clientEntry: './src/entry.client',
36
- });
37
- ```
38
-
39
- ### 2. Create the API handler
40
-
41
- ```typescript
42
- // src/api.handler.ts
43
- import { createApiRouter } from '@veloxts/web';
44
- import { createApp } from '@veloxts/core';
45
- import { userProcedures } from './procedures/users';
46
-
47
- const app = await createApp();
48
- // Register your procedures...
49
-
50
- export default createApiRouter({
51
- app,
52
- basePath: '/api',
53
- });
54
- ```
55
-
56
- ### 3. Create pages
57
-
58
- ```tsx
59
- // app/pages/index.tsx
60
- export default async function HomePage() {
61
- return (
62
- <div>
63
- <h1>Welcome to VeloxTS</h1>
64
- <p>Full-stack TypeScript with React Server Components</p>
65
- </div>
66
- );
67
- }
68
- ```
69
-
70
- ## Server Actions
71
-
72
- ### Basic Action with Validation
73
-
74
- ```typescript
75
- // src/actions/users.ts
76
- import { action } from '@veloxts/web';
77
- import { z } from 'zod';
78
-
79
- // Define a server action with input/output validation
80
- export const createUser = action()
81
- .input(z.object({
82
- name: z.string().min(1),
83
- email: z.string().email(),
84
- }))
85
- .output(z.object({
86
- id: z.string(),
87
- name: z.string(),
88
- email: z.string(),
89
- createdAt: z.string(),
90
- }))
91
- .handler(async (input) => {
92
- // Create user in database
93
- const user = await db.user.create({
94
- data: { name: input.name, email: input.email },
95
- });
96
- return {
97
- id: user.id,
98
- name: user.name,
99
- email: user.email,
100
- createdAt: user.createdAt.toISOString(),
101
- };
102
- });
103
- ```
104
-
105
- ### Protected Actions (Require Authentication)
106
-
107
- ```typescript
108
- // src/actions/profile.ts
109
- import { action } from '@veloxts/web';
110
- import { z } from 'zod';
111
-
112
- export const updateProfile = action()
113
- .input(z.object({
114
- name: z.string().min(1),
115
- bio: z.string().optional(),
116
- }))
117
- .protected() // Requires authentication
118
- .handler(async (input, ctx) => {
119
- // ctx.user is guaranteed to exist
120
- return await db.user.update({
121
- where: { id: ctx.user.id },
122
- data: input,
123
- });
124
- });
125
- ```
126
-
127
- ### Using Actions in React Components
128
-
129
- ```tsx
130
- // app/pages/users/new.tsx
131
- 'use client';
132
-
133
- import { createUser } from '@/actions/users';
134
-
135
- export default function NewUserPage() {
136
- async function handleSubmit(formData: FormData) {
137
- const result = await createUser({
138
- name: formData.get('name') as string,
139
- email: formData.get('email') as string,
140
- });
141
-
142
- if (result.success) {
143
- console.log('Created user:', result.data);
144
- } else {
145
- console.error('Error:', result.error.message);
146
- }
147
- }
148
-
149
- return (
150
- <form action={handleSubmit}>
151
- <input name="name" placeholder="Name" required />
152
- <input name="email" type="email" placeholder="Email" required />
153
- <button type="submit">Create User</button>
154
- </form>
155
- );
156
- }
157
- ```
158
-
159
- ## tRPC Bridge
160
-
161
- Connect server actions to existing tRPC procedures with full type safety.
162
-
163
- ### Setup
164
-
165
- ```typescript
166
- // src/actions/bridge.ts
167
- import { createTrpcBridge } from '@veloxts/web';
168
- import type { AppRouter } from '@/trpc/router';
169
-
170
- // Create a type-safe bridge to your tRPC router
171
- export const bridge = createTrpcBridge<AppRouter>({
172
- trpcBase: '/trpc',
173
- });
174
- ```
175
-
176
- ### Create Actions from Procedures
177
-
178
- ```typescript
179
- // src/actions/users.ts
180
- import { bridge } from './bridge';
181
-
182
- // Type-safe: 'users.get' is validated against AppRouter
183
- export const getUser = bridge.createAction('users.get');
184
-
185
- // With options
186
- export const updateUser = bridge.createAction('users.update', {
187
- requireAuth: true,
188
- });
189
-
190
- // Protected shorthand
191
- export const deleteUser = bridge.createProtectedAction('users.delete');
192
- ```
193
-
194
- ### Custom Action with Multiple Procedure Calls
195
-
196
- ```typescript
197
- // src/actions/checkout.ts
198
- import { bridge } from './bridge';
199
- import { z } from 'zod';
200
-
201
- export const checkout = bridge.handler(
202
- async (input, ctx, call) => {
203
- // Call multiple procedures in sequence
204
- const cart = await call('cart.get', { userId: ctx.user.id });
205
-
206
- if (!cart.success) {
207
- throw new Error('Cart not found');
208
- }
209
-
210
- const order = await call('orders.create', {
211
- items: cart.data.items,
212
- total: cart.data.total,
213
- });
214
-
215
- await call('cart.clear', { userId: ctx.user.id });
216
-
217
- return order.data;
218
- },
219
- {
220
- input: z.object({ paymentMethod: z.string() }),
221
- requireAuth: true,
222
- }
223
- );
224
- ```
225
-
226
- ## File-Based Routing
227
-
228
- Routes are derived from the file structure in `app/pages/`:
229
-
230
- | File Path | Route Pattern | Description |
231
- |-----------|--------------|-------------|
232
- | `index.tsx` | `/` | Home page |
233
- | `about.tsx` | `/about` | Static page |
234
- | `users/index.tsx` | `/users` | List page |
235
- | `users/[id].tsx` | `/users/:id` | Dynamic segment |
236
- | `users/[id]/edit.tsx` | `/users/:id/edit` | Nested dynamic |
237
- | `docs/[...slug].tsx` | `/docs/*` | Catch-all route |
238
- | `(auth)/login.tsx` | `/login` | Route group (no /auth prefix) |
239
- | `(marketing)/pricing.tsx` | `/pricing` | Another route group |
240
-
241
- ### Dynamic Routes
242
-
243
- ```tsx
244
- // app/pages/users/[id].tsx
245
- interface PageProps {
246
- params: { id: string };
247
- searchParams: Record<string, string | string[]>;
248
- }
249
-
250
- export default async function UserPage({ params }: PageProps) {
251
- const user = await db.user.findUnique({
252
- where: { id: params.id },
253
- });
254
-
255
- if (!user) {
256
- return <div>User not found</div>;
257
- }
258
-
259
- return (
260
- <div>
261
- <h1>{user.name}</h1>
262
- <p>{user.email}</p>
263
- </div>
264
- );
265
- }
266
- ```
267
-
268
- ### Catch-All Routes
269
-
270
- ```tsx
271
- // app/pages/docs/[...slug].tsx
272
- interface PageProps {
273
- params: { '*': string }; // e.g., "getting-started/installation"
274
- }
275
-
276
- export default async function DocsPage({ params }: PageProps) {
277
- const slugParts = params['*'].split('/');
278
- const doc = await loadDoc(slugParts);
279
-
280
- return <article dangerouslySetInnerHTML={{ __html: doc.html }} />;
281
- }
282
- ```
283
-
284
- ### Layouts
285
-
286
- Create `_layout.tsx` files for shared layouts:
287
-
288
- ```tsx
289
- // app/pages/_layout.tsx (root layout)
290
- export default function RootLayout({ children }: { children: React.ReactNode }) {
291
- return (
292
- <html lang="en">
293
- <head>
294
- <meta charSet="UTF-8" />
295
- <title>My App</title>
296
- </head>
297
- <body>
298
- <nav>{/* Navigation */}</nav>
299
- <main>{children}</main>
300
- <footer>{/* Footer */}</footer>
301
- </body>
302
- </html>
303
- );
304
- }
305
-
306
- // app/pages/(dashboard)/_layout.tsx (group layout)
307
- export default function DashboardLayout({ children }: { children: React.ReactNode }) {
308
- return (
309
- <div className="dashboard">
310
- <aside>{/* Sidebar */}</aside>
311
- <div className="content">{children}</div>
312
- </div>
313
- );
314
- }
315
- ```
316
-
317
- ## SSR Rendering
318
-
319
- ### Basic SSR Setup
320
-
321
- ```typescript
322
- // src/entry.server.tsx
323
- import { createSsrRouter, createFileRouter, renderToStream } from '@veloxts/web';
324
-
325
- const fileRouter = createFileRouter({
326
- pagesDir: 'app/pages',
327
- });
328
-
329
- export default createSsrRouter({
330
- resolveRoute: (path) => fileRouter.match(path),
331
- render: (match, request) => renderToStream(match, request, {
332
- bootstrapScripts: ['/_build/client.js'],
333
- }),
334
- });
335
- ```
336
-
337
- ### With Initial Data
338
-
339
- ```typescript
340
- // src/entry.server.tsx
341
- export default createSsrRouter({
342
- resolveRoute: (path) => fileRouter.match(path),
343
- render: async (match, request) => {
344
- // Fetch data for the page
345
- const user = await getCurrentUser(request);
346
-
347
- return renderToStream(match, request, {
348
- bootstrapScripts: ['/_build/client.js'],
349
- initialData: { user },
350
- });
351
- },
352
- });
353
- ```
354
-
355
- ## Client Hydration
356
-
357
- ### Basic Hydration
358
-
359
- ```typescript
360
- // src/entry.client.tsx
361
- import { hydrate } from '@veloxts/web';
362
- import App from './App';
363
-
364
- hydrate(<App />);
365
- ```
366
-
367
- ### With Initial Data
368
-
369
- ```typescript
370
- // src/entry.client.tsx
371
- import { hydrate, getInitialData } from '@veloxts/web';
372
- import App from './App';
373
-
374
- const result = hydrate(<App />);
375
-
376
- // Access initial data from server
377
- const initialData = result.initialData;
378
- // Or use the helper
379
- const data = getInitialData<{ user: User }>();
380
- ```
381
-
382
- ### Custom Error Handling
383
-
384
- ```typescript
385
- // src/entry.client.tsx
386
- import { hydrate } from '@veloxts/web';
387
- import App from './App';
388
-
389
- hydrate(<App />, {
390
- onRecoverableError: (error) => {
391
- // Custom error reporting
392
- console.error('Hydration error:', error);
393
- reportToSentry(error);
394
- },
395
- });
396
- ```
397
-
398
- ## Error Handling
399
-
400
- ### Action Error Codes
401
-
402
- Server actions return typed results with error codes:
403
-
404
- ```typescript
405
- type ActionErrorCode =
406
- | 'VALIDATION_ERROR' // Input validation failed
407
- | 'UNAUTHORIZED' // Not authenticated
408
- | 'FORBIDDEN' // Not authorized
409
- | 'NOT_FOUND' // Resource not found
410
- | 'CONFLICT' // Duplicate/conflict error
411
- | 'RATE_LIMITED' // Too many requests
412
- | 'BAD_REQUEST' // Invalid request
413
- | 'INTERNAL_ERROR'; // Server error
414
- ```
415
-
416
- ### Handling Errors in Components
417
-
418
- ```tsx
419
- import { createUser } from '@/actions/users';
420
-
421
- async function handleCreate(data: FormData) {
422
- const result = await createUser({
423
- name: data.get('name') as string,
424
- email: data.get('email') as string,
425
- });
426
-
427
- if (!result.success) {
428
- switch (result.error.code) {
429
- case 'VALIDATION_ERROR':
430
- setErrors(result.error.message);
431
- break;
432
- case 'CONFLICT':
433
- setErrors('Email already exists');
434
- break;
435
- case 'RATE_LIMITED':
436
- setErrors('Too many attempts. Please wait.');
437
- break;
438
- default:
439
- setErrors('Something went wrong');
440
- }
441
- return;
442
- }
443
-
444
- // Success
445
- router.push(`/users/${result.data.id}`);
446
- }
447
- ```
448
-
449
- ### Custom Error Handler
450
-
451
- ```typescript
452
- import { action } from '@veloxts/web';
453
-
454
- export const riskyAction = action()
455
- .input(z.object({ id: z.string() }))
456
- .onError((error, ctx) => {
457
- // Custom error handling
458
- logError(error, { userId: ctx.user?.id });
459
-
460
- // Return custom error response
461
- return {
462
- success: false,
463
- error: {
464
- code: 'INTERNAL_ERROR',
465
- message: 'Operation failed. Please try again.',
466
- },
467
- };
468
- })
469
- .handler(async (input) => {
470
- // Handler logic
471
- });
472
- ```
473
-
474
- ## API Reference
475
-
476
- ### Actions
477
-
478
- | Export | Description |
479
- |--------|-------------|
480
- | `action()` | Create a server action with fluent builder API |
481
- | `createAction()` | Low-level action creator |
482
- | `createTrpcBridge()` | Create a tRPC bridge for calling procedures |
483
-
484
- ### Routing
485
-
486
- | Export | Description |
487
- |--------|-------------|
488
- | `createFileRouter()` | Create file-based router |
489
- | `createSsrRouter()` | Create SSR request handler |
490
- | `createApiRouter()` | Create API route handler |
491
- | `createClientRouter()` | Create client-side router |
492
-
493
- ### Rendering
494
-
495
- | Export | Description |
496
- |--------|-------------|
497
- | `renderToStream()` | Render React to streaming response |
498
- | `hydrate()` | Hydrate server-rendered React |
499
- | `getInitialData()` | Get initial data passed from server |
500
- | `Document` | Default HTML document component |
501
-
502
- ### Utilities
503
-
504
- | Export | Description |
505
- |--------|-------------|
506
- | `escapeHtml()` | Escape HTML special characters (XSS prevention) |
507
- | `isSuccess()` | Type guard for successful action results |
508
- | `isError()` | Type guard for error action results |
509
-
510
- ## Architecture
511
-
512
- ```
513
- ┌─────────────────────────────────────────────────────────┐
514
- │ Vinxi (HTTP Entry Point) │
515
- ├─────────────────────────────────────────────────────────┤
516
- │ /api/* → Fastify (Embedded via createApiRouter) │
517
- │ /trpc/* → tRPC (via Fastify adapter) │
518
- │ /_build/* → Static Assets (Vite client bundle) │
519
- │ /* → RSC Renderer (React Flight streaming) │
520
- └─────────────────────────────────────────────────────────┘
521
-
522
-
523
- ┌─────────────────────────────────────────────────────────┐
524
- │ Server Actions (via tRPC Bridge) │
525
- ├─────────────────────────────────────────────────────────┤
526
- │ action() → Validated handlers with Zod schemas │
527
- │ bridge.call → Type-safe procedure invocation │
528
- │ ctx.user → Authenticated user from session │
529
- └─────────────────────────────────────────────────────────┘
530
- ```
531
-
532
- ## Requirements
533
-
534
- - Node.js 20+
535
- - React 19+
536
- - TypeScript 5.5+
537
-
538
- ## Status
539
-
540
- **v0.5.0** - Production ready for MVP applications.
541
-
542
- - 615 tests passing
543
- - 94.4% code coverage
544
- - Full TypeScript support with zero `any` types
5
+ React Server Components integration for VeloxTS Framework using Vinxi - provides file-based routing, streaming SSR, server actions with Zod validation, and tRPC bridge. Learn more at [@veloxts/velox](https://www.npmjs.com/package/@veloxts/velox).
545
6
 
546
7
  ## License
547
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/web",
3
- "version": "0.6.23",
3
+ "version": "0.6.26",
4
4
  "description": "React Server Components integration for VeloxTS framework using Vinxi",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -41,10 +41,10 @@
41
41
  "peerDependencies": {
42
42
  "react": ">=19.2.0",
43
43
  "react-dom": ">=19.2.0",
44
- "@veloxts/auth": "0.6.23",
45
- "@veloxts/core": "0.6.23",
46
- "@veloxts/router": "0.6.23",
47
- "@veloxts/client": "0.6.23"
44
+ "@veloxts/auth": "0.6.26",
45
+ "@veloxts/client": "0.6.26",
46
+ "@veloxts/core": "0.6.26",
47
+ "@veloxts/router": "0.6.26"
48
48
  },
49
49
  "peerDependenciesMeta": {
50
50
  "@veloxts/auth": {
@@ -67,10 +67,10 @@
67
67
  "react-server-dom-webpack": "19.2.3",
68
68
  "typescript": "5.9.3",
69
69
  "vitest": "4.0.16",
70
- "@veloxts/auth": "0.6.23",
71
- "@veloxts/core": "0.6.23",
72
- "@veloxts/router": "0.6.23",
73
- "@veloxts/client": "0.6.23"
70
+ "@veloxts/client": "0.6.26",
71
+ "@veloxts/auth": "0.6.26",
72
+ "@veloxts/router": "0.6.26",
73
+ "@veloxts/core": "0.6.26"
74
74
  },
75
75
  "keywords": [
76
76
  "velox",