@velajs/vela 1.9.0 → 1.12.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 +122 -0
- package/dist/__tests__/workers-als/entry-als.d.ts +9 -0
- package/dist/application.d.ts +18 -0
- package/dist/application.js +45 -0
- package/dist/cache/cache.module.d.ts +5 -7
- package/dist/cache/cache.module.js +44 -66
- package/dist/cache/cache.types.d.ts +26 -0
- package/dist/cache/cache.types.js +1 -1
- package/dist/cache/index.d.ts +2 -1
- package/dist/cache/index.js +1 -0
- package/dist/cache/tiered-cache.store.d.ts +19 -0
- package/dist/cache/tiered-cache.store.js +40 -0
- package/dist/config/config.module.d.ts +6 -5
- package/dist/config/config.module.js +46 -49
- package/dist/container/container.d.ts +58 -0
- package/dist/container/container.js +223 -18
- package/dist/container/disposable.d.ts +5 -0
- package/dist/container/disposable.js +33 -0
- package/dist/container/types.d.ts +8 -0
- package/dist/cors/cors.module.d.ts +5 -5
- package/dist/cors/cors.module.js +26 -25
- package/dist/discovery/discoverable.decorator.d.ts +38 -0
- package/dist/discovery/discoverable.decorator.js +42 -0
- package/dist/discovery/discovery.service.d.ts +94 -0
- package/dist/discovery/discovery.service.js +166 -0
- package/dist/discovery/index.d.ts +2 -0
- package/dist/discovery/index.js +2 -0
- package/dist/entrypoint/entrypoint.registry.d.ts +40 -0
- package/dist/entrypoint/entrypoint.registry.js +106 -0
- package/dist/entrypoint/entrypoint.types.d.ts +41 -0
- package/dist/entrypoint/entrypoint.types.js +3 -0
- package/dist/entrypoint/execution-context.d.ts +15 -0
- package/dist/entrypoint/execution-context.js +21 -0
- package/dist/entrypoint/execution-scope.d.ts +21 -0
- package/dist/entrypoint/execution-scope.js +28 -0
- package/dist/entrypoint/index.d.ts +4 -0
- package/dist/entrypoint/index.js +4 -0
- package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
- package/dist/event-emitter/event-emitter.subscriber.js +12 -29
- package/dist/factory/adapter.d.ts +36 -0
- package/dist/factory/adapter.js +18 -0
- package/dist/factory/bootstrap.js +20 -2
- package/dist/factory.d.ts +11 -1
- package/dist/factory.js +23 -2
- package/dist/fetch/fetch.module.d.ts +5 -6
- package/dist/fetch/fetch.module.js +9 -42
- package/dist/http/ambient.d.ts +17 -0
- package/dist/http/ambient.js +44 -0
- package/dist/http/execution-context.js +4 -1
- package/dist/http/handler-executor.js +22 -19
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/route-contributor.d.ts +68 -0
- package/dist/http/route-contributor.js +22 -0
- package/dist/http/route.manager.d.ts +19 -0
- package/dist/http/route.manager.js +112 -19
- package/dist/i18n/deep-merge.d.ts +2 -0
- package/dist/i18n/deep-merge.js +15 -0
- package/dist/i18n/i18n.middleware.d.ts +15 -0
- package/dist/i18n/i18n.middleware.js +94 -0
- package/dist/i18n/i18n.module.d.ts +13 -0
- package/dist/i18n/i18n.module.js +64 -0
- package/dist/i18n/i18n.options.d.ts +30 -0
- package/dist/i18n/i18n.options.js +16 -0
- package/dist/i18n/i18n.service.d.ts +20 -0
- package/dist/i18n/i18n.service.js +49 -0
- package/dist/i18n/i18n.tokens.d.ts +6 -0
- package/dist/i18n/i18n.tokens.js +3 -0
- package/dist/i18n/i18n.types.d.ts +6 -0
- package/dist/i18n/i18n.types.js +1 -0
- package/dist/i18n/index.d.ts +10 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/message-loader.service.d.ts +21 -0
- package/dist/i18n/message-loader.service.js +90 -0
- package/dist/i18n/message-registry.d.ts +16 -0
- package/dist/i18n/message-registry.js +49 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +14 -3
- package/dist/internal.d.ts +0 -2
- package/dist/internal.js +0 -7
- package/dist/module/configurable-module.builder.d.ts +56 -0
- package/dist/module/configurable-module.builder.js +84 -0
- package/dist/module/configurable-module.types.d.ts +93 -0
- package/dist/module/configurable-module.types.js +5 -0
- package/dist/module/define-module.d.ts +105 -0
- package/dist/module/define-module.js +227 -0
- package/dist/module/index.d.ts +4 -0
- package/dist/module/index.js +3 -0
- package/dist/module/lazy-provider.d.ts +65 -0
- package/dist/module/lazy-provider.js +111 -0
- package/dist/openapi/document.js +16 -15
- package/dist/pipeline/component.manager.d.ts +15 -9
- package/dist/pipeline/component.manager.js +32 -46
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/pipeline-runner.d.ts +31 -0
- package/dist/pipeline/pipeline-runner.js +40 -0
- package/dist/pipeline/types.d.ts +21 -4
- package/dist/registry/metadata.registry.d.ts +24 -18
- package/dist/registry/metadata.registry.js +137 -49
- package/dist/schedule/schedule.module.d.ts +5 -0
- package/dist/schedule/schedule.module.js +18 -8
- package/dist/schedule/schedule.registry.d.ts +3 -3
- package/dist/schedule/schedule.registry.js +23 -44
- package/dist/schedule-node/schedule-node.module.d.ts +5 -0
- package/dist/schedule-node/schedule-node.module.js +20 -10
- package/dist/seeder/index.d.ts +5 -0
- package/dist/seeder/index.js +4 -0
- package/dist/seeder/seeder.decorator.d.ts +7 -0
- package/dist/seeder/seeder.decorator.js +15 -0
- package/dist/seeder/seeder.module.d.ts +16 -0
- package/dist/seeder/seeder.module.js +41 -0
- package/dist/seeder/seeder.registry.d.ts +23 -0
- package/dist/seeder/seeder.registry.js +83 -0
- package/dist/seeder/seeder.tokens.d.ts +2 -0
- package/dist/seeder/seeder.tokens.js +1 -0
- package/dist/seeder/seeder.types.d.ts +20 -0
- package/dist/seeder/seeder.types.js +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/path-template.d.ts +9 -0
- package/dist/storage/path-template.js +20 -0
- package/dist/storage/signed-url.d.ts +12 -0
- package/dist/storage/signed-url.js +55 -0
- package/dist/storage/storage.types.d.ts +41 -0
- package/dist/storage/storage.types.js +5 -0
- package/dist/throttler/throttler.module.d.ts +5 -8
- package/dist/throttler/throttler.module.js +27 -55
- package/dist/websocket/index.d.ts +12 -0
- package/dist/websocket/index.js +16 -0
- package/dist/websocket/websocket.decorators.d.ts +33 -0
- package/dist/websocket/websocket.decorators.js +58 -0
- package/dist/websocket/websocket.module.d.ts +35 -0
- package/dist/websocket/websocket.module.js +63 -0
- package/dist/websocket/websocket.tokens.d.ts +23 -0
- package/dist/websocket/websocket.tokens.js +21 -0
- package/dist/websocket/websocket.types.d.ts +86 -0
- package/dist/websocket/websocket.types.js +4 -0
- package/dist/websocket/ws-argument-resolver.d.ts +13 -0
- package/dist/websocket/ws-argument-resolver.js +40 -0
- package/dist/websocket/ws-dispatcher.d.ts +45 -0
- package/dist/websocket/ws-dispatcher.js +259 -0
- package/dist/websocket/ws-exception.d.ts +16 -0
- package/dist/websocket/ws-exception.js +32 -0
- package/dist/websocket/ws-execution-context.d.ts +9 -0
- package/dist/websocket/ws-execution-context.js +24 -0
- package/dist/websocket/ws-server.d.ts +31 -0
- package/dist/websocket/ws-server.js +74 -0
- package/dist/websocket/ws-sync.d.ts +47 -0
- package/dist/websocket/ws-sync.js +74 -0
- package/dist/websocket-node/index.d.ts +4 -0
- package/dist/websocket-node/index.js +5 -0
- package/dist/websocket-node/node-ws-client.d.ts +24 -0
- package/dist/websocket-node/node-ws-client.js +49 -0
- package/dist/websocket-node/redis-sync.d.ts +33 -0
- package/dist/websocket-node/redis-sync.js +56 -0
- package/dist/websocket-node/register-gateways.d.ts +20 -0
- package/dist/websocket-node/register-gateways.js +61 -0
- package/package.json +31 -1
- package/dist/http/crud-bridge.d.ts +0 -90
- package/dist/http/crud-bridge.js +0 -25
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { VelaApplication } from '../application';
|
|
3
|
+
import type { Container } from '../container/container';
|
|
4
|
+
import type { DiscoveryService } from '../discovery/discovery.service';
|
|
5
|
+
import type { RouteManager } from '../http/route.manager';
|
|
6
|
+
/** The framework's resolved world, handed to adapter hooks. */
|
|
7
|
+
export interface AdapterContext {
|
|
8
|
+
app: VelaApplication;
|
|
9
|
+
container: Container;
|
|
10
|
+
routeManager: RouteManager;
|
|
11
|
+
discovery: DiscoveryService;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The contract a runtime binding implements to plug a platform into a Vela
|
|
15
|
+
* app — what `@velajs/cloudflare` (bindings + Durable Object WebSocket) and
|
|
16
|
+
* the Node/Bun WebSocket transport do, made first-class:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const app = await VelaFactory.create(AppModule, {
|
|
20
|
+
* adapters: [cloudflareAdapter({ bindings })],
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* - `requestMiddleware` is prepended to the global middleware chain (runs
|
|
25
|
+
* before consumer middleware — e.g. capture `c.env` for binding services).
|
|
26
|
+
* - `onBootstrap` runs after DI + lifecycle hooks, with `app.entrypoints`
|
|
27
|
+
* available, BEFORE routes are built — register platform services here.
|
|
28
|
+
* - `onRoutesBuilt` runs after the Hono app exists — mount platform routes
|
|
29
|
+
* (WebSocket upgrades, health endpoints) here via `ctx.app.getHonoApp()`.
|
|
30
|
+
*/
|
|
31
|
+
export interface RuntimeAdapter {
|
|
32
|
+
name: string;
|
|
33
|
+
requestMiddleware?: MiddlewareHandler[];
|
|
34
|
+
onBootstrap?(ctx: AdapterContext): void | Promise<void>;
|
|
35
|
+
onRoutesBuilt?(ctx: AdapterContext): void | Promise<void>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract a runtime binding implements to plug a platform into a Vela
|
|
3
|
+
* app — what `@velajs/cloudflare` (bindings + Durable Object WebSocket) and
|
|
4
|
+
* the Node/Bun WebSocket transport do, made first-class:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const app = await VelaFactory.create(AppModule, {
|
|
8
|
+
* adapters: [cloudflareAdapter({ bindings })],
|
|
9
|
+
* });
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* - `requestMiddleware` is prepended to the global middleware chain (runs
|
|
13
|
+
* before consumer middleware — e.g. capture `c.env` for binding services).
|
|
14
|
+
* - `onBootstrap` runs after DI + lifecycle hooks, with `app.entrypoints`
|
|
15
|
+
* available, BEFORE routes are built — register platform services here.
|
|
16
|
+
* - `onRoutesBuilt` runs after the Hono app exists — mount platform routes
|
|
17
|
+
* (WebSocket upgrades, health endpoints) here via `ctx.app.getHonoApp()`.
|
|
18
|
+
*/ export { };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Scope } from "../constants.js";
|
|
2
2
|
import { Container } from "../container/container.js";
|
|
3
3
|
import { ModuleRef } from "../container/module-ref.js";
|
|
4
|
+
import { DiscoveryService } from "../discovery/discovery.service.js";
|
|
4
5
|
import { REQUEST_CONTEXT } from "../http/request-context.js";
|
|
5
6
|
import { RouteManager } from "../http/route.manager.js";
|
|
6
7
|
import { ModuleLoader } from "../module/module-loader.js";
|
|
7
8
|
import { bindAppProviders } from "../pipeline/app-providers.js";
|
|
8
|
-
import { ComponentManager } from "../pipeline/component.manager.js";
|
|
9
9
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
|
|
10
10
|
/**
|
|
11
11
|
* Wire the DI graph for `rootModule` and prepare the route manager — without
|
|
@@ -32,6 +32,15 @@ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from
|
|
|
32
32
|
]
|
|
33
33
|
});
|
|
34
34
|
container.markGlobalToken(ModuleRef);
|
|
35
|
+
// Decorator-driven discovery — global so any provider can inject it.
|
|
36
|
+
container.register({
|
|
37
|
+
provide: DiscoveryService,
|
|
38
|
+
useFactory: (c)=>new DiscoveryService(c),
|
|
39
|
+
inject: [
|
|
40
|
+
Container
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
container.markGlobalToken(DiscoveryService);
|
|
35
44
|
for (const t of [
|
|
36
45
|
APP_GUARD,
|
|
37
46
|
APP_PIPE,
|
|
@@ -54,7 +63,13 @@ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from
|
|
|
54
63
|
});
|
|
55
64
|
container.markGlobalToken(REQUEST_CONTEXT);
|
|
56
65
|
const routeManager = new RouteManager(container, options);
|
|
57
|
-
|
|
66
|
+
// Resolvable so non-HTTP transports (the WebSocket dispatcher) can read the
|
|
67
|
+
// same global-tier components (APP_* + app.useGlobalX()).
|
|
68
|
+
container.register({
|
|
69
|
+
provide: RouteManager,
|
|
70
|
+
useValue: routeManager
|
|
71
|
+
});
|
|
72
|
+
container.markGlobalToken(RouteManager);
|
|
58
73
|
const loader = new ModuleLoader(container, routeManager);
|
|
59
74
|
loader.load(rootModule);
|
|
60
75
|
bindAppProviders(routeManager, container, loader);
|
|
@@ -68,6 +83,9 @@ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from
|
|
|
68
83
|
};
|
|
69
84
|
routeManager.useGlobalMiddleware(mw);
|
|
70
85
|
}
|
|
86
|
+
// All providers are registered — compute request-scope bubbling so effective
|
|
87
|
+
// scopes are known before eager instantiation and request resolution.
|
|
88
|
+
container.computeEffectiveScopes();
|
|
71
89
|
return {
|
|
72
90
|
container,
|
|
73
91
|
routeManager,
|
package/dist/factory.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { VelaApplication } from './application';
|
|
2
2
|
import type { Type } from './container/types';
|
|
3
|
+
import type { RuntimeAdapter } from './factory/adapter';
|
|
3
4
|
import type { BootstrapOptions } from './factory/bootstrap';
|
|
5
|
+
export interface VelaCreateOptions extends BootstrapOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Runtime adapters binding this app to a platform (Cloudflare bindings,
|
|
8
|
+
* WebSocket transports, …). Their `requestMiddleware` is prepended to the
|
|
9
|
+
* global middleware chain; `onBootstrap`/`onRoutesBuilt` hooks run around
|
|
10
|
+
* route building. See {@link RuntimeAdapter}.
|
|
11
|
+
*/
|
|
12
|
+
adapters?: RuntimeAdapter[];
|
|
13
|
+
}
|
|
4
14
|
export declare const VelaFactory: {
|
|
5
|
-
create(rootModule: Type, options?:
|
|
15
|
+
create(rootModule: Type, options?: VelaCreateOptions): Promise<VelaApplication>;
|
|
6
16
|
};
|
package/dist/factory.js
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
import { VelaApplication } from "./application.js";
|
|
2
|
+
import { DiscoveryService } from "./discovery/discovery.service.js";
|
|
2
3
|
import { bootstrap } from "./factory/bootstrap.js";
|
|
3
4
|
export const VelaFactory = {
|
|
4
5
|
async create (rootModule, options = {}) {
|
|
5
|
-
const {
|
|
6
|
+
const { adapters = [], ...bootstrapOptions } = options;
|
|
7
|
+
const adapterMiddleware = adapters.flatMap((a)=>a.requestMiddleware ?? []);
|
|
8
|
+
if (adapterMiddleware.length > 0) {
|
|
9
|
+
bootstrapOptions.middleware = [
|
|
10
|
+
...adapterMiddleware,
|
|
11
|
+
...bootstrapOptions.middleware ?? []
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
const { container, routeManager, loader } = await bootstrap(rootModule, bootstrapOptions);
|
|
6
15
|
const app = new VelaApplication(container, routeManager);
|
|
7
16
|
const instances = await loader.resolveAllInstances();
|
|
8
17
|
app.setInstances(instances);
|
|
9
18
|
await app.callOnModuleInit();
|
|
10
19
|
await app.callOnApplicationBootstrap();
|
|
11
|
-
|
|
20
|
+
const adapterContext = {
|
|
21
|
+
app,
|
|
22
|
+
container,
|
|
23
|
+
routeManager,
|
|
24
|
+
discovery: container.resolve(DiscoveryService)
|
|
25
|
+
};
|
|
26
|
+
for (const adapter of adapters){
|
|
27
|
+
await adapter.onBootstrap?.(adapterContext);
|
|
28
|
+
}
|
|
29
|
+
// Build routes (async — supports route-contributor integration)
|
|
12
30
|
await app.initRoutes();
|
|
31
|
+
for (const adapter of adapters){
|
|
32
|
+
await adapter.onRoutesBuilt?.(adapterContext);
|
|
33
|
+
}
|
|
13
34
|
return app;
|
|
14
35
|
}
|
|
15
36
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { AsyncModuleOptions, DynamicModule } from '../module/types';
|
|
2
1
|
import type { HttpModuleOptions } from './fetch.types';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}): DynamicModule;
|
|
2
|
+
declare const ConfigurableModuleClass: import("..").ConfigurableModuleClassType<HttpModuleOptions, "forRoot", "create", {
|
|
3
|
+
isGlobal?: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
export declare class HttpModule extends ConfigurableModuleClass {
|
|
8
6
|
}
|
|
7
|
+
export {};
|
|
@@ -5,52 +5,18 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
}
|
|
7
7
|
import { Module } from "../module/decorators.js";
|
|
8
|
-
import {
|
|
8
|
+
import { ConfigurableModuleBuilder } from "../module/configurable-module.builder.js";
|
|
9
9
|
import { HttpService, HTTP_MODULE_OPTIONS } from "./fetch.service.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
providers: [
|
|
16
|
-
{
|
|
17
|
-
provide: HTTP_MODULE_OPTIONS,
|
|
18
|
-
useValue: options
|
|
19
|
-
},
|
|
20
|
-
HttpService
|
|
21
|
-
],
|
|
22
|
-
exports: [
|
|
23
|
-
HttpService,
|
|
24
|
-
HTTP_MODULE_OPTIONS
|
|
25
|
-
]
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
static forRootAsync(options) {
|
|
29
|
-
return {
|
|
30
|
-
module: HttpModule,
|
|
31
|
-
key: options.key ?? stableHash({
|
|
32
|
-
inject: options.inject,
|
|
33
|
-
useFactory: options.useFactory
|
|
34
|
-
}),
|
|
35
|
-
imports: options.imports ?? [],
|
|
36
|
-
providers: [
|
|
37
|
-
{
|
|
38
|
-
provide: HTTP_MODULE_OPTIONS,
|
|
39
|
-
useFactory: options.useFactory,
|
|
40
|
-
inject: options.inject ?? []
|
|
41
|
-
},
|
|
42
|
-
HttpService
|
|
43
|
-
],
|
|
44
|
-
exports: [
|
|
45
|
-
HttpService,
|
|
46
|
-
HTTP_MODULE_OPTIONS
|
|
47
|
-
]
|
|
48
|
-
};
|
|
49
|
-
}
|
|
10
|
+
const { ConfigurableModuleClass } = new ConfigurableModuleBuilder({
|
|
11
|
+
moduleName: 'Http',
|
|
12
|
+
optionsInjectionToken: HTTP_MODULE_OPTIONS
|
|
13
|
+
}).build();
|
|
14
|
+
export class HttpModule extends ConfigurableModuleClass {
|
|
50
15
|
}
|
|
51
16
|
HttpModule = _ts_decorate([
|
|
52
17
|
Module({
|
|
53
18
|
providers: [
|
|
19
|
+
// Default for bare `imports: [HttpModule]`; forRoot(options) overrides via merge.
|
|
54
20
|
{
|
|
55
21
|
provide: HTTP_MODULE_OPTIONS,
|
|
56
22
|
useValue: {}
|
|
@@ -58,7 +24,8 @@ HttpModule = _ts_decorate([
|
|
|
58
24
|
HttpService
|
|
59
25
|
],
|
|
60
26
|
exports: [
|
|
61
|
-
HttpService
|
|
27
|
+
HttpService,
|
|
28
|
+
HTTP_MODULE_OPTIONS
|
|
62
29
|
]
|
|
63
30
|
})
|
|
64
31
|
], HttpModule);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { Container } from '../container/container';
|
|
3
|
+
import { type RequestContext } from './request-context';
|
|
4
|
+
/**
|
|
5
|
+
* Returns Hono's `contextStorage()` middleware. Register it as the FIRST global
|
|
6
|
+
* middleware so `getCurrentContainer()` works everywhere downstream. When using
|
|
7
|
+
* `VelaFactory.create(module, { ambientContainer: true })` this is wired
|
|
8
|
+
* automatically — call this only for manual/raw Hono setups.
|
|
9
|
+
*/
|
|
10
|
+
export declare function enableAmbientContainer(): MiddlewareHandler;
|
|
11
|
+
/**
|
|
12
|
+
* The current request's DI container. Throws with actionable guidance if
|
|
13
|
+
* ambient access isn't enabled or this runs outside a request.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCurrentContainer(): Container;
|
|
16
|
+
/** The current request's {@link RequestContext} (via the ambient container). */
|
|
17
|
+
export declare function getCurrentRequestContext(): RequestContext;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { contextStorage, getContext } from "hono/context-storage";
|
|
2
|
+
import { REQUEST_CONTEXT } from "./request-context.js";
|
|
3
|
+
// Opt-in ambient access to the per-request DI container / RequestContext,
|
|
4
|
+
// for deep code that lacks the Hono `Context` (services, standalone helpers).
|
|
5
|
+
//
|
|
6
|
+
// Portable across vela's target runtimes because the `node:async_hooks`
|
|
7
|
+
// dependency lives inside Hono's `contextStorage()` middleware — vela's own
|
|
8
|
+
// source never imports `node:*`. Uses only the workerd-safe ALS subset
|
|
9
|
+
// (`run()`/`getStore()`); no `enterWith`, no cross-thenable propagation.
|
|
10
|
+
//
|
|
11
|
+
// Default path is unchanged: request scope is still carried by the per-request
|
|
12
|
+
// child container (see RouteManager.getRequestContainer). This is a second
|
|
13
|
+
// READ path, active only when enabled.
|
|
14
|
+
/**
|
|
15
|
+
* Returns Hono's `contextStorage()` middleware. Register it as the FIRST global
|
|
16
|
+
* middleware so `getCurrentContainer()` works everywhere downstream. When using
|
|
17
|
+
* `VelaFactory.create(module, { ambientContainer: true })` this is wired
|
|
18
|
+
* automatically — call this only for manual/raw Hono setups.
|
|
19
|
+
*/ export function enableAmbientContainer() {
|
|
20
|
+
return contextStorage();
|
|
21
|
+
}
|
|
22
|
+
function tryGetContext() {
|
|
23
|
+
try {
|
|
24
|
+
return getContext();
|
|
25
|
+
} catch {
|
|
26
|
+
// Thrown when called outside the contextStorage() middleware (i.e. ambient
|
|
27
|
+
// access not enabled, or outside a request).
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The current request's DI container. Throws with actionable guidance if
|
|
33
|
+
* ambient access isn't enabled or this runs outside a request.
|
|
34
|
+
*/ export function getCurrentContainer() {
|
|
35
|
+
const context = tryGetContext();
|
|
36
|
+
const container = context?.get('container');
|
|
37
|
+
if (!container) {
|
|
38
|
+
throw new Error('getCurrentContainer() is unavailable: enable ambient access via ' + "VelaFactory.create(module, { ambientContainer: true }) (or app.use(enableAmbientContainer())) " + 'and only call it within a request.');
|
|
39
|
+
}
|
|
40
|
+
return container;
|
|
41
|
+
}
|
|
42
|
+
/** The current request's {@link RequestContext} (via the ambient container). */ export function getCurrentRequestContext() {
|
|
43
|
+
return getCurrentContainer().resolve(REQUEST_CONTEXT);
|
|
44
|
+
}
|
|
@@ -10,7 +10,10 @@ export function buildExecutionContext(c, controller, handlerName) {
|
|
|
10
10
|
switchToHttp: ()=>({
|
|
11
11
|
getRequest: ()=>c.req.raw,
|
|
12
12
|
getResponse: ()=>c
|
|
13
|
-
})
|
|
13
|
+
}),
|
|
14
|
+
switchToWs: ()=>{
|
|
15
|
+
throw new Error('switchToWs() called on an HTTP ExecutionContext. This handler runs over HTTP, not a WebSocket gateway.');
|
|
16
|
+
}
|
|
14
17
|
};
|
|
15
18
|
}
|
|
16
19
|
// Sentinel marker exposed via `ExecutionContext.getClass()` when the host is a
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HttpException } from "../errors/http-exception.js";
|
|
2
2
|
import { ComponentManager } from "../pipeline/component.manager.js";
|
|
3
3
|
import { shouldFilterCatch } from "../pipeline/decorators.js";
|
|
4
|
+
import { PipelineRunner } from "../pipeline/pipeline-runner.js";
|
|
4
5
|
import { getHttpCode, getRedirect, getResponseHeaders } from "./decorators.js";
|
|
5
6
|
import { buildExecutionContext } from "./execution-context.js";
|
|
6
7
|
import { instantiateMany } from "./instantiate.js";
|
|
@@ -25,12 +26,12 @@ export class HandlerExecutor {
|
|
|
25
26
|
// Routed via Reflect so the polyfill funnels both src-level and dist-level
|
|
26
27
|
// consumers to the same registry (matters in tests that import from dist).
|
|
27
28
|
const paramTypes = Reflect.getMetadata('design:paramtypes', controller.prototype, route.handlerName);
|
|
28
|
-
const methodGuards = ComponentManager.
|
|
29
|
-
const methodPipes = ComponentManager.
|
|
30
|
-
const methodInterceptors = ComponentManager.
|
|
29
|
+
const methodGuards = ComponentManager.getScopedComponents('guard', controller, route.handlerName);
|
|
30
|
+
const methodPipes = ComponentManager.getScopedComponents('pipe', controller, route.handlerName);
|
|
31
|
+
const methodInterceptors = ComponentManager.getScopedComponents('interceptor', controller, route.handlerName);
|
|
31
32
|
// Filters: reverse order (handler → controller → global) — closest to handler runs first
|
|
32
33
|
const methodFilters = [
|
|
33
|
-
...ComponentManager.
|
|
34
|
+
...ComponentManager.getScopedComponents('filter', controller, route.handlerName)
|
|
34
35
|
].reverse();
|
|
35
36
|
const httpCode = getHttpCode(controller, route.handlerName);
|
|
36
37
|
const responseHeaders = getResponseHeaders(controller, route.handlerName);
|
|
@@ -58,24 +59,20 @@ export class HandlerExecutor {
|
|
|
58
59
|
const executionContext = buildExecutionContext(c, controller, route.handlerName);
|
|
59
60
|
try {
|
|
60
61
|
const instance = requestContainer.resolve(controller);
|
|
61
|
-
// 1. Extract args + run pipes (vela order: args before guards).
|
|
62
|
-
const args = await this.argumentResolver.extract(c, paramMetadata, pipes, requestContainer, paramTypes);
|
|
63
|
-
// 2. Guards (fail-fast).
|
|
64
|
-
for (const guard of guards){
|
|
65
|
-
const canActivate = await guard.canActivate(executionContext);
|
|
66
|
-
if (!canActivate) {
|
|
67
|
-
throw new ForbiddenException();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// 3. Get handler method.
|
|
71
62
|
const method = Reflect.get(instance, route.handlerName);
|
|
72
63
|
if (typeof method !== 'function') {
|
|
73
64
|
throw new Error(`Method ${String(route.handlerName)} not found on controller`);
|
|
74
65
|
}
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
// Args + pipes → guards → interceptor chain → handler, via the shared
|
|
67
|
+
// runner. `argsBeforeGuards` preserves the deliberate vela HTTP order.
|
|
68
|
+
const result = await PipelineRunner.run({
|
|
69
|
+
context: executionContext,
|
|
70
|
+
guards,
|
|
71
|
+
interceptors,
|
|
72
|
+
argsBeforeGuards: true,
|
|
73
|
+
resolveArgs: ()=>this.argumentResolver.extract(c, paramMetadata, pipes, requestContainer, paramTypes),
|
|
74
|
+
invoke: async (args)=>Reflect.apply(method, instance, args)
|
|
75
|
+
});
|
|
79
76
|
if (redirect) {
|
|
80
77
|
return mapRedirect(c, result, redirect);
|
|
81
78
|
}
|
|
@@ -98,6 +95,12 @@ export class HandlerExecutor {
|
|
|
98
95
|
const status = error.getStatus();
|
|
99
96
|
return c.json(response, status);
|
|
100
97
|
}
|
|
98
|
+
// An unknown error reaching here means no filter claimed it — never
|
|
99
|
+
// swallow it silently: the response is a generic 500, but the cause
|
|
100
|
+
// must land in the logs (diagnostics 'silent' opts out).
|
|
101
|
+
if (requestContainer.getDiagnostics() !== 'silent') {
|
|
102
|
+
console.error(`[vela] unhandled error in ${controller.name}.${String(route.handlerName)}:`, error);
|
|
103
|
+
}
|
|
101
104
|
return c.json({
|
|
102
105
|
statusCode: 500,
|
|
103
106
|
message: 'Internal Server Error'
|
package/dist/http/index.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ export { MiddlewareBuilder } from '../module/middleware';
|
|
|
4
4
|
export type { MiddlewareConsumer, MiddlewareConfigProxy, RouteInfo, NestModule, MiddlewareRouteDefinition } from '../module/middleware';
|
|
5
5
|
export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators, isController, } from './decorators';
|
|
6
6
|
export { createLazyParamDecorator } from './lazy-param.decorator';
|
|
7
|
+
export { enableAmbientContainer, getCurrentContainer, getCurrentRequestContext, } from './ambient';
|
|
7
8
|
export type { RouteMetadata, ControllerMetadata, ControllerOptions, ParamMetadata, ControllerRegistration, } from './types';
|
package/dist/http/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { RouteManager } from "./route.manager.js";
|
|
|
2
2
|
export { MiddlewareBuilder } from "../module/middleware.js";
|
|
3
3
|
export { Controller, Version, Get, Post, Put, Patch, Delete, Options, Head, All, Sse, Param, Query, Body, Headers, Req, Res, Ip, Cookie, Cookies, RawBody, HttpCode, Header, Redirect, createParamDecorator, applyDecorators, isController } from "./decorators.js";
|
|
4
4
|
export { createLazyParamDecorator } from "./lazy-param.decorator.js";
|
|
5
|
+
export { enableAmbientContainer, getCurrentContainer, getCurrentRequestContext } from "./ambient.js";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import type { Container } from '../container/container';
|
|
3
|
+
import type { Type } from '../container/types';
|
|
4
|
+
import type { OpenApiPathItem } from '../openapi/types';
|
|
5
|
+
import type { CanActivate } from '../pipeline/types';
|
|
6
|
+
/**
|
|
7
|
+
* Context passed to {@link RouteContributor.buildRoutes} for each controller
|
|
8
|
+
* the contributor claims. The contributor receives the framework's resolved
|
|
9
|
+
* view of the world so generated routes behave identically to hand-written
|
|
10
|
+
* ones: same global prefix, same global guard instances, same path joining.
|
|
11
|
+
*/
|
|
12
|
+
export interface RouteContributorContext {
|
|
13
|
+
/** The claimed controller class. */
|
|
14
|
+
controller: Type;
|
|
15
|
+
/** Controller-level prefix as registered with `@Controller(...)`. */
|
|
16
|
+
controllerPrefix: string;
|
|
17
|
+
/** The metadata value (under `claimsMetaKey`) that claimed this controller. */
|
|
18
|
+
meta: unknown;
|
|
19
|
+
/** Application-wide path prefix (e.g. `/api`), already normalized. */
|
|
20
|
+
globalPrefix: string;
|
|
21
|
+
/** Global guard instances, pre-resolved from the root container. */
|
|
22
|
+
globalGuards: CanActivate[];
|
|
23
|
+
/** The app's root container — resolve controller/services from here. */
|
|
24
|
+
container: Container;
|
|
25
|
+
/** Path joiner used by the framework — handles slash normalization. */
|
|
26
|
+
joinPaths: (...parts: string[]) => string;
|
|
27
|
+
}
|
|
28
|
+
/** Context for {@link RouteContributor.buildOpenApiPaths}. */
|
|
29
|
+
export interface RouteContributorOpenApiContext {
|
|
30
|
+
controller: Type;
|
|
31
|
+
meta: unknown;
|
|
32
|
+
/** Application-wide path prefix (e.g. `/api`), already normalized. */
|
|
33
|
+
globalPrefix: string;
|
|
34
|
+
/** Controller-level prefix as registered with `@Controller(...)`. */
|
|
35
|
+
controllerPrefix: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A route generator that claims controllers by class-level metadata and
|
|
39
|
+
* mounts routes for them — the public extension point behind `@Crud()` (and
|
|
40
|
+
* any future metadata-driven route generator: storage HTTP surfaces, admin
|
|
41
|
+
* panels, RPC bridges).
|
|
42
|
+
*
|
|
43
|
+
* Register once at import time (`registerRouteContributor`); the framework
|
|
44
|
+
* consults contributors during `RouteManager.build` — AFTER all explicit
|
|
45
|
+
* `@Get`/`@Post` routes, so generated `/:id` catch-alls never shadow custom
|
|
46
|
+
* routes — and during OpenAPI document generation.
|
|
47
|
+
*
|
|
48
|
+
* This contract exists instead of dynamic `import()` because esbuild leaves
|
|
49
|
+
* `await import(variable)` as a runtime import Cloudflare Workers cannot
|
|
50
|
+
* resolve.
|
|
51
|
+
*/
|
|
52
|
+
export interface RouteContributor {
|
|
53
|
+
/** Stable identifier; re-registering the same id overwrites (HMR-friendly). */
|
|
54
|
+
id: string;
|
|
55
|
+
/** Class-level metadata key whose presence claims a controller. */
|
|
56
|
+
claimsMetaKey: string;
|
|
57
|
+
buildRoutes(app: Hono, ctx: RouteContributorContext): void | Promise<void>;
|
|
58
|
+
buildOpenApiPaths?(ctx: RouteContributorOpenApiContext): Record<string, OpenApiPathItem>;
|
|
59
|
+
}
|
|
60
|
+
/** Register a contributor. Last registration per `id` wins. */
|
|
61
|
+
export declare function registerRouteContributor(contributor: RouteContributor): void;
|
|
62
|
+
/** All registered contributors (registration order). */
|
|
63
|
+
export declare function getRouteContributors(): RouteContributor[];
|
|
64
|
+
/**
|
|
65
|
+
* Reset all contributor registrations. Test-only.
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
export declare function _resetRouteContributors(): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Contributors register at import time (package side effect), so the store is
|
|
2
|
+
// anchored on `globalThis` like MetadataRegistry state: a Vite HMR re-eval
|
|
3
|
+
// reuses the same store instead of minting an empty one.
|
|
4
|
+
const STORE_KEY = Symbol.for('vela:route-contributors:v1');
|
|
5
|
+
function store() {
|
|
6
|
+
const g = globalThis;
|
|
7
|
+
return g[STORE_KEY] ??= new Map();
|
|
8
|
+
}
|
|
9
|
+
/** Register a contributor. Last registration per `id` wins. */ export function registerRouteContributor(contributor) {
|
|
10
|
+
store().set(contributor.id, contributor);
|
|
11
|
+
}
|
|
12
|
+
/** All registered contributors (registration order). */ export function getRouteContributors() {
|
|
13
|
+
return [
|
|
14
|
+
...store().values()
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reset all contributor registrations. Test-only.
|
|
19
|
+
* @internal
|
|
20
|
+
*/ export function _resetRouteContributors() {
|
|
21
|
+
store().clear();
|
|
22
|
+
}
|
|
@@ -9,6 +9,12 @@ export interface RouteManagerOptions {
|
|
|
9
9
|
getClientIp?: (c: Context) => string | null;
|
|
10
10
|
middleware?: MiddlewareHandler[];
|
|
11
11
|
globalPrefix?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Opt into ambient request-container access (`getCurrentContainer()` /
|
|
14
|
+
* `getCurrentRequestContext()`). Registers Hono's `contextStorage()` as the
|
|
15
|
+
* first middleware. Off by default — the explicit child container is unchanged.
|
|
16
|
+
*/
|
|
17
|
+
ambientContainer?: boolean;
|
|
12
18
|
}
|
|
13
19
|
export declare class RouteManager {
|
|
14
20
|
private container;
|
|
@@ -22,7 +28,20 @@ export declare class RouteManager {
|
|
|
22
28
|
private globalPrefix;
|
|
23
29
|
private consumerMiddlewareDefinitions;
|
|
24
30
|
private readonly handlerExecutor;
|
|
31
|
+
private readonly ambientContainer;
|
|
25
32
|
constructor(container: Container, options?: RouteManagerOptions);
|
|
33
|
+
/**
|
|
34
|
+
* The global-tier components (`APP_*` provider tokens + imperative
|
|
35
|
+
* `app.useGlobalX()` instances). Exposed so non-HTTP transports (the
|
|
36
|
+
* WebSocket dispatcher) can apply the same app-wide guards/pipes/interceptors/
|
|
37
|
+
* filters as HTTP routes, read live so late registrations propagate.
|
|
38
|
+
*/
|
|
39
|
+
getGlobalComponents(): {
|
|
40
|
+
guards: Array<GuardType | Token<CanActivate>>;
|
|
41
|
+
pipes: Array<PipeType | Token<PipeTransform>>;
|
|
42
|
+
interceptors: Array<InterceptorType | Token<NestInterceptor>>;
|
|
43
|
+
filters: Array<FilterType | Token<ExceptionFilter>>;
|
|
44
|
+
};
|
|
26
45
|
registerConsumerMiddleware(definitions: MiddlewareRouteDefinition[]): this;
|
|
27
46
|
setGlobalPrefix(prefix: string): this;
|
|
28
47
|
useGlobalMiddleware(...middleware: MiddlewareType[]): this;
|