@velajs/vela 1.10.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 +102 -0
- package/dist/application.d.ts +8 -0
- package/dist/application.js +20 -0
- package/dist/container/container.d.ts +25 -0
- package/dist/container/container.js +67 -11
- package/dist/container/disposable.js +1 -1
- 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 +10 -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 +28 -19
- package/dist/i18n/i18n.module.js +4 -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/define-module.d.ts +105 -0
- package/dist/module/define-module.js +227 -0
- package/dist/module/index.d.ts +3 -1
- package/dist/module/index.js +3 -1
- 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/registry/metadata.registry.d.ts +9 -3
- package/dist/registry/metadata.registry.js +55 -25
- 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/seeder.module.d.ts +8 -10
- package/dist/seeder/seeder.module.js +16 -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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.11.0 (2026-07-04)
|
|
4
|
+
|
|
5
|
+
The module-model release: one blessed authoring path plus public kernel
|
|
6
|
+
extension points, so feature modules (websocket, storage, queue, …) are built
|
|
7
|
+
entirely on the public API. See `MODULE_AUTHORING.md` for the author contract.
|
|
8
|
+
Contains deliberate breaking changes (no deprecation shims); coordinated
|
|
9
|
+
releases of `@velajs/{storage,better-auth,testing,crud,cloudflare}` accompany
|
|
10
|
+
this version.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`defineModule`** — the single module-authoring engine: generates `forRoot`
|
|
15
|
+
AND `forRootAsync` (typed `inject` inference), derives deterministic
|
|
16
|
+
`stableHash` keys (`key(options)` override + explicit `key` passthrough),
|
|
17
|
+
and accepts contributions (providers/controllers/imports/exports) **as
|
|
18
|
+
functions of the options** plus a standardized `global:` component slot.
|
|
19
|
+
`ConfigurableModuleBuilder` is now a thin adapter over it (unchanged API).
|
|
20
|
+
- **Authoring primitives**: `lazyProvider` (memoized deferred thunk — replaces
|
|
21
|
+
the hand-rolled `(...deps)=>()=>fn(...deps)` closures), `provideGlobal`
|
|
22
|
+
(the one `APP_*` wiring idiom), `sideEffectModule` (first-class
|
|
23
|
+
contribution-only modules; supported form of the i18n empty-marker trick),
|
|
24
|
+
`moduleToken`, `moduleKey`.
|
|
25
|
+
- **`DiscoveryService` + `createDiscoverableDecorator`** — public
|
|
26
|
+
decorator-driven discovery (`providersWithMeta`, `methodsWithMeta`,
|
|
27
|
+
`getProviders`) backed by a reverse metadata index inside
|
|
28
|
+
`MetadataRegistry`; honors container diagnostics in one place;
|
|
29
|
+
request-scoped providers are surfaced but not materialized (opt in with
|
|
30
|
+
`includeRequestScoped`). The event-emitter, schedule, and websocket
|
|
31
|
+
bootstrap scans now all run through it.
|
|
32
|
+
- **Open entrypoint registry** — `registerEntrypointKind({ kind, metaKey,
|
|
33
|
+
level })`, the `ContributesEntrypoints` interface, and per-application
|
|
34
|
+
`app.entrypoints` (`ofKind`/`kinds`/`all`), built at the end of
|
|
35
|
+
`callOnApplicationBootstrap()` so slim bootstrap paths (Cloudflare Durable
|
|
36
|
+
Objects) get it too. Transports query entrypoints instead of module
|
|
37
|
+
internals; a new kind (queue, cron, CLI) needs **zero core changes**.
|
|
38
|
+
- **`RouteContributor`** — public metadata-claimed route generation
|
|
39
|
+
(`registerRouteContributor`), consulted after explicit routes and during
|
|
40
|
+
OpenAPI generation with verb-level merge. Replaces the internal CrudBridge.
|
|
41
|
+
- **`RuntimeAdapter`** — `VelaFactory.create(module, { adapters: [...] })`
|
|
42
|
+
with `requestMiddleware` (prepended to the global chain), `onBootstrap`
|
|
43
|
+
(after lifecycle + entrypoints, before routes) and `onRoutesBuilt` hooks.
|
|
44
|
+
- **`PipelineRunner`** — the shared guard → pipe → interceptor execution core
|
|
45
|
+
used by HTTP and WebSocket dispatch (and any custom dispatcher);
|
|
46
|
+
configurable args/guards order, transport-specific guard-rejection error.
|
|
47
|
+
- **`Container.replaceProvider(provider, { buckets })`** — supported
|
|
48
|
+
force-replace across module buckets (what test harnesses need).
|
|
49
|
+
- **`buildEntrypointExecutionContext(kind, class, handler, payload)`** — the
|
|
50
|
+
entrypoint sibling of the HTTP/WS execution contexts, so guards/
|
|
51
|
+
interceptors/filters written against `getClass()`/`getHandler()`/`getType()`
|
|
52
|
+
run unchanged around queue batches, scheduled ticks, and custom kinds
|
|
53
|
+
(`EntrypointExecutionContext.getPayload()`). `@velajs/cloudflare` dispatches
|
|
54
|
+
queue/scheduled handlers through `PipelineRunner` with consumer-scoped
|
|
55
|
+
components (HTTP-global components deliberately do not apply; unclaimed
|
|
56
|
+
errors rethrow to preserve platform retry semantics).
|
|
57
|
+
- **`runInEntrypointScope(container, fn)`** — the non-HTTP dispatch scope
|
|
58
|
+
primitive: runs one unit of work (queue batch, scheduled tick, RPC call) in
|
|
59
|
+
a fresh request-scoped child with LIFO disposal — the per-request-child
|
|
60
|
+
equivalent for entrypoint dispatchers. `@velajs/cloudflare`'s queue and
|
|
61
|
+
scheduled handlers run on it (request-scoped consumer deps rebuild per
|
|
62
|
+
batch/tick instead of capturing boot instances).
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- **Factory dependency visibility**: `useFactory`/`forRootAsync` `inject`
|
|
67
|
+
deps now resolve from the declaring module's scope FIRST (imports and
|
|
68
|
+
exports are honored), with the legacy no-requester lookup kept as fallback.
|
|
69
|
+
- **All in-core configurable modules are on the one engine**: `CorsModule` and
|
|
70
|
+
`SeederModule` rebuilt on `defineModule` (Cors gains `forRootAsync`; both
|
|
71
|
+
keep their token/key identities), `ScheduleModule`/`ScheduleNodeModule`
|
|
72
|
+
normalized to zero-config `@Module` bags with parity `forRoot()` sugar.
|
|
73
|
+
Builder-based modules (Config/Cache/I18n/Throttler/Http) already run on it
|
|
74
|
+
through the `ConfigurableModuleBuilder` adapter.
|
|
75
|
+
- **Unhandled handler errors are logged**: an error no exception filter claims
|
|
76
|
+
still maps to the generic 500 response, but the cause now lands in the logs
|
|
77
|
+
(`console.error`, gated on container diagnostics ≠ `silent`) — closing the
|
|
78
|
+
silent-500 gap.
|
|
79
|
+
- **`WebSocketModule`** rebuilt on `defineModule`: registry → driver → server
|
|
80
|
+
construction moved into chained provider factories (single shared registry
|
|
81
|
+
preserved; everything materializes at bootstrap), deterministic key
|
|
82
|
+
`ws#<sync-kind>` — **two identical `forRoot()` calls now dedup**
|
|
83
|
+
(HMR-idempotent; pass explicit `key` for exotic multi-instance),
|
|
84
|
+
`forRootAsync` available. `WsDispatcher` contributes `'websocket'`
|
|
85
|
+
entrypoints; `registerWebSocketGateways` consumes
|
|
86
|
+
`app.entrypoints.ofKind('websocket')`.
|
|
87
|
+
|
|
88
|
+
### Breaking
|
|
89
|
+
|
|
90
|
+
- **`WS_MODULE_OPTIONS`** is now a typed `InjectionToken` (was the raw string
|
|
91
|
+
`'vela:ws-module-options'`).
|
|
92
|
+
- **`ComponentManager`** is stateless: `init()` and the process-global
|
|
93
|
+
container are gone; `resolve*` methods require an explicit container;
|
|
94
|
+
`getComponents` replaced by `getScopedComponents` (controller + handler
|
|
95
|
+
only — app-wide components have one source: `RouteManager`).
|
|
96
|
+
`registerGlobal`/`MetadataRegistry.getGlobal` (a dead tier with no readers
|
|
97
|
+
on the request path) are removed; `MetadataRegistry.clear()` is now a
|
|
98
|
+
no-op.
|
|
99
|
+
- **CrudBridge removed** (`registerCrudBridge`/`getCrudBridge` and the
|
|
100
|
+
`/internal` exports): use `registerRouteContributor`. `@velajs/crud`
|
|
101
|
+
migrates in its coordinated release.
|
|
102
|
+
- `forRootAsync` structural fields (non-async keys passed alongside
|
|
103
|
+
`useFactory`) now merge under the resolved options.
|
|
104
|
+
|
|
3
105
|
## 1.10.0 (2026-07-01)
|
|
4
106
|
|
|
5
107
|
### Added
|
package/dist/application.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import type { Container } from './container/container';
|
|
3
3
|
import type { Token } from './container/types';
|
|
4
|
+
import { EntrypointRegistry } from './entrypoint/entrypoint.registry';
|
|
4
5
|
import type { RouteManager } from './http/route.manager';
|
|
5
6
|
import type { MountOpenApiOptions } from './openapi/types';
|
|
6
7
|
import type { FilterType, GuardType, InterceptorType, PipeType } from './registry/types';
|
|
@@ -9,6 +10,7 @@ export declare class VelaApplication {
|
|
|
9
10
|
private readonly routeManager;
|
|
10
11
|
private instances;
|
|
11
12
|
private honoApp;
|
|
13
|
+
private entrypointRegistry;
|
|
12
14
|
private disposed;
|
|
13
15
|
constructor(container: Container, routeManager: RouteManager);
|
|
14
16
|
/** Pre-build routes (handles async CRUD imports). Called by VelaFactory. */
|
|
@@ -44,6 +46,12 @@ export declare class VelaApplication {
|
|
|
44
46
|
mountOpenApi(options: MountOpenApiOptions): this;
|
|
45
47
|
callOnModuleInit(): Promise<void>;
|
|
46
48
|
callOnApplicationBootstrap(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Every entrypoint contributed by the module graph, grouped by kind —
|
|
51
|
+
* what runtime adapters/transports query instead of re-scanning providers:
|
|
52
|
+
* `app.entrypoints.ofKind('websocket')`.
|
|
53
|
+
*/
|
|
54
|
+
get entrypoints(): EntrypointRegistry;
|
|
47
55
|
close(signal?: string): Promise<void>;
|
|
48
56
|
/**
|
|
49
57
|
* Full teardown: run shutdown lifecycle hooks ({@link close}) then dispose
|
package/dist/application.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DiscoveryService } from "./discovery/discovery.service.js";
|
|
2
|
+
import { EntrypointRegistry } from "./entrypoint/entrypoint.registry.js";
|
|
1
3
|
import { hasBeforeApplicationShutdown, hasOnApplicationBootstrap, hasOnApplicationShutdown, hasOnModuleDestroy, hasOnModuleInit } from "./lifecycle/index.js";
|
|
2
4
|
import { renderScalarUi } from "./openapi/scalar-ui.js";
|
|
3
5
|
import { renderSwaggerUi } from "./openapi/swagger-ui.js";
|
|
@@ -7,6 +9,7 @@ export class VelaApplication {
|
|
|
7
9
|
routeManager;
|
|
8
10
|
instances = [];
|
|
9
11
|
honoApp = null;
|
|
12
|
+
entrypointRegistry = null;
|
|
10
13
|
disposed = false;
|
|
11
14
|
constructor(container, routeManager){
|
|
12
15
|
this.container = container;
|
|
@@ -137,6 +140,23 @@ export class VelaApplication {
|
|
|
137
140
|
await instance.onApplicationBootstrap();
|
|
138
141
|
}
|
|
139
142
|
}
|
|
143
|
+
// Build the per-app entrypoint registry AFTER the hooks: dispatchers that
|
|
144
|
+
// implement ContributesEntrypoints (WsDispatcher) finish their own
|
|
145
|
+
// discovery inside onApplicationBootstrap. Built here — not in
|
|
146
|
+
// VelaFactory/initRoutes — so slim bootstrap paths that never build HTTP
|
|
147
|
+
// routes (the Cloudflare Durable Object) still get `app.entrypoints`.
|
|
148
|
+
const discovery = this.container.has(DiscoveryService) ? this.container.resolve(DiscoveryService) : new DiscoveryService(this.container);
|
|
149
|
+
this.entrypointRegistry = await EntrypointRegistry.build(discovery, this.instances);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Every entrypoint contributed by the module graph, grouped by kind —
|
|
153
|
+
* what runtime adapters/transports query instead of re-scanning providers:
|
|
154
|
+
* `app.entrypoints.ofKind('websocket')`.
|
|
155
|
+
*/ get entrypoints() {
|
|
156
|
+
if (!this.entrypointRegistry) {
|
|
157
|
+
throw new Error('Entrypoints are not built yet — they are assembled at the end of ' + 'callOnApplicationBootstrap(). Finish bootstrapping before querying them.');
|
|
158
|
+
}
|
|
159
|
+
return this.entrypointRegistry;
|
|
140
160
|
}
|
|
141
161
|
async close(signal) {
|
|
142
162
|
const reversed = [
|
|
@@ -52,6 +52,21 @@ export declare class Container {
|
|
|
52
52
|
has(token: Token): boolean;
|
|
53
53
|
/** True if the specified module's bucket has a registration for `token`. */
|
|
54
54
|
hasInScope(token: Token, moduleId: string): boolean;
|
|
55
|
+
/** Module buckets that hold a registration for `token` (registration order). */
|
|
56
|
+
getOwnerModuleIds(token: Token): string[];
|
|
57
|
+
/**
|
|
58
|
+
* Force-replace a provider across module scopes — the supported form of the
|
|
59
|
+
* override loop test harnesses need (module-scoped resolution consults the
|
|
60
|
+
* module's own bucket first, so a root-only override would never win there).
|
|
61
|
+
*
|
|
62
|
+
* - `'all-existing'` (default): every non-root bucket that already holds the
|
|
63
|
+
* token, plus `__root__` (so framework-internal lookups see it too).
|
|
64
|
+
* - `'root'`: only the `__root__` bucket.
|
|
65
|
+
* - `string[]`: exactly these bucket ids.
|
|
66
|
+
*/
|
|
67
|
+
replaceProvider<T>(provider: Type<T> | ProviderOptions<T>, options?: {
|
|
68
|
+
buckets?: 'all-existing' | 'root' | string[];
|
|
69
|
+
}): this;
|
|
55
70
|
getProviderScope(token: Token): Scope | undefined;
|
|
56
71
|
getTokens(): Token[];
|
|
57
72
|
/**
|
|
@@ -95,6 +110,16 @@ export declare class Container {
|
|
|
95
110
|
dispose(): Promise<void>;
|
|
96
111
|
private resolveRegistration;
|
|
97
112
|
private resolveClass;
|
|
113
|
+
/**
|
|
114
|
+
* Resolve one factory `inject` dependency. Since 1.11 the declaring module's
|
|
115
|
+
* scope is consulted FIRST (proper visibility semantics — a `forRootAsync`
|
|
116
|
+
* factory can inject providers reachable through its module's imports); on
|
|
117
|
+
* any failure the legacy no-requester lookup (root bucket, then first
|
|
118
|
+
* exporter) is kept as fallback, so every previously-resolving graph still
|
|
119
|
+
* resolves.
|
|
120
|
+
*/
|
|
121
|
+
private resolveFactoryDependency;
|
|
122
|
+
private resolveFactoryDependencyAsync;
|
|
98
123
|
private resolveFactory;
|
|
99
124
|
resolveAsync<T>(token: Token<T>, requestingModuleId?: string): Promise<T>;
|
|
100
125
|
private createLazyProxy;
|
|
@@ -288,6 +288,39 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
288
288
|
/** True if the specified module's bucket has a registration for `token`. */ hasInScope(token, moduleId) {
|
|
289
289
|
return this.providers.get(moduleId)?.has(token) ?? false;
|
|
290
290
|
}
|
|
291
|
+
/** Module buckets that hold a registration for `token` (registration order). */ getOwnerModuleIds(token) {
|
|
292
|
+
return [
|
|
293
|
+
...this.exporterIndex.get(token) ?? []
|
|
294
|
+
];
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Force-replace a provider across module scopes — the supported form of the
|
|
298
|
+
* override loop test harnesses need (module-scoped resolution consults the
|
|
299
|
+
* module's own bucket first, so a root-only override would never win there).
|
|
300
|
+
*
|
|
301
|
+
* - `'all-existing'` (default): every non-root bucket that already holds the
|
|
302
|
+
* token, plus `__root__` (so framework-internal lookups see it too).
|
|
303
|
+
* - `'root'`: only the `__root__` bucket.
|
|
304
|
+
* - `string[]`: exactly these bucket ids.
|
|
305
|
+
*/ replaceProvider(provider, options = {}) {
|
|
306
|
+
const buckets = options.buckets ?? 'all-existing';
|
|
307
|
+
if (buckets === 'root') {
|
|
308
|
+
return this.register(provider);
|
|
309
|
+
}
|
|
310
|
+
if (Array.isArray(buckets)) {
|
|
311
|
+
for (const moduleId of buckets)this.register(provider, moduleId);
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
const token = typeof provider === 'function' ? provider : provider.provide;
|
|
315
|
+
if (!token) {
|
|
316
|
+
throw new Error('replaceProvider requires a token (`provide`)');
|
|
317
|
+
}
|
|
318
|
+
for (const [moduleId, bucket] of this.providers){
|
|
319
|
+
if (moduleId === ROOT_MODULE_ID) continue;
|
|
320
|
+
if (bucket.has(token)) this.register(provider, moduleId);
|
|
321
|
+
}
|
|
322
|
+
return this.register(provider);
|
|
323
|
+
}
|
|
291
324
|
getProviderScope(token) {
|
|
292
325
|
const exporters = this.exporterIndex.get(token);
|
|
293
326
|
if (!exporters) return undefined;
|
|
@@ -516,10 +549,9 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
516
549
|
try {
|
|
517
550
|
let instance;
|
|
518
551
|
if (registration.useFactory) {
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
// declaring module's POV — same escape-hatch shape as ModuleRef.
|
|
552
|
+
// Factory inject deps resolve from the declaring module's scope first
|
|
553
|
+
// (visibility-correct for forRootAsync), with the legacy no-requester
|
|
554
|
+
// lookup kept as fallback — see resolveFactoryDependency.
|
|
523
555
|
instance = this.resolveFactory(registration);
|
|
524
556
|
} else if (registration.useClass) {
|
|
525
557
|
instance = this.resolveClass(registration.useClass, registration.declaringModuleId);
|
|
@@ -578,16 +610,40 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
578
610
|
});
|
|
579
611
|
return new target(...dependencies);
|
|
580
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* Resolve one factory `inject` dependency. Since 1.11 the declaring module's
|
|
615
|
+
* scope is consulted FIRST (proper visibility semantics — a `forRootAsync`
|
|
616
|
+
* factory can inject providers reachable through its module's imports); on
|
|
617
|
+
* any failure the legacy no-requester lookup (root bucket, then first
|
|
618
|
+
* exporter) is kept as fallback, so every previously-resolving graph still
|
|
619
|
+
* resolves.
|
|
620
|
+
*/ resolveFactoryDependency(token, declaringModuleId) {
|
|
621
|
+
if (declaringModuleId !== ROOT_MODULE_ID && this.scopes.has(declaringModuleId)) {
|
|
622
|
+
try {
|
|
623
|
+
return this.resolve(token, declaringModuleId);
|
|
624
|
+
} catch {
|
|
625
|
+
// Fall through to the legacy escape hatch below.
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return this.resolve(token);
|
|
629
|
+
}
|
|
630
|
+
async resolveFactoryDependencyAsync(token, declaringModuleId) {
|
|
631
|
+
if (declaringModuleId !== ROOT_MODULE_ID && this.scopes.has(declaringModuleId)) {
|
|
632
|
+
try {
|
|
633
|
+
return await this.resolveAsync(token, declaringModuleId);
|
|
634
|
+
} catch {
|
|
635
|
+
// Fall through to the legacy escape hatch below.
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return this.resolveAsync(token);
|
|
639
|
+
}
|
|
581
640
|
resolveFactory(registration) {
|
|
582
641
|
if (!registration.useFactory) {
|
|
583
642
|
throw new Error('Factory function is missing');
|
|
584
643
|
}
|
|
585
|
-
// Factory inject deps resolve without a requester — Vela has no
|
|
586
|
-
// `forRootAsync({ imports })` surface to track which module the factory
|
|
587
|
-
// resolves from, so it uses the same escape-hatch shape as ModuleRef.
|
|
588
644
|
const dependencies = (registration.inject || []).map((token)=>{
|
|
589
645
|
const resolved = token instanceof ForwardRef ? token.factory() : token;
|
|
590
|
-
return this.
|
|
646
|
+
return this.resolveFactoryDependency(resolved, registration.declaringModuleId);
|
|
591
647
|
});
|
|
592
648
|
const result = registration.useFactory(...dependencies);
|
|
593
649
|
if (result instanceof Promise) {
|
|
@@ -618,11 +674,11 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
618
674
|
if (scope === Scope.SINGLETON && registration.instance !== undefined) {
|
|
619
675
|
return registration.instance;
|
|
620
676
|
}
|
|
621
|
-
//
|
|
622
|
-
// sync resolveFactory
|
|
677
|
+
// Module scope first, legacy no-requester fallback — same policy as the
|
|
678
|
+
// sync resolveFactory path.
|
|
623
679
|
const dependencies = await Promise.all((registration.inject || []).map((t)=>{
|
|
624
680
|
const resolved = t instanceof ForwardRef ? t.factory() : t;
|
|
625
|
-
return this.
|
|
681
|
+
return this.resolveFactoryDependencyAsync(resolved, registration.declaringModuleId);
|
|
626
682
|
}));
|
|
627
683
|
const instance = await registration.useFactory(...dependencies);
|
|
628
684
|
if (scope === Scope.SINGLETON) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Edge-safe disposal contract
|
|
1
|
+
// Edge-safe disposal contract. Precedence:
|
|
2
2
|
// Symbol.asyncDispose > Symbol.dispose > .dispose()
|
|
3
3
|
// Symbol.asyncDispose/Symbol.dispose are resolved at runtime (they are not in
|
|
4
4
|
// the ES2022 lib the project targets), so nothing here depends on the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { DynamicModule } from '../module/types';
|
|
2
1
|
import type { CorsOptions } from './cors.types';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
declare const ConfigurableModuleClass: import("..").ConfigurableModuleClassType<CorsOptions, "forRoot", "create", {
|
|
3
|
+
isGlobal?: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
export declare class CorsModule extends ConfigurableModuleClass {
|
|
7
6
|
}
|
|
7
|
+
export {};
|
package/dist/cors/cors.module.js
CHANGED
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
import { cors } from "hono/cors";
|
|
2
|
-
import {
|
|
2
|
+
import { defineModule } from "../module/define-module.js";
|
|
3
3
|
import { APP_MIDDLEWARE } from "../pipeline/tokens.js";
|
|
4
4
|
import { CORS_OPTIONS } from "./cors.tokens.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
function buildCorsMiddleware(options) {
|
|
6
|
+
const corsMiddleware = cors({
|
|
7
|
+
origin: options.origin ?? '*',
|
|
8
|
+
allowMethods: options.allowMethods,
|
|
9
|
+
allowHeaders: options.allowHeaders,
|
|
10
|
+
exposeHeaders: options.exposeHeaders,
|
|
11
|
+
credentials: options.credentials,
|
|
12
|
+
maxAge: options.maxAge
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
use: (c, next)=>corsMiddleware(c, next)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const { ConfigurableModuleClass } = defineModule({
|
|
19
|
+
name: 'Cors',
|
|
20
|
+
optionsToken: CORS_OPTIONS,
|
|
21
|
+
setup: ({ OPTIONS })=>({
|
|
21
22
|
providers: [
|
|
22
|
-
{
|
|
23
|
-
provide: CORS_OPTIONS,
|
|
24
|
-
useValue: options
|
|
25
|
-
},
|
|
26
23
|
{
|
|
27
24
|
provide: APP_MIDDLEWARE,
|
|
28
|
-
|
|
25
|
+
useFactory: (options)=>buildCorsMiddleware(options),
|
|
26
|
+
inject: [
|
|
27
|
+
OPTIONS
|
|
28
|
+
]
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
31
|
exports: [
|
|
32
|
-
|
|
32
|
+
OPTIONS
|
|
33
33
|
]
|
|
34
|
-
}
|
|
35
|
-
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
export class CorsModule extends ConfigurableModuleClass {
|
|
36
37
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A decorator whose annotated classes/methods are findable through
|
|
3
|
+
* `DiscoveryService` by the decorator itself (no string key at the call site).
|
|
4
|
+
*/
|
|
5
|
+
export interface DiscoverableDecorator<T> {
|
|
6
|
+
(value: T): ClassDecorator & MethodDecorator;
|
|
7
|
+
/** The metadata key backing this decorator — stable across HMR re-evals. */
|
|
8
|
+
KEY: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CreateDiscoverableDecoratorOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Append to a class-level list instead of overwriting a single slot —
|
|
13
|
+
* for stackable method decorators (`@Cron`-style: each application pushes
|
|
14
|
+
* `{ methodName, ...value }` onto the class's list, which
|
|
15
|
+
* `DiscoveryService.methodsWithMeta` flattens back to per-method entries).
|
|
16
|
+
*/
|
|
17
|
+
append?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Mint a discoverable decorator for a module's extension surface:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* export interface QueueConsumerMeta { queue: string }
|
|
24
|
+
* export const QueueConsumer = createDiscoverableDecorator<QueueConsumerMeta>('vela:queue:consumer');
|
|
25
|
+
*
|
|
26
|
+
* @QueueConsumer({ queue: 'emails' })
|
|
27
|
+
* @Injectable()
|
|
28
|
+
* class EmailConsumer { ... }
|
|
29
|
+
*
|
|
30
|
+
* // At bootstrap, anywhere:
|
|
31
|
+
* discovery.providersWithMeta(QueueConsumer) // typed { meta: QueueConsumerMeta }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Unlike `Reflector.createDecorator`, the key is required and caller-chosen:
|
|
35
|
+
* a random key would mint a fresh identity on every HMR re-eval and orphan
|
|
36
|
+
* previously-decorated classes. Namespace it (`'<pkg>:<area>:<thing>'`).
|
|
37
|
+
*/
|
|
38
|
+
export declare function createDiscoverableDecorator<T>(key: string, options?: CreateDiscoverableDecoratorOptions): DiscoverableDecorator<T>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* Mint a discoverable decorator for a module's extension surface:
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* export interface QueueConsumerMeta { queue: string }
|
|
7
|
+
* export const QueueConsumer = createDiscoverableDecorator<QueueConsumerMeta>('vela:queue:consumer');
|
|
8
|
+
*
|
|
9
|
+
* @QueueConsumer({ queue: 'emails' })
|
|
10
|
+
* @Injectable()
|
|
11
|
+
* class EmailConsumer { ... }
|
|
12
|
+
*
|
|
13
|
+
* // At bootstrap, anywhere:
|
|
14
|
+
* discovery.providersWithMeta(QueueConsumer) // typed { meta: QueueConsumerMeta }
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Unlike `Reflector.createDecorator`, the key is required and caller-chosen:
|
|
18
|
+
* a random key would mint a fresh identity on every HMR re-eval and orphan
|
|
19
|
+
* previously-decorated classes. Namespace it (`'<pkg>:<area>:<thing>'`).
|
|
20
|
+
*/ export function createDiscoverableDecorator(key, options = {}) {
|
|
21
|
+
const decorator = (value)=>{
|
|
22
|
+
return (target, propertyKey)=>{
|
|
23
|
+
const ctor = propertyKey !== undefined ? target.constructor : target;
|
|
24
|
+
if (options.append) {
|
|
25
|
+
if (propertyKey !== undefined) {
|
|
26
|
+
MetadataRegistry.appendCustomClassMeta(ctor, key, {
|
|
27
|
+
methodName: propertyKey,
|
|
28
|
+
...value
|
|
29
|
+
});
|
|
30
|
+
} else {
|
|
31
|
+
MetadataRegistry.appendCustomClassMeta(ctor, key, value);
|
|
32
|
+
}
|
|
33
|
+
} else if (propertyKey !== undefined) {
|
|
34
|
+
MetadataRegistry.setCustomHandlerMeta(ctor, propertyKey, key, value);
|
|
35
|
+
} else {
|
|
36
|
+
MetadataRegistry.setCustomClassMeta(ctor, key, value);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
decorator.KEY = key;
|
|
41
|
+
return decorator;
|
|
42
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Scope } from '../constants';
|
|
2
|
+
import { Container } from '../container/container';
|
|
3
|
+
import type { Token, Type } from '../container/types';
|
|
4
|
+
import type { Constructor } from '../registry/types';
|
|
5
|
+
import type { DiscoverableDecorator } from './discoverable.decorator';
|
|
6
|
+
/** A container-registered class provider surfaced by discovery. */
|
|
7
|
+
export interface DiscoveredClass<T = unknown> {
|
|
8
|
+
token: Token;
|
|
9
|
+
metatype: Type<T>;
|
|
10
|
+
/** Module buckets holding this token (registration order; first = primary). */
|
|
11
|
+
moduleIds: string[];
|
|
12
|
+
scope: Scope;
|
|
13
|
+
/**
|
|
14
|
+
* Resolved instance. `undefined` only when the provider is request-scoped
|
|
15
|
+
* and `includeRequestScoped` was not set — request-scoped providers cannot
|
|
16
|
+
* be materialized at bootstrap without fabricating a phantom request.
|
|
17
|
+
*/
|
|
18
|
+
instance: T | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface DiscoveredMethodMeta<M = unknown> {
|
|
21
|
+
class: DiscoveredClass;
|
|
22
|
+
methodName: string | symbol;
|
|
23
|
+
meta: M;
|
|
24
|
+
}
|
|
25
|
+
export interface DiscoveryFilter {
|
|
26
|
+
/** Restrict to providers declared by these module buckets. */
|
|
27
|
+
moduleId?: string | string[];
|
|
28
|
+
/**
|
|
29
|
+
* Resolve request-scoped providers too (constructs an instance outside any
|
|
30
|
+
* request — only for callers that know what they're doing). Default false:
|
|
31
|
+
* request-scoped hits are skipped with a diagnostics warning.
|
|
32
|
+
*/
|
|
33
|
+
includeRequestScoped?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decorator-driven provider discovery — the public replacement for the
|
|
37
|
+
* bootstrap scan every subsystem used to hand-roll (`container.getTokens()` +
|
|
38
|
+
* `MetadataRegistry.getCustomClassMeta` + `resolve` + diagnostics handling).
|
|
39
|
+
*
|
|
40
|
+
* Registered globally by `bootstrap()`, so any provider can inject it:
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* @Injectable()
|
|
44
|
+
* class MyRegistry implements OnApplicationBootstrap {
|
|
45
|
+
* constructor(private readonly discovery: DiscoveryService) {}
|
|
46
|
+
* onApplicationBootstrap() {
|
|
47
|
+
* for (const { instance, meta } of this.discovery.providersWithMeta<MyMeta>(MY_KEY)) {
|
|
48
|
+
* this.register(instance, meta);
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* Discovery is kernel-level, not encapsulation-scoped: module visibility
|
|
55
|
+
* protects modules from *each other*, never from the framework's own
|
|
56
|
+
* dispatchers. Use `DiscoveryFilter.moduleId` for the rare narrowing case.
|
|
57
|
+
*
|
|
58
|
+
* The resolve-failure policy honors the container's diagnostics mode exactly
|
|
59
|
+
* like the legacy loops: `throw` rethrows, `log` warns and skips, `silent`
|
|
60
|
+
* skips — so a broken provider never silently changes discovery semantics.
|
|
61
|
+
*/
|
|
62
|
+
export declare class DiscoveryService {
|
|
63
|
+
private readonly container;
|
|
64
|
+
constructor(container: Container);
|
|
65
|
+
/** Every class-token provider registered in the container. */
|
|
66
|
+
getProviders(filter?: DiscoveryFilter): DiscoveredClass[];
|
|
67
|
+
/**
|
|
68
|
+
* Providers whose class carries class-level metadata under `key` — both
|
|
69
|
+
* class decorators (single value) and method decorators that append
|
|
70
|
+
* class-level lists (`@Cron`, `@OnEvent`, `@SubscribeMessage`).
|
|
71
|
+
*/
|
|
72
|
+
providersWithMeta<M>(key: string | DiscoverableDecorator<M>, filter?: DiscoveryFilter): Array<DiscoveredClass & {
|
|
73
|
+
meta: M;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Handler methods carrying metadata under `key`. Merges the two storage
|
|
77
|
+
* conventions:
|
|
78
|
+
* - class-level appended lists whose items carry `methodName`
|
|
79
|
+
* (`@Cron`/`@OnEvent`-style method decorators), and
|
|
80
|
+
* - true handler-level metadata (`@SetMetadata`/`createDiscoverableDecorator`
|
|
81
|
+
* applied to a method).
|
|
82
|
+
*/
|
|
83
|
+
methodsWithMeta<M>(key: string | DiscoverableDecorator<M>, filter?: DiscoveryFilter): DiscoveredMethodMeta<M>[];
|
|
84
|
+
/**
|
|
85
|
+
* Index-first candidate walk with a legacy full-scan fallback: when the
|
|
86
|
+
* reverse index knows nothing about a key, an older copy of the registry
|
|
87
|
+
* module may have taken the writes (mixed dist/src or package versions in
|
|
88
|
+
* one process) — fall back to scanning container tokens the way the
|
|
89
|
+
* pre-DiscoveryService loops did.
|
|
90
|
+
*/
|
|
91
|
+
private candidatesWithClassMeta;
|
|
92
|
+
private buildEntry;
|
|
93
|
+
}
|
|
94
|
+
export type { Constructor };
|