@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
|
@@ -33,9 +33,40 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
33
33
|
// Container-constructed instances in creation order, for LIFO disposal.
|
|
34
34
|
// useValue providers are never tracked (they return before construction).
|
|
35
35
|
disposables = [];
|
|
36
|
+
// Lazy-module seam (root-owned; children reach it via this.root). A
|
|
37
|
+
// resolution of a deferred registration CLAIMS its module; claimed groups
|
|
38
|
+
// are completed (constructed + hooks replayed) only when the resolution
|
|
39
|
+
// stack has unwound and no resolveAsync cascade is in flight — running the
|
|
40
|
+
// replay mid-construction could force-resolve a class currently on the
|
|
41
|
+
// resolution stack (discovery cascades) and mint a spurious
|
|
42
|
+
// circular-dependency error.
|
|
43
|
+
lazyHook;
|
|
44
|
+
asyncDepth = 0;
|
|
36
45
|
constructor(options = {}){
|
|
37
46
|
this.diagnostics = options.diagnostics ?? 'log';
|
|
38
47
|
}
|
|
48
|
+
/** Install the lazy-module seam (bootstrap-time; root container only). */ setLazyHook(hook) {
|
|
49
|
+
this.root.lazyHook = hook;
|
|
50
|
+
}
|
|
51
|
+
claimLazyModule(declaringModuleId) {
|
|
52
|
+
const hook = this.root.lazyHook;
|
|
53
|
+
if (hook?.isPending(declaringModuleId)) {
|
|
54
|
+
hook.claim(declaringModuleId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
maybeDrainSync() {
|
|
58
|
+
const root = this.root;
|
|
59
|
+
const hook = root.lazyHook;
|
|
60
|
+
if (!hook?.hasClaimed()) return;
|
|
61
|
+
// Never replay hooks while construction is in flight; an async cascade
|
|
62
|
+
// drains (with await) at its own end instead. A running drain picks
|
|
63
|
+
// pending claims up itself — re-entering it is at best a no-op and on
|
|
64
|
+
// the async path a self-deadlock.
|
|
65
|
+
if (this.resolutionStack.size > 0) return;
|
|
66
|
+
if (root.asyncDepth > 0) return;
|
|
67
|
+
if (hook.isDraining()) return;
|
|
68
|
+
hook.drainSync();
|
|
69
|
+
}
|
|
39
70
|
register(provider, declaringModuleId) {
|
|
40
71
|
const moduleId = declaringModuleId ?? ROOT_MODULE_ID;
|
|
41
72
|
if (typeof provider === 'function') {
|
|
@@ -125,7 +156,9 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
125
156
|
resolve(token, requestingModuleId) {
|
|
126
157
|
const registration = this.findRegistration(token, requestingModuleId);
|
|
127
158
|
if (registration) {
|
|
128
|
-
|
|
159
|
+
const instance = this.resolveRegistration(registration, requestingModuleId);
|
|
160
|
+
this.maybeDrainSync();
|
|
161
|
+
return instance;
|
|
129
162
|
}
|
|
130
163
|
// Visibility error fires first when the token exists in some bucket but
|
|
131
164
|
// isn't reachable from the requester's scope — the user wants to know
|
|
@@ -155,7 +188,9 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
155
188
|
}
|
|
156
189
|
resolveAll(token, requestingModuleId) {
|
|
157
190
|
const registrations = this.findAllRegistrations(token, requestingModuleId);
|
|
158
|
-
|
|
191
|
+
const instances = registrations.map((r)=>this.resolveRegistration(r, requestingModuleId));
|
|
192
|
+
this.maybeDrainSync();
|
|
193
|
+
return instances;
|
|
159
194
|
}
|
|
160
195
|
/**
|
|
161
196
|
* Find a single reachable registration for a token, applying the visibility
|
|
@@ -288,6 +323,65 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
288
323
|
/** True if the specified module's bucket has a registration for `token`. */ hasInScope(token, moduleId) {
|
|
289
324
|
return this.providers.get(moduleId)?.has(token) ?? false;
|
|
290
325
|
}
|
|
326
|
+
/** Module buckets that hold a registration for `token` (registration order). */ getOwnerModuleIds(token) {
|
|
327
|
+
return [
|
|
328
|
+
...this.exporterIndex.get(token) ?? []
|
|
329
|
+
];
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Force-replace a provider across module scopes — the supported form of the
|
|
333
|
+
* override loop test harnesses need (module-scoped resolution consults the
|
|
334
|
+
* module's own bucket first, so a root-only override would never win there).
|
|
335
|
+
*
|
|
336
|
+
* - `'all-existing'` (default): every non-root bucket that already holds the
|
|
337
|
+
* token, plus `__root__` (so framework-internal lookups see it too).
|
|
338
|
+
* - `'root'`: only the `__root__` bucket.
|
|
339
|
+
* - `string[]`: exactly these bucket ids.
|
|
340
|
+
*/ replaceProvider(provider, options = {}) {
|
|
341
|
+
const buckets = options.buckets ?? 'all-existing';
|
|
342
|
+
if (buckets === 'root') {
|
|
343
|
+
return this.register(provider);
|
|
344
|
+
}
|
|
345
|
+
if (Array.isArray(buckets)) {
|
|
346
|
+
for (const moduleId of buckets)this.register(provider, moduleId);
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
const token = typeof provider === 'function' ? provider : provider.provide;
|
|
350
|
+
if (!token) {
|
|
351
|
+
throw new Error('replaceProvider requires a token (`provide`)');
|
|
352
|
+
}
|
|
353
|
+
for (const [moduleId, bucket] of this.providers){
|
|
354
|
+
if (moduleId === ROOT_MODULE_ID) continue;
|
|
355
|
+
if (bucket.has(token)) this.register(provider, moduleId);
|
|
356
|
+
}
|
|
357
|
+
return this.register(provider);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* True when the token belongs exclusively to lazy modules that have not
|
|
361
|
+
* been materialized yet — resolving it would trigger materialization.
|
|
362
|
+
* Build-time probes (route-manager middleware priority, entrypoint
|
|
363
|
+
* snapshots) use this to defer instead of forcing the group.
|
|
364
|
+
*/ isLazyPending(token) {
|
|
365
|
+
const hook = this.root.lazyHook;
|
|
366
|
+
if (!hook) return false;
|
|
367
|
+
const owners = this.exporterIndex.get(token);
|
|
368
|
+
if (!owners || owners.size === 0) return false;
|
|
369
|
+
for (const owner of owners){
|
|
370
|
+
if (!hook.isPending(owner)) return false;
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* True when any registration of the token holds a constructed instance.
|
|
376
|
+
* Diagnostic helper (cold-start tests): checks WITHOUT resolving, so it
|
|
377
|
+
* never triggers lazy materialization.
|
|
378
|
+
*/ isInstantiated(token) {
|
|
379
|
+
for (const owner of this.exporterIndex.get(token) ?? []){
|
|
380
|
+
const reg = this.providers.get(owner)?.get(token);
|
|
381
|
+
if (reg && reg.instance !== undefined) return true;
|
|
382
|
+
}
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
291
385
|
getProviderScope(token) {
|
|
292
386
|
const exporters = this.exporterIndex.get(token);
|
|
293
387
|
if (!exporters) return undefined;
|
|
@@ -434,6 +528,13 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
434
528
|
child.exporterIndex = clonedIndex;
|
|
435
529
|
child.scopes = this.scopes;
|
|
436
530
|
child.globals = this.globals;
|
|
531
|
+
// Registration objects are shallow-shared, so a sandbox resolve of a lazy
|
|
532
|
+
// module's singleton caches onto the SHARED registration. Point the
|
|
533
|
+
// sandbox at the real root so that resolution claims the module and
|
|
534
|
+
// replays its hooks like any other trigger — otherwise ModuleRef.create
|
|
535
|
+
// would leave a hook-less instance poisoning the shared cache (and its
|
|
536
|
+
// disposables tracked on an ephemeral root).
|
|
537
|
+
child.root = this.root;
|
|
437
538
|
return child;
|
|
438
539
|
}
|
|
439
540
|
clear() {
|
|
@@ -484,8 +585,12 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
484
585
|
}
|
|
485
586
|
resolveRegistration(registration, requestingModuleId) {
|
|
486
587
|
if (registration.useValue !== undefined) {
|
|
588
|
+
// Deliberately BEFORE the lazy claim: reading a lazy module's useValue
|
|
589
|
+
// (options tokens) has no construction cost to defer and must not
|
|
590
|
+
// materialize the group.
|
|
487
591
|
return registration.useValue;
|
|
488
592
|
}
|
|
593
|
+
this.claimLazyModule(registration.declaringModuleId);
|
|
489
594
|
if (registration.useExisting) {
|
|
490
595
|
// Pass through the original requester to catch alias leaks
|
|
491
596
|
return this.resolve(registration.useExisting, requestingModuleId);
|
|
@@ -516,10 +621,9 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
516
621
|
try {
|
|
517
622
|
let instance;
|
|
518
623
|
if (registration.useFactory) {
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
// declaring module's POV — same escape-hatch shape as ModuleRef.
|
|
624
|
+
// Factory inject deps resolve from the declaring module's scope first
|
|
625
|
+
// (visibility-correct for forRootAsync), with the legacy no-requester
|
|
626
|
+
// lookup kept as fallback — see resolveFactoryDependency.
|
|
523
627
|
instance = this.resolveFactory(registration);
|
|
524
628
|
} else if (registration.useClass) {
|
|
525
629
|
instance = this.resolveClass(registration.useClass, registration.declaringModuleId);
|
|
@@ -578,16 +682,40 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
578
682
|
});
|
|
579
683
|
return new target(...dependencies);
|
|
580
684
|
}
|
|
685
|
+
/**
|
|
686
|
+
* Resolve one factory `inject` dependency. Since 1.11 the declaring module's
|
|
687
|
+
* scope is consulted FIRST (proper visibility semantics — a `forRootAsync`
|
|
688
|
+
* factory can inject providers reachable through its module's imports); on
|
|
689
|
+
* any failure the legacy no-requester lookup (root bucket, then first
|
|
690
|
+
* exporter) is kept as fallback, so every previously-resolving graph still
|
|
691
|
+
* resolves.
|
|
692
|
+
*/ resolveFactoryDependency(token, declaringModuleId) {
|
|
693
|
+
if (declaringModuleId !== ROOT_MODULE_ID && this.scopes.has(declaringModuleId)) {
|
|
694
|
+
try {
|
|
695
|
+
return this.resolve(token, declaringModuleId);
|
|
696
|
+
} catch {
|
|
697
|
+
// Fall through to the legacy escape hatch below.
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return this.resolve(token);
|
|
701
|
+
}
|
|
702
|
+
async resolveFactoryDependencyAsync(token, declaringModuleId) {
|
|
703
|
+
if (declaringModuleId !== ROOT_MODULE_ID && this.scopes.has(declaringModuleId)) {
|
|
704
|
+
try {
|
|
705
|
+
return await this.resolveAsync(token, declaringModuleId);
|
|
706
|
+
} catch {
|
|
707
|
+
// Fall through to the legacy escape hatch below.
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return this.resolveAsync(token);
|
|
711
|
+
}
|
|
581
712
|
resolveFactory(registration) {
|
|
582
713
|
if (!registration.useFactory) {
|
|
583
714
|
throw new Error('Factory function is missing');
|
|
584
715
|
}
|
|
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
716
|
const dependencies = (registration.inject || []).map((token)=>{
|
|
589
717
|
const resolved = token instanceof ForwardRef ? token.factory() : token;
|
|
590
|
-
return this.
|
|
718
|
+
return this.resolveFactoryDependency(resolved, registration.declaringModuleId);
|
|
591
719
|
});
|
|
592
720
|
const result = registration.useFactory(...dependencies);
|
|
593
721
|
if (result instanceof Promise) {
|
|
@@ -596,6 +724,20 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
596
724
|
return result;
|
|
597
725
|
}
|
|
598
726
|
async resolveAsync(token, requestingModuleId) {
|
|
727
|
+
const root = this.root;
|
|
728
|
+
root.asyncDepth++;
|
|
729
|
+
let result;
|
|
730
|
+
try {
|
|
731
|
+
result = await this.resolveAsyncInner(token, requestingModuleId);
|
|
732
|
+
} finally{
|
|
733
|
+
root.asyncDepth--;
|
|
734
|
+
}
|
|
735
|
+
if (root.asyncDepth === 0 && root.lazyHook?.hasClaimed() && !root.lazyHook.isDraining()) {
|
|
736
|
+
await root.lazyHook.drainAsync();
|
|
737
|
+
}
|
|
738
|
+
return result;
|
|
739
|
+
}
|
|
740
|
+
async resolveAsyncInner(token, requestingModuleId) {
|
|
599
741
|
const registration = this.findRegistration(token, requestingModuleId);
|
|
600
742
|
if (!registration) {
|
|
601
743
|
if (requestingModuleId !== undefined && this.scopes.has(requestingModuleId) && this.exporterIndex.has(token)) {
|
|
@@ -618,11 +760,12 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
618
760
|
if (scope === Scope.SINGLETON && registration.instance !== undefined) {
|
|
619
761
|
return registration.instance;
|
|
620
762
|
}
|
|
621
|
-
|
|
622
|
-
//
|
|
763
|
+
this.claimLazyModule(registration.declaringModuleId);
|
|
764
|
+
// Module scope first, legacy no-requester fallback — same policy as the
|
|
765
|
+
// sync resolveFactory path.
|
|
623
766
|
const dependencies = await Promise.all((registration.inject || []).map((t)=>{
|
|
624
767
|
const resolved = t instanceof ForwardRef ? t.factory() : t;
|
|
625
|
-
return this.
|
|
768
|
+
return this.resolveFactoryDependencyAsync(resolved, registration.declaringModuleId);
|
|
626
769
|
}));
|
|
627
770
|
const instance = await registration.useFactory(...dependencies);
|
|
628
771
|
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
|
|
@@ -85,6 +85,33 @@ export interface ModuleScope {
|
|
|
85
85
|
importedModules: Set<string>;
|
|
86
86
|
exportedTokens: Set<Token>;
|
|
87
87
|
isGlobal: boolean;
|
|
88
|
+
/** Module instance opted into deferred (first-use) materialization. */
|
|
89
|
+
lazy?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The container's seam into lazy-module materialization (implemented by
|
|
93
|
+
* `LazyModuleManager`). The container only ever *claims* a pending module at
|
|
94
|
+
* a resolution trigger and *drains* completed claims when the resolution
|
|
95
|
+
* stack has fully unwound — construction and hook replay live behind this
|
|
96
|
+
* interface so the container stays module-system-agnostic.
|
|
97
|
+
*/
|
|
98
|
+
export interface LazyResolutionHook {
|
|
99
|
+
/** Is this module instance still deferred (untriggered)? */
|
|
100
|
+
isPending(moduleId: string): boolean;
|
|
101
|
+
/** Mark a pending module as triggered; idempotent. */
|
|
102
|
+
claim(moduleId: string): void;
|
|
103
|
+
/** Any claimed-but-unmaterialized groups? (cheap fast-path check) */
|
|
104
|
+
hasClaimed(): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* A drain loop is currently running. The container must NOT start (or
|
|
107
|
+
* await) another drain from inside it — the running loop picks pending
|
|
108
|
+
* claims up; awaiting would self-deadlock on the async path.
|
|
109
|
+
*/
|
|
110
|
+
isDraining(): boolean;
|
|
111
|
+
/** Complete claimed groups synchronously; throws if async work surfaces. */
|
|
112
|
+
drainSync(): void;
|
|
113
|
+
/** Complete claimed groups, awaiting async construction and hooks. */
|
|
114
|
+
drainAsync(): Promise<void>;
|
|
88
115
|
}
|
|
89
116
|
export type Diagnostics = 'silent' | 'log' | 'throw';
|
|
90
117
|
export interface ContainerOptions {
|
|
@@ -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,102 @@
|
|
|
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
|
+
* Return providers of not-yet-materialized lazy modules as metadata-only
|
|
36
|
+
* entries (`instance: undefined`, mirroring the request-scoped convention)
|
|
37
|
+
* instead of resolving them — which would force the whole module group.
|
|
38
|
+
* Used by `EntrypointRegistry.build`; default false so every existing
|
|
39
|
+
* scanner keeps its transparent cascade-materialization semantics.
|
|
40
|
+
*/
|
|
41
|
+
deferLazy?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Decorator-driven provider discovery — the public replacement for the
|
|
45
|
+
* bootstrap scan every subsystem used to hand-roll (`container.getTokens()` +
|
|
46
|
+
* `MetadataRegistry.getCustomClassMeta` + `resolve` + diagnostics handling).
|
|
47
|
+
*
|
|
48
|
+
* Registered globally by `bootstrap()`, so any provider can inject it:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* @Injectable()
|
|
52
|
+
* class MyRegistry implements OnApplicationBootstrap {
|
|
53
|
+
* constructor(private readonly discovery: DiscoveryService) {}
|
|
54
|
+
* onApplicationBootstrap() {
|
|
55
|
+
* for (const { instance, meta } of this.discovery.providersWithMeta<MyMeta>(MY_KEY)) {
|
|
56
|
+
* this.register(instance, meta);
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* Discovery is kernel-level, not encapsulation-scoped: module visibility
|
|
63
|
+
* protects modules from *each other*, never from the framework's own
|
|
64
|
+
* dispatchers. Use `DiscoveryFilter.moduleId` for the rare narrowing case.
|
|
65
|
+
*
|
|
66
|
+
* The resolve-failure policy honors the container's diagnostics mode exactly
|
|
67
|
+
* like the legacy loops: `throw` rethrows, `log` warns and skips, `silent`
|
|
68
|
+
* skips — so a broken provider never silently changes discovery semantics.
|
|
69
|
+
*/
|
|
70
|
+
export declare class DiscoveryService {
|
|
71
|
+
private readonly container;
|
|
72
|
+
constructor(container: Container);
|
|
73
|
+
/** Every class-token provider registered in the container. */
|
|
74
|
+
getProviders(filter?: DiscoveryFilter): DiscoveredClass[];
|
|
75
|
+
/**
|
|
76
|
+
* Providers whose class carries class-level metadata under `key` — both
|
|
77
|
+
* class decorators (single value) and method decorators that append
|
|
78
|
+
* class-level lists (`@Cron`, `@OnEvent`, `@SubscribeMessage`).
|
|
79
|
+
*/
|
|
80
|
+
providersWithMeta<M>(key: string | DiscoverableDecorator<M>, filter?: DiscoveryFilter): Array<DiscoveredClass & {
|
|
81
|
+
meta: M;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Handler methods carrying metadata under `key`. Merges the two storage
|
|
85
|
+
* conventions:
|
|
86
|
+
* - class-level appended lists whose items carry `methodName`
|
|
87
|
+
* (`@Cron`/`@OnEvent`-style method decorators), and
|
|
88
|
+
* - true handler-level metadata (`@SetMetadata`/`createDiscoverableDecorator`
|
|
89
|
+
* applied to a method).
|
|
90
|
+
*/
|
|
91
|
+
methodsWithMeta<M>(key: string | DiscoverableDecorator<M>, filter?: DiscoveryFilter): DiscoveredMethodMeta<M>[];
|
|
92
|
+
/**
|
|
93
|
+
* Index-first candidate walk with a legacy full-scan fallback: when the
|
|
94
|
+
* reverse index knows nothing about a key, an older copy of the registry
|
|
95
|
+
* module may have taken the writes (mixed dist/src or package versions in
|
|
96
|
+
* one process) — fall back to scanning container tokens the way the
|
|
97
|
+
* pre-DiscoveryService loops did.
|
|
98
|
+
*/
|
|
99
|
+
private candidatesWithClassMeta;
|
|
100
|
+
private buildEntry;
|
|
101
|
+
}
|
|
102
|
+
export type { Constructor };
|