@getvision/server 0.4.0-6e5c887-develop → 0.4.1-b47357d-develop
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/CHANGELOG.md +27 -0
- package/package.json +1 -1
- package/src/service.ts +56 -7
- package/src/vision-app.ts +2 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @getvision/server
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6e5c887: Migrate all adapters to use UniversalValidator supporting Zod, Valibot, and Standard Schema v1. The new validation system provides:
|
|
8
|
+
- Unified `validator()` function that works with any validation library
|
|
9
|
+
- Automatic error response formatting with proper issue paths
|
|
10
|
+
- Schema introspection for template generation
|
|
11
|
+
- Backward compatibility with existing zValidator (deprecated)
|
|
12
|
+
|
|
13
|
+
**Breaking changes:**
|
|
14
|
+
- `zValidator` is now deprecated in favor of universal `validator()`
|
|
15
|
+
- Error response format has been standardized across all adapters
|
|
16
|
+
- Some internal types have changed to support multiple validation libraries
|
|
17
|
+
|
|
18
|
+
Migration guide:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// Before
|
|
22
|
+
import { zValidator } from "@getvision/adapter-hono";
|
|
23
|
+
app.post("/users", zValidator("json", userSchema));
|
|
24
|
+
|
|
25
|
+
// After (works with Zod, Valibot, or Standard Schema)
|
|
26
|
+
import { validator } from "@getvision/adapter-hono";
|
|
27
|
+
app.post("/users", validator("json", userSchema));
|
|
28
|
+
```
|
|
29
|
+
|
|
3
30
|
## 0.3.6
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/package.json
CHANGED
package/src/service.ts
CHANGED
|
@@ -145,20 +145,42 @@ export class ServiceBuilder<
|
|
|
145
145
|
public getRoutesMetadata(): Array<{
|
|
146
146
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
147
147
|
path: string
|
|
148
|
+
queryParams?: any
|
|
148
149
|
requestBody?: any
|
|
149
150
|
responseBody?: any
|
|
150
151
|
}> {
|
|
151
152
|
const routes: Array<{
|
|
152
153
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
|
|
153
154
|
path: string
|
|
155
|
+
queryParams?: any
|
|
154
156
|
requestBody?: any
|
|
155
157
|
responseBody?: any
|
|
156
158
|
}> = []
|
|
157
159
|
this.endpoints.forEach((ep) => {
|
|
158
160
|
let requestBody = undefined
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
let queryParams = undefined
|
|
162
|
+
|
|
163
|
+
if (ep.schema.input) {
|
|
164
|
+
if (['POST', 'PUT', 'PATCH'].includes(ep.method)) {
|
|
165
|
+
requestBody = generateTemplate(ep.schema.input)
|
|
166
|
+
} else if (ep.method === 'GET' || ep.method === 'DELETE') {
|
|
167
|
+
// Exclude path params from query params
|
|
168
|
+
const pathParamNames = (ep.path.match(/:([^/]+)/g) || []).map((p: string) => p.slice(1))
|
|
169
|
+
const fullTemplate = generateTemplate(ep.schema.input)
|
|
170
|
+
|
|
171
|
+
if (fullTemplate && pathParamNames.length > 0) {
|
|
172
|
+
const queryFields = fullTemplate.fields.filter(
|
|
173
|
+
(f: { name: string }) => !pathParamNames.includes(f.name)
|
|
174
|
+
)
|
|
175
|
+
if (queryFields.length > 0) {
|
|
176
|
+
queryParams = { ...fullTemplate, fields: queryFields }
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
queryParams = fullTemplate
|
|
180
|
+
}
|
|
181
|
+
}
|
|
161
182
|
}
|
|
183
|
+
|
|
162
184
|
let responseBody = undefined
|
|
163
185
|
if (ep.schema.output) {
|
|
164
186
|
responseBody = generateTemplate(ep.schema.output)
|
|
@@ -166,6 +188,7 @@ export class ServiceBuilder<
|
|
|
166
188
|
routes.push({
|
|
167
189
|
method: ep.method,
|
|
168
190
|
path: ep.path,
|
|
191
|
+
queryParams,
|
|
169
192
|
requestBody,
|
|
170
193
|
responseBody,
|
|
171
194
|
})
|
|
@@ -384,13 +407,34 @@ export class ServiceBuilder<
|
|
|
384
407
|
build(app: Hono, servicesAccumulator?: Array<{ name: string; routes: any[] }>) {
|
|
385
408
|
// Prepare routes with Zod schemas
|
|
386
409
|
const routes = Array.from(this.endpoints.values()).map(ep => {
|
|
387
|
-
// Generate requestBody schema (input)
|
|
410
|
+
// Generate requestBody schema (input) for POST/PUT/PATCH
|
|
388
411
|
let requestBody = undefined
|
|
389
|
-
|
|
390
|
-
|
|
412
|
+
let queryParams = undefined
|
|
413
|
+
|
|
414
|
+
if (ep.schema.input) {
|
|
415
|
+
if (['POST', 'PUT', 'PATCH'].includes(ep.method)) {
|
|
416
|
+
requestBody = generateTemplate(ep.schema.input)
|
|
417
|
+
} else if (ep.method === 'GET' || ep.method === 'DELETE') {
|
|
418
|
+
// For GET/DELETE, input schema represents query parameters
|
|
419
|
+
// BUT we need to exclude path params from query params
|
|
420
|
+
const pathParamNames = (ep.path.match(/:([^/]+)/g) || []).map((p: string) => p.slice(1))
|
|
421
|
+
const fullTemplate = generateTemplate(ep.schema.input)
|
|
422
|
+
|
|
423
|
+
if (fullTemplate && pathParamNames.length > 0) {
|
|
424
|
+
// Filter out path params from query params
|
|
425
|
+
const queryFields = fullTemplate.fields.filter(
|
|
426
|
+
(f: { name: string }) => !pathParamNames.includes(f.name)
|
|
427
|
+
)
|
|
428
|
+
if (queryFields.length > 0) {
|
|
429
|
+
queryParams = { ...fullTemplate, fields: queryFields }
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
queryParams = fullTemplate
|
|
433
|
+
}
|
|
434
|
+
}
|
|
391
435
|
}
|
|
392
436
|
|
|
393
|
-
// Generate responseBody schema (output)
|
|
437
|
+
// Generate responseBody schema (output)
|
|
394
438
|
let responseBody = undefined
|
|
395
439
|
if (ep.schema.output) {
|
|
396
440
|
responseBody = generateTemplate(ep.schema.output)
|
|
@@ -401,6 +445,7 @@ export class ServiceBuilder<
|
|
|
401
445
|
path: ep.path,
|
|
402
446
|
handler: this.name,
|
|
403
447
|
middleware: [],
|
|
448
|
+
queryParams,
|
|
404
449
|
requestBody,
|
|
405
450
|
responseBody,
|
|
406
451
|
}
|
|
@@ -525,8 +570,12 @@ export class ServiceBuilder<
|
|
|
525
570
|
// Validate input with UniversalValidator (supports Zod, Valibot, etc.)
|
|
526
571
|
const validated = UniversalValidator.parse(ep.schema.input, input)
|
|
527
572
|
|
|
573
|
+
// Merge back path params that are not in the schema
|
|
574
|
+
// This ensures path params like :id are always available to the handler
|
|
575
|
+
const finalInput = { ...params, ...(validated || {}) }
|
|
576
|
+
|
|
528
577
|
// Execute handler
|
|
529
|
-
const result = await ep.handler(
|
|
578
|
+
const result = await ep.handler(finalInput, c as any)
|
|
530
579
|
|
|
531
580
|
// If an output schema exists, validate and return JSON
|
|
532
581
|
if (ep.schema.output) {
|
package/src/vision-app.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
|
-
import type { Env, Schema
|
|
2
|
+
import type { Env, Schema } from 'hono'
|
|
3
3
|
import { VisionCore, runInTraceContext } from '@getvision/core'
|
|
4
4
|
import type { RouteMetadata } from '@getvision/core'
|
|
5
5
|
import { AsyncLocalStorage } from 'async_hooks'
|
|
@@ -161,9 +161,7 @@ export class Vision<
|
|
|
161
161
|
private eventBus: EventBus
|
|
162
162
|
private config: VisionConfig
|
|
163
163
|
private serviceBuilders: ServiceBuilder<any, E>[] = []
|
|
164
|
-
private fileBasedRoutes: RouteMetadata[] = []
|
|
165
164
|
private bunServer?: any
|
|
166
|
-
private _cleanupPromise?: Promise<void>
|
|
167
165
|
private signalHandler?: () => Promise<void>
|
|
168
166
|
|
|
169
167
|
constructor(config?: VisionConfig) {
|
|
@@ -540,6 +538,7 @@ export class Vision<
|
|
|
540
538
|
method: r.method,
|
|
541
539
|
path: r.path,
|
|
542
540
|
handler: name,
|
|
541
|
+
queryParams: r.queryParams,
|
|
543
542
|
requestBody: r.requestBody,
|
|
544
543
|
responseBody: r.responseBody,
|
|
545
544
|
}))
|
|
@@ -559,31 +558,6 @@ export class Vision<
|
|
|
559
558
|
builder.build(this as any, allServices)
|
|
560
559
|
}
|
|
561
560
|
|
|
562
|
-
// Group file-based routes by path prefix (e.g., /products, /analytics)
|
|
563
|
-
if (this.fileBasedRoutes.length > 0) {
|
|
564
|
-
const groupedRoutes = new Map<string, RouteMetadata[]>()
|
|
565
|
-
|
|
566
|
-
for (const route of this.fileBasedRoutes) {
|
|
567
|
-
// Extract first path segment as service name
|
|
568
|
-
const segments = route.path.split('/').filter(s => s && !s.startsWith(':'))
|
|
569
|
-
const serviceName = segments[0] || 'root'
|
|
570
|
-
|
|
571
|
-
if (!groupedRoutes.has(serviceName)) {
|
|
572
|
-
groupedRoutes.set(serviceName, [])
|
|
573
|
-
}
|
|
574
|
-
groupedRoutes.get(serviceName)!.push(route)
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Add each group as a service
|
|
578
|
-
for (const [name, routes] of groupedRoutes.entries()) {
|
|
579
|
-
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1)
|
|
580
|
-
allServices.push({
|
|
581
|
-
name: capitalizedName,
|
|
582
|
-
routes
|
|
583
|
-
})
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
561
|
// Don't register to VisionCore here - let start() handle it after sub-apps are loaded
|
|
588
562
|
// Just return allServices so they can be registered later
|
|
589
563
|
return allServices
|