@getvision/adapter-express 0.0.0-develop-20251031183955

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 ADDED
@@ -0,0 +1,435 @@
1
+ import { VisionCore, autoDetectPackageInfo, autoDetectIntegrations, } from '@getvision/core';
2
+ import { AsyncLocalStorage } from 'async_hooks';
3
+ import { generateZodTemplate } from './zod-utils';
4
+ const visionContext = new AsyncLocalStorage();
5
+ /**
6
+ * Get current vision context (vision instance and traceId)
7
+ * Available in route handlers when using visionMiddleware
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * app.get('/users', (req, res) => {
12
+ * const { vision, traceId } = getVisionContext()
13
+ * // ...
14
+ * })
15
+ * ```
16
+ */
17
+ export function getVisionContext() {
18
+ const context = visionContext.getStore();
19
+ if (!context) {
20
+ throw new Error('Vision context not available. Make sure visionMiddleware is enabled.');
21
+ }
22
+ return context;
23
+ }
24
+ /**
25
+ * Create span helper using current trace context
26
+ * Child spans will be nested under the root http.request span
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * app.get('/users', async (req, res) => {
31
+ * const withSpan = useVisionSpan()
32
+ *
33
+ * const users = withSpan('db.select', { 'db.table': 'users' }, () => {
34
+ * return db.select().from(users).all()
35
+ * })
36
+ * })
37
+ * ```
38
+ */
39
+ export function useVisionSpan() {
40
+ const { vision, traceId, rootSpanId } = getVisionContext();
41
+ const tracer = vision.getTracer();
42
+ return (name, attributes = {}, fn) => {
43
+ // Start child span with parentId = rootSpanId
44
+ const span = tracer.startSpan(name, traceId, rootSpanId);
45
+ console.log(`[useVisionSpan] Created span: ${name} with parentId: ${rootSpanId}`);
46
+ // Add attributes
47
+ for (const [key, value] of Object.entries(attributes)) {
48
+ tracer.setAttribute(span.id, key, value);
49
+ }
50
+ try {
51
+ const result = fn();
52
+ const completedSpan = tracer.endSpan(span.id);
53
+ // Add span to trace store
54
+ if (completedSpan) {
55
+ vision.getTraceStore().addSpan(traceId, completedSpan);
56
+ }
57
+ return result;
58
+ }
59
+ catch (error) {
60
+ tracer.setAttribute(span.id, 'error', true);
61
+ tracer.setAttribute(span.id, 'error.message', error instanceof Error ? error.message : String(error));
62
+ const completedSpan = tracer.endSpan(span.id);
63
+ // Add span to trace store even on error
64
+ if (completedSpan) {
65
+ vision.getTraceStore().addSpan(traceId, completedSpan);
66
+ }
67
+ throw error;
68
+ }
69
+ };
70
+ }
71
+ let visionInstance = null;
72
+ const discoveredRoutes = [];
73
+ /**
74
+ * Express middleware for Vision Dashboard
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * import express from 'express'
79
+ * import { visionMiddleware } from '@getvision/adapter-express'
80
+ *
81
+ * const app = express()
82
+ *
83
+ * if (process.env.NODE_ENV === 'development') {
84
+ * app.use(visionMiddleware({ port: 9500 }))
85
+ * }
86
+ *
87
+ * app.get('/hello', (req, res) => {
88
+ * res.json({ message: 'Hello!' })
89
+ * })
90
+ *
91
+ * app.listen(3000)
92
+ * ```
93
+ */
94
+ export function visionMiddleware(options = {}) {
95
+ const enabled = options.enabled ?? (process.env.VISION_ENABLED !== 'false');
96
+ if (!enabled) {
97
+ return (req, res, next) => next();
98
+ }
99
+ // Initialize Vision instance
100
+ if (!visionInstance) {
101
+ visionInstance = new VisionCore({
102
+ port: options.port ?? parseInt(process.env.VISION_PORT || '9500'),
103
+ maxTraces: options.maxTraces ?? 1000,
104
+ maxLogs: options.maxLogs ?? 10000,
105
+ });
106
+ // Auto-detect service info
107
+ const pkgInfo = autoDetectPackageInfo();
108
+ const autoIntegrations = autoDetectIntegrations();
109
+ // Merge with user-provided config
110
+ const serviceName = options.service?.name || pkgInfo.name;
111
+ const serviceVersion = options.service?.version || pkgInfo.version;
112
+ const serviceDesc = options.service?.description;
113
+ const integrations = {
114
+ ...autoIntegrations,
115
+ ...options.service?.integrations,
116
+ };
117
+ // Filter out undefined values from integrations
118
+ const cleanIntegrations = {};
119
+ for (const [key, value] of Object.entries(integrations)) {
120
+ if (value !== undefined) {
121
+ cleanIntegrations[key] = value;
122
+ }
123
+ }
124
+ visionInstance.setAppStatus({
125
+ name: serviceName,
126
+ version: serviceVersion,
127
+ description: serviceDesc,
128
+ environment: process.env.NODE_ENV || 'development',
129
+ running: true,
130
+ metadata: {
131
+ framework: "Express",
132
+ integrations: Object.keys(cleanIntegrations).length > 0 ? cleanIntegrations : undefined,
133
+ }
134
+ });
135
+ }
136
+ const vision = visionInstance;
137
+ const enableCors = options.cors !== false;
138
+ const logging = options.logging !== false;
139
+ // Return middleware function
140
+ return (req, res, next) => {
141
+ // Add CORS headers for Vision
142
+ if (enableCors) {
143
+ res.setHeader('Access-Control-Allow-Origin', '*');
144
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
145
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Vision-Trace-Id, X-Vision-Session');
146
+ res.setHeader('Access-Control-Expose-Headers', 'X-Vision-Trace-Id, X-Vision-Session');
147
+ // Handle preflight
148
+ if (req.method === 'OPTIONS') {
149
+ return res.status(204).end();
150
+ }
151
+ }
152
+ const startTime = Date.now();
153
+ // Create trace
154
+ const trace = vision.createTrace(req.method, req.path || req.url);
155
+ // Add trace ID to response header
156
+ res.setHeader('X-Vision-Trace-Id', trace.id);
157
+ // Start main root span for the entire request
158
+ const tracer = vision.getTracer();
159
+ const rootSpan = tracer.startSpan('http.request', trace.id);
160
+ // Add request attributes to span
161
+ tracer.setAttribute(rootSpan.id, 'http.method', req.method);
162
+ tracer.setAttribute(rootSpan.id, 'http.path', req.path || req.url);
163
+ tracer.setAttribute(rootSpan.id, 'http.url', req.originalUrl || req.url);
164
+ // Add query params if any
165
+ if (req.query && Object.keys(req.query).length > 0) {
166
+ tracer.setAttribute(rootSpan.id, 'http.query', req.query);
167
+ }
168
+ // Capture request metadata
169
+ const requestMeta = {
170
+ method: req.method,
171
+ url: req.originalUrl || req.url,
172
+ headers: req.headers,
173
+ query: Object.keys(req.query || {}).length ? req.query : undefined,
174
+ body: req.body,
175
+ };
176
+ tracer.setAttribute(rootSpan.id, 'http.request', requestMeta);
177
+ trace.metadata = { ...trace.metadata, request: requestMeta };
178
+ // Session ID tracking
179
+ const sessionId = req.headers['x-vision-session'];
180
+ if (sessionId) {
181
+ tracer.setAttribute(rootSpan.id, 'session.id', sessionId);
182
+ trace.metadata = { ...trace.metadata, sessionId };
183
+ }
184
+ // Log request start if logging enabled
185
+ if (logging) {
186
+ const parts = [`method=${req.method}`, `path=${req.path || req.url}`];
187
+ if (sessionId)
188
+ parts.push(`sessionId=${sessionId}`);
189
+ parts.push(`traceId=${trace.id}`);
190
+ console.info(`INF starting request ${parts.join(' ')}`);
191
+ }
192
+ // Capture response body
193
+ let responseBody = null;
194
+ let isJsonResponse = false;
195
+ const originalSend = res.send;
196
+ const originalJson = res.json;
197
+ res.send = function (body) {
198
+ // Only capture if not already captured by res.json
199
+ if (!isJsonResponse) {
200
+ responseBody = body;
201
+ }
202
+ return originalSend.call(this, body);
203
+ };
204
+ res.json = function (body) {
205
+ // Capture the object BEFORE it's stringified
206
+ responseBody = body;
207
+ isJsonResponse = true;
208
+ return originalJson.call(this, body);
209
+ };
210
+ // Wrap next() to handle completion in finally block (like Hono)
211
+ const wrappedNext = () => {
212
+ try {
213
+ // Run handler in AsyncLocalStorage context with rootSpanId
214
+ visionContext.run({ vision, traceId: trace.id, rootSpanId: rootSpan.id }, () => {
215
+ next();
216
+ });
217
+ }
218
+ catch (error) {
219
+ // Track error in span
220
+ tracer.addEvent(rootSpan.id, 'error', {
221
+ message: error instanceof Error ? error.message : 'Unknown error',
222
+ stack: error instanceof Error ? error.stack : undefined,
223
+ });
224
+ tracer.setAttribute(rootSpan.id, 'error', true);
225
+ throw error;
226
+ }
227
+ };
228
+ // Listen for response finish to complete span
229
+ res.on('finish', () => {
230
+ try {
231
+ const duration = Date.now() - startTime;
232
+ // Add response attributes
233
+ tracer.setAttribute(rootSpan.id, 'http.status_code', res.statusCode);
234
+ const responseMeta = {
235
+ status: res.statusCode,
236
+ headers: res.getHeaders(),
237
+ body: responseBody,
238
+ };
239
+ tracer.setAttribute(rootSpan.id, 'http.response', responseMeta);
240
+ trace.metadata = { ...trace.metadata, response: responseMeta };
241
+ // End span and add to trace
242
+ const completedSpan = tracer.endSpan(rootSpan.id);
243
+ if (completedSpan) {
244
+ vision.getTraceStore().addSpan(trace.id, completedSpan);
245
+ }
246
+ // Complete trace (broadcasts to dashboard)
247
+ vision.completeTrace(trace.id, res.statusCode, duration);
248
+ // Log completion
249
+ if (logging) {
250
+ console.info(`INF request completed code=${res.statusCode} duration=${duration}ms method=${req.method} path=${req.path || req.url} traceId=${trace.id}`);
251
+ }
252
+ }
253
+ catch (error) {
254
+ console.error('Vision: Error completing trace:', error);
255
+ }
256
+ });
257
+ // Execute next
258
+ wrappedNext();
259
+ };
260
+ }
261
+ /**
262
+ * Match route path against pattern (simple glob-like matching)
263
+ */
264
+ function matchPattern(path, pattern) {
265
+ if (pattern.endsWith('/*')) {
266
+ const prefix = pattern.slice(0, -2);
267
+ return path === prefix || path.startsWith(prefix + '/');
268
+ }
269
+ return path === pattern;
270
+ }
271
+ /**
272
+ * Group routes by services (auto or manual)
273
+ */
274
+ function groupRoutesByServices(routes, servicesConfig) {
275
+ const groups = {};
276
+ // Manual grouping if config provided
277
+ if (servicesConfig && servicesConfig.length > 0) {
278
+ servicesConfig.forEach((svc) => {
279
+ groups[svc.name] = { name: svc.name, description: svc.description, routes: [] };
280
+ });
281
+ groups['__uncategorized'] = { name: 'Uncategorized', routes: [] };
282
+ routes.forEach((route) => {
283
+ let matched = false;
284
+ for (const svc of servicesConfig) {
285
+ if (svc.routes.some((pattern) => matchPattern(route.path, pattern))) {
286
+ groups[svc.name].routes.push(route);
287
+ matched = true;
288
+ break;
289
+ }
290
+ }
291
+ if (!matched) {
292
+ groups['__uncategorized'].routes.push(route);
293
+ }
294
+ });
295
+ if (groups['__uncategorized'].routes.length === 0) {
296
+ delete groups['__uncategorized'];
297
+ }
298
+ }
299
+ else {
300
+ // Auto-grouping: group by first path segment
301
+ groups['root'] = { name: 'Root', routes: [] };
302
+ const routesBySegment = new Map();
303
+ for (const route of routes) {
304
+ const segments = route.path.split('/').filter(Boolean);
305
+ const serviceName = segments.length > 0 ? segments[0] : 'root';
306
+ if (!routesBySegment.has(serviceName)) {
307
+ routesBySegment.set(serviceName, []);
308
+ }
309
+ routesBySegment.get(serviceName).push(route);
310
+ }
311
+ for (const [serviceName, serviceRoutes] of Array.from(routesBySegment.entries())) {
312
+ const hasMultiSegment = serviceRoutes.some((r) => r.path.split('/').filter(Boolean).length > 1);
313
+ if (hasMultiSegment || serviceName === 'root') {
314
+ const capitalizedName = serviceName === 'root' ? 'Root' : serviceName.charAt(0).toUpperCase() + serviceName.slice(1);
315
+ if (serviceName === 'root') {
316
+ groups['root'].routes.push(...serviceRoutes);
317
+ }
318
+ else {
319
+ groups[serviceName] = { name: capitalizedName, routes: serviceRoutes };
320
+ }
321
+ }
322
+ else {
323
+ groups['root'].routes.push(...serviceRoutes);
324
+ }
325
+ }
326
+ if (groups['root'].routes.length === 0) {
327
+ delete groups['root'];
328
+ }
329
+ }
330
+ return groups;
331
+ }
332
+ /**
333
+ * Enable automatic route discovery for Express app
334
+ *
335
+ * @example
336
+ * ```ts
337
+ * const app = express()
338
+ * app.use(visionMiddleware())
339
+ *
340
+ * // Define routes...
341
+ * app.get('/users', handler)
342
+ * app.post('/users', handler)
343
+ *
344
+ * // Enable auto-discovery after all routes defined
345
+ * enableAutoDiscovery(app)
346
+ * ```
347
+ */
348
+ export function enableAutoDiscovery(app, options) {
349
+ if (!visionInstance) {
350
+ console.warn('⚠️ Vision not initialized. Call visionMiddleware() first.');
351
+ return;
352
+ }
353
+ const routes = [];
354
+ // Express stores routes in app._router.stack
355
+ const router = app._router;
356
+ if (!router) {
357
+ console.warn('⚠️ Express router not found');
358
+ return;
359
+ }
360
+ function extractRoutes(stack, basePath = '') {
361
+ stack.forEach((layer) => {
362
+ // Skip built-in middleware and Vision middleware
363
+ if (!layer.route && layer.name &&
364
+ ['query', 'expressInit', 'jsonParser', 'urlencodedParser', 'corsMiddleware'].includes(layer.name)) {
365
+ return;
366
+ }
367
+ if (layer.route) {
368
+ // Regular route
369
+ const methods = Object.keys(layer.route.methods);
370
+ methods.forEach(method => {
371
+ const routePath = basePath + layer.route.path;
372
+ const routeMethod = method.toUpperCase();
373
+ // Try to get handler name and schema from stack
374
+ let handlerName = 'anonymous';
375
+ let schema = undefined;
376
+ if (layer.route.stack && layer.route.stack.length > 0) {
377
+ // Look for zValidator middleware with schema
378
+ for (const stackItem of layer.route.stack) {
379
+ if (stackItem.handle && stackItem.handle.__visionSchema) {
380
+ schema = stackItem.handle.__visionSchema;
381
+ }
382
+ // Find the actual handler (last non-middleware function)
383
+ if (stackItem.name && !['bound dispatch'].includes(stackItem.name)) {
384
+ handlerName = stackItem.name;
385
+ }
386
+ }
387
+ }
388
+ const route = {
389
+ method: routeMethod,
390
+ path: routePath,
391
+ handler: handlerName,
392
+ };
393
+ if (schema) {
394
+ route.schema = schema;
395
+ // Generate template from Zod schema
396
+ const requestBody = generateZodTemplate(schema);
397
+ if (requestBody) {
398
+ route.requestBody = requestBody;
399
+ }
400
+ }
401
+ routes.push(route);
402
+ });
403
+ }
404
+ else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
405
+ // Nested router - try to extract base path from regexp
406
+ let routerPath = '';
407
+ if (layer.regexp) {
408
+ const regexpSource = layer.regexp.source;
409
+ // Try to extract simple path from regexp
410
+ const match = regexpSource.match(/^\^\\\/([^\\?()]+)/);
411
+ if (match) {
412
+ routerPath = '/' + match[1].replace(/\\\//g, '/');
413
+ }
414
+ }
415
+ extractRoutes(layer.handle.stack, basePath + routerPath);
416
+ }
417
+ });
418
+ }
419
+ extractRoutes(router.stack);
420
+ visionInstance.registerRoutes(routes);
421
+ // Group routes by services
422
+ const grouped = groupRoutesByServices(routes, options?.services);
423
+ const services = Object.values(grouped);
424
+ visionInstance.registerServices(services);
425
+ const schemasCount = routes.filter(r => r.schema).length;
426
+ console.log(`📋 Vision: Discovered ${routes.length} routes (${services.length} services, ${schemasCount} schemas)`);
427
+ }
428
+ /**
429
+ * Get the current Vision instance
430
+ */
431
+ export function getVisionInstance() {
432
+ return visionInstance;
433
+ }
434
+ // Export Zod validator for schema-based validation
435
+ export { zValidator, getRouteSchema, getAllRouteSchemas } from './zod-validator';
@@ -0,0 +1,7 @@
1
+ import type { ZodType } from 'zod';
2
+ import type { RequestBodySchema } from '@getvision/core';
3
+ /**
4
+ * Generate JSON template with comments from Zod schema
5
+ */
6
+ export declare function generateZodTemplate(schema: ZodType): RequestBodySchema | undefined;
7
+ //# sourceMappingURL=zod-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-utils.d.ts","sourceRoot":"","sources":["../src/zod-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAyB,MAAM,KAAK,CAAA;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAe,MAAM,iBAAiB,CAAA;AAErE;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAalF"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Generate JSON template with comments from Zod schema
3
+ */
4
+ export function generateZodTemplate(schema) {
5
+ try {
6
+ const fields = extractZodFields(schema);
7
+ const template = generateJsonTemplate(fields);
8
+ return {
9
+ template,
10
+ fields,
11
+ };
12
+ }
13
+ catch (error) {
14
+ console.warn('Failed to generate template from Zod schema:', error);
15
+ return undefined;
16
+ }
17
+ }
18
+ /**
19
+ * Extract fields from Zod schema (supports v3 and v4)
20
+ */
21
+ function extractZodFields(schema, path = []) {
22
+ const fields = [];
23
+ // Support both v3 (_def) and v4 (def)
24
+ const def = schema.def || schema._def;
25
+ if (!def)
26
+ return fields;
27
+ // Unwrap ZodOptional, ZodNullable, ZodDefault - not needed for top level object
28
+ let unwrapped = schema;
29
+ const currentDef = unwrapped.def || unwrapped._def;
30
+ const typeName = currentDef?.type || currentDef?.typeName;
31
+ if (typeName === 'object' || typeName === 'ZodObject') {
32
+ // Get shape - it can be a function (getter) or direct object
33
+ const shapeValue = currentDef?.shape;
34
+ const shape = typeof shapeValue === 'function' ? shapeValue() : shapeValue;
35
+ for (const [key, value] of Object.entries(shape || {})) {
36
+ const fieldSchema = value;
37
+ const fieldDef = fieldSchema.def || fieldSchema._def;
38
+ const isOptional = fieldSchema.type === 'optional' ||
39
+ fieldDef?.type === 'optional' ||
40
+ fieldDef?.typeName === 'ZodOptional' ||
41
+ fieldDef?.typeName === 'ZodDefault';
42
+ // Get description - might be in wrapped schema for optional fields
43
+ let description = fieldDef?.description || fieldSchema.description;
44
+ if (!description && fieldDef?.wrapped) {
45
+ const wrappedDef = fieldDef.wrapped.def || fieldDef.wrapped._def;
46
+ description = wrappedDef?.description || fieldDef.wrapped.description;
47
+ }
48
+ // Determine type
49
+ let fieldType = getZodType(fieldSchema);
50
+ // Check for nested objects/arrays
51
+ const nested = extractZodFields(fieldSchema, [...path, key]);
52
+ fields.push({
53
+ name: key,
54
+ type: fieldType,
55
+ description,
56
+ required: !isOptional,
57
+ nested: nested.length > 0 ? nested : undefined,
58
+ example: getZodExample(fieldSchema, fieldType),
59
+ });
60
+ }
61
+ }
62
+ return fields;
63
+ }
64
+ /**
65
+ * Get Zod type as string (supports v3 and v4)
66
+ */
67
+ function getZodType(schema) {
68
+ let unwrapped = schema;
69
+ let def = unwrapped.def || unwrapped._def;
70
+ // Unwrap optional/nullable/default
71
+ while (def?.type === 'optional' ||
72
+ def?.type === 'nullable' ||
73
+ def?.type === 'default' ||
74
+ def?.typeName === 'ZodOptional' ||
75
+ def?.typeName === 'ZodNullable' ||
76
+ def?.typeName === 'ZodDefault') {
77
+ unwrapped = def?.innerType || def?.wrapped || unwrapped;
78
+ def = unwrapped.def || unwrapped._def;
79
+ }
80
+ // Support both v4 (type) and v3 (typeName)
81
+ const typeName = def?.type || def?.typeName || unwrapped.type;
82
+ switch (typeName) {
83
+ case 'string':
84
+ case 'ZodString':
85
+ return 'string';
86
+ case 'number':
87
+ case 'ZodNumber':
88
+ return 'number';
89
+ case 'boolean':
90
+ case 'ZodBoolean':
91
+ return 'boolean';
92
+ case 'array':
93
+ case 'ZodArray':
94
+ return 'array';
95
+ case 'object':
96
+ case 'ZodObject':
97
+ return 'object';
98
+ case 'enum':
99
+ case 'ZodEnum':
100
+ return 'enum';
101
+ case 'date':
102
+ case 'ZodDate':
103
+ return 'date';
104
+ default:
105
+ return 'any';
106
+ }
107
+ }
108
+ /**
109
+ * Get example value for Zod type
110
+ */
111
+ function getZodExample(schema, type) {
112
+ switch (type) {
113
+ case 'string': return '';
114
+ case 'number': return 0;
115
+ case 'boolean': return false;
116
+ case 'array': return [];
117
+ case 'object': return {};
118
+ default: return null;
119
+ }
120
+ }
121
+ /**
122
+ * Generate JSONC template with comments
123
+ */
124
+ function generateJsonTemplate(fields, indent = 0) {
125
+ const lines = [];
126
+ const spacing = ' '.repeat(indent);
127
+ lines.push('{');
128
+ fields.forEach((field, index) => {
129
+ const isLast = index === fields.length - 1;
130
+ const description = field.description || field.name;
131
+ // Add comment
132
+ lines.push(`${spacing} // ${description}`);
133
+ // Add field
134
+ let value;
135
+ if (field.nested && field.nested.length > 0) {
136
+ value = generateJsonTemplate(field.nested, indent + 1);
137
+ }
138
+ else {
139
+ value = JSON.stringify(field.example);
140
+ }
141
+ lines.push(`${spacing} "${field.name}": ${value}${isLast ? '' : ','}`);
142
+ });
143
+ lines.push(`${spacing}}`);
144
+ return lines.join('\n');
145
+ }
@@ -0,0 +1,44 @@
1
+ import type { RequestHandler } from 'express';
2
+ import type { ZodSchema } from 'zod';
3
+ /**
4
+ * Get stored schema for a route
5
+ */
6
+ export declare function getRouteSchema(method: string, path: string): ZodSchema | undefined;
7
+ /**
8
+ * Get all stored schemas
9
+ */
10
+ export declare function getAllRouteSchemas(): Map<string, {
11
+ method: string;
12
+ path: string;
13
+ schema: ZodSchema;
14
+ }>;
15
+ type ValidateTarget = 'body' | 'query' | 'params';
16
+ /**
17
+ * Zod validator middleware for Express
18
+ * Similar to @hono/zod-validator but stores schema for Vision introspection
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * import { zValidator } from '@getvision/adapter-express'
23
+ * import { z } from 'zod'
24
+ *
25
+ * const schema = z.object({
26
+ * name: z.string().describe('User name'),
27
+ * email: z.string().email().describe('User email'),
28
+ * })
29
+ *
30
+ * app.post('/users', zValidator('body', schema), (req, res) => {
31
+ * // req.body is now typed and validated
32
+ * const { name, email } = req.body
33
+ * res.json({ name, email })
34
+ * })
35
+ * ```
36
+ */
37
+ export declare function zValidator<T extends ZodSchema>(target: ValidateTarget, schema: T): RequestHandler;
38
+ /**
39
+ * Extract Zod schema from validator middleware
40
+ * Used internally by Vision to generate API docs
41
+ */
42
+ export declare function extractSchema(middleware: RequestHandler): ZodSchema | undefined;
43
+ export {};
44
+ //# sourceMappingURL=zod-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-validator.d.ts","sourceRoot":"","sources":["../src/zod-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAA;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAY,MAAM,KAAK,CAAA;AAK9C;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAGlF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,CAErG;AAED,KAAK,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAEjD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,SAAS,EAC5C,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,CAAC,GACR,cAAc,CA6DhB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,cAAc,GAAG,SAAS,GAAG,SAAS,CAG/E"}