@velajs/vela 1.9.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/dist/__tests__/workers-als/entry-als.d.ts +9 -0
- package/dist/application.d.ts +18 -0
- package/dist/application.js +45 -0
- package/dist/cache/cache.module.d.ts +5 -7
- package/dist/cache/cache.module.js +44 -66
- package/dist/cache/cache.types.d.ts +26 -0
- package/dist/cache/cache.types.js +1 -1
- package/dist/cache/index.d.ts +2 -1
- package/dist/cache/index.js +1 -0
- package/dist/cache/tiered-cache.store.d.ts +19 -0
- package/dist/cache/tiered-cache.store.js +40 -0
- package/dist/config/config.module.d.ts +6 -5
- package/dist/config/config.module.js +46 -49
- package/dist/container/container.d.ts +58 -0
- package/dist/container/container.js +223 -18
- package/dist/container/disposable.d.ts +5 -0
- package/dist/container/disposable.js +33 -0
- package/dist/container/types.d.ts +8 -0
- package/dist/cors/cors.module.d.ts +5 -5
- package/dist/cors/cors.module.js +26 -25
- package/dist/discovery/discoverable.decorator.d.ts +38 -0
- package/dist/discovery/discoverable.decorator.js +42 -0
- package/dist/discovery/discovery.service.d.ts +94 -0
- package/dist/discovery/discovery.service.js +166 -0
- package/dist/discovery/index.d.ts +2 -0
- package/dist/discovery/index.js +2 -0
- package/dist/entrypoint/entrypoint.registry.d.ts +40 -0
- package/dist/entrypoint/entrypoint.registry.js +106 -0
- package/dist/entrypoint/entrypoint.types.d.ts +41 -0
- package/dist/entrypoint/entrypoint.types.js +3 -0
- package/dist/entrypoint/execution-context.d.ts +15 -0
- package/dist/entrypoint/execution-context.js +21 -0
- package/dist/entrypoint/execution-scope.d.ts +21 -0
- package/dist/entrypoint/execution-scope.js +28 -0
- package/dist/entrypoint/index.d.ts +4 -0
- package/dist/entrypoint/index.js +4 -0
- package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
- package/dist/event-emitter/event-emitter.subscriber.js +12 -29
- package/dist/factory/adapter.d.ts +36 -0
- package/dist/factory/adapter.js +18 -0
- package/dist/factory/bootstrap.js +20 -2
- package/dist/factory.d.ts +11 -1
- package/dist/factory.js +23 -2
- package/dist/fetch/fetch.module.d.ts +5 -6
- package/dist/fetch/fetch.module.js +9 -42
- package/dist/http/ambient.d.ts +17 -0
- package/dist/http/ambient.js +44 -0
- package/dist/http/execution-context.js +4 -1
- package/dist/http/handler-executor.js +22 -19
- package/dist/http/index.d.ts +1 -0
- package/dist/http/index.js +1 -0
- package/dist/http/route-contributor.d.ts +68 -0
- package/dist/http/route-contributor.js +22 -0
- package/dist/http/route.manager.d.ts +19 -0
- package/dist/http/route.manager.js +112 -19
- package/dist/i18n/deep-merge.d.ts +2 -0
- package/dist/i18n/deep-merge.js +15 -0
- package/dist/i18n/i18n.middleware.d.ts +15 -0
- package/dist/i18n/i18n.middleware.js +94 -0
- package/dist/i18n/i18n.module.d.ts +13 -0
- package/dist/i18n/i18n.module.js +64 -0
- package/dist/i18n/i18n.options.d.ts +30 -0
- package/dist/i18n/i18n.options.js +16 -0
- package/dist/i18n/i18n.service.d.ts +20 -0
- package/dist/i18n/i18n.service.js +49 -0
- package/dist/i18n/i18n.tokens.d.ts +6 -0
- package/dist/i18n/i18n.tokens.js +3 -0
- package/dist/i18n/i18n.types.d.ts +6 -0
- package/dist/i18n/i18n.types.js +1 -0
- package/dist/i18n/index.d.ts +10 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/message-loader.service.d.ts +21 -0
- package/dist/i18n/message-loader.service.js +90 -0
- package/dist/i18n/message-registry.d.ts +16 -0
- package/dist/i18n/message-registry.js +49 -0
- package/dist/index.d.ts +17 -5
- package/dist/index.js +14 -3
- package/dist/internal.d.ts +0 -2
- package/dist/internal.js +0 -7
- package/dist/module/configurable-module.builder.d.ts +56 -0
- package/dist/module/configurable-module.builder.js +84 -0
- package/dist/module/configurable-module.types.d.ts +93 -0
- package/dist/module/configurable-module.types.js +5 -0
- package/dist/module/define-module.d.ts +105 -0
- package/dist/module/define-module.js +227 -0
- package/dist/module/index.d.ts +4 -0
- package/dist/module/index.js +3 -0
- package/dist/module/lazy-provider.d.ts +65 -0
- package/dist/module/lazy-provider.js +111 -0
- package/dist/openapi/document.js +16 -15
- package/dist/pipeline/component.manager.d.ts +15 -9
- package/dist/pipeline/component.manager.js +32 -46
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/pipeline-runner.d.ts +31 -0
- package/dist/pipeline/pipeline-runner.js +40 -0
- package/dist/pipeline/types.d.ts +21 -4
- package/dist/registry/metadata.registry.d.ts +24 -18
- package/dist/registry/metadata.registry.js +137 -49
- package/dist/schedule/schedule.module.d.ts +5 -0
- package/dist/schedule/schedule.module.js +18 -8
- package/dist/schedule/schedule.registry.d.ts +3 -3
- package/dist/schedule/schedule.registry.js +23 -44
- package/dist/schedule-node/schedule-node.module.d.ts +5 -0
- package/dist/schedule-node/schedule-node.module.js +20 -10
- package/dist/seeder/index.d.ts +5 -0
- package/dist/seeder/index.js +4 -0
- package/dist/seeder/seeder.decorator.d.ts +7 -0
- package/dist/seeder/seeder.decorator.js +15 -0
- package/dist/seeder/seeder.module.d.ts +16 -0
- package/dist/seeder/seeder.module.js +41 -0
- package/dist/seeder/seeder.registry.d.ts +23 -0
- package/dist/seeder/seeder.registry.js +83 -0
- package/dist/seeder/seeder.tokens.d.ts +2 -0
- package/dist/seeder/seeder.tokens.js +1 -0
- package/dist/seeder/seeder.types.d.ts +20 -0
- package/dist/seeder/seeder.types.js +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/path-template.d.ts +9 -0
- package/dist/storage/path-template.js +20 -0
- package/dist/storage/signed-url.d.ts +12 -0
- package/dist/storage/signed-url.js +55 -0
- package/dist/storage/storage.types.d.ts +41 -0
- package/dist/storage/storage.types.js +5 -0
- package/dist/throttler/throttler.module.d.ts +5 -8
- package/dist/throttler/throttler.module.js +27 -55
- package/dist/websocket/index.d.ts +12 -0
- package/dist/websocket/index.js +16 -0
- package/dist/websocket/websocket.decorators.d.ts +33 -0
- package/dist/websocket/websocket.decorators.js +58 -0
- package/dist/websocket/websocket.module.d.ts +35 -0
- package/dist/websocket/websocket.module.js +63 -0
- package/dist/websocket/websocket.tokens.d.ts +23 -0
- package/dist/websocket/websocket.tokens.js +21 -0
- package/dist/websocket/websocket.types.d.ts +86 -0
- package/dist/websocket/websocket.types.js +4 -0
- package/dist/websocket/ws-argument-resolver.d.ts +13 -0
- package/dist/websocket/ws-argument-resolver.js +40 -0
- package/dist/websocket/ws-dispatcher.d.ts +45 -0
- package/dist/websocket/ws-dispatcher.js +259 -0
- package/dist/websocket/ws-exception.d.ts +16 -0
- package/dist/websocket/ws-exception.js +32 -0
- package/dist/websocket/ws-execution-context.d.ts +9 -0
- package/dist/websocket/ws-execution-context.js +24 -0
- package/dist/websocket/ws-server.d.ts +31 -0
- package/dist/websocket/ws-server.js +74 -0
- package/dist/websocket/ws-sync.d.ts +47 -0
- package/dist/websocket/ws-sync.js +74 -0
- package/dist/websocket-node/index.d.ts +4 -0
- package/dist/websocket-node/index.js +5 -0
- package/dist/websocket-node/node-ws-client.d.ts +24 -0
- package/dist/websocket-node/node-ws-client.js +49 -0
- package/dist/websocket-node/redis-sync.d.ts +33 -0
- package/dist/websocket-node/redis-sync.js +56 -0
- package/dist/websocket-node/register-gateways.d.ts +20 -0
- package/dist/websocket-node/register-gateways.js +61 -0
- package/package.json +31 -1
- package/dist/http/crud-bridge.d.ts +0 -90
- package/dist/http/crud-bridge.js +0 -25
|
@@ -17,6 +17,8 @@ export declare class Container {
|
|
|
17
17
|
private scopes;
|
|
18
18
|
private globals;
|
|
19
19
|
private diagnostics;
|
|
20
|
+
private root;
|
|
21
|
+
private disposables;
|
|
20
22
|
constructor(options?: ContainerOptions);
|
|
21
23
|
register<T>(provider: Type<T> | ProviderOptions<T>, declaringModuleId?: string): this;
|
|
22
24
|
private registerClass;
|
|
@@ -50,8 +52,41 @@ export declare class Container {
|
|
|
50
52
|
has(token: Token): boolean;
|
|
51
53
|
/** True if the specified module's bucket has a registration for `token`. */
|
|
52
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;
|
|
53
70
|
getProviderScope(token: Token): Scope | undefined;
|
|
54
71
|
getTokens(): Token[];
|
|
72
|
+
/**
|
|
73
|
+
* Every statically-provided (useValue) instance across ALL module buckets.
|
|
74
|
+
* Unlike resolving a token (which yields only the first bucket's registration),
|
|
75
|
+
* this surfaces per-instance values — e.g. one BindingRef per multi-instance
|
|
76
|
+
* binding module — so framework adapters can initialize all of them.
|
|
77
|
+
*/
|
|
78
|
+
getUseValues(): unknown[];
|
|
79
|
+
/**
|
|
80
|
+
* Compute request-scope bubbling for every registration (call once at
|
|
81
|
+
* bootstrap, after all providers are registered). A provider whose declared
|
|
82
|
+
* scope is not REQUEST but which (transitively) depends on a request-scoped
|
|
83
|
+
* provider is marked `effectiveScope = REQUEST`, so it is rebuilt per request
|
|
84
|
+
* instead of capturing the first request's instance — matching NestJS.
|
|
85
|
+
*/
|
|
86
|
+
computeEffectiveScopes(): void;
|
|
87
|
+
/** The dependency tokens a registration would resolve when constructed. */
|
|
88
|
+
private dependencyTokensOf;
|
|
89
|
+
private tryFindRegistration;
|
|
55
90
|
/**
|
|
56
91
|
* Create a child container for request-scoped resolution.
|
|
57
92
|
* The child shares the parent's providers but caches REQUEST-scoped
|
|
@@ -60,8 +95,31 @@ export declare class Container {
|
|
|
60
95
|
createChild(): Container;
|
|
61
96
|
createDetached(): Container;
|
|
62
97
|
clear(): void;
|
|
98
|
+
/** Whether this container has any tracked disposables (cheap request-path check). */
|
|
99
|
+
hasDisposables(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Dispose container-constructed instances (Symbol.asyncDispose /
|
|
102
|
+
* Symbol.dispose / .dispose()) in reverse creation order (LIFO). Errors are
|
|
103
|
+
* logged and skipped so one bad teardown never blocks the rest.
|
|
104
|
+
*
|
|
105
|
+
* A REQUEST child disposes only its own request-scoped instances; the root
|
|
106
|
+
* disposes shared SINGLETONs and clears their cached instance so a dev HMR
|
|
107
|
+
* generation cannot leak a stale graph. Shared provider maps are left intact
|
|
108
|
+
* for a request child (they belong to the root).
|
|
109
|
+
*/
|
|
110
|
+
dispose(): Promise<void>;
|
|
63
111
|
private resolveRegistration;
|
|
64
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;
|
|
65
123
|
private resolveFactory;
|
|
66
124
|
resolveAsync<T>(token: Token<T>, requestingModuleId?: string): Promise<T>;
|
|
67
125
|
private createLazyProxy;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Scope } from "../constants.js";
|
|
2
|
+
import { disposeInstance, isDisposable } from "./disposable.js";
|
|
2
3
|
import { getConstructorDependencies, getInjectMetadata, getScope, isInjectable } from "./decorators.js";
|
|
3
4
|
import { ForwardRef, InjectionToken, ModuleVisibilityError, MultipleProvidersFoundError, ROOT_MODULE_ID } from "./types.js";
|
|
4
5
|
const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips type-only imports at ' + 'runtime and `design:paramtypes` emits `Object`/`undefined` for their ' + 'positions. Use a runtime `import { X }` for DI tokens.';
|
|
@@ -25,6 +26,13 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
25
26
|
scopes = new Map();
|
|
26
27
|
globals = new Set();
|
|
27
28
|
diagnostics;
|
|
29
|
+
// The root container that owns shared state; a request child points back here
|
|
30
|
+
// so container-constructed SINGLETONs are tracked (and disposed) at the root,
|
|
31
|
+
// never by the ephemeral child that happened to first resolve them.
|
|
32
|
+
root = this;
|
|
33
|
+
// Container-constructed instances in creation order, for LIFO disposal.
|
|
34
|
+
// useValue providers are never tracked (they return before construction).
|
|
35
|
+
disposables = [];
|
|
28
36
|
constructor(options = {}){
|
|
29
37
|
this.diagnostics = options.diagnostics ?? 'log';
|
|
30
38
|
}
|
|
@@ -280,12 +288,45 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
280
288
|
/** True if the specified module's bucket has a registration for `token`. */ hasInScope(token, moduleId) {
|
|
281
289
|
return this.providers.get(moduleId)?.has(token) ?? false;
|
|
282
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
|
+
}
|
|
283
324
|
getProviderScope(token) {
|
|
284
325
|
const exporters = this.exporterIndex.get(token);
|
|
285
326
|
if (!exporters) return undefined;
|
|
286
327
|
for (const owner of exporters){
|
|
287
328
|
const reg = this.providers.get(owner)?.get(token);
|
|
288
|
-
if (reg) return reg.scope;
|
|
329
|
+
if (reg) return reg.effectiveScope ?? reg.scope;
|
|
289
330
|
}
|
|
290
331
|
return undefined;
|
|
291
332
|
}
|
|
@@ -299,6 +340,95 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
299
340
|
];
|
|
300
341
|
}
|
|
301
342
|
/**
|
|
343
|
+
* Every statically-provided (useValue) instance across ALL module buckets.
|
|
344
|
+
* Unlike resolving a token (which yields only the first bucket's registration),
|
|
345
|
+
* this surfaces per-instance values — e.g. one BindingRef per multi-instance
|
|
346
|
+
* binding module — so framework adapters can initialize all of them.
|
|
347
|
+
*/ getUseValues() {
|
|
348
|
+
const out = [];
|
|
349
|
+
for (const bucket of this.providers.values()){
|
|
350
|
+
for (const reg of bucket.values()){
|
|
351
|
+
if (reg.useValue !== undefined) out.push(reg.useValue);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return out;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Compute request-scope bubbling for every registration (call once at
|
|
358
|
+
* bootstrap, after all providers are registered). A provider whose declared
|
|
359
|
+
* scope is not REQUEST but which (transitively) depends on a request-scoped
|
|
360
|
+
* provider is marked `effectiveScope = REQUEST`, so it is rebuilt per request
|
|
361
|
+
* instead of capturing the first request's instance — matching NestJS.
|
|
362
|
+
*/ computeEffectiveScopes() {
|
|
363
|
+
// Reverse-propagation worklist: seed with every declared-REQUEST provider,
|
|
364
|
+
// then flood "request-ness" to consumers transitively. This is order- and
|
|
365
|
+
// cycle-independent (a node flips to REQUEST at most once), avoiding the
|
|
366
|
+
// finalize-during-in-progress-cycle hazard of a recursive DFS.
|
|
367
|
+
const regs = [];
|
|
368
|
+
for (const bucket of this.providers.values()){
|
|
369
|
+
for (const reg of bucket.values()){
|
|
370
|
+
reg.effectiveScope = reg.scope;
|
|
371
|
+
regs.push(reg);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// Build reverse edges: dependency registration -> registrations that consume it.
|
|
375
|
+
const consumers = new Map();
|
|
376
|
+
for (const reg of regs){
|
|
377
|
+
for (const depToken of this.dependencyTokensOf(reg)){
|
|
378
|
+
const depReg = this.tryFindRegistration(depToken, reg.declaringModuleId);
|
|
379
|
+
if (!depReg) continue;
|
|
380
|
+
const list = consumers.get(depReg);
|
|
381
|
+
if (list) list.push(reg);
|
|
382
|
+
else consumers.set(depReg, [
|
|
383
|
+
reg
|
|
384
|
+
]);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const queue = regs.filter((r)=>r.effectiveScope === Scope.REQUEST);
|
|
388
|
+
while(queue.length > 0){
|
|
389
|
+
const dep = queue.pop();
|
|
390
|
+
for (const consumer of consumers.get(dep) ?? []){
|
|
391
|
+
if (consumer.effectiveScope !== Scope.REQUEST) {
|
|
392
|
+
consumer.effectiveScope = Scope.REQUEST;
|
|
393
|
+
queue.push(consumer);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/** The dependency tokens a registration would resolve when constructed. */ dependencyTokensOf(reg) {
|
|
399
|
+
if (reg.useExisting) return [
|
|
400
|
+
reg.useExisting
|
|
401
|
+
];
|
|
402
|
+
if (reg.useFactory) {
|
|
403
|
+
return (reg.inject ?? []).map((t)=>t instanceof ForwardRef ? t.factory() : t);
|
|
404
|
+
}
|
|
405
|
+
const cls = reg.useClass ?? (typeof reg.provide === 'function' ? reg.provide : undefined);
|
|
406
|
+
if (!cls) return [];
|
|
407
|
+
const paramTypes = getConstructorDependencies(cls);
|
|
408
|
+
const injectMetadata = getInjectMetadata(cls);
|
|
409
|
+
const injectMap = new Map(injectMetadata.map((m)=>[
|
|
410
|
+
m.index,
|
|
411
|
+
m
|
|
412
|
+
]));
|
|
413
|
+
const arity = Math.max(paramTypes.length, injectMetadata.reduce((max, m)=>Math.max(max, m.index + 1), 0));
|
|
414
|
+
const tokens = [];
|
|
415
|
+
for(let i = 0; i < arity; i++){
|
|
416
|
+
const raw = injectMap.get(i)?.token;
|
|
417
|
+
const token = raw instanceof ForwardRef ? raw.factory() : raw ?? paramTypes[i];
|
|
418
|
+
if (token && !isErasedTypeToken(token)) tokens.push(token);
|
|
419
|
+
}
|
|
420
|
+
return tokens;
|
|
421
|
+
}
|
|
422
|
+
tryFindRegistration(token, requestingModuleId) {
|
|
423
|
+
try {
|
|
424
|
+
return this.findRegistration(token, requestingModuleId);
|
|
425
|
+
} catch {
|
|
426
|
+
// Ambiguous (MultipleProvidersFoundError) or unreachable — treat as
|
|
427
|
+
// unknown for scope computation; real resolution will surface any error.
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
302
432
|
* Create a child container for request-scoped resolution.
|
|
303
433
|
* The child shares the parent's providers but caches REQUEST-scoped
|
|
304
434
|
* instances separately per child (per request).
|
|
@@ -312,6 +442,9 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
312
442
|
child.exporterIndex = this.exporterIndex;
|
|
313
443
|
child.scopes = this.scopes;
|
|
314
444
|
child.globals = this.globals;
|
|
445
|
+
// SINGLETON disposal is owned by the root; the child keeps only its own
|
|
446
|
+
// REQUEST-scoped disposables (cleared when the request ends).
|
|
447
|
+
child.root = this.root;
|
|
315
448
|
return child;
|
|
316
449
|
}
|
|
317
450
|
createDetached() {
|
|
@@ -343,6 +476,44 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
343
476
|
this.requestInstances.clear();
|
|
344
477
|
this.scopes.clear();
|
|
345
478
|
this.globals.clear();
|
|
479
|
+
this.disposables = [];
|
|
480
|
+
}
|
|
481
|
+
/** Whether this container has any tracked disposables (cheap request-path check). */ hasDisposables() {
|
|
482
|
+
return this.disposables.length > 0;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Dispose container-constructed instances (Symbol.asyncDispose /
|
|
486
|
+
* Symbol.dispose / .dispose()) in reverse creation order (LIFO). Errors are
|
|
487
|
+
* logged and skipped so one bad teardown never blocks the rest.
|
|
488
|
+
*
|
|
489
|
+
* A REQUEST child disposes only its own request-scoped instances; the root
|
|
490
|
+
* disposes shared SINGLETONs and clears their cached instance so a dev HMR
|
|
491
|
+
* generation cannot leak a stale graph. Shared provider maps are left intact
|
|
492
|
+
* for a request child (they belong to the root).
|
|
493
|
+
*/ async dispose() {
|
|
494
|
+
const pending = this.disposables;
|
|
495
|
+
this.disposables = [];
|
|
496
|
+
for(let i = pending.length - 1; i >= 0; i--){
|
|
497
|
+
try {
|
|
498
|
+
await disposeInstance(pending[i]);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (this.diagnostics !== 'silent') {
|
|
501
|
+
console.error('Error disposing instance:', error);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
this.requestInstances.clear();
|
|
506
|
+
if (this.root === this) {
|
|
507
|
+
// Drop cached singleton instances (except useValue, which the app owns)
|
|
508
|
+
// so a rebuilt graph starts clean.
|
|
509
|
+
for (const bucket of this.providers.values()){
|
|
510
|
+
for (const registration of bucket.values()){
|
|
511
|
+
if (registration.scope === Scope.SINGLETON && registration.useValue === undefined) {
|
|
512
|
+
registration.instance = undefined;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
346
517
|
}
|
|
347
518
|
resolveRegistration(registration, requestingModuleId) {
|
|
348
519
|
if (registration.useValue !== undefined) {
|
|
@@ -352,12 +523,16 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
352
523
|
// Pass through the original requester to catch alias leaks
|
|
353
524
|
return this.resolve(registration.useExisting, requestingModuleId);
|
|
354
525
|
}
|
|
526
|
+
// Effective scope accounts for request-scope bubbling: a SINGLETON that
|
|
527
|
+
// (transitively) depends on a request-scoped provider is treated as REQUEST
|
|
528
|
+
// so it is rebuilt per request instead of capturing the first one.
|
|
529
|
+
const scope = registration.effectiveScope ?? registration.scope;
|
|
355
530
|
// Singleton: return cached from registration (shared across all containers)
|
|
356
|
-
if (
|
|
531
|
+
if (scope === Scope.SINGLETON && registration.instance !== undefined) {
|
|
357
532
|
return registration.instance;
|
|
358
533
|
}
|
|
359
534
|
// Request: return cached from this child's requestInstances
|
|
360
|
-
if (
|
|
535
|
+
if (scope === Scope.REQUEST) {
|
|
361
536
|
const cached = this.requestInstances.get(registration.provide);
|
|
362
537
|
if (cached !== undefined) {
|
|
363
538
|
return cached;
|
|
@@ -374,20 +549,23 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
374
549
|
try {
|
|
375
550
|
let instance;
|
|
376
551
|
if (registration.useFactory) {
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
// 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.
|
|
381
555
|
instance = this.resolveFactory(registration);
|
|
382
556
|
} else if (registration.useClass) {
|
|
383
557
|
instance = this.resolveClass(registration.useClass, registration.declaringModuleId);
|
|
384
558
|
} else {
|
|
385
559
|
throw new Error(`Invalid provider registration for: ${this.tokenToString(registration.provide)}`);
|
|
386
560
|
}
|
|
387
|
-
if (
|
|
561
|
+
if (scope === Scope.SINGLETON) {
|
|
388
562
|
registration.instance = instance;
|
|
389
|
-
|
|
563
|
+
// Track for disposal on the ROOT — a singleton outlives the request
|
|
564
|
+
// child that may have first constructed it.
|
|
565
|
+
if (isDisposable(instance)) this.root.disposables.push(instance);
|
|
566
|
+
} else if (scope === Scope.REQUEST) {
|
|
390
567
|
this.requestInstances.set(registration.provide, instance);
|
|
568
|
+
if (isDisposable(instance)) this.disposables.push(instance);
|
|
391
569
|
}
|
|
392
570
|
return instance;
|
|
393
571
|
} finally{
|
|
@@ -432,16 +610,40 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
432
610
|
});
|
|
433
611
|
return new target(...dependencies);
|
|
434
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
|
+
}
|
|
435
640
|
resolveFactory(registration) {
|
|
436
641
|
if (!registration.useFactory) {
|
|
437
642
|
throw new Error('Factory function is missing');
|
|
438
643
|
}
|
|
439
|
-
// Factory inject deps resolve without a requester — Vela has no
|
|
440
|
-
// `forRootAsync({ imports })` surface to track which module the factory
|
|
441
|
-
// resolves from, so it uses the same escape-hatch shape as ModuleRef.
|
|
442
644
|
const dependencies = (registration.inject || []).map((token)=>{
|
|
443
645
|
const resolved = token instanceof ForwardRef ? token.factory() : token;
|
|
444
|
-
return this.
|
|
646
|
+
return this.resolveFactoryDependency(resolved, registration.declaringModuleId);
|
|
445
647
|
});
|
|
446
648
|
const result = registration.useFactory(...dependencies);
|
|
447
649
|
if (result instanceof Promise) {
|
|
@@ -468,18 +670,21 @@ const IMPORT_TYPE_HINT = 'Did you use `import type { X }`? TypeScript strips typ
|
|
|
468
670
|
throw new Error(`No provider found for token: ${this.tokenToString(token)}`);
|
|
469
671
|
}
|
|
470
672
|
if (registration.useFactory) {
|
|
471
|
-
|
|
673
|
+
const scope = registration.effectiveScope ?? registration.scope;
|
|
674
|
+
if (scope === Scope.SINGLETON && registration.instance !== undefined) {
|
|
472
675
|
return registration.instance;
|
|
473
676
|
}
|
|
474
|
-
//
|
|
475
|
-
// sync resolveFactory
|
|
677
|
+
// Module scope first, legacy no-requester fallback — same policy as the
|
|
678
|
+
// sync resolveFactory path.
|
|
476
679
|
const dependencies = await Promise.all((registration.inject || []).map((t)=>{
|
|
477
680
|
const resolved = t instanceof ForwardRef ? t.factory() : t;
|
|
478
|
-
return this.
|
|
681
|
+
return this.resolveFactoryDependencyAsync(resolved, registration.declaringModuleId);
|
|
479
682
|
}));
|
|
480
683
|
const instance = await registration.useFactory(...dependencies);
|
|
481
|
-
if (
|
|
684
|
+
if (scope === Scope.SINGLETON) {
|
|
482
685
|
registration.instance = instance;
|
|
686
|
+
// Track for disposal on the ROOT, matching the sync resolveRegistration path.
|
|
687
|
+
if (isDisposable(instance)) this.root.disposables.push(instance);
|
|
483
688
|
}
|
|
484
689
|
return instance;
|
|
485
690
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Edge-safe disposal contract. Precedence:
|
|
2
|
+
// Symbol.asyncDispose > Symbol.dispose > .dispose()
|
|
3
|
+
// Symbol.asyncDispose/Symbol.dispose are resolved at runtime (they are not in
|
|
4
|
+
// the ES2022 lib the project targets), so nothing here depends on the
|
|
5
|
+
// esnext.disposable typings.
|
|
6
|
+
const ASYNC_DISPOSE = Symbol.asyncDispose;
|
|
7
|
+
const SYNC_DISPOSE = Symbol.dispose;
|
|
8
|
+
function asRecord(value) {
|
|
9
|
+
if (value === null) return undefined;
|
|
10
|
+
return typeof value === 'object' || typeof value === 'function' ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
export function isDisposable(value) {
|
|
13
|
+
const obj = asRecord(value);
|
|
14
|
+
if (!obj) return false;
|
|
15
|
+
if (ASYNC_DISPOSE && typeof obj[ASYNC_DISPOSE] === 'function') return true;
|
|
16
|
+
if (SYNC_DISPOSE && typeof obj[SYNC_DISPOSE] === 'function') return true;
|
|
17
|
+
return typeof obj.dispose === 'function';
|
|
18
|
+
}
|
|
19
|
+
export async function disposeInstance(value) {
|
|
20
|
+
const obj = asRecord(value);
|
|
21
|
+
if (!obj) return;
|
|
22
|
+
if (ASYNC_DISPOSE && typeof obj[ASYNC_DISPOSE] === 'function') {
|
|
23
|
+
await obj[ASYNC_DISPOSE].call(obj);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (SYNC_DISPOSE && typeof obj[SYNC_DISPOSE] === 'function') {
|
|
27
|
+
obj[SYNC_DISPOSE].call(obj);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (typeof obj.dispose === 'function') {
|
|
31
|
+
await obj.dispose.call(obj);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -57,7 +57,15 @@ export interface ProviderOptions<T = unknown> {
|
|
|
57
57
|
}
|
|
58
58
|
export interface ProviderRegistration<T = unknown> {
|
|
59
59
|
provide: Token<T>;
|
|
60
|
+
/** The declared scope (from `@Injectable`/`@Controller`/provider options). */
|
|
60
61
|
scope: Scope;
|
|
62
|
+
/**
|
|
63
|
+
* Scope after request-scope bubbling: `REQUEST` if this provider (transitively)
|
|
64
|
+
* depends on a request-scoped provider, else the declared scope. Computed once
|
|
65
|
+
* at bootstrap (`Container.computeEffectiveScopes`); falls back to `scope`
|
|
66
|
+
* until then. Governs caching + eager instantiation.
|
|
67
|
+
*/
|
|
68
|
+
effectiveScope?: Scope;
|
|
61
69
|
/**
|
|
62
70
|
* Module that owns this registration. Used by `resolveClass` to determine
|
|
63
71
|
* the POV from which the class's dependencies resolve. Sandbox/bootstrap
|
|
@@ -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
|
+
}
|