@velajs/vela 1.10.0 → 1.13.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 +157 -0
- package/dist/application.d.ts +25 -0
- package/dist/application.js +103 -2
- package/dist/container/container.d.ts +46 -1
- package/dist/container/container.js +156 -13
- package/dist/container/disposable.js +1 -1
- package/dist/container/types.d.ts +27 -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 +102 -0
- package/dist/discovery/discovery.service.js +177 -0
- package/dist/discovery/index.d.ts +2 -0
- package/dist/discovery/index.js +2 -0
- package/dist/entrypoint/entrypoint.registry.d.ts +42 -0
- package/dist/entrypoint/entrypoint.registry.js +112 -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.module.js +1 -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 +14 -2
- package/dist/factory.d.ts +11 -1
- package/dist/factory.js +23 -2
- package/dist/http/handler-executor.js +22 -19
- package/dist/http/route-contributor.d.ts +68 -0
- package/dist/http/route-contributor.js +22 -0
- package/dist/http/route.manager.js +33 -19
- package/dist/i18n/i18n.module.js +9 -1
- package/dist/index.d.ts +12 -3
- package/dist/index.js +9 -2
- package/dist/internal.d.ts +0 -2
- package/dist/internal.js +0 -7
- package/dist/module/configurable-module.builder.d.ts +12 -0
- package/dist/module/configurable-module.builder.js +21 -114
- package/dist/module/decorators.js +4 -2
- package/dist/module/define-module.d.ts +112 -0
- package/dist/module/define-module.js +232 -0
- package/dist/module/index.d.ts +3 -1
- package/dist/module/index.js +3 -1
- package/dist/module/lazy-modules.d.ts +82 -0
- package/dist/module/lazy-modules.js +231 -0
- package/dist/module/lazy-provider.d.ts +65 -0
- package/dist/module/lazy-provider.js +111 -0
- package/dist/module/module-loader.d.ts +21 -0
- package/dist/module/module-loader.js +99 -24
- 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/registry/metadata.registry.d.ts +9 -3
- package/dist/registry/metadata.registry.js +55 -25
- package/dist/registry/types.d.ts +10 -0
- package/dist/schedule/schedule.module.d.ts +5 -0
- package/dist/schedule/schedule.module.js +21 -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/seeder.module.d.ts +8 -10
- package/dist/seeder/seeder.module.js +19 -15
- package/dist/websocket/index.d.ts +1 -1
- package/dist/websocket/index.js +1 -1
- package/dist/websocket/websocket.module.d.ts +20 -3
- package/dist/websocket/websocket.module.js +50 -39
- package/dist/websocket/websocket.tokens.d.ts +2 -1
- package/dist/websocket/websocket.tokens.js +3 -2
- package/dist/websocket/ws-dispatcher.d.ts +21 -9
- package/dist/websocket/ws-dispatcher.js +44 -38
- package/dist/websocket-node/register-gateways.js +3 -3
- package/package.json +1 -1
- package/dist/http/crud-bridge.d.ts +0 -90
- package/dist/http/crud-bridge.js +0 -25
|
@@ -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,
|
|
@@ -61,8 +70,11 @@ import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from
|
|
|
61
70
|
useValue: routeManager
|
|
62
71
|
});
|
|
63
72
|
container.markGlobalToken(RouteManager);
|
|
64
|
-
ComponentManager.init(container);
|
|
65
73
|
const loader = new ModuleLoader(container, routeManager);
|
|
74
|
+
// loader.load() also arms the deferred-init seam (LazyModuleManager) — kept
|
|
75
|
+
// inside the loader so hand-rolled bootstrap paths that never call this
|
|
76
|
+
// function (@velajs/testing's TestingModuleBuilder.compile) get identical
|
|
77
|
+
// lazy semantics.
|
|
66
78
|
loader.load(rootModule);
|
|
67
79
|
bindAppProviders(routeManager, container, loader);
|
|
68
80
|
routeManager.registerConsumerMiddleware(loader.getConsumerMiddlewareDefinitions());
|
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,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'
|
|
@@ -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
|
+
}
|
|
@@ -5,7 +5,7 @@ import { HttpException } from "../errors/http-exception.js";
|
|
|
5
5
|
import { getMetadata } from "../metadata.js";
|
|
6
6
|
import { joinPaths } from "../registry/paths.js";
|
|
7
7
|
import { ArgumentResolver } from "./argument-resolver.js";
|
|
8
|
-
import {
|
|
8
|
+
import { getRouteContributors } from "./route-contributor.js";
|
|
9
9
|
import { buildMiddlewareExecutionContext } from "./execution-context.js";
|
|
10
10
|
import { HandlerExecutor } from "./handler-executor.js";
|
|
11
11
|
import { instantiate, instantiateMany } from "./instantiate.js";
|
|
@@ -177,6 +177,11 @@ export class RouteManager {
|
|
|
177
177
|
if (typeof inst.priority === 'number') return inst.priority;
|
|
178
178
|
if (typeof inst.constructor?.priority === 'number') return inst.constructor.priority;
|
|
179
179
|
}
|
|
180
|
+
// A token owned exclusively by unmaterialized lazy modules must not be
|
|
181
|
+
// instantiate-probed here — the probe at route build would defeat the
|
|
182
|
+
// module's deferral (i18n's APP_MIDDLEWARE). Default priority instead;
|
|
183
|
+
// the middleware still materializes on its first request.
|
|
184
|
+
if (this.container.isLazyPending(entry)) return 0;
|
|
180
185
|
try {
|
|
181
186
|
const resolved = instantiate(entry, this.container);
|
|
182
187
|
if (resolved && typeof resolved === 'object') {
|
|
@@ -348,7 +353,9 @@ export class RouteManager {
|
|
|
348
353
|
for (const route of routes){
|
|
349
354
|
const effectiveVersion = route.version ?? metadata.version;
|
|
350
355
|
const versionedPaths = this.buildVersionedPaths(this.globalPrefix, metadata.prefix, route.path, effectiveVersion);
|
|
351
|
-
|
|
356
|
+
// Scoped (controller/handler) middleware only — global middleware is
|
|
357
|
+
// applied once by this manager's own global pass, never re-read here.
|
|
358
|
+
const middlewareItems = ComponentManager.getScopedComponents('middleware', controller, route.handlerName);
|
|
352
359
|
const handler = this.handlerExecutor.create(route, controller, allParamMetadata);
|
|
353
360
|
for (const fullPath of versionedPaths){
|
|
354
361
|
for (const middlewareItem of middlewareItems){
|
|
@@ -363,26 +370,33 @@ export class RouteManager {
|
|
|
363
370
|
}
|
|
364
371
|
}
|
|
365
372
|
}
|
|
366
|
-
// Second pass:
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
|
|
373
|
+
// Second pass: route contributors (generated routes registered last, so a
|
|
374
|
+
// contributor's `/:id` catch-alls never shadow explicit routes). Each
|
|
375
|
+
// contributor claims controllers by class-level metadata; `@velajs/crud`
|
|
376
|
+
// registers one as an import side effect. The registry contract (instead
|
|
377
|
+
// of dynamic `import()`) exists because esbuild leaves
|
|
378
|
+
// `await import(variable)` as a runtime import Cloudflare Workers cannot
|
|
379
|
+
// resolve.
|
|
380
|
+
const contributors = getRouteContributors();
|
|
374
381
|
for (const { controller, metadata } of this.controllers){
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
382
|
+
for (const contributor of contributors){
|
|
383
|
+
const meta = getMetadata(contributor.claimsMetaKey, controller);
|
|
384
|
+
if (meta === undefined) continue;
|
|
385
|
+
await contributor.buildRoutes(app, {
|
|
386
|
+
controller,
|
|
387
|
+
controllerPrefix: metadata.prefix,
|
|
388
|
+
meta,
|
|
389
|
+
globalPrefix: this.globalPrefix,
|
|
390
|
+
globalGuards: instantiateMany(this.globalGuards, this.container),
|
|
391
|
+
container: this.container,
|
|
392
|
+
joinPaths
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
// DX guard for the common mistake: @Crud() metadata present but the
|
|
396
|
+
// contributor package never imported.
|
|
397
|
+
if (getMetadata('vela:crud', controller) !== undefined && !contributors.some((c)=>c.claimsMetaKey === 'vela:crud')) {
|
|
379
398
|
throw new Error("@Crud() requires '@velajs/crud'. Install it: pnpm add @velajs/crud");
|
|
380
399
|
}
|
|
381
|
-
await bridge.buildRoutes(app, controller, metadata.prefix, crudConfig, {
|
|
382
|
-
globalPrefix: this.globalPrefix,
|
|
383
|
-
globalGuards: instantiateMany(this.globalGuards, this.container),
|
|
384
|
-
joinPaths
|
|
385
|
-
});
|
|
386
400
|
}
|
|
387
401
|
return app;
|
|
388
402
|
}
|
package/dist/i18n/i18n.module.js
CHANGED
|
@@ -20,7 +20,10 @@ const { ConfigurableModuleClass } = new ConfigurableModuleBuilder({
|
|
|
20
20
|
// Empty marker module for `registerMessages`. The contribution is a side-effect
|
|
21
21
|
// on the global MessageRegistry; this carries NO providers so it never
|
|
22
22
|
// duplicates I18nModule's providers (which would trip vela's multi-instance
|
|
23
|
-
// encapsulation → MultipleProvidersFoundError).
|
|
23
|
+
// encapsulation → MultipleProvidersFoundError). New modules whose contribution
|
|
24
|
+
// is providers (not an external registry) should use `sideEffectModule` from
|
|
25
|
+
// the authoring kit; this predates it and keeps a shared-class identity so
|
|
26
|
+
// identical message trees dedup as one module instance.
|
|
24
27
|
let I18nMessagesModule = class I18nMessagesModule {
|
|
25
28
|
};
|
|
26
29
|
I18nMessagesModule = _ts_decorate([
|
|
@@ -41,6 +44,11 @@ export class I18nModule extends ConfigurableModuleClass {
|
|
|
41
44
|
}
|
|
42
45
|
I18nModule = _ts_decorate([
|
|
43
46
|
Module({
|
|
47
|
+
// Lazy: the merged-message snapshot (MessageLoaderService constructor) and
|
|
48
|
+
// locale middleware materialize on the first request that reaches them —
|
|
49
|
+
// message registration itself is import-time and unaffected. Safe because
|
|
50
|
+
// the route-build priority probe skips lazy-pending APP_MIDDLEWARE tokens.
|
|
51
|
+
lazy: true,
|
|
44
52
|
providers: [
|
|
45
53
|
MessageRegistry,
|
|
46
54
|
MessageLoaderService,
|
package/dist/index.d.ts
CHANGED
|
@@ -33,12 +33,21 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
|
|
|
33
33
|
export type { HealthCheckResult, HealthCheckStatus, HealthIndicatorResult, HealthIndicatorFunction, ResponseCheckCallback, HttpPingOptions, } from './health/index';
|
|
34
34
|
export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA, } from './throttler/index';
|
|
35
35
|
export type { ThrottlerModuleOptions, ThrottleConfig, ThrottlerStore, ThrottlerStorageRecord, RateLimitInfo, } from './throttler/index';
|
|
36
|
-
export { Global, Module, defineDynamicModule, stableHash, ConfigurableModuleBuilder, defineConfigurableModule, } from './module/index';
|
|
37
|
-
export type { ModuleOptions, DynamicModule, AsyncModuleOptions, ModuleImport, ConfigurableModuleAsyncOptions, ConfigurableModuleBuilderOptions, ConfigurableModuleClassType, ConfigurableModuleExtras, ConfigurableModuleExtrasTransform, ConfigurableModuleHost, ConfigurableModuleOptionsFactory, DefineConfigurableModuleSpec, } from './module/index';
|
|
36
|
+
export { Global, Module, defineDynamicModule, stableHash, moduleKey, ConfigurableModuleBuilder, defineConfigurableModule, defineModule, buildAsyncOptionsProviders, lazyProvider, moduleToken, provideGlobal, sideEffectModule, } from './module/index';
|
|
37
|
+
export type { ModuleOptions, DynamicModule, AsyncModuleOptions, ModuleImport, ConfigurableModuleAsyncOptions, ConfigurableModuleBuilderOptions, ConfigurableModuleClassType, ConfigurableModuleExtras, ConfigurableModuleExtrasTransform, ConfigurableModuleHost, ConfigurableModuleOptionsFactory, DefineConfigurableModuleSpec, DefineModuleSpec, GlobalComponentSlot, ModuleContributions, ModuleSetupContext, LazyProviderSpec, } from './module/index';
|
|
38
38
|
export type { MiddlewareConsumer, NestModule, RouteInfo } from './http/index';
|
|
39
|
+
export { DiscoveryService, createDiscoverableDecorator, } from './discovery/index';
|
|
40
|
+
export type { DiscoveredClass, DiscoveredMethodMeta, DiscoveryFilter, DiscoverableDecorator, CreateDiscoverableDecoratorOptions, } from './discovery/index';
|
|
41
|
+
export { EntrypointRegistry, registerEntrypointKind, getEntrypointKinds, contributesEntrypoints, runInEntrypointScope, buildEntrypointExecutionContext, } from './entrypoint/index';
|
|
42
|
+
export type { ContributesEntrypoints, Entrypoint, EntrypointKind, EntrypointExecutionContext, } from './entrypoint/index';
|
|
43
|
+
export { registerRouteContributor, getRouteContributors, } from './http/route-contributor';
|
|
44
|
+
export type { RouteContributor, RouteContributorContext, RouteContributorOpenApiContext, } from './http/route-contributor';
|
|
45
|
+
export type { AdapterContext, RuntimeAdapter } from './factory/adapter';
|
|
46
|
+
export type { VelaCreateOptions } from './factory';
|
|
39
47
|
export { definePlugin, composePlugins, PluginRegistry, PluginRootModule, PLUGIN_REGISTRY_TOKEN, } from './plugin/plugin';
|
|
40
48
|
export type { Plugin } from './plugin/plugin';
|
|
41
|
-
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE, } from './pipeline/index';
|
|
49
|
+
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, PipelineRunner, getCatchTypes, shouldFilterCatch, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE, } from './pipeline/index';
|
|
50
|
+
export type { PipelineRunOptions } from './pipeline/index';
|
|
42
51
|
export type { HttpArgumentsHost, ExecutionContext, CanActivate, CallHandler, NestInterceptor, NestMiddleware, PipeTransform, ExceptionFilter, ArgumentMetadata, ReflectableDecorator, CreateDecoratorOptions, } from './pipeline/index';
|
|
43
52
|
export type { Constructor, MiddlewareType, GuardType, PipeType, InterceptorType, FilterType, } from './registry/index';
|
|
44
53
|
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, ParseUUIDPipe, ParseEnumPipe, ParseArrayPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe, } from './pipeline/index';
|
package/dist/index.js
CHANGED
|
@@ -37,11 +37,18 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
|
|
|
37
37
|
// Throttler
|
|
38
38
|
export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA } from "./throttler/index.js";
|
|
39
39
|
// Module
|
|
40
|
-
export { Global, Module, defineDynamicModule, stableHash, ConfigurableModuleBuilder, defineConfigurableModule } from "./module/index.js";
|
|
40
|
+
export { Global, Module, defineDynamicModule, stableHash, moduleKey, ConfigurableModuleBuilder, defineConfigurableModule, defineModule, buildAsyncOptionsProviders, lazyProvider, moduleToken, provideGlobal, sideEffectModule } from "./module/index.js";
|
|
41
|
+
// Discovery — decorator-driven provider discovery (the public replacement for
|
|
42
|
+
// hand-rolled bootstrap scans)
|
|
43
|
+
export { DiscoveryService, createDiscoverableDecorator } from "./discovery/index.js";
|
|
44
|
+
// Entrypoints — the open non-HTTP entry surface (websocket, queue, cron, …)
|
|
45
|
+
export { EntrypointRegistry, registerEntrypointKind, getEntrypointKinds, contributesEntrypoints, runInEntrypointScope, buildEntrypointExecutionContext } from "./entrypoint/index.js";
|
|
46
|
+
// Route contribution — metadata-claimed route generators (@Crud-style)
|
|
47
|
+
export { registerRouteContributor, getRouteContributors } from "./http/route-contributor.js";
|
|
41
48
|
// Plugin manifest + composer
|
|
42
49
|
export { definePlugin, composePlugins, PluginRegistry, PluginRootModule, PLUGIN_REGISTRY_TOKEN } from "./plugin/plugin.js";
|
|
43
50
|
// Pipeline Decorators
|
|
44
|
-
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/index.js";
|
|
51
|
+
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, PipelineRunner, getCatchTypes, shouldFilterCatch, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/index.js";
|
|
45
52
|
// Built-in Pipes
|
|
46
53
|
export { ParseIntPipe, ParseFloatPipe, ParseBoolPipe, ParseUUIDPipe, ParseEnumPipe, ParseArrayPipe, DefaultValuePipe, RequiredPipe, ZodValidationPipe } from "./pipeline/index.js";
|
|
47
54
|
// Errors
|
package/dist/internal.d.ts
CHANGED
|
@@ -5,8 +5,6 @@ export type { ModuleScope, ContainerOptions, Diagnostics, } from './container/ty
|
|
|
5
5
|
export { bindAppProviders } from './pipeline/app-providers';
|
|
6
6
|
export { RouteManager } from './http/route.manager';
|
|
7
7
|
export type { RouteManagerOptions } from './http/route.manager';
|
|
8
|
-
export { registerCrudBridge, getCrudBridge, } from './http/crud-bridge';
|
|
9
|
-
export type { CrudBridge, CrudBridgeRouteContext, CrudBridgeOpenApiContext, } from './http/crud-bridge';
|
|
10
8
|
export { ModuleLoader } from './module/module-loader';
|
|
11
9
|
export { ComponentManager } from './pipeline/component.manager';
|
|
12
10
|
export { VelaApplication } from './application';
|
package/dist/internal.js
CHANGED
|
@@ -6,13 +6,6 @@ export { ModuleRef } from "./container/module-ref.js";
|
|
|
6
6
|
export { ModuleVisibilityError } from "./container/types.js";
|
|
7
7
|
export { bindAppProviders } from "./pipeline/app-providers.js";
|
|
8
8
|
export { RouteManager } from "./http/route.manager.js";
|
|
9
|
-
// CRUD bridge registry — the integration seam for `@velajs/crud` (and any
|
|
10
|
-
// future CRUD route generator). `@velajs/crud` calls `registerCrudBridge`
|
|
11
|
-
// once at import time; the framework consults `getCrudBridge` whenever it
|
|
12
|
-
// encounters a controller carrying `vela:crud` metadata, both at route
|
|
13
|
-
// build time and at OpenAPI document generation. The types are exposed so
|
|
14
|
-
// integrators can write their own bridges or strongly-typed mocks in tests.
|
|
15
|
-
export { registerCrudBridge, getCrudBridge } from "./http/crud-bridge.js";
|
|
16
9
|
export { ModuleLoader } from "./module/module-loader.js";
|
|
17
10
|
export { ComponentManager } from "./pipeline/component.manager.js";
|
|
18
11
|
export { VelaApplication } from "./application.js";
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import type { DynamicModule } from '../registry/types';
|
|
2
2
|
import type { ConfigurableModuleBuilderOptions, ConfigurableModuleExtras, ConfigurableModuleExtrasTransform, ConfigurableModuleHost, DefineConfigurableModuleSpec } from './configurable-module.types';
|
|
3
|
+
/**
|
|
4
|
+
* Blessed key-derivation helper for hand-written `forRoot` statics: the one
|
|
5
|
+
* documented way to derive a `DynamicModule.key` from an options bag so
|
|
6
|
+
* identical configurations dedup (HMR-idempotent) and distinct ones coexist.
|
|
7
|
+
* Alias of {@link stableHash} with a module-authoring name.
|
|
8
|
+
*/
|
|
9
|
+
export declare const moduleKey: (options: unknown) => string;
|
|
3
10
|
/**
|
|
4
11
|
* NestJS-parity builder that generates `forRoot`/`forRootAsync` (and `key`,
|
|
5
12
|
* `global`, factory-param inference) from a tiny spec — so a module is just its
|
|
6
13
|
* tokens + options type + service + a `@Module({...})` bag, while vela keeps its
|
|
7
14
|
* encapsulation (`exports`/visibility) and multi-instance `key` dedup.
|
|
8
15
|
*
|
|
16
|
+
* Since 1.11 this is a thin adapter over {@link defineModule} — the single
|
|
17
|
+
* authoring engine. Prefer `defineModule` for new modules: it additionally
|
|
18
|
+
* supports contributions (providers/controllers/imports/exports/global
|
|
19
|
+
* components) computed as functions of the options.
|
|
20
|
+
*
|
|
9
21
|
* @example
|
|
10
22
|
* ```ts
|
|
11
23
|
* const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineModule } from "./define-module.js";
|
|
2
2
|
import { stableHash } from "./stable-hash.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} : def;
|
|
3
|
+
/**
|
|
4
|
+
* Blessed key-derivation helper for hand-written `forRoot` statics: the one
|
|
5
|
+
* documented way to derive a `DynamicModule.key` from an options bag so
|
|
6
|
+
* identical configurations dedup (HMR-idempotent) and distinct ones coexist.
|
|
7
|
+
* Alias of {@link stableHash} with a module-authoring name.
|
|
8
|
+
*/ export const moduleKey = stableHash;
|
|
10
9
|
/**
|
|
11
10
|
* NestJS-parity builder that generates `forRoot`/`forRootAsync` (and `key`,
|
|
12
11
|
* `global`, factory-param inference) from a tiny spec — so a module is just its
|
|
13
12
|
* tokens + options type + service + a `@Module({...})` bag, while vela keeps its
|
|
14
13
|
* encapsulation (`exports`/visibility) and multi-instance `key` dedup.
|
|
15
14
|
*
|
|
15
|
+
* Since 1.11 this is a thin adapter over {@link defineModule} — the single
|
|
16
|
+
* authoring engine. Prefer `defineModule` for new modules: it additionally
|
|
17
|
+
* supports contributions (providers/controllers/imports/exports/global
|
|
18
|
+
* components) computed as functions of the options.
|
|
19
|
+
*
|
|
16
20
|
* @example
|
|
17
21
|
* ```ts
|
|
18
22
|
* const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
|
|
@@ -28,8 +32,8 @@ const DEFAULT_TRANSFORM = (def, extras)=>extras.isGlobal ? {
|
|
|
28
32
|
options;
|
|
29
33
|
classMethodName = 'forRoot';
|
|
30
34
|
factoryMethodName = 'create';
|
|
31
|
-
extrasDefaults
|
|
32
|
-
extrasTransform
|
|
35
|
+
extrasDefaults;
|
|
36
|
+
extrasTransform;
|
|
33
37
|
constructor(options = {}){
|
|
34
38
|
this.options = options;
|
|
35
39
|
}
|
|
@@ -47,112 +51,15 @@ const DEFAULT_TRANSFORM = (def, extras)=>extras.isGlobal ? {
|
|
|
47
51
|
return this;
|
|
48
52
|
}
|
|
49
53
|
build() {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
let ConfigurableModuleClass = class ConfigurableModuleClass {
|
|
58
|
-
};
|
|
59
|
-
Object.defineProperty(ConfigurableModuleClass, syncName, {
|
|
60
|
-
configurable: true,
|
|
61
|
-
writable: true,
|
|
62
|
-
enumerable: false,
|
|
63
|
-
value (options = {}) {
|
|
64
|
-
const { key, ...rest } = options;
|
|
65
|
-
const definition = {
|
|
66
|
-
module: this,
|
|
67
|
-
key: key ?? stableHash(rest),
|
|
68
|
-
providers: [
|
|
69
|
-
{
|
|
70
|
-
provide: optionsToken,
|
|
71
|
-
useValue: rest
|
|
72
|
-
}
|
|
73
|
-
]
|
|
74
|
-
};
|
|
75
|
-
return transform(definition, {
|
|
76
|
-
...extrasDefaults,
|
|
77
|
-
...rest
|
|
78
|
-
});
|
|
79
|
-
}
|
|
54
|
+
return defineModule({
|
|
55
|
+
name: this.options.moduleName ?? 'ConfigurableModule',
|
|
56
|
+
optionsToken: this.options.optionsInjectionToken,
|
|
57
|
+
extras: this.extrasDefaults,
|
|
58
|
+
transform: this.extrasTransform,
|
|
59
|
+
methodName: this.classMethodName,
|
|
60
|
+
factoryMethodName: this.factoryMethodName
|
|
80
61
|
});
|
|
81
|
-
Object.defineProperty(ConfigurableModuleClass, asyncName, {
|
|
82
|
-
configurable: true,
|
|
83
|
-
writable: true,
|
|
84
|
-
enumerable: false,
|
|
85
|
-
value (options = {}) {
|
|
86
|
-
const { key, imports, inject, useFactory, useClass, useExisting, ...restExtras } = options;
|
|
87
|
-
const definition = {
|
|
88
|
-
module: this,
|
|
89
|
-
// Include extras (e.g. isGlobal) in the key — like forRoot — so two
|
|
90
|
-
// async instances differing only in an extra don't wrongly dedup.
|
|
91
|
-
key: key ?? stableHash({
|
|
92
|
-
inject,
|
|
93
|
-
useFactory,
|
|
94
|
-
useClass,
|
|
95
|
-
useExisting,
|
|
96
|
-
...restExtras
|
|
97
|
-
}),
|
|
98
|
-
imports: imports ?? [],
|
|
99
|
-
providers: buildAsyncOptionsProviders(optionsToken, factoryMethodName, {
|
|
100
|
-
inject,
|
|
101
|
-
useFactory,
|
|
102
|
-
useClass,
|
|
103
|
-
useExisting
|
|
104
|
-
})
|
|
105
|
-
};
|
|
106
|
-
return transform(definition, {
|
|
107
|
-
...extrasDefaults,
|
|
108
|
-
...restExtras
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
return {
|
|
113
|
-
ConfigurableModuleClass: ConfigurableModuleClass,
|
|
114
|
-
MODULE_OPTIONS_TOKEN: optionsToken,
|
|
115
|
-
// Type-only sentinels — never read at runtime.
|
|
116
|
-
OPTIONS_TYPE: undefined,
|
|
117
|
-
ASYNC_OPTIONS_TYPE: undefined
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/** Lower `useFactory`/`useClass`/`useExisting` async options into provider registrations. */ function buildAsyncOptionsProviders(optionsToken, factoryMethodName, async) {
|
|
122
|
-
if (async.useFactory) {
|
|
123
|
-
return [
|
|
124
|
-
{
|
|
125
|
-
provide: optionsToken,
|
|
126
|
-
useFactory: async.useFactory,
|
|
127
|
-
inject: async.inject ?? []
|
|
128
|
-
}
|
|
129
|
-
];
|
|
130
|
-
}
|
|
131
|
-
if (async.useClass) {
|
|
132
|
-
const factoryClass = async.useClass;
|
|
133
|
-
return [
|
|
134
|
-
factoryClass,
|
|
135
|
-
{
|
|
136
|
-
provide: optionsToken,
|
|
137
|
-
useFactory: (instance)=>instance[factoryMethodName](),
|
|
138
|
-
inject: [
|
|
139
|
-
factoryClass
|
|
140
|
-
]
|
|
141
|
-
}
|
|
142
|
-
];
|
|
143
|
-
}
|
|
144
|
-
if (async.useExisting) {
|
|
145
|
-
return [
|
|
146
|
-
{
|
|
147
|
-
provide: optionsToken,
|
|
148
|
-
useFactory: (instance)=>instance[factoryMethodName](),
|
|
149
|
-
inject: [
|
|
150
|
-
async.useExisting
|
|
151
|
-
]
|
|
152
|
-
}
|
|
153
|
-
];
|
|
154
62
|
}
|
|
155
|
-
throw new Error('Async module options require one of `useFactory`, `useClass`, or `useExisting`.');
|
|
156
63
|
}
|
|
157
64
|
/**
|
|
158
65
|
* Lower-level engine for cases a class-mixin can't express — chiefly a
|