@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.
Files changed (85) hide show
  1. package/CHANGELOG.md +157 -0
  2. package/dist/application.d.ts +25 -0
  3. package/dist/application.js +103 -2
  4. package/dist/container/container.d.ts +46 -1
  5. package/dist/container/container.js +156 -13
  6. package/dist/container/disposable.js +1 -1
  7. package/dist/container/types.d.ts +27 -0
  8. package/dist/cors/cors.module.d.ts +5 -5
  9. package/dist/cors/cors.module.js +26 -25
  10. package/dist/discovery/discoverable.decorator.d.ts +38 -0
  11. package/dist/discovery/discoverable.decorator.js +42 -0
  12. package/dist/discovery/discovery.service.d.ts +102 -0
  13. package/dist/discovery/discovery.service.js +177 -0
  14. package/dist/discovery/index.d.ts +2 -0
  15. package/dist/discovery/index.js +2 -0
  16. package/dist/entrypoint/entrypoint.registry.d.ts +42 -0
  17. package/dist/entrypoint/entrypoint.registry.js +112 -0
  18. package/dist/entrypoint/entrypoint.types.d.ts +41 -0
  19. package/dist/entrypoint/entrypoint.types.js +3 -0
  20. package/dist/entrypoint/execution-context.d.ts +15 -0
  21. package/dist/entrypoint/execution-context.js +21 -0
  22. package/dist/entrypoint/execution-scope.d.ts +21 -0
  23. package/dist/entrypoint/execution-scope.js +28 -0
  24. package/dist/entrypoint/index.d.ts +4 -0
  25. package/dist/entrypoint/index.js +4 -0
  26. package/dist/event-emitter/event-emitter.module.js +1 -0
  27. package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
  28. package/dist/event-emitter/event-emitter.subscriber.js +12 -29
  29. package/dist/factory/adapter.d.ts +36 -0
  30. package/dist/factory/adapter.js +18 -0
  31. package/dist/factory/bootstrap.js +14 -2
  32. package/dist/factory.d.ts +11 -1
  33. package/dist/factory.js +23 -2
  34. package/dist/http/handler-executor.js +22 -19
  35. package/dist/http/route-contributor.d.ts +68 -0
  36. package/dist/http/route-contributor.js +22 -0
  37. package/dist/http/route.manager.js +33 -19
  38. package/dist/i18n/i18n.module.js +9 -1
  39. package/dist/index.d.ts +12 -3
  40. package/dist/index.js +9 -2
  41. package/dist/internal.d.ts +0 -2
  42. package/dist/internal.js +0 -7
  43. package/dist/module/configurable-module.builder.d.ts +12 -0
  44. package/dist/module/configurable-module.builder.js +21 -114
  45. package/dist/module/decorators.js +4 -2
  46. package/dist/module/define-module.d.ts +112 -0
  47. package/dist/module/define-module.js +232 -0
  48. package/dist/module/index.d.ts +3 -1
  49. package/dist/module/index.js +3 -1
  50. package/dist/module/lazy-modules.d.ts +82 -0
  51. package/dist/module/lazy-modules.js +231 -0
  52. package/dist/module/lazy-provider.d.ts +65 -0
  53. package/dist/module/lazy-provider.js +111 -0
  54. package/dist/module/module-loader.d.ts +21 -0
  55. package/dist/module/module-loader.js +99 -24
  56. package/dist/openapi/document.js +16 -15
  57. package/dist/pipeline/component.manager.d.ts +15 -9
  58. package/dist/pipeline/component.manager.js +32 -46
  59. package/dist/pipeline/index.d.ts +2 -0
  60. package/dist/pipeline/index.js +1 -0
  61. package/dist/pipeline/pipeline-runner.d.ts +31 -0
  62. package/dist/pipeline/pipeline-runner.js +40 -0
  63. package/dist/registry/metadata.registry.d.ts +9 -3
  64. package/dist/registry/metadata.registry.js +55 -25
  65. package/dist/registry/types.d.ts +10 -0
  66. package/dist/schedule/schedule.module.d.ts +5 -0
  67. package/dist/schedule/schedule.module.js +21 -8
  68. package/dist/schedule/schedule.registry.d.ts +3 -3
  69. package/dist/schedule/schedule.registry.js +23 -44
  70. package/dist/schedule-node/schedule-node.module.d.ts +5 -0
  71. package/dist/schedule-node/schedule-node.module.js +20 -10
  72. package/dist/seeder/seeder.module.d.ts +8 -10
  73. package/dist/seeder/seeder.module.js +19 -15
  74. package/dist/websocket/index.d.ts +1 -1
  75. package/dist/websocket/index.js +1 -1
  76. package/dist/websocket/websocket.module.d.ts +20 -3
  77. package/dist/websocket/websocket.module.js +50 -39
  78. package/dist/websocket/websocket.tokens.d.ts +2 -1
  79. package/dist/websocket/websocket.tokens.js +3 -2
  80. package/dist/websocket/ws-dispatcher.d.ts +21 -9
  81. package/dist/websocket/ws-dispatcher.js +44 -38
  82. package/dist/websocket-node/register-gateways.js +3 -3
  83. package/package.json +1 -1
  84. package/dist/http/crud-bridge.d.ts +0 -90
  85. 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
@@ -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
@@ -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
- for (const instance of this.instances){
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 (const instance of this.instances){
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
  }