@getvision/server 0.2.1-ec9cf8b-develop → 0.2.2-a88ad97-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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # @getvision/server
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ec9cf8b: Redis password is now passed correctly and REDIS_URL is honored.
8
+ - d0f3a53: Add event handler context
9
+
10
+ Event handlers receive a Hono-like `Context` as the second argument. You can run service-level middleware to inject resources using `c.set(...)` and then access them in the handler via `c.get(...)`.
11
+
12
+ - 648a711: If Redis is configured use production mode in PubSub
13
+
3
14
  ## 0.2.0
4
15
 
5
16
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvision/server",
3
- "version": "0.2.1-ec9cf8b-develop",
3
+ "version": "0.2.2-a88ad97-develop",
4
4
  "type": "module",
5
5
  "description": "Vision Server - Meta-framework with built-in observability, pub/sub, and type-safe APIs",
6
6
  "exports": {
@@ -14,7 +14,7 @@
14
14
  "license": "MIT",
15
15
  "dependencies": {
16
16
  "@getvision/core": "0.0.3",
17
- "@hono/node-server": "^1.19.5",
17
+ "@hono/node-server": "^1.19.6",
18
18
  "bullmq": "^5.62.0",
19
19
  "hono-rate-limiter": "^0.4.2",
20
20
  "hono": "^4.10.4",
package/src/event-bus.ts CHANGED
@@ -55,8 +55,22 @@ export class EventBus {
55
55
  // Merge: explicit config.redis overrides env-derived values
56
56
  const mergedRedis = { ...(envRedis || {}), ...(config.redis || {}) }
57
57
 
58
+ // Determine if Redis is configured by env or explicit config
59
+ const hasRedisFromEnv = Boolean(envUrl)
60
+ const hasRedisFromConfig = Boolean(
61
+ config.redis && (config.redis.host || config.redis.port || config.redis.password)
62
+ )
63
+ const hasRedis = hasRedisFromEnv || hasRedisFromConfig
64
+
65
+ // devMode precedence:
66
+ // 1) Respect explicit config.devMode when provided (true/false)
67
+ // 2) Otherwise, if Redis is configured (env or config), use production mode (devMode=false)
68
+ // 3) Otherwise, default to devMode=true (in-memory)
69
+ const resolvedDevMode =
70
+ typeof config.devMode === 'boolean' ? config.devMode : !hasRedis
71
+
58
72
  this.config = {
59
- devMode: config.devMode ?? process.env.NODE_ENV === 'development',
73
+ devMode: resolvedDevMode,
60
74
  redis: mergedRedis,
61
75
  }
62
76
  }
package/src/index.ts CHANGED
@@ -51,6 +51,10 @@ export type { VisionConfig } from './vision-app'
51
51
  // Service builder (usually accessed via app.service())
52
52
  export { ServiceBuilder } from './service'
53
53
 
54
+ // Event bus
55
+ export { EventBus } from './event-bus'
56
+ export type { EventBusConfig } from './event-bus'
57
+
54
58
  // Types
55
59
  export type {
56
60
  EndpointConfig,
package/src/router.ts CHANGED
@@ -2,6 +2,7 @@ import type { Hono } from 'hono'
2
2
  import { readdirSync, statSync } from 'fs'
3
3
  import { join, resolve, relative, sep } from 'path'
4
4
  import { pathToFileURL } from 'url'
5
+ import type { EventBus } from './event-bus'
5
6
 
6
7
  /**
7
8
  * Autoload Vision/Hono sub-apps from a directory structure like app/routes/.../index.ts
@@ -12,7 +13,7 @@ import { pathToFileURL } from 'url'
12
13
  * - app/routes/users/[id]/index.ts -> /users/:id
13
14
  * - app/routes/index.ts -> /
14
15
  */
15
- export async function loadSubApps(app: Hono, routesDir: string = './app/routes'): Promise<Array<{ name: string; routes: any[] }>> {
16
+ export async function loadSubApps(app: Hono, routesDir: string = './app/routes', eventBus?: EventBus): Promise<Array<{ name: string; routes: any[] }>> {
16
17
  const mounted: Array<{ base: string }> = []
17
18
  const allSubAppSummaries: Array<{ name: string; routes: any[] }> = []
18
19
 
@@ -37,6 +38,10 @@ export async function loadSubApps(app: Hono, routesDir: string = './app/routes')
37
38
  const mod: any = await import(modUrl)
38
39
  const subApp = mod?.default
39
40
  if (subApp) {
41
+ // Inject EventBus into sub-app if it's a Vision instance
42
+ if (eventBus && typeof subApp?.setEventBus === 'function') {
43
+ subApp.setEventBus(eventBus)
44
+ }
40
45
  const base = toBasePath(dir)
41
46
  // If it's a Vision sub-app, build its services before mounting
42
47
  try {
package/src/service.ts CHANGED
@@ -459,8 +459,10 @@ export class ServiceBuilder<
459
459
  throw error
460
460
  }
461
461
  }
462
-
463
- // Add emit() method to context with type-safe event validation
462
+ }
463
+
464
+ // Always provide emit() so events work in sub-apps without local VisionCore
465
+ if (!(c as any).emit) {
464
466
  (c as any).emit = async <K extends keyof TEvents>(
465
467
  eventName: K,
466
468
  data: TEvents[K]
package/src/vision-app.ts CHANGED
@@ -17,6 +17,30 @@ export interface VisionALSContext {
17
17
 
18
18
  const visionContext = new AsyncLocalStorage<VisionALSContext>()
19
19
 
20
+ // Simple deep merge utility (objects only, arrays are overwritten by source)
21
+ function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
22
+ const output: any = { ...target }
23
+ if (source && typeof source === 'object') {
24
+ for (const key of Object.keys(source)) {
25
+ const srcVal = source[key]
26
+ const tgtVal = output[key]
27
+ if (
28
+ srcVal &&
29
+ typeof srcVal === 'object' &&
30
+ !Array.isArray(srcVal) &&
31
+ tgtVal &&
32
+ typeof tgtVal === 'object' &&
33
+ !Array.isArray(tgtVal)
34
+ ) {
35
+ output[key] = deepMerge(tgtVal, srcVal)
36
+ } else {
37
+ output[key] = srcVal
38
+ }
39
+ }
40
+ }
41
+ return output as T
42
+ }
43
+
20
44
  /**
21
45
  * Vision Server configuration
22
46
  */
@@ -49,6 +73,7 @@ export interface VisionConfig {
49
73
  password?: string
50
74
  }
51
75
  devMode?: boolean // Use in-memory event bus (no Redis required)
76
+ eventBus?: EventBus // Share EventBus instance across apps (for sub-apps)
52
77
  }
53
78
  }
54
79
 
@@ -61,20 +86,13 @@ export interface VisionConfig {
61
86
  * service: {
62
87
  * name: 'My API',
63
88
  * version: '1.0.0'
64
- * },
65
- * pubsub: {
66
- * schemas: {
67
- * 'user/created': {
68
- * data: z.object({ userId: z.string() })
69
- * }
70
- * }
71
89
  * }
72
90
  * })
73
91
  *
74
92
  * const userService = app.service('users')
75
- * .endpoint('GET', '/users/:id', schema, handler)
76
93
  * .on('user/created', handler)
77
- *
94
+ * .endpoint('GET', '/users/:id', schema, handler)
95
+ *
78
96
  * app.start(3000)
79
97
  * ```
80
98
  */
@@ -100,24 +118,16 @@ export class Vision<
100
118
  enabled: false,
101
119
  port: 9500,
102
120
  },
103
- pubsub: {
104
- devMode: true,
105
- },
121
+ // Do not set a default devMode here; let EventBus derive from Redis presence
122
+ pubsub: {},
106
123
  routes: {
107
124
  autodiscover: true,
108
125
  dirs: ['app/routes'],
109
126
  },
110
127
  }
111
128
 
112
- // Merge shallowly (good enough for our config structure)
113
- this.config = {
114
- ...defaultConfig,
115
- ...(config || {}),
116
- service: { ...defaultConfig.service, ...(config?.service || {}) },
117
- vision: { ...defaultConfig.vision, ...(config?.vision || {}) },
118
- pubsub: { ...defaultConfig.pubsub, ...(config?.pubsub || {}) },
119
- routes: { ...defaultConfig.routes, ...(config?.routes || {}) },
120
- }
129
+ // Deep merge to respect nested overrides
130
+ this.config = deepMerge(defaultConfig, config || {})
121
131
 
122
132
  // Initialize Vision Core
123
133
  const visionEnabled = this.config.vision?.enabled !== false
@@ -184,8 +194,9 @@ export class Vision<
184
194
  this.visionCore = null as any
185
195
  }
186
196
 
187
- // Initialize EventBus
188
- this.eventBus = new EventBus({
197
+ // Use provided EventBus or create a new one
198
+ // Root app creates EventBus, sub-apps can share it via config.pubsub.eventBus
199
+ this.eventBus = this.config.pubsub?.eventBus || new EventBus({
189
200
  redis: this.config.pubsub?.redis,
190
201
  devMode: this.config.pubsub?.devMode,
191
202
  })
@@ -536,7 +547,8 @@ export class Vision<
536
547
  let allSubAppSummaries: Array<{ name: string; routes: any[] }> = []
537
548
  for (const d of existing) {
538
549
  try {
539
- const summaries = await loadSubApps(this as any, d)
550
+ // Pass EventBus to sub-apps so they share the same instance
551
+ const summaries = await loadSubApps(this as any, d, this.eventBus)
540
552
  allSubAppSummaries = allSubAppSummaries.concat(summaries)
541
553
  } catch (e) {
542
554
  console.error(`❌ Failed to load sub-apps from ${d}:`, (e as any)?.message || e)
@@ -610,6 +622,13 @@ export class Vision<
610
622
  return this
611
623
  }
612
624
  }
625
+
626
+ /**
627
+ * Set the EventBus instance (used internally by router to inject shared EventBus)
628
+ */
629
+ setEventBus(eventBus: EventBus): void {
630
+ this.eventBus = eventBus
631
+ }
613
632
  }
614
633
 
615
634
  /**