@velajs/vela 1.2.0 → 1.4.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 +67 -0
- package/dist/__tests__/workers/entry.d.ts +9 -0
- package/dist/container/container.d.ts +17 -5
- package/dist/container/container.js +151 -34
- package/dist/container/index.d.ts +2 -2
- package/dist/container/index.js +1 -1
- package/dist/container/types.d.ts +16 -0
- package/dist/container/types.js +14 -0
- package/dist/event-emitter/event-emitter.subscriber.js +6 -1
- package/dist/factory/bootstrap.d.ts +23 -0
- package/dist/factory/bootstrap.js +76 -0
- package/dist/factory.d.ts +2 -2
- package/dist/factory.js +2 -33
- package/dist/http/request-context.d.ts +13 -0
- package/dist/http/request-context.js +22 -0
- package/dist/http/route.manager.js +2 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +6 -1
- package/dist/internal.d.ts +6 -0
- package/dist/internal.js +6 -0
- package/dist/module/module-loader.d.ts +4 -0
- package/dist/module/module-loader.js +80 -23
- package/dist/plugin/plugin.d.ts +31 -0
- package/dist/plugin/plugin.js +111 -0
- package/dist/registry/metadata.registry.js +0 -1
- package/dist/schedule/schedule.registry.js +6 -1
- package/dist/schedule-node/schedule.executor.d.ts +3 -1
- package/dist/schedule-node/schedule.executor.js +21 -5
- package/package.json +6 -2
package/dist/factory.js
CHANGED
|
@@ -1,39 +1,8 @@
|
|
|
1
1
|
import { VelaApplication } from "./application.js";
|
|
2
|
-
import {
|
|
3
|
-
import { ModuleRef } from "./container/module-ref.js";
|
|
4
|
-
import { bindAppProviders } from "./pipeline/app-providers.js";
|
|
5
|
-
import { RouteManager } from "./http/route.manager.js";
|
|
6
|
-
import { ModuleLoader } from "./module/module-loader.js";
|
|
7
|
-
import { ComponentManager } from "./pipeline/component.manager.js";
|
|
2
|
+
import { bootstrap } from "./factory/bootstrap.js";
|
|
8
3
|
export const VelaFactory = {
|
|
9
4
|
async create (rootModule, options = {}) {
|
|
10
|
-
const container =
|
|
11
|
-
container.register({
|
|
12
|
-
provide: Container,
|
|
13
|
-
useValue: container
|
|
14
|
-
});
|
|
15
|
-
container.register({
|
|
16
|
-
provide: ModuleRef,
|
|
17
|
-
useFactory: (c)=>new ModuleRef(c),
|
|
18
|
-
inject: [
|
|
19
|
-
Container
|
|
20
|
-
]
|
|
21
|
-
});
|
|
22
|
-
const routeManager = new RouteManager(container, options);
|
|
23
|
-
ComponentManager.init(container);
|
|
24
|
-
const loader = new ModuleLoader(container, routeManager);
|
|
25
|
-
loader.load(rootModule);
|
|
26
|
-
bindAppProviders(routeManager, container, loader);
|
|
27
|
-
routeManager.registerConsumerMiddleware(loader.getConsumerMiddlewareDefinitions());
|
|
28
|
-
if (options.globalPrefix) {
|
|
29
|
-
routeManager.setGlobalPrefix(options.globalPrefix);
|
|
30
|
-
}
|
|
31
|
-
for (const handler of options.middleware ?? []){
|
|
32
|
-
const mw = {
|
|
33
|
-
use: handler
|
|
34
|
-
};
|
|
35
|
-
routeManager.useGlobalMiddleware(mw);
|
|
36
|
-
}
|
|
5
|
+
const { container, routeManager, loader } = await bootstrap(rootModule, options);
|
|
37
6
|
const app = new VelaApplication(container, routeManager);
|
|
38
7
|
const instances = await loader.resolveAllInstances();
|
|
39
8
|
app.setInstances(instances);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
import { InjectionToken } from '../container/types';
|
|
3
|
+
export interface RequestContext {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly receivedAt: Date;
|
|
6
|
+
readonly request: Request;
|
|
7
|
+
readonly hono: Context;
|
|
8
|
+
set<T>(key: string | symbol, value: T): void;
|
|
9
|
+
get<T>(key: string | symbol): T | undefined;
|
|
10
|
+
has(key: string | symbol): boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const REQUEST_CONTEXT: InjectionToken<RequestContext>;
|
|
13
|
+
export declare function createRequestContext(c: Context): RequestContext;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { InjectionToken } from "../container/types.js";
|
|
2
|
+
export const REQUEST_CONTEXT = new InjectionToken('vela.RequestContext');
|
|
3
|
+
export function createRequestContext(c) {
|
|
4
|
+
const bag = new Map();
|
|
5
|
+
const inboundId = c.req.raw.headers.get('x-request-id');
|
|
6
|
+
const id = inboundId && inboundId.length > 0 ? inboundId : crypto.randomUUID();
|
|
7
|
+
return {
|
|
8
|
+
id,
|
|
9
|
+
receivedAt: new Date(),
|
|
10
|
+
request: c.req.raw,
|
|
11
|
+
hono: c,
|
|
12
|
+
set (key, value) {
|
|
13
|
+
bag.set(key, value);
|
|
14
|
+
},
|
|
15
|
+
get (key) {
|
|
16
|
+
return bag.get(key);
|
|
17
|
+
},
|
|
18
|
+
has (key) {
|
|
19
|
+
return bag.has(key);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -4,6 +4,7 @@ import { HttpMethod, ParamType } from "../constants.js";
|
|
|
4
4
|
import { getMetadata } from "../metadata.js";
|
|
5
5
|
import { joinPaths } from "../registry/paths.js";
|
|
6
6
|
import { buildExecutionContext } from "./execution-context.js";
|
|
7
|
+
import { REQUEST_CONTEXT, createRequestContext } from "./request-context.js";
|
|
7
8
|
import { ForbiddenException, HttpException } from "../errors/http-exception.js";
|
|
8
9
|
import { ComponentManager } from "../pipeline/component.manager.js";
|
|
9
10
|
import { shouldFilterCatch } from "../pipeline/decorators.js";
|
|
@@ -206,6 +207,7 @@ export class RouteManager {
|
|
|
206
207
|
return existing;
|
|
207
208
|
}
|
|
208
209
|
const child = this.container.createChild();
|
|
210
|
+
child.setRequestInstance(REQUEST_CONTEXT, createRequestContext(c));
|
|
209
211
|
c.set('container', child);
|
|
210
212
|
return child;
|
|
211
213
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,16 @@ import './metadata';
|
|
|
2
2
|
export { defineMetadata, getMetadata } from './metadata';
|
|
3
3
|
export { VelaFactory } from './factory';
|
|
4
4
|
export { VelaApplication } from './application';
|
|
5
|
+
export { bootstrap } from './factory/bootstrap';
|
|
6
|
+
export type { BootstrapOptions, BootstrapResult } from './factory/bootstrap';
|
|
5
7
|
export { createOpenApiDocument, ApiDoc, ApiTags, ApiResponse, zodToJsonSchema, } from './openapi/index';
|
|
6
8
|
export type { OpenApiDocument, OpenApiInfo, OpenApiOperation, OpenApiParameter, OpenApiPathItem, OpenApiRequestBody, OpenApiResponse, ApiDocMetadata, ApiResponseEntry, ApiResponseOptions, CreateOpenApiDocumentOptions, HttpVerb, JsonSchema, } from './openapi/index';
|
|
7
|
-
export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, mixin } from './container/index';
|
|
8
|
-
export type { Type, Token, InjectableOptions, ProviderOptions, } from './container/index';
|
|
9
|
+
export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, ModuleVisibilityError, mixin, } from './container/index';
|
|
10
|
+
export type { Type, Token, InjectableOptions, ProviderOptions, ModuleScope, ContainerOptions, Diagnostics, } from './container/index';
|
|
9
11
|
export { METADATA_KEYS, HttpMethod, ParamType, Scope } from './constants';
|
|
10
12
|
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, } from './http/index';
|
|
13
|
+
export { REQUEST_CONTEXT } from './http/request-context';
|
|
14
|
+
export type { RequestContext } from './http/request-context';
|
|
11
15
|
export { Logger, LogLevel } from './services/index';
|
|
12
16
|
export type { LoggerService, ContextProvider, Writer, LoggerLevelName } from './services/index';
|
|
13
17
|
export { ConfigModule, ConfigService, CONFIG_OPTIONS } from './config/index';
|
|
@@ -29,6 +33,8 @@ export type { ThrottlerModuleOptions, ThrottleConfig, ThrottlerStore, ThrottlerS
|
|
|
29
33
|
export { Global, Module, createModuleRef } from './module/index';
|
|
30
34
|
export type { ModuleOptions, DynamicModule, AsyncModuleOptions, ModuleImport } from './module/index';
|
|
31
35
|
export type { MiddlewareConsumer, NestModule, RouteInfo } from './http/index';
|
|
36
|
+
export { definePlugin, composePlugins, PluginRegistry, PluginRootModule, PLUGIN_REGISTRY_TOKEN, } from './plugin/plugin';
|
|
37
|
+
export type { Plugin } from './plugin/plugin';
|
|
32
38
|
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE, } from './pipeline/index';
|
|
33
39
|
export type { HttpArgumentsHost, ExecutionContext, CanActivate, CallHandler, NestInterceptor, NestMiddleware, PipeTransform, ExceptionFilter, ArgumentMetadata, ReflectableDecorator, CreateDecoratorOptions, } from './pipeline/index';
|
|
34
40
|
export type { Constructor, MiddlewareType, GuardType, PipeType, InterceptorType, FilterType, } from './registry/index';
|
package/dist/index.js
CHANGED
|
@@ -3,14 +3,17 @@ export { defineMetadata, getMetadata } from "./metadata.js";
|
|
|
3
3
|
// Factory & Application
|
|
4
4
|
export { VelaFactory } from "./factory.js";
|
|
5
5
|
export { VelaApplication } from "./application.js";
|
|
6
|
+
export { bootstrap } from "./factory/bootstrap.js";
|
|
6
7
|
// OpenAPI
|
|
7
8
|
export { createOpenApiDocument, ApiDoc, ApiTags, ApiResponse, zodToJsonSchema } from "./openapi/index.js";
|
|
8
9
|
// DI Container
|
|
9
|
-
export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, mixin } from "./container/index.js";
|
|
10
|
+
export { Container, Injectable, Inject, Optional, InjectionToken, ForwardRef, forwardRef, ModuleRef, ModuleVisibilityError, mixin } from "./container/index.js";
|
|
10
11
|
// Constants
|
|
11
12
|
export { METADATA_KEYS, HttpMethod, ParamType, Scope } from "./constants.js";
|
|
12
13
|
// HTTP Decorators
|
|
13
14
|
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 } from "./http/index.js";
|
|
15
|
+
// Request-scoped context primitive
|
|
16
|
+
export { REQUEST_CONTEXT } from "./http/request-context.js";
|
|
14
17
|
// Services
|
|
15
18
|
export { Logger, LogLevel } from "./services/index.js";
|
|
16
19
|
// Config
|
|
@@ -31,6 +34,8 @@ export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthInd
|
|
|
31
34
|
export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA } from "./throttler/index.js";
|
|
32
35
|
// Module
|
|
33
36
|
export { Global, Module, createModuleRef } from "./module/index.js";
|
|
37
|
+
// Plugin manifest + composer
|
|
38
|
+
export { definePlugin, composePlugins, PluginRegistry, PluginRootModule, PLUGIN_REGISTRY_TOKEN } from "./plugin/plugin.js";
|
|
34
39
|
// Pipeline Decorators
|
|
35
40
|
export { UseMiddleware, UseGuards, UsePipes, UseInterceptors, UseFilters, Catch, SetMetadata, Reflector, APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/index.js";
|
|
36
41
|
// Built-in Pipes
|
package/dist/internal.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { Container } from './container/container';
|
|
2
2
|
export { ModuleRef } from './container/module-ref';
|
|
3
|
+
export { ModuleVisibilityError } from './container/types';
|
|
4
|
+
export type { ModuleScope, ContainerOptions, Diagnostics, } from './container/types';
|
|
3
5
|
export { bindAppProviders } from './pipeline/app-providers';
|
|
4
6
|
export { RouteManager } from './http/route.manager';
|
|
5
7
|
export type { RouteManagerOptions } from './http/route.manager';
|
|
@@ -9,3 +11,7 @@ export { VelaApplication } from './application';
|
|
|
9
11
|
export { MetadataRegistry } from './registry/metadata.registry';
|
|
10
12
|
export { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE, } from './pipeline/tokens';
|
|
11
13
|
export { getModuleMetadata, isModule } from './module/decorators';
|
|
14
|
+
export { bootstrap } from './factory/bootstrap';
|
|
15
|
+
export type { BootstrapOptions, BootstrapResult } from './factory/bootstrap';
|
|
16
|
+
export { definePlugin, composePlugins, PluginRegistry, PluginRootModule, PLUGIN_REGISTRY_TOKEN, } from './plugin/plugin';
|
|
17
|
+
export type { Plugin } from './plugin/plugin';
|
package/dist/internal.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// to the same degree as the public surface (./index).
|
|
4
4
|
export { Container } from "./container/container.js";
|
|
5
5
|
export { ModuleRef } from "./container/module-ref.js";
|
|
6
|
+
export { ModuleVisibilityError } from "./container/types.js";
|
|
6
7
|
export { bindAppProviders } from "./pipeline/app-providers.js";
|
|
7
8
|
export { RouteManager } from "./http/route.manager.js";
|
|
8
9
|
export { ModuleLoader } from "./module/module-loader.js";
|
|
@@ -11,3 +12,8 @@ export { VelaApplication } from "./application.js";
|
|
|
11
12
|
export { MetadataRegistry } from "./registry/metadata.registry.js";
|
|
12
13
|
export { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/tokens.js";
|
|
13
14
|
export { getModuleMetadata, isModule } from "./module/decorators.js";
|
|
15
|
+
// Bootstrap primitive — used by VelaFactory.create, @velajs/testing, and any
|
|
16
|
+
// non-HTTP consumer (CLI tools, custom runtimes).
|
|
17
|
+
export { bootstrap } from "./factory/bootstrap.js";
|
|
18
|
+
// Plugin manifest + composer
|
|
19
|
+
export { definePlugin, composePlugins, PluginRegistry, PluginRootModule, PLUGIN_REGISTRY_TOKEN } from "./plugin/plugin.js";
|
|
@@ -14,8 +14,11 @@ export declare class ModuleLoader {
|
|
|
14
14
|
private consumerMiddlewareDefinitions;
|
|
15
15
|
private appProviderCounter;
|
|
16
16
|
private appProviderTokens;
|
|
17
|
+
private moduleIdByClass;
|
|
18
|
+
private seenModuleIds;
|
|
17
19
|
constructor(container: Container, router: RouteManager);
|
|
18
20
|
load(rootModule: Type): void;
|
|
21
|
+
private getModuleId;
|
|
19
22
|
private processModule;
|
|
20
23
|
private registerProvider;
|
|
21
24
|
private isAppToken;
|
|
@@ -25,4 +28,5 @@ export declare class ModuleLoader {
|
|
|
25
28
|
getAppProviderTokens(token: Token): Token[];
|
|
26
29
|
getConsumerMiddlewareDefinitions(): MiddlewareRouteDefinition[];
|
|
27
30
|
resolveAllInstances(): Promise<unknown[]>;
|
|
31
|
+
private routeError;
|
|
28
32
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Scope } from "../constants.js";
|
|
2
|
-
import { ForwardRef, InjectionToken } from "../container/types.js";
|
|
2
|
+
import { ForwardRef, InjectionToken, ModuleVisibilityError } from "../container/types.js";
|
|
3
3
|
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
|
|
4
4
|
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
5
5
|
import { getOrCreateArray } from "../registry/util.js";
|
|
@@ -18,6 +18,9 @@ function isDynamicModule(value) {
|
|
|
18
18
|
function implementsNestModule(cls) {
|
|
19
19
|
return typeof cls.prototype?.configure === "function";
|
|
20
20
|
}
|
|
21
|
+
function tokenOfProvider(provider) {
|
|
22
|
+
return typeof provider === "function" ? provider : provider.provide;
|
|
23
|
+
}
|
|
21
24
|
export class ModuleLoader {
|
|
22
25
|
container;
|
|
23
26
|
router;
|
|
@@ -51,6 +54,8 @@ export class ModuleLoader {
|
|
|
51
54
|
[]
|
|
52
55
|
]
|
|
53
56
|
]);
|
|
57
|
+
moduleIdByClass = new Map();
|
|
58
|
+
seenModuleIds = new Set();
|
|
54
59
|
constructor(container, router){
|
|
55
60
|
this.container = container;
|
|
56
61
|
this.router = router;
|
|
@@ -61,6 +66,19 @@ export class ModuleLoader {
|
|
|
61
66
|
this.router.registerController(controller);
|
|
62
67
|
}
|
|
63
68
|
}
|
|
69
|
+
getModuleId(moduleClass) {
|
|
70
|
+
const cached = this.moduleIdByClass.get(moduleClass);
|
|
71
|
+
if (cached) return cached;
|
|
72
|
+
const baseName = moduleClass.name || "AnonModule";
|
|
73
|
+
let id = baseName;
|
|
74
|
+
let counter = 0;
|
|
75
|
+
while(this.seenModuleIds.has(id)){
|
|
76
|
+
id = `${baseName}#${++counter}`;
|
|
77
|
+
}
|
|
78
|
+
this.seenModuleIds.add(id);
|
|
79
|
+
this.moduleIdByClass.set(moduleClass, id);
|
|
80
|
+
return id;
|
|
81
|
+
}
|
|
64
82
|
processModule(moduleClassOrDynamic) {
|
|
65
83
|
// Handle dynamic modules ({ module, imports, controllers, providers })
|
|
66
84
|
let moduleClass;
|
|
@@ -103,8 +121,12 @@ export class ModuleLoader {
|
|
|
103
121
|
throw new Error(`Failed to get module metadata for ${moduleClass.name}`);
|
|
104
122
|
}
|
|
105
123
|
this.processingStack.add(moduleClass);
|
|
124
|
+
const moduleId = this.getModuleId(moduleClass);
|
|
106
125
|
try {
|
|
107
126
|
const importedProviders = new Set(this.globalExports);
|
|
127
|
+
// Determine importedModuleIds eagerly (before recursing) so child
|
|
128
|
+
// modules can be referenced in our scope's importedModules set.
|
|
129
|
+
const importedModuleIds = new Set();
|
|
108
130
|
for (const entry of [
|
|
109
131
|
...metadata.imports,
|
|
110
132
|
...extraImports
|
|
@@ -113,6 +135,7 @@ export class ModuleLoader {
|
|
|
113
135
|
const isForwardRef = entry instanceof ForwardRef;
|
|
114
136
|
const importedModule = isForwardRef ? entry.factory() : entry;
|
|
115
137
|
const importedModuleClass = isDynamicModule(importedModule) ? importedModule.module : importedModule;
|
|
138
|
+
importedModuleIds.add(this.getModuleId(importedModuleClass));
|
|
116
139
|
// If this forwardRef-wrapped import is currently being processed, skip it to
|
|
117
140
|
// break the circular chain. Non-forwardRef circular imports still throw.
|
|
118
141
|
if (isForwardRef && this.processingStack.has(importedModuleClass)) {
|
|
@@ -123,19 +146,42 @@ export class ModuleLoader {
|
|
|
123
146
|
importedProviders.add(token);
|
|
124
147
|
}
|
|
125
148
|
}
|
|
126
|
-
// Register metadata providers + dynamic module extra providers
|
|
127
149
|
const allProviders = [
|
|
128
150
|
...metadata.providers,
|
|
129
151
|
...extraProviders
|
|
130
152
|
];
|
|
131
|
-
for (const provider of allProviders){
|
|
132
|
-
this.registerProvider(provider);
|
|
133
|
-
}
|
|
134
|
-
// Collect metadata controllers + dynamic module extra controllers
|
|
135
153
|
const allControllers = [
|
|
136
154
|
...metadata.controllers,
|
|
137
155
|
...extraControllers
|
|
138
156
|
];
|
|
157
|
+
const allExports = [
|
|
158
|
+
...metadata.exports,
|
|
159
|
+
...extraExports
|
|
160
|
+
];
|
|
161
|
+
// Build the ModuleScope BEFORE registering providers so the visibility
|
|
162
|
+
// check sees the local-provider set as we register.
|
|
163
|
+
const localProviders = new Set();
|
|
164
|
+
for (const provider of allProviders){
|
|
165
|
+
const tk = tokenOfProvider(provider);
|
|
166
|
+
if (tk !== undefined) localProviders.add(tk);
|
|
167
|
+
}
|
|
168
|
+
for (const controller of allControllers){
|
|
169
|
+
localProviders.add(controller);
|
|
170
|
+
}
|
|
171
|
+
// The module class itself can be DI-resolved (NestModule.configure path);
|
|
172
|
+
// include it in its own scope.
|
|
173
|
+
localProviders.add(moduleClass);
|
|
174
|
+
const isGlobal = metadata.isGlobal || isDynamicModule(moduleClassOrDynamic) && moduleClassOrDynamic.global === true;
|
|
175
|
+
this.container.registerScope({
|
|
176
|
+
moduleId,
|
|
177
|
+
localProviders,
|
|
178
|
+
importedModules: importedModuleIds,
|
|
179
|
+
exportedTokens: new Set(allExports),
|
|
180
|
+
isGlobal
|
|
181
|
+
});
|
|
182
|
+
for (const provider of allProviders){
|
|
183
|
+
this.registerProvider(provider, moduleId);
|
|
184
|
+
}
|
|
139
185
|
for (const controller of allControllers){
|
|
140
186
|
this.collectedControllers.add(controller);
|
|
141
187
|
}
|
|
@@ -144,13 +190,8 @@ export class ModuleLoader {
|
|
|
144
190
|
MetadataRegistry.propagateControllerComponents(moduleClass, controller);
|
|
145
191
|
}
|
|
146
192
|
this.processedModules.add(moduleClass);
|
|
147
|
-
const allExports = [
|
|
148
|
-
...metadata.exports,
|
|
149
|
-
...extraExports
|
|
150
|
-
];
|
|
151
193
|
const exports = this.buildExportSet(allExports, allProviders, importedProviders);
|
|
152
194
|
this.moduleExportsCache.set(moduleClass, exports);
|
|
153
|
-
const isGlobal = metadata.isGlobal || isDynamicModule(moduleClassOrDynamic) && moduleClassOrDynamic.global === true;
|
|
154
195
|
if (isGlobal) {
|
|
155
196
|
for (const token of exports){
|
|
156
197
|
this.globalExports.add(token);
|
|
@@ -160,9 +201,9 @@ export class ModuleLoader {
|
|
|
160
201
|
// resolved through the container so it can have its own DI dependencies.
|
|
161
202
|
if (implementsNestModule(moduleClass)) {
|
|
162
203
|
if (!this.container.has(moduleClass)) {
|
|
163
|
-
this.container.register(moduleClass);
|
|
204
|
+
this.container.register(moduleClass, moduleId);
|
|
164
205
|
}
|
|
165
|
-
const instance = this.container.resolve(moduleClass);
|
|
206
|
+
const instance = this.container.resolve(moduleClass, moduleId);
|
|
166
207
|
const builder = new MiddlewareBuilder();
|
|
167
208
|
instance.configure(builder);
|
|
168
209
|
this.consumerMiddlewareDefinitions.push(...builder.getDefinitions());
|
|
@@ -172,16 +213,16 @@ export class ModuleLoader {
|
|
|
172
213
|
this.processingStack.delete(moduleClass);
|
|
173
214
|
}
|
|
174
215
|
}
|
|
175
|
-
registerProvider(provider) {
|
|
216
|
+
registerProvider(provider, moduleId) {
|
|
176
217
|
if (typeof provider === "function") {
|
|
177
218
|
if (!this.container.has(provider)) {
|
|
178
|
-
this.container.register(provider);
|
|
219
|
+
this.container.register(provider, moduleId);
|
|
179
220
|
this.registeredProviders.push(provider);
|
|
180
221
|
}
|
|
181
222
|
} else {
|
|
182
223
|
const token = provider.provide;
|
|
183
224
|
if (!token) {
|
|
184
|
-
this.container.register(provider);
|
|
225
|
+
this.container.register(provider, moduleId);
|
|
185
226
|
return;
|
|
186
227
|
}
|
|
187
228
|
if (this.isAppToken(token)) {
|
|
@@ -189,13 +230,16 @@ export class ModuleLoader {
|
|
|
189
230
|
this.container.register({
|
|
190
231
|
...provider,
|
|
191
232
|
provide: syntheticToken
|
|
192
|
-
});
|
|
233
|
+
}, moduleId);
|
|
234
|
+
// Synthetic APP_* tokens are resolved at request time by RouteManager
|
|
235
|
+
// with no requester; mark them global so the visibility check passes.
|
|
236
|
+
this.container.markGlobalToken(syntheticToken);
|
|
193
237
|
this.registeredProviders.push(syntheticToken);
|
|
194
238
|
getOrCreateArray(this.appProviderTokens, token).push(syntheticToken);
|
|
195
239
|
return;
|
|
196
240
|
}
|
|
197
241
|
if (!this.container.has(token)) {
|
|
198
|
-
this.container.register(provider);
|
|
242
|
+
this.container.register(provider, moduleId);
|
|
199
243
|
this.registeredProviders.push(token);
|
|
200
244
|
}
|
|
201
245
|
}
|
|
@@ -207,7 +251,7 @@ export class ModuleLoader {
|
|
|
207
251
|
const exportSet = new Set();
|
|
208
252
|
const providerTokens = new Set();
|
|
209
253
|
for (const p of providers){
|
|
210
|
-
const token =
|
|
254
|
+
const token = tokenOfProvider(p);
|
|
211
255
|
if (token !== undefined) providerTokens.add(token);
|
|
212
256
|
}
|
|
213
257
|
for (const exported of exports){
|
|
@@ -250,8 +294,11 @@ export class ModuleLoader {
|
|
|
250
294
|
}
|
|
251
295
|
const instance = await this.container.resolveAsync(token);
|
|
252
296
|
instanceSet.add(instance);
|
|
253
|
-
} catch
|
|
254
|
-
|
|
297
|
+
} catch (err) {
|
|
298
|
+
// Module visibility errors must always propagate — they indicate a
|
|
299
|
+
// genuine wiring bug, not something to swallow as a discovery skip.
|
|
300
|
+
if (err instanceof ModuleVisibilityError) throw err;
|
|
301
|
+
this.routeError(err, `resolve provider`);
|
|
255
302
|
}
|
|
256
303
|
}
|
|
257
304
|
for (const controller of this.collectedControllers){
|
|
@@ -261,12 +308,22 @@ export class ModuleLoader {
|
|
|
261
308
|
}
|
|
262
309
|
const instance = await this.container.resolveAsync(controller);
|
|
263
310
|
instanceSet.add(instance);
|
|
264
|
-
} catch
|
|
265
|
-
|
|
311
|
+
} catch (err) {
|
|
312
|
+
if (err instanceof ModuleVisibilityError) throw err;
|
|
313
|
+
this.routeError(err, `resolve controller ${controller.name}`);
|
|
266
314
|
}
|
|
267
315
|
}
|
|
268
316
|
return [
|
|
269
317
|
...instanceSet
|
|
270
318
|
];
|
|
271
319
|
}
|
|
320
|
+
routeError(err, context) {
|
|
321
|
+
const mode = this.container.getDiagnostics();
|
|
322
|
+
if (mode === "silent") return;
|
|
323
|
+
if (mode === "throw") {
|
|
324
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
325
|
+
}
|
|
326
|
+
// 'log'
|
|
327
|
+
console.warn(`[vela] ${context} failed:`, err);
|
|
328
|
+
}
|
|
272
329
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { InjectionToken } from '../container/types';
|
|
2
|
+
import type { Type } from '../container/types';
|
|
3
|
+
import type { DynamicModule } from '../registry/types';
|
|
4
|
+
export interface Plugin {
|
|
5
|
+
id: string;
|
|
6
|
+
version: string;
|
|
7
|
+
module: Type | DynamicModule;
|
|
8
|
+
dependsOn?: string[];
|
|
9
|
+
metadata?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Freeze and validate a plugin manifest. Returns the same object so it can
|
|
13
|
+
* be used in `composePlugins([definePlugin(...), ...])`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function definePlugin(plugin: Plugin): Plugin;
|
|
16
|
+
export declare class PluginRegistry {
|
|
17
|
+
private byId;
|
|
18
|
+
constructor(plugins: readonly Plugin[]);
|
|
19
|
+
list(): Plugin[];
|
|
20
|
+
get(id: string): Plugin | undefined;
|
|
21
|
+
dependents(id: string): Plugin[];
|
|
22
|
+
}
|
|
23
|
+
export declare const PLUGIN_REGISTRY_TOKEN: InjectionToken<PluginRegistry>;
|
|
24
|
+
export declare class PluginRootModule {
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Compose a list of plugins into a single dynamic module that imports each
|
|
28
|
+
* plugin's module in dependency order, registers a `PluginRegistry`, and
|
|
29
|
+
* exposes it globally so any plugin can introspect peers.
|
|
30
|
+
*/
|
|
31
|
+
export declare function composePlugins(plugins: readonly Plugin[]): DynamicModule;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { InjectionToken } from "../container/types.js";
|
|
8
|
+
import { Module } from "../module/decorators.js";
|
|
9
|
+
/**
|
|
10
|
+
* Freeze and validate a plugin manifest. Returns the same object so it can
|
|
11
|
+
* be used in `composePlugins([definePlugin(...), ...])`.
|
|
12
|
+
*/ export function definePlugin(plugin) {
|
|
13
|
+
if (!plugin.id) throw new Error('Plugin requires an id');
|
|
14
|
+
if (!plugin.version) throw new Error('Plugin requires a version');
|
|
15
|
+
if (!plugin.module) throw new Error('Plugin requires a module');
|
|
16
|
+
return Object.freeze({
|
|
17
|
+
...plugin
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export class PluginRegistry {
|
|
21
|
+
byId;
|
|
22
|
+
constructor(plugins){
|
|
23
|
+
this.byId = new Map();
|
|
24
|
+
for (const p of plugins){
|
|
25
|
+
if (this.byId.has(p.id)) {
|
|
26
|
+
throw new Error(`Duplicate plugin id: '${p.id}'`);
|
|
27
|
+
}
|
|
28
|
+
this.byId.set(p.id, p);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
list() {
|
|
32
|
+
return [
|
|
33
|
+
...this.byId.values()
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
get(id) {
|
|
37
|
+
return this.byId.get(id);
|
|
38
|
+
}
|
|
39
|
+
dependents(id) {
|
|
40
|
+
return this.list().filter((p)=>p.dependsOn?.includes(id) ?? false);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export const PLUGIN_REGISTRY_TOKEN = new InjectionToken('PLUGIN_REGISTRY');
|
|
44
|
+
export class PluginRootModule {
|
|
45
|
+
}
|
|
46
|
+
PluginRootModule = _ts_decorate([
|
|
47
|
+
Module({})
|
|
48
|
+
], PluginRootModule);
|
|
49
|
+
/**
|
|
50
|
+
* Topologically sort plugins so each plugin's dependencies appear first.
|
|
51
|
+
* Throws on missing dependencies and cycles.
|
|
52
|
+
*/ function topologicalSort(plugins) {
|
|
53
|
+
const byId = new Map();
|
|
54
|
+
for (const p of plugins){
|
|
55
|
+
if (byId.has(p.id)) {
|
|
56
|
+
throw new Error(`Duplicate plugin id: '${p.id}'`);
|
|
57
|
+
}
|
|
58
|
+
byId.set(p.id, p);
|
|
59
|
+
}
|
|
60
|
+
const result = [];
|
|
61
|
+
const visiting = new Set();
|
|
62
|
+
const visited = new Set();
|
|
63
|
+
const visit = (p, path)=>{
|
|
64
|
+
if (visited.has(p.id)) return;
|
|
65
|
+
if (visiting.has(p.id)) {
|
|
66
|
+
const cycle = [
|
|
67
|
+
...path,
|
|
68
|
+
p.id
|
|
69
|
+
].join(' -> ');
|
|
70
|
+
throw new Error(`Plugin cycle detected: ${cycle}`);
|
|
71
|
+
}
|
|
72
|
+
visiting.add(p.id);
|
|
73
|
+
for (const depId of p.dependsOn ?? []){
|
|
74
|
+
const dep = byId.get(depId);
|
|
75
|
+
if (!dep) {
|
|
76
|
+
throw new Error(`Plugin '${p.id}' depends on missing plugin '${depId}'`);
|
|
77
|
+
}
|
|
78
|
+
visit(dep, [
|
|
79
|
+
...path,
|
|
80
|
+
p.id
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
visiting.delete(p.id);
|
|
84
|
+
visited.add(p.id);
|
|
85
|
+
result.push(p);
|
|
86
|
+
};
|
|
87
|
+
for (const p of plugins)visit(p, []);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Compose a list of plugins into a single dynamic module that imports each
|
|
92
|
+
* plugin's module in dependency order, registers a `PluginRegistry`, and
|
|
93
|
+
* exposes it globally so any plugin can introspect peers.
|
|
94
|
+
*/ export function composePlugins(plugins) {
|
|
95
|
+
const sorted = topologicalSort(plugins);
|
|
96
|
+
const registry = new PluginRegistry(sorted);
|
|
97
|
+
return {
|
|
98
|
+
module: PluginRootModule,
|
|
99
|
+
imports: sorted.map((p)=>p.module),
|
|
100
|
+
providers: [
|
|
101
|
+
{
|
|
102
|
+
provide: PLUGIN_REGISTRY_TOKEN,
|
|
103
|
+
useValue: registry
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
exports: [
|
|
107
|
+
PLUGIN_REGISTRY_TOKEN
|
|
108
|
+
],
|
|
109
|
+
global: true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -33,7 +33,12 @@ export class ScheduleRegistry {
|
|
|
33
33
|
let instance;
|
|
34
34
|
try {
|
|
35
35
|
instance = this.container.resolve(token);
|
|
36
|
-
} catch
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const mode = this.container.getDiagnostics();
|
|
38
|
+
if (mode === 'throw') throw err;
|
|
39
|
+
if (mode === 'log') {
|
|
40
|
+
console.warn(`[vela] schedule discovery: cannot resolve ${token.name || String(token)}:`, err);
|
|
41
|
+
}
|
|
37
42
|
continue;
|
|
38
43
|
}
|
|
39
44
|
if (cronMeta) {
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { Container } from '../container/container';
|
|
1
2
|
import type { OnApplicationBootstrap, OnModuleDestroy } from '../lifecycle/index';
|
|
2
3
|
import { ScheduleRegistry } from '../schedule/schedule.registry';
|
|
3
4
|
export declare class ScheduleExecutor implements OnApplicationBootstrap, OnModuleDestroy {
|
|
4
5
|
private registry;
|
|
6
|
+
private container;
|
|
5
7
|
private intervalTimers;
|
|
6
8
|
private cronTimers;
|
|
7
9
|
private cronMatcherCache;
|
|
8
10
|
private lastCronMinute;
|
|
9
11
|
private running;
|
|
10
|
-
constructor(registry: ScheduleRegistry);
|
|
12
|
+
constructor(registry: ScheduleRegistry, container: Container);
|
|
11
13
|
onApplicationBootstrap(): void;
|
|
12
14
|
private scheduleInterval;
|
|
13
15
|
private scheduleCron;
|