@getvision/server 0.2.7 → 0.3.0
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 -1
- package/src/event-bus.ts +14 -1
- package/src/router.ts +14 -1
- package/src/service.ts +8 -1
- package/src/vision-app.ts +21 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @getvision/server
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 28c86e6: Added support for runtime-specific `start` options. Introduced new `VisionStartOptions` type for better handling of configuration parameters.
|
|
8
|
+
Added support for configurable worker concurrency per handler and default event bus settings
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 2d4e753: Fix priority for dynamic and static routes
|
|
13
|
+
|
|
3
14
|
## 0.2.7
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvision/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vision Server - Meta-framework with built-in observability, pub/sub, and type-safe APIs",
|
|
6
6
|
"exports": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"@repo/eslint-config": "0.0.1",
|
|
27
27
|
"@repo/typescript-config": "0.0.1",
|
|
28
28
|
"@types/node": "^20.14.9",
|
|
29
|
+
"@types/bun": "^1.3.5",
|
|
29
30
|
"typescript": "5.9.3"
|
|
30
31
|
},
|
|
31
32
|
"keywords": [
|
package/src/event-bus.ts
CHANGED
|
@@ -11,6 +11,10 @@ export interface EventBusConfig {
|
|
|
11
11
|
port?: number
|
|
12
12
|
password?: string
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Default BullMQ worker concurrency (per event). Per-handler options override this.
|
|
16
|
+
*/
|
|
17
|
+
workerConcurrency?: number
|
|
14
18
|
// Dev mode - use in-memory (no Redis required)
|
|
15
19
|
devMode?: boolean
|
|
16
20
|
}
|
|
@@ -72,6 +76,7 @@ export class EventBus {
|
|
|
72
76
|
this.config = {
|
|
73
77
|
devMode: resolvedDevMode,
|
|
74
78
|
redis: mergedRedis,
|
|
79
|
+
workerConcurrency: config.workerConcurrency,
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
|
|
@@ -178,7 +183,14 @@ export class EventBus {
|
|
|
178
183
|
*/
|
|
179
184
|
registerHandler<T>(
|
|
180
185
|
eventName: string,
|
|
181
|
-
handler: (data: T) => Promise<void
|
|
186
|
+
handler: (data: T) => Promise<void>,
|
|
187
|
+
options?: {
|
|
188
|
+
/**
|
|
189
|
+
* Max number of concurrent jobs this handler will process.
|
|
190
|
+
* Defaults to config.workerConcurrency (or 1).
|
|
191
|
+
*/
|
|
192
|
+
concurrency?: number
|
|
193
|
+
}
|
|
182
194
|
): void {
|
|
183
195
|
if (this.config.devMode) {
|
|
184
196
|
// Dev mode - store handlers in memory
|
|
@@ -203,6 +215,7 @@ export class EventBus {
|
|
|
203
215
|
},
|
|
204
216
|
{
|
|
205
217
|
connection,
|
|
218
|
+
concurrency: options?.concurrency ?? this.config.workerConcurrency ?? 1,
|
|
206
219
|
}
|
|
207
220
|
)
|
|
208
221
|
|
package/src/router.ts
CHANGED
|
@@ -27,6 +27,10 @@ export async function loadSubApps(app: Hono, routesDir: string = './app/routes',
|
|
|
27
27
|
return '/' + segments.join('/')
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function isDynamicSegment(name: string): boolean {
|
|
31
|
+
return name.startsWith('[') && name.endsWith(']')
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
async function scan(dir: string) {
|
|
31
35
|
const entries = readdirSync(dir)
|
|
32
36
|
// If folder contains index.ts or index.js, treat it as a sub-app root
|
|
@@ -73,7 +77,16 @@ export async function loadSubApps(app: Hono, routesDir: string = './app/routes',
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
// Recurse into child directories
|
|
76
|
-
|
|
80
|
+
// Sort entries: static folders first, then dynamic [param] folders
|
|
81
|
+
// This ensures static routes have priority over dynamic routes
|
|
82
|
+
const sortedEntries = [...entries].sort((a, b) => {
|
|
83
|
+
const aIsDynamic = isDynamicSegment(a)
|
|
84
|
+
const bIsDynamic = isDynamicSegment(b)
|
|
85
|
+
if (aIsDynamic && !bIsDynamic) return 1 // dynamic after static
|
|
86
|
+
if (!aIsDynamic && bIsDynamic) return -1 // static before dynamic
|
|
87
|
+
return a.localeCompare(b) // alphabetical within same type
|
|
88
|
+
})
|
|
89
|
+
for (const name of sortedEntries) {
|
|
77
90
|
const full = join(dir, name)
|
|
78
91
|
const st = statSync(full)
|
|
79
92
|
if (st.isDirectory()) await scan(full)
|
package/src/service.ts
CHANGED
|
@@ -240,6 +240,11 @@ export class ServiceBuilder<
|
|
|
240
240
|
description?: string
|
|
241
241
|
icon?: string
|
|
242
242
|
tags?: string[]
|
|
243
|
+
/**
|
|
244
|
+
* Max number of concurrent jobs this handler will process.
|
|
245
|
+
* Falls back to EventBus config.workerConcurrency (or 1).
|
|
246
|
+
*/
|
|
247
|
+
concurrency?: number
|
|
243
248
|
handler: (event: T, c: Context<E, any, I>) => Promise<void>
|
|
244
249
|
}
|
|
245
250
|
): ServiceBuilder<TEvents & { [key in K]: T }, E, I> {
|
|
@@ -268,7 +273,9 @@ export class ServiceBuilder<
|
|
|
268
273
|
)
|
|
269
274
|
|
|
270
275
|
// Register wrapped handler in event bus
|
|
271
|
-
this.eventBus.registerHandler(eventName, wrappedHandler
|
|
276
|
+
this.eventBus.registerHandler(eventName, wrappedHandler, {
|
|
277
|
+
concurrency: config.concurrency,
|
|
278
|
+
})
|
|
272
279
|
|
|
273
280
|
// Store for later reference
|
|
274
281
|
this.eventHandlers.set(eventName, config)
|
package/src/vision-app.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { spawn, spawnSync, type ChildProcess } from 'child_process'
|
|
|
8
8
|
import { ServiceBuilder } from './service'
|
|
9
9
|
import { EventBus } from './event-bus'
|
|
10
10
|
import { eventRegistry } from './event-registry'
|
|
11
|
+
import type { serve as honoServe } from '@hono/node-server'
|
|
11
12
|
|
|
12
13
|
export interface VisionALSContext {
|
|
13
14
|
vision: VisionCore
|
|
@@ -17,6 +18,12 @@ export interface VisionALSContext {
|
|
|
17
18
|
|
|
18
19
|
const visionContext = new AsyncLocalStorage<VisionALSContext>()
|
|
19
20
|
|
|
21
|
+
type BunServeOptions = Parameters<typeof Bun['serve']>[0]
|
|
22
|
+
type NodeServeOptions = Parameters<typeof honoServe>[0]
|
|
23
|
+
|
|
24
|
+
type VisionStartOptions = Omit<Partial<BunServeOptions>, 'fetch' | 'port'> &
|
|
25
|
+
Omit<Partial<NodeServeOptions>, 'fetch' | 'port'>
|
|
26
|
+
|
|
20
27
|
// Simple deep merge utility (objects only, arrays are overwritten by source)
|
|
21
28
|
function deepMerge<T extends Record<string, any>>(target: T, source: Partial<T>): T {
|
|
22
29
|
const output: any = { ...target }
|
|
@@ -75,6 +82,10 @@ export interface VisionConfig {
|
|
|
75
82
|
}
|
|
76
83
|
devMode?: boolean // Use in-memory event bus (no Redis required)
|
|
77
84
|
eventBus?: EventBus // Share EventBus instance across apps (for sub-apps)
|
|
85
|
+
/**
|
|
86
|
+
* Default BullMQ worker concurrency for all handlers (overridable per handler)
|
|
87
|
+
*/
|
|
88
|
+
workerConcurrency?: number
|
|
78
89
|
}
|
|
79
90
|
}
|
|
80
91
|
|
|
@@ -203,6 +214,7 @@ export class Vision<
|
|
|
203
214
|
this.eventBus = this.config.pubsub?.eventBus || new EventBus({
|
|
204
215
|
redis: this.config.pubsub?.redis,
|
|
205
216
|
devMode: this.config.pubsub?.devMode,
|
|
217
|
+
workerConcurrency: this.config.pubsub?.workerConcurrency,
|
|
206
218
|
})
|
|
207
219
|
|
|
208
220
|
// Register JSON-RPC methods for events/cron
|
|
@@ -567,7 +579,11 @@ export class Vision<
|
|
|
567
579
|
/**
|
|
568
580
|
* Start the server (convenience method)
|
|
569
581
|
*/
|
|
570
|
-
async start(port: number = 3000, options?:
|
|
582
|
+
async start(port: number = 3000, options?: VisionStartOptions) {
|
|
583
|
+
const { hostname, ...restOptions } = options || {}
|
|
584
|
+
const { fetch: _bf, port: _bp, ...bunRest } = restOptions as Partial<BunServeOptions>
|
|
585
|
+
const { fetch: _nf, port: _np, ...nodeRest } = restOptions as Partial<NodeServeOptions>
|
|
586
|
+
|
|
571
587
|
// Build all services WITHOUT registering to VisionCore yet
|
|
572
588
|
const rootSummaries = this.buildAllServices()
|
|
573
589
|
// Autoload file-based Vision/Hono sub-apps if enabled (returns merged sub-app summaries)
|
|
@@ -638,9 +654,10 @@ export class Vision<
|
|
|
638
654
|
}
|
|
639
655
|
} catch {}
|
|
640
656
|
this.bunServer = BunServe({
|
|
657
|
+
...bunRest,
|
|
641
658
|
fetch: this.fetch.bind(this),
|
|
642
659
|
port,
|
|
643
|
-
hostname
|
|
660
|
+
hostname
|
|
644
661
|
})
|
|
645
662
|
try { (globalThis as any).__vision_bun_server = this.bunServer } catch {}
|
|
646
663
|
} else {
|
|
@@ -651,9 +668,10 @@ export class Vision<
|
|
|
651
668
|
const { serve } = await import('@hono/node-server')
|
|
652
669
|
console.log(`Node.js detected`)
|
|
653
670
|
serve({
|
|
671
|
+
...nodeRest,
|
|
654
672
|
fetch: this.fetch.bind(this),
|
|
655
673
|
port,
|
|
656
|
-
hostname
|
|
674
|
+
hostname
|
|
657
675
|
})
|
|
658
676
|
} else {
|
|
659
677
|
// For other runtimes, just return the app
|