@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,162 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
Cold-start laziness (roadmap phase 3): modules can defer their entire init to
|
|
6
|
+
first use, and the in-core subsystems an HTTP-only worker doesn't touch now
|
|
7
|
+
cost it nothing at bootstrap.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Lazy modules** — `@Module({ lazy: true })`, `DynamicModule.lazy`, and
|
|
12
|
+
`defineModule({ lazy: true })` (also recognized per call site like
|
|
13
|
+
`isGlobal`) defer a module *instance*'s entire provider/controller group:
|
|
14
|
+
nothing constructs during `VelaFactory.create`. The first resolution of any
|
|
15
|
+
of its tokens (injection, `app.get()`, a request hitting its controller, a
|
|
16
|
+
dispatcher re-resolving an entrypoint token) claims the module; when the
|
|
17
|
+
resolution stack unwinds, the group materializes and its
|
|
18
|
+
`onModuleInit`/`onApplicationBootstrap` hooks replay in registration order,
|
|
19
|
+
exactly once (memoized). Materialized instances join the instance flow so
|
|
20
|
+
shutdown hooks stay symmetric; untouched modules get neither init nor
|
|
21
|
+
shutdown hooks. Triggers during bootstrap absorb the group into the normal
|
|
22
|
+
hook phases, ordered dependency-before-consumer. `useValue` reads (options
|
|
23
|
+
tokens) do not trigger. Sync seams (`app.get`, the request pipeline) throw
|
|
24
|
+
a descriptive error for lazy modules with async providers/hooks — reach
|
|
25
|
+
those through `app.materializeLazyModules()` (the new warmup escape hatch)
|
|
26
|
+
or keep them sync. Authoring contract: MODULE_AUTHORING.md "Lazy modules".
|
|
27
|
+
- **`app.materializeLazyModules()`** — materialize every still-pending lazy
|
|
28
|
+
module (async-safe); warmup/eager-everything escape hatch.
|
|
29
|
+
- **`Container.isLazyPending(token)` / `Container.isInstantiated(token)`** —
|
|
30
|
+
non-triggering diagnostics (build-time probes, cold-start regression tests).
|
|
31
|
+
- **`DiscoveryFilter.deferLazy`** — discovery returns providers of
|
|
32
|
+
unmaterialized lazy modules as metadata-only entries (`instance:
|
|
33
|
+
undefined`, mirroring the request-scoped convention) instead of forcing the
|
|
34
|
+
group. `EntrypointRegistry.build` uses it: declared-kind entrypoints of
|
|
35
|
+
lazy modules are metadata-only in `app.entrypoints`; dispatchers that
|
|
36
|
+
re-resolve by token (cloudflare cron/queue/scheduled already do)
|
|
37
|
+
materialize the owning module at dispatch time. `ContributesEntrypoints`
|
|
38
|
+
providers in lazy modules are materialized right before the snapshot —
|
|
39
|
+
computed contributions can't defer (documented cost).
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **`EventEmitterModule`, `ScheduleModule`, `SeederModule`, `I18nModule` are
|
|
44
|
+
now lazy.** An HTTP-only worker that imports them but never emits an event,
|
|
45
|
+
reads the schedule registry, runs seeders, or translates pays zero
|
|
46
|
+
cold-start cost for them — no subscriber wiring pass, no `@Cron` discovery
|
|
47
|
+
walk, no merged-message snapshot. Every consumer path is a trigger, so
|
|
48
|
+
observable behavior is unchanged (`app.get(EventEmitter).emit(...)` wires
|
|
49
|
+
subscribers first; `runSeeders()` populates the registry via hook replay).
|
|
50
|
+
`WebSocketModule` and `ScheduleNodeModule` deliberately stay eager (gateway
|
|
51
|
+
injection drags the WS chain in anyway; the node executor is self-driving).
|
|
52
|
+
- `ModuleLoader.resolveAllInstances()` skips tokens owned exclusively by lazy
|
|
53
|
+
module instances; a token also registered by a non-lazy module stays on the
|
|
54
|
+
eager pass. Route building no longer instantiate-probes middleware tokens
|
|
55
|
+
that are lazy-pending (default priority 0) — the probe would have defeated
|
|
56
|
+
i18n's deferral at route build.
|
|
57
|
+
|
|
58
|
+
## 1.12.0 (2026-07-04)
|
|
59
|
+
|
|
60
|
+
The module-model release: one blessed authoring path plus public kernel
|
|
61
|
+
extension points, so feature modules (websocket, storage, queue, …) are built
|
|
62
|
+
entirely on the public API. See `MODULE_AUTHORING.md` for the author contract.
|
|
63
|
+
Contains deliberate breaking changes (no deprecation shims); coordinated
|
|
64
|
+
releases of `@velajs/{storage,better-auth,testing,crud,cloudflare}` accompany
|
|
65
|
+
this version.
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- **`defineModule`** — the single module-authoring engine: generates `forRoot`
|
|
70
|
+
AND `forRootAsync` (typed `inject` inference), derives deterministic
|
|
71
|
+
`stableHash` keys (`key(options)` override + explicit `key` passthrough),
|
|
72
|
+
and accepts contributions (providers/controllers/imports/exports) **as
|
|
73
|
+
functions of the options** plus a standardized `global:` component slot.
|
|
74
|
+
`ConfigurableModuleBuilder` is now a thin adapter over it (unchanged API).
|
|
75
|
+
- **Authoring primitives**: `lazyProvider` (memoized deferred thunk — replaces
|
|
76
|
+
the hand-rolled `(...deps)=>()=>fn(...deps)` closures), `provideGlobal`
|
|
77
|
+
(the one `APP_*` wiring idiom), `sideEffectModule` (first-class
|
|
78
|
+
contribution-only modules; supported form of the i18n empty-marker trick),
|
|
79
|
+
`moduleToken`, `moduleKey`.
|
|
80
|
+
- **`DiscoveryService` + `createDiscoverableDecorator`** — public
|
|
81
|
+
decorator-driven discovery (`providersWithMeta`, `methodsWithMeta`,
|
|
82
|
+
`getProviders`) backed by a reverse metadata index inside
|
|
83
|
+
`MetadataRegistry`; honors container diagnostics in one place;
|
|
84
|
+
request-scoped providers are surfaced but not materialized (opt in with
|
|
85
|
+
`includeRequestScoped`). The event-emitter, schedule, and websocket
|
|
86
|
+
bootstrap scans now all run through it.
|
|
87
|
+
- **Open entrypoint registry** — `registerEntrypointKind({ kind, metaKey,
|
|
88
|
+
level })`, the `ContributesEntrypoints` interface, and per-application
|
|
89
|
+
`app.entrypoints` (`ofKind`/`kinds`/`all`), built at the end of
|
|
90
|
+
`callOnApplicationBootstrap()` so slim bootstrap paths (Cloudflare Durable
|
|
91
|
+
Objects) get it too. Transports query entrypoints instead of module
|
|
92
|
+
internals; a new kind (queue, cron, CLI) needs **zero core changes**.
|
|
93
|
+
- **`RouteContributor`** — public metadata-claimed route generation
|
|
94
|
+
(`registerRouteContributor`), consulted after explicit routes and during
|
|
95
|
+
OpenAPI generation with verb-level merge. Replaces the internal CrudBridge.
|
|
96
|
+
- **`RuntimeAdapter`** — `VelaFactory.create(module, { adapters: [...] })`
|
|
97
|
+
with `requestMiddleware` (prepended to the global chain), `onBootstrap`
|
|
98
|
+
(after lifecycle + entrypoints, before routes) and `onRoutesBuilt` hooks.
|
|
99
|
+
- **`PipelineRunner`** — the shared guard → pipe → interceptor execution core
|
|
100
|
+
used by HTTP and WebSocket dispatch (and any custom dispatcher);
|
|
101
|
+
configurable args/guards order, transport-specific guard-rejection error.
|
|
102
|
+
- **`Container.replaceProvider(provider, { buckets })`** — supported
|
|
103
|
+
force-replace across module buckets (what test harnesses need).
|
|
104
|
+
- **`buildEntrypointExecutionContext(kind, class, handler, payload)`** — the
|
|
105
|
+
entrypoint sibling of the HTTP/WS execution contexts, so guards/
|
|
106
|
+
interceptors/filters written against `getClass()`/`getHandler()`/`getType()`
|
|
107
|
+
run unchanged around queue batches, scheduled ticks, and custom kinds
|
|
108
|
+
(`EntrypointExecutionContext.getPayload()`). `@velajs/cloudflare` dispatches
|
|
109
|
+
queue/scheduled handlers through `PipelineRunner` with consumer-scoped
|
|
110
|
+
components (HTTP-global components deliberately do not apply; unclaimed
|
|
111
|
+
errors rethrow to preserve platform retry semantics).
|
|
112
|
+
- **`runInEntrypointScope(container, fn)`** — the non-HTTP dispatch scope
|
|
113
|
+
primitive: runs one unit of work (queue batch, scheduled tick, RPC call) in
|
|
114
|
+
a fresh request-scoped child with LIFO disposal — the per-request-child
|
|
115
|
+
equivalent for entrypoint dispatchers. `@velajs/cloudflare`'s queue and
|
|
116
|
+
scheduled handlers run on it (request-scoped consumer deps rebuild per
|
|
117
|
+
batch/tick instead of capturing boot instances).
|
|
118
|
+
|
|
119
|
+
### Changed
|
|
120
|
+
|
|
121
|
+
- **Factory dependency visibility**: `useFactory`/`forRootAsync` `inject`
|
|
122
|
+
deps now resolve from the declaring module's scope FIRST (imports and
|
|
123
|
+
exports are honored), with the legacy no-requester lookup kept as fallback.
|
|
124
|
+
- **All in-core configurable modules are on the one engine**: `CorsModule` and
|
|
125
|
+
`SeederModule` rebuilt on `defineModule` (Cors gains `forRootAsync`; both
|
|
126
|
+
keep their token/key identities), `ScheduleModule`/`ScheduleNodeModule`
|
|
127
|
+
normalized to zero-config `@Module` bags with parity `forRoot()` sugar.
|
|
128
|
+
Builder-based modules (Config/Cache/I18n/Throttler/Http) already run on it
|
|
129
|
+
through the `ConfigurableModuleBuilder` adapter.
|
|
130
|
+
- **Unhandled handler errors are logged**: an error no exception filter claims
|
|
131
|
+
still maps to the generic 500 response, but the cause now lands in the logs
|
|
132
|
+
(`console.error`, gated on container diagnostics ≠ `silent`) — closing the
|
|
133
|
+
silent-500 gap.
|
|
134
|
+
- **`WebSocketModule`** rebuilt on `defineModule`: registry → driver → server
|
|
135
|
+
construction moved into chained provider factories (single shared registry
|
|
136
|
+
preserved; everything materializes at bootstrap), deterministic key
|
|
137
|
+
`ws#<sync-kind>` — **two identical `forRoot()` calls now dedup**
|
|
138
|
+
(HMR-idempotent; pass explicit `key` for exotic multi-instance),
|
|
139
|
+
`forRootAsync` available. `WsDispatcher` contributes `'websocket'`
|
|
140
|
+
entrypoints; `registerWebSocketGateways` consumes
|
|
141
|
+
`app.entrypoints.ofKind('websocket')`.
|
|
142
|
+
|
|
143
|
+
### Breaking
|
|
144
|
+
|
|
145
|
+
- **`WS_MODULE_OPTIONS`** is now a typed `InjectionToken` (was the raw string
|
|
146
|
+
`'vela:ws-module-options'`).
|
|
147
|
+
- **`ComponentManager`** is stateless: `init()` and the process-global
|
|
148
|
+
container are gone; `resolve*` methods require an explicit container;
|
|
149
|
+
`getComponents` replaced by `getScopedComponents` (controller + handler
|
|
150
|
+
only — app-wide components have one source: `RouteManager`).
|
|
151
|
+
`registerGlobal`/`MetadataRegistry.getGlobal` (a dead tier with no readers
|
|
152
|
+
on the request path) are removed; `MetadataRegistry.clear()` is now a
|
|
153
|
+
no-op.
|
|
154
|
+
- **CrudBridge removed** (`registerCrudBridge`/`getCrudBridge` and the
|
|
155
|
+
`/internal` exports): use `registerRouteContributor`. `@velajs/crud`
|
|
156
|
+
migrates in its coordinated release.
|
|
157
|
+
- `forRootAsync` structural fields (non-async keys passed alongside
|
|
158
|
+
`useFactory`) now merge under the resolved options.
|
|
159
|
+
|
|
3
160
|
## 1.10.0 (2026-07-01)
|
|
4
161
|
|
|
5
162
|
### 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,8 +10,12 @@ export declare class VelaApplication {
|
|
|
9
10
|
private readonly routeManager;
|
|
10
11
|
private instances;
|
|
11
12
|
private honoApp;
|
|
13
|
+
private entrypointRegistry;
|
|
12
14
|
private disposed;
|
|
15
|
+
private readonly lazyManager;
|
|
16
|
+
private readonly knownInstances;
|
|
13
17
|
constructor(container: Container, routeManager: RouteManager);
|
|
18
|
+
private trackNew;
|
|
14
19
|
/** Pre-build routes (handles async CRUD imports). Called by VelaFactory. */
|
|
15
20
|
initRoutes(): Promise<void>;
|
|
16
21
|
private getApp;
|
|
@@ -42,8 +47,28 @@ export declare class VelaApplication {
|
|
|
42
47
|
* ```
|
|
43
48
|
*/
|
|
44
49
|
mountOpenApi(options: MountOpenApiOptions): this;
|
|
50
|
+
/**
|
|
51
|
+
* Pull instances materialized during the bootstrap phase (lazy modules
|
|
52
|
+
* dragged in by eager consumers) into the front of the instance list —
|
|
53
|
+
* dependency-before-consumer: a group absorbed because an eager provider
|
|
54
|
+
* injected it must be initialized before that consumer's hooks read it.
|
|
55
|
+
*/
|
|
56
|
+
private absorbLazyInstances;
|
|
45
57
|
callOnModuleInit(): Promise<void>;
|
|
46
58
|
callOnApplicationBootstrap(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Materialize every still-pending lazy module (async-safe): construct the
|
|
61
|
+
* groups, replay their lifecycle hooks, and add their instances to the
|
|
62
|
+
* shutdown flow. Warmup escape hatch for tests and node runtimes that want
|
|
63
|
+
* eager-everything semantics back after bootstrap.
|
|
64
|
+
*/
|
|
65
|
+
materializeLazyModules(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Every entrypoint contributed by the module graph, grouped by kind —
|
|
68
|
+
* what runtime adapters/transports query instead of re-scanning providers:
|
|
69
|
+
* `app.entrypoints.ofKind('websocket')`.
|
|
70
|
+
*/
|
|
71
|
+
get entrypoints(): EntrypointRegistry;
|
|
47
72
|
close(signal?: string): Promise<void>;
|
|
48
73
|
/**
|
|
49
74
|
* Full teardown: run shutdown lifecycle hooks ({@link close}) then dispose
|
package/dist/application.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { DiscoveryService } from "./discovery/discovery.service.js";
|
|
2
|
+
import { EntrypointRegistry } from "./entrypoint/entrypoint.registry.js";
|
|
3
|
+
import { LazyModuleManager } from "./module/lazy-modules.js";
|
|
1
4
|
import { hasBeforeApplicationShutdown, hasOnApplicationBootstrap, hasOnApplicationShutdown, hasOnModuleDestroy, hasOnModuleInit } from "./lifecycle/index.js";
|
|
2
5
|
import { renderScalarUi } from "./openapi/scalar-ui.js";
|
|
3
6
|
import { renderSwaggerUi } from "./openapi/swagger-ui.js";
|
|
@@ -7,10 +10,29 @@ export class VelaApplication {
|
|
|
7
10
|
routeManager;
|
|
8
11
|
instances = [];
|
|
9
12
|
honoApp = null;
|
|
13
|
+
entrypointRegistry = null;
|
|
10
14
|
disposed = false;
|
|
15
|
+
lazyManager;
|
|
16
|
+
// Identity guard for the instance flow: a token registered by BOTH a lazy
|
|
17
|
+
// and an eager module reaches us through the eager pass AND the absorbed
|
|
18
|
+
// batch — without dedup its hooks would run twice.
|
|
19
|
+
knownInstances = new Set();
|
|
11
20
|
constructor(container, routeManager){
|
|
12
21
|
this.container = container;
|
|
13
22
|
this.routeManager = routeManager;
|
|
23
|
+
// bootstrap() registers the manager; a hand-built container may not have
|
|
24
|
+
// one (container unit tests) — lazy semantics simply don't engage then.
|
|
25
|
+
this.lazyManager = container.has(LazyModuleManager) ? container.resolve(LazyModuleManager) : undefined;
|
|
26
|
+
// Live-phase materializations join the instance flow so close()/dispose()
|
|
27
|
+
// run shutdown hooks over them (LIFO — appended last, destroyed first).
|
|
28
|
+
this.lazyManager?.setOnMaterialized((instances)=>{
|
|
29
|
+
this.instances.push(...this.trackNew(instances));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
trackNew(batch) {
|
|
33
|
+
const fresh = batch.filter((i)=>!this.knownInstances.has(i));
|
|
34
|
+
for (const i of fresh)this.knownInstances.add(i);
|
|
35
|
+
return fresh;
|
|
14
36
|
}
|
|
15
37
|
/** Pre-build routes (handles async CRUD imports). Called by VelaFactory. */ async initRoutes() {
|
|
16
38
|
this.honoApp = await this.routeManager.build();
|
|
@@ -32,6 +54,8 @@ export class VelaApplication {
|
|
|
32
54
|
}
|
|
33
55
|
setInstances(instances) {
|
|
34
56
|
this.instances = instances;
|
|
57
|
+
this.knownInstances.clear();
|
|
58
|
+
for (const i of instances)this.knownInstances.add(i);
|
|
35
59
|
}
|
|
36
60
|
get(token) {
|
|
37
61
|
return this.container.resolve(token);
|
|
@@ -124,19 +148,96 @@ export class VelaApplication {
|
|
|
124
148
|
return this;
|
|
125
149
|
}
|
|
126
150
|
// Lifecycle hooks
|
|
151
|
+
/**
|
|
152
|
+
* Pull instances materialized during the bootstrap phase (lazy modules
|
|
153
|
+
* dragged in by eager consumers) into the front of the instance list —
|
|
154
|
+
* dependency-before-consumer: a group absorbed because an eager provider
|
|
155
|
+
* injected it must be initialized before that consumer's hooks read it.
|
|
156
|
+
*/ absorbLazyInstances(prepend) {
|
|
157
|
+
const batch = this.trackNew(this.lazyManager?.takeAbsorbed() ?? []);
|
|
158
|
+
if (batch.length === 0) return batch;
|
|
159
|
+
if (prepend) {
|
|
160
|
+
this.instances = [
|
|
161
|
+
...batch,
|
|
162
|
+
...this.instances
|
|
163
|
+
];
|
|
164
|
+
} else {
|
|
165
|
+
this.instances.push(...batch);
|
|
166
|
+
}
|
|
167
|
+
return batch;
|
|
168
|
+
}
|
|
127
169
|
async callOnModuleInit() {
|
|
128
|
-
|
|
170
|
+
this.absorbLazyInstances(true);
|
|
171
|
+
// Index loop: hooks can trigger further absorptions, which append —
|
|
172
|
+
// the loop naturally covers them.
|
|
173
|
+
for(let i = 0; i < this.instances.length; i++){
|
|
174
|
+
const instance = this.instances[i];
|
|
129
175
|
if (hasOnModuleInit(instance)) {
|
|
130
176
|
await instance.onModuleInit();
|
|
131
177
|
}
|
|
178
|
+
this.absorbLazyInstances(false);
|
|
132
179
|
}
|
|
133
180
|
}
|
|
134
181
|
async callOnApplicationBootstrap() {
|
|
135
|
-
for
|
|
182
|
+
for(let i = 0; i < this.instances.length; i++){
|
|
183
|
+
const instance = this.instances[i];
|
|
136
184
|
if (hasOnApplicationBootstrap(instance)) {
|
|
137
185
|
await instance.onApplicationBootstrap();
|
|
138
186
|
}
|
|
187
|
+
// Instances absorbed mid-phase already missed the init pass — run
|
|
188
|
+
// onModuleInit now; the loop then reaches them for the bootstrap hook.
|
|
189
|
+
for (const late of this.absorbLazyInstances(false)){
|
|
190
|
+
if (hasOnModuleInit(late)) await late.onModuleInit();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Computed-entrypoint contributors (ContributesEntrypoints) in lazy
|
|
194
|
+
// modules must exist before the snapshot below — materialize them now
|
|
195
|
+
// (the documented cost of contributing computed entrypoints), then run
|
|
196
|
+
// their hooks through the same absorb loop.
|
|
197
|
+
if (this.lazyManager) {
|
|
198
|
+
await this.lazyManager.materializeContributors();
|
|
199
|
+
let batch;
|
|
200
|
+
while((batch = this.absorbLazyInstances(false)).length > 0){
|
|
201
|
+
for (const late of batch){
|
|
202
|
+
if (hasOnModuleInit(late)) await late.onModuleInit();
|
|
203
|
+
}
|
|
204
|
+
for (const late of batch){
|
|
205
|
+
if (hasOnApplicationBootstrap(late)) await late.onApplicationBootstrap();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// From here on, materializations replay their hooks at the trigger.
|
|
209
|
+
this.lazyManager.setPhaseLive();
|
|
210
|
+
}
|
|
211
|
+
// Build the per-app entrypoint registry AFTER the hooks: dispatchers that
|
|
212
|
+
// implement ContributesEntrypoints (WsDispatcher) finish their own
|
|
213
|
+
// discovery inside onApplicationBootstrap. Built here — not in
|
|
214
|
+
// VelaFactory/initRoutes — so slim bootstrap paths that never build HTTP
|
|
215
|
+
// routes (the Cloudflare Durable Object) still get `app.entrypoints`.
|
|
216
|
+
// Lazy-pending providers of declared kinds yield metadata-only entries
|
|
217
|
+
// (instance: undefined) — dispatchers re-resolve by token per event,
|
|
218
|
+
// which materializes the owning module at dispatch time.
|
|
219
|
+
const discovery = this.container.has(DiscoveryService) ? this.container.resolve(DiscoveryService) : new DiscoveryService(this.container);
|
|
220
|
+
this.entrypointRegistry = await EntrypointRegistry.build(discovery, this.instances, {
|
|
221
|
+
deferLazy: true
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Materialize every still-pending lazy module (async-safe): construct the
|
|
226
|
+
* groups, replay their lifecycle hooks, and add their instances to the
|
|
227
|
+
* shutdown flow. Warmup escape hatch for tests and node runtimes that want
|
|
228
|
+
* eager-everything semantics back after bootstrap.
|
|
229
|
+
*/ async materializeLazyModules() {
|
|
230
|
+
await this.lazyManager?.materializeAll();
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Every entrypoint contributed by the module graph, grouped by kind —
|
|
234
|
+
* what runtime adapters/transports query instead of re-scanning providers:
|
|
235
|
+
* `app.entrypoints.ofKind('websocket')`.
|
|
236
|
+
*/ get entrypoints() {
|
|
237
|
+
if (!this.entrypointRegistry) {
|
|
238
|
+
throw new Error('Entrypoints are not built yet — they are assembled at the end of ' + 'callOnApplicationBootstrap(). Finish bootstrapping before querying them.');
|
|
139
239
|
}
|
|
240
|
+
return this.entrypointRegistry;
|
|
140
241
|
}
|
|
141
242
|
async close(signal) {
|
|
142
243
|
const reversed = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Scope } from '../constants';
|
|
2
|
-
import type { ContainerOptions, Diagnostics, ModuleScope, ProviderOptions, Token, Type } from './types';
|
|
2
|
+
import type { ContainerOptions, Diagnostics, LazyResolutionHook, ModuleScope, ProviderOptions, Token, Type } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Per-module provider buckets. Each module instance owns its providers under
|
|
5
5
|
* its `moduleId`; the same logical token can have distinct registrations in
|
|
@@ -19,7 +19,13 @@ export declare class Container {
|
|
|
19
19
|
private diagnostics;
|
|
20
20
|
private root;
|
|
21
21
|
private disposables;
|
|
22
|
+
private lazyHook?;
|
|
23
|
+
private asyncDepth;
|
|
22
24
|
constructor(options?: ContainerOptions);
|
|
25
|
+
/** Install the lazy-module seam (bootstrap-time; root container only). */
|
|
26
|
+
setLazyHook(hook: LazyResolutionHook): void;
|
|
27
|
+
private claimLazyModule;
|
|
28
|
+
private maybeDrainSync;
|
|
23
29
|
register<T>(provider: Type<T> | ProviderOptions<T>, declaringModuleId?: string): this;
|
|
24
30
|
private registerClass;
|
|
25
31
|
private registerOptions;
|
|
@@ -52,6 +58,34 @@ export declare class Container {
|
|
|
52
58
|
has(token: Token): boolean;
|
|
53
59
|
/** True if the specified module's bucket has a registration for `token`. */
|
|
54
60
|
hasInScope(token: Token, moduleId: string): boolean;
|
|
61
|
+
/** Module buckets that hold a registration for `token` (registration order). */
|
|
62
|
+
getOwnerModuleIds(token: Token): string[];
|
|
63
|
+
/**
|
|
64
|
+
* Force-replace a provider across module scopes — the supported form of the
|
|
65
|
+
* override loop test harnesses need (module-scoped resolution consults the
|
|
66
|
+
* module's own bucket first, so a root-only override would never win there).
|
|
67
|
+
*
|
|
68
|
+
* - `'all-existing'` (default): every non-root bucket that already holds the
|
|
69
|
+
* token, plus `__root__` (so framework-internal lookups see it too).
|
|
70
|
+
* - `'root'`: only the `__root__` bucket.
|
|
71
|
+
* - `string[]`: exactly these bucket ids.
|
|
72
|
+
*/
|
|
73
|
+
replaceProvider<T>(provider: Type<T> | ProviderOptions<T>, options?: {
|
|
74
|
+
buckets?: 'all-existing' | 'root' | string[];
|
|
75
|
+
}): this;
|
|
76
|
+
/**
|
|
77
|
+
* True when the token belongs exclusively to lazy modules that have not
|
|
78
|
+
* been materialized yet — resolving it would trigger materialization.
|
|
79
|
+
* Build-time probes (route-manager middleware priority, entrypoint
|
|
80
|
+
* snapshots) use this to defer instead of forcing the group.
|
|
81
|
+
*/
|
|
82
|
+
isLazyPending(token: Token): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* True when any registration of the token holds a constructed instance.
|
|
85
|
+
* Diagnostic helper (cold-start tests): checks WITHOUT resolving, so it
|
|
86
|
+
* never triggers lazy materialization.
|
|
87
|
+
*/
|
|
88
|
+
isInstantiated(token: Token): boolean;
|
|
55
89
|
getProviderScope(token: Token): Scope | undefined;
|
|
56
90
|
getTokens(): Token[];
|
|
57
91
|
/**
|
|
@@ -95,8 +129,19 @@ export declare class Container {
|
|
|
95
129
|
dispose(): Promise<void>;
|
|
96
130
|
private resolveRegistration;
|
|
97
131
|
private resolveClass;
|
|
132
|
+
/**
|
|
133
|
+
* Resolve one factory `inject` dependency. Since 1.11 the declaring module's
|
|
134
|
+
* scope is consulted FIRST (proper visibility semantics — a `forRootAsync`
|
|
135
|
+
* factory can inject providers reachable through its module's imports); on
|
|
136
|
+
* any failure the legacy no-requester lookup (root bucket, then first
|
|
137
|
+
* exporter) is kept as fallback, so every previously-resolving graph still
|
|
138
|
+
* resolves.
|
|
139
|
+
*/
|
|
140
|
+
private resolveFactoryDependency;
|
|
141
|
+
private resolveFactoryDependencyAsync;
|
|
98
142
|
private resolveFactory;
|
|
99
143
|
resolveAsync<T>(token: Token<T>, requestingModuleId?: string): Promise<T>;
|
|
144
|
+
private resolveAsyncInner;
|
|
100
145
|
private createLazyProxy;
|
|
101
146
|
private tokenToString;
|
|
102
147
|
}
|