@getvision/server 0.4.3 → 0.4.4-44d79d9-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.
Files changed (44) hide show
  1. package/dist/event-bus.d.ts +87 -0
  2. package/dist/event-bus.d.ts.map +1 -0
  3. package/dist/event-bus.js +265 -0
  4. package/dist/event-bus.js.map +10 -0
  5. package/dist/event-registry.d.ts +79 -0
  6. package/dist/event-registry.d.ts.map +1 -0
  7. package/dist/event-registry.js +93 -0
  8. package/dist/event-registry.js.map +10 -0
  9. package/{src/index.ts → dist/index.d.ts} +14 -28
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +33 -0
  12. package/dist/index.js.map +10 -0
  13. package/dist/router.d.ts +16 -0
  14. package/dist/router.d.ts.map +1 -0
  15. package/dist/router.js +117 -0
  16. package/dist/router.js.map +10 -0
  17. package/dist/service.d.ts +151 -0
  18. package/dist/service.d.ts.map +1 -0
  19. package/dist/service.js +341 -0
  20. package/dist/service.js.map +10 -0
  21. package/dist/types.d.ts +71 -0
  22. package/dist/types.d.ts.map +1 -0
  23. package/dist/types.js +2 -0
  24. package/dist/types.js.map +9 -0
  25. package/dist/vision-app.d.ts +166 -0
  26. package/dist/vision-app.d.ts.map +1 -0
  27. package/dist/vision-app.js +611 -0
  28. package/dist/vision-app.js.map +10 -0
  29. package/dist/vision.d.ts +63 -0
  30. package/dist/vision.d.ts.map +1 -0
  31. package/dist/vision.js +223 -0
  32. package/dist/vision.js.map +10 -0
  33. package/package.json +13 -3
  34. package/.env.example +0 -3
  35. package/.eslintrc.cjs +0 -7
  36. package/.turbo/turbo-build.log +0 -1
  37. package/src/event-bus.ts +0 -409
  38. package/src/event-registry.ts +0 -158
  39. package/src/router.ts +0 -118
  40. package/src/service.ts +0 -618
  41. package/src/types.ts +0 -93
  42. package/src/vision-app.ts +0 -880
  43. package/src/vision.ts +0 -319
  44. package/tsconfig.json +0 -9
package/src/service.ts DELETED
@@ -1,618 +0,0 @@
1
- import type { Hono, Context, MiddlewareHandler, Env, Input } from 'hono'
2
- import type { z } from 'zod'
3
- import { VisionCore, generateTemplate } from '@getvision/core'
4
- import {
5
- ValidationError,
6
- createValidationErrorResponse,
7
- UniversalValidator
8
- } from '@getvision/core'
9
- import type { EndpointConfig, Handler } from './types'
10
- import { getVisionContext } from './vision-app'
11
- import { eventRegistry } from './event-registry'
12
- import type { EventBus } from './event-bus'
13
- import { rateLimiter } from 'hono-rate-limiter'
14
-
15
- // Simple window parser supporting values like '15m', '1h', '30s', '2d' or plain milliseconds as number string
16
- function parseWindowMs(window: string): number {
17
- const trimmed = window.trim()
18
- if (/^\d+$/.test(trimmed)) return Number(trimmed)
19
- const match = trimmed.match(/^(\d+)\s*([smhd])$/i)
20
- if (!match) throw new Error(`Invalid ratelimit window: ${window}`)
21
- const value = Number(match[1])
22
- const unit = match[2].toLowerCase()
23
- const multipliers: Record<string, number> = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }
24
- return value * multipliers[unit]
25
- }
26
-
27
- function getClientKey(c: Context, method: string, path: string): string {
28
- const ip =
29
- c.req.header('x-forwarded-for')?.split(',')[0].trim() ||
30
- c.req.header('x-real-ip') ||
31
- c.req.header('cf-connecting-ip') ||
32
- c.req.header('fly-client-ip') ||
33
- c.req.header('x-client-ip') ||
34
- ''
35
- // Fallback to UA if no IP available (still scoped per endpoint)
36
- const ua = c.req.header('user-agent') || 'unknown'
37
- return `${ip || ua}:${method}:${path}`
38
- }
39
-
40
- /**
41
- * Create a minimal Hono-like Context for event handlers
42
- * so service-level middleware can populate c.set(...)
43
- */
44
- function createEventContext<E extends Env = any, I extends Input = {}>(): Context<E, any, I> {
45
- const store: Record<string, any> = {}
46
- const mockRequest = new Request('http://localhost/event-trigger')
47
- const resHeaders = new Headers()
48
-
49
- const fake: Partial<Context<E, any, I>> = {
50
- get: (key: string) => store[key],
51
- set: (key: string, value: any) => { store[key] = value },
52
- header: (key: string, value: string | undefined) => {
53
- if (value === undefined) {
54
- resHeaders.delete(key)
55
- } else {
56
- resHeaders.set(key, value)
57
- }
58
- },
59
- status: (code: number) => {},
60
- req: {
61
- header: ((name?: string) => name ? undefined : {}) as any,
62
- param: () => ({}),
63
- query: () => ({}),
64
- json: async () => ({}),
65
- raw: mockRequest,
66
- url: 'http://localhost/event-trigger',
67
- method: 'POST',
68
- } as any,
69
- res: {
70
- headers: resHeaders,
71
- } as any,
72
- }
73
- return fake as Context<E, any, I>
74
- }
75
-
76
- /**
77
- * Run Hono middleware chain on a context
78
- */
79
- async function runMiddlewareChain<E extends Env, I extends Input>(
80
- middlewares: MiddlewareHandler<E, string, any, any>[],
81
- c: Context<E, any, I>
82
- ): Promise<void> {
83
- let index = -1
84
- const dispatch = async (i: number): Promise<void> => {
85
- if (i <= index) throw new Error('next() called multiple times')
86
- index = i
87
- const mw = middlewares[i]
88
- if (!mw) return
89
- await mw(c, () => dispatch(i + 1))
90
- }
91
- await dispatch(0)
92
- }
93
-
94
- /**
95
- * Event schema map - accumulates event types as they're registered
96
- */
97
- type EventSchemaMap = Record<string, z.ZodSchema<any>>
98
-
99
- /**
100
- * ServiceBuilder - Builder pattern API for defining services
101
- *
102
- * Automatically infers event types from Zod schemas passed to .on()
103
- *
104
- * @example
105
- * ```ts
106
- * const userService = app.service('users')
107
- * .use(logger())
108
- * .endpoint('GET', '/users/:id', { input, output }, handler)
109
- * .on('user/created', {
110
- * schema: z.object({ userId: z.string(), email: z.string() }),
111
- * handler: async (event) => {
112
- * // event is fully typed: { userId: string, email: string }
113
- * }
114
- * })
115
- * ```
116
- */
117
- export class ServiceBuilder<
118
- TEvents extends EventSchemaMap = {},
119
- E extends Env = Env,
120
- I extends Input = {}
121
- > {
122
- private endpoints: Map<string, any> = new Map()
123
- private eventHandlers: Map<string, any> = new Map()
124
- private cronJobs: Map<string, any> = new Map()
125
- private globalMiddleware: MiddlewareHandler<E, string, any, any>[] = []
126
- private eventSchemas: EventSchemaMap = {}
127
-
128
- constructor(
129
- private name: string,
130
- private eventBus: EventBus,
131
- private visionCore?: VisionCore
132
- ) {}
133
-
134
- /**
135
- * Add global middleware for all endpoints in this service
136
- */
137
- use(...middleware: MiddlewareHandler<E, string, any, any>[]) {
138
- this.globalMiddleware.push(...middleware)
139
- return this
140
- }
141
-
142
- /**
143
- * Get service name (capitalized) and route metadata without registering
144
- */
145
- public getRoutesMetadata(): Array<{
146
- method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
147
- path: string
148
- queryParams?: any
149
- requestBody?: any
150
- responseBody?: any
151
- }> {
152
- const routes: Array<{
153
- method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
154
- path: string
155
- queryParams?: any
156
- requestBody?: any
157
- responseBody?: any
158
- }> = []
159
- this.endpoints.forEach((ep) => {
160
- let requestBody = undefined
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
- }
182
- }
183
-
184
- let responseBody = undefined
185
- if (ep.schema.output) {
186
- responseBody = generateTemplate(ep.schema.output)
187
- }
188
- routes.push({
189
- method: ep.method,
190
- path: ep.path,
191
- queryParams,
192
- requestBody,
193
- responseBody,
194
- })
195
- })
196
- return routes
197
- }
198
-
199
- public getDisplayName(): string {
200
- return this.name.charAt(0).toUpperCase() + this.name.slice(1)
201
- }
202
-
203
- /**
204
- * Define an HTTP endpoint with Zod validation
205
- *
206
- * @example
207
- * ```ts
208
- * service.endpoint(
209
- * 'GET',
210
- * '/users/:id',
211
- * {
212
- * input: z.object({ id: z.string() }),
213
- * output: z.object({ id: z.string(), name: z.string() })
214
- * },
215
- * async ({ id }, c) => {
216
- * return { id, name: 'John' }
217
- * },
218
- * { middleware: [authMiddleware] }
219
- * )
220
- * ```
221
- */
222
- endpoint<
223
- TInputSchema extends z.ZodType,
224
- TOutputSchema extends z.ZodType | undefined,
225
- PPath extends string
226
- >(
227
- method: EndpointConfig['method'],
228
- path: PPath,
229
- schema: {
230
- input: TInputSchema
231
- output?: TOutputSchema
232
- },
233
- handler: Handler<
234
- z.infer<TInputSchema>,
235
- TOutputSchema extends z.ZodType ? z.infer<TOutputSchema> : any,
236
- TEvents,
237
- E,
238
- PPath,
239
- I
240
- >,
241
- config?: Partial<EndpointConfig>
242
- ) {
243
- this.endpoints.set(`${method}:${path}`, {
244
- method,
245
- path,
246
- handler,
247
- schema,
248
- config: { ...config, method, path },
249
- middleware: config?.middleware || []
250
- })
251
- return this
252
- }
253
-
254
- /**
255
- * Subscribe to events with Zod schema validation
256
- *
257
- * Automatically infers the event type from the Zod schema.
258
- * TypeScript will ensure that c.emit() calls match the registered schema.
259
- *
260
- * @example
261
- * ```ts
262
- * service.on('user/created', {
263
- * schema: z.object({
264
- * userId: z.string().uuid(),
265
- * email: z.string().email()
266
- * }),
267
- * description: 'User account created',
268
- * icon: '👤',
269
- * tags: ['user', 'auth'],
270
- * handler: async (event) => {
271
- * // event is fully typed: { userId: string, email: string }
272
- * console.log('User created:', event.email)
273
- * }
274
- * })
275
- * ```
276
- */
277
- on<
278
- K extends string,
279
- T extends Record<string, any>
280
- >(
281
- eventName: K,
282
- config: {
283
- schema: z.ZodSchema<T>
284
- description?: string
285
- icon?: string
286
- tags?: string[]
287
- /**
288
- * Max number of concurrent jobs this handler will process.
289
- * Falls back to EventBus config.workerConcurrency (or 1).
290
- */
291
- concurrency?: number
292
- handler: (event: T, c: Context<E, any, I>) => Promise<void>
293
- }
294
- ): ServiceBuilder<TEvents & { [key in K]: T }, E, I> {
295
- const { schema, handler, description, icon, tags } = config
296
-
297
- // Store schema for type inference
298
- this.eventSchemas[eventName] = schema
299
-
300
- // Wrap handler to provide Hono-like context with middleware support
301
- const wrappedHandler = async (data: T) => {
302
- const ctx = createEventContext<E, I>()
303
- // Run service-level middleware to populate ctx (e.g., c.set('db', db))
304
- if (this.globalMiddleware.length > 0) {
305
- await runMiddlewareChain(this.globalMiddleware, ctx)
306
- }
307
- // Call original handler with event data and context
308
- await handler(data, ctx)
309
- }
310
-
311
- // Register wrapped handler in event registry (for metadata/stats)
312
- eventRegistry.registerEvent(
313
- eventName,
314
- schema,
315
- wrappedHandler,
316
- { description, icon, tags }
317
- )
318
-
319
- // Register wrapped handler in event bus
320
- this.eventBus.registerHandler(eventName, wrappedHandler, {
321
- concurrency: config.concurrency,
322
- })
323
-
324
- // Store for later reference
325
- this.eventHandlers.set(eventName, config)
326
-
327
- // Return typed ServiceBuilder with accumulated events
328
- return this as ServiceBuilder<TEvents & { [key in K]: T }, E, I>
329
- }
330
-
331
- /**
332
- * Schedule a cron job using BullMQ Repeatable
333
- *
334
- * @example
335
- * ```ts
336
- * service.cron('0 0 * * *', {
337
- * description: 'Daily cleanup',
338
- * icon: '🧹',
339
- * tags: ['maintenance'],
340
- * handler: async (c) => {
341
- * console.log('Daily cleanup')
342
- * }
343
- * })
344
- * ```
345
- */
346
- cron(
347
- schedule: string,
348
- config: {
349
- description?: string
350
- icon?: string
351
- tags?: string[]
352
- handler: (context: any) => Promise<void>
353
- }
354
- ) {
355
- const { handler, description, icon, tags } = config
356
- const cronName = `${this.name}.cron.${schedule}`
357
-
358
- // Register in event registry
359
- eventRegistry.registerCron(
360
- cronName,
361
- schedule,
362
- handler,
363
- { description, icon, tags }
364
- )
365
-
366
- // Store for later reference
367
- this.cronJobs.set(cronName, { schedule, ...config })
368
-
369
- // Setup BullMQ repeatable job
370
- // This will be called when the service is built
371
- this.setupCronJob(cronName, schedule, handler)
372
-
373
- return this
374
- }
375
-
376
- /**
377
- * Setup BullMQ repeatable job for cron
378
- */
379
- private async setupCronJob(
380
- cronName: string,
381
- schedule: string,
382
- handler: (context: any) => Promise<void>
383
- ) {
384
- // Get queue from EventBus (we'll add a getQueue method)
385
- const queue = await this.eventBus.getQueueForCron(cronName)
386
-
387
- // Register cron job using BullMQ upsertJobScheduler
388
- await queue.upsertJobScheduler(
389
- cronName,
390
- {
391
- pattern: schedule, // Cron expression (e.g., '0 0 * * *')
392
- },
393
- {
394
- name: cronName,
395
- data: {},
396
- opts: {},
397
- }
398
- )
399
-
400
- // Register worker to process cron jobs
401
- this.eventBus.registerCronHandler(cronName, handler)
402
- }
403
-
404
- /**
405
- * Build and register all endpoints with Hono
406
- */
407
- build(app: Hono, servicesAccumulator?: Array<{ name: string; routes: any[] }>) {
408
- // Prepare routes with Zod schemas
409
- const routes = Array.from(this.endpoints.values()).map(ep => {
410
- // Generate requestBody schema (input) for POST/PUT/PATCH
411
- let requestBody = undefined
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
- }
435
- }
436
-
437
- // Generate responseBody schema (output)
438
- let responseBody = undefined
439
- if (ep.schema.output) {
440
- responseBody = generateTemplate(ep.schema.output)
441
- }
442
-
443
- return {
444
- method: ep.method,
445
- path: ep.path,
446
- handler: this.name,
447
- middleware: [],
448
- queryParams,
449
- requestBody,
450
- responseBody,
451
- }
452
- })
453
-
454
- const capitalizedName = this.name.charAt(0).toUpperCase() + this.name.slice(1)
455
-
456
- // Add to accumulator (для батч реєстрації в buildAllServices)
457
- if (servicesAccumulator) {
458
- servicesAccumulator.push({
459
- name: capitalizedName,
460
- routes
461
- })
462
- }
463
-
464
- // Register HTTP endpoints
465
- this.endpoints.forEach((ep) => {
466
- // Prepare rate limiter when configured per-endpoint
467
- let rateLimitMw: MiddlewareHandler<E, string, any, any> | undefined
468
- const rl = ep.config?.ratelimit as EndpointConfig['ratelimit'] | undefined
469
- if (rl) {
470
- const windowMs = parseWindowMs(rl.window)
471
- const limit = rl.requests
472
- rateLimitMw = rateLimiter({
473
- windowMs,
474
- limit,
475
- standardHeaders: 'draft-6',
476
- keyGenerator: (c) => getClientKey(c, ep.method, ep.path),
477
- // If user provides a distributed store (e.g., RedisStore), pass it through
478
- ...(rl.store ? { store: rl.store } : {}),
479
- })
480
- }
481
-
482
- // Combine global + rate-limit (if any) + endpoint-specific middleware
483
- const allMiddleware = [
484
- ...this.globalMiddleware,
485
- ...(rateLimitMw ? [rateLimitMw] as MiddlewareHandler<E, string, any, any>[] : []),
486
- ...ep.middleware,
487
- ]
488
-
489
- // Create handler with middleware chain
490
- const finalHandler = async (c: Context<E, any, I>) => {
491
- try {
492
- // Add span helper and emit to context
493
- const visionCtx = getVisionContext()
494
- if (visionCtx && this.visionCore) {
495
- const { vision, traceId, rootSpanId } = visionCtx
496
- const tracer = vision.getTracer();
497
-
498
- // Add addContext() method to context
499
- (c as any).addContext = (context: Record<string, unknown>) => {
500
- const current = getVisionContext()
501
- // Use current traceId from context if available (handles nested spans/async correctly)
502
- const targetTraceId = current?.traceId || traceId
503
-
504
- const visionTrace = vision.getTraceStore().getTrace(targetTraceId)
505
- if (visionTrace) {
506
- visionTrace.metadata = { ...(visionTrace.metadata || {}), ...context }
507
- }
508
- }
509
-
510
- // Add span() method to context
511
- (c as any).span = <T>(
512
- name: string,
513
- attributes: Record<string, any> = {},
514
- fn?: () => T
515
- ): T => {
516
- const span = tracer.startSpan(name, traceId, rootSpanId)
517
-
518
- for (const [key, value] of Object.entries(attributes)) {
519
- tracer.setAttribute(span.id, key, value)
520
- }
521
-
522
- try {
523
- const result = fn ? fn() : (undefined as any)
524
- const completedSpan = tracer.endSpan(span.id)
525
-
526
- if (completedSpan) {
527
- vision.getTraceStore().addSpan(traceId, completedSpan)
528
- }
529
-
530
- return result
531
- } catch (error) {
532
- tracer.setAttribute(span.id, 'error', true)
533
- tracer.setAttribute(
534
- span.id,
535
- 'error.message',
536
- error instanceof Error ? error.message : String(error)
537
- )
538
- const completedSpan = tracer.endSpan(span.id)
539
-
540
- if (completedSpan) {
541
- vision.getTraceStore().addSpan(traceId, completedSpan)
542
- }
543
-
544
- throw error
545
- }
546
- }
547
- }
548
-
549
- // Always provide emit() so events work in sub-apps without local VisionCore
550
- if (!(c as any).emit) {
551
- (c as any).emit = async <K extends keyof TEvents>(
552
- eventName: K,
553
- data: TEvents[K]
554
- ): Promise<void> => {
555
- return this.eventBus.emit(eventName as string, data)
556
- }
557
- }
558
-
559
- // Parse and merge params, body, query
560
- const params = c.req.param()
561
- const query = c.req.query()
562
- let body = {}
563
-
564
- if (['POST', 'PUT', 'PATCH'].includes(ep.method)) {
565
- body = await c.req.json().catch(() => ({}))
566
- }
567
-
568
- const input = { ...params, ...query, ...body }
569
-
570
- // Validate input with UniversalValidator (supports Zod, Valibot, etc.)
571
- const validated = UniversalValidator.parse(ep.schema.input, input)
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
-
577
- // Execute handler
578
- const result = await ep.handler(finalInput, c as any)
579
-
580
- // If an output schema exists, validate and return JSON
581
- if (ep.schema.output) {
582
- const validatedOutput = UniversalValidator.parse(ep.schema.output, result)
583
- return c.json(validatedOutput)
584
- }
585
-
586
- // No output schema: allow raw Response or JSON
587
- if (result instanceof Response) {
588
- return result
589
- }
590
- return c.json(result)
591
- } catch (error) {
592
- if (error instanceof ValidationError) {
593
- const requestId = c.req.header('x-request-id')
594
- return c.json(
595
- createValidationErrorResponse(error.issues, requestId),
596
- 400
597
- )
598
- }
599
- throw error
600
- }
601
- }
602
-
603
- // Register with middleware chain
604
- if (allMiddleware.length > 0) {
605
- app.on([ep.method], ep.path, ...allMiddleware, finalHandler)
606
- } else {
607
- app.on([ep.method], ep.path, finalHandler)
608
- }
609
- })
610
-
611
- return {
612
- endpoints: Array.from(this.endpoints.values()),
613
- eventHandlers: Array.from(this.eventHandlers.values()),
614
- cronJobs: Array.from(this.cronJobs.values())
615
- }
616
- }
617
- }
618
-
package/src/types.ts DELETED
@@ -1,93 +0,0 @@
1
- import type { Context, Env, Input, MiddlewareHandler } from 'hono'
2
- import type { VisionCore } from '@getvision/core'
3
-
4
- /**
5
- * Vision context stored in AsyncLocalStorage
6
- */
7
- export interface VisionContext<E extends Env = any, P extends string = any, I extends Input = {}> extends Context<E, P, I> {
8
- vision: VisionCore
9
- traceId: string
10
- rootSpanId: string
11
- }
12
-
13
- /**
14
- * Endpoint configuration options
15
- */
16
- export interface EndpointConfig {
17
- method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
18
- path: string
19
- middleware?: MiddlewareHandler[]
20
- // TODO: Below not implemented yet features
21
- auth?: boolean
22
- ratelimit?: { requests: number; window: string; store?: any }
23
- cache?: { ttl: number }
24
- }
25
-
26
- /**
27
- * Extended Context with span helper and emit
28
- *
29
- * Generic TEvents parameter allows type-safe event emission
30
- */
31
- export interface ExtendedContext<
32
- TEvents extends Record<string, any> = {},
33
- E extends Env = any,
34
- P extends string = any,
35
- I extends Input = {}
36
- > extends Context<E, P, I> {
37
- span<T>(
38
- name: string,
39
- attributes?: Record<string, any>,
40
- fn?: () => T
41
- ): T
42
-
43
- /**
44
- * Add context to the current active trace
45
- * This is the "Wide Event" API - allowing adding high-cardinality data
46
- * to the current request context.
47
- */
48
- addContext(context: Record<string, unknown>): void
49
-
50
- /**
51
- * Emit an event with type-safe validation
52
- *
53
- * The event name and data are validated against registered Zod schemas.
54
- * TypeScript will ensure:
55
- * 1. The event name is registered (via .on())
56
- * 2. The data matches the schema exactly
57
- *
58
- * @example
59
- * ```ts
60
- * // After .on('user/created', { schema: z.object({ userId: z.string(), email: z.string() }) })
61
- *
62
- * // ✅ TypeScript allows this:
63
- * await c.emit('user/created', {
64
- * userId: '123',
65
- * email: 'user@example.com'
66
- * })
67
- *
68
- * // ❌ TypeScript errors:
69
- * await c.emit('unknown/event', {}) // Event not registered
70
- * await c.emit('user/created', { userId: '123' }) // Missing email
71
- * await c.emit('user/created', { userId: '123', email: 'x', extra: 'extra' }) // Extra field
72
- * ```
73
- */
74
- emit<K extends keyof TEvents>(
75
- eventName: K,
76
- data: TEvents[K]
77
- ): Promise<void>
78
- }
79
-
80
- /**
81
- * Handler type with Zod-validated input and Vision-enhanced context
82
- */
83
- export type Handler<
84
- TInput = any,
85
- TOutput = any,
86
- TEvents extends Record<string, any> = {},
87
- E extends Env = any,
88
- P extends string = any,
89
- I extends Input = {}
90
- > = (
91
- req: TInput,
92
- ctx: ExtendedContext<TEvents, E, P, I>
93
- ) => Promise<TOutput> | TOutput