@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 +11 -0
- package/package.json +2 -2
- package/src/event-bus.ts +15 -1
- package/src/index.ts +4 -0
- package/src/router.ts +6 -1
- package/src/service.ts +4 -2
- package/src/vision-app.ts +43 -24
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.
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
188
|
-
|
|
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
|
-
|
|
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
|
/**
|