Skip to main content

nestrs_core/
lib.rs

1use std::any::{Any, TypeId};
2use std::collections::{HashMap, HashSet};
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::Arc;
6use std::sync::OnceLock;
7use std::sync::RwLock;
8
9use async_trait::async_trait;
10use axum::Router;
11
12mod admin_snapshot;
13pub mod als;
14pub mod client_ip;
15mod database;
16mod discovery;
17mod execution_context;
18mod guard;
19mod metadata;
20mod module_ref;
21mod pipe;
22mod platform;
23mod route_registry;
24mod strategy;
25mod trace;
26
27#[cfg(feature = "sse")]
28pub mod sse;
29
30pub use admin_snapshot::AdminSnapshot;
31pub use als::{AlsContext, AlsError};
32pub use client_ip::{
33    best_effort_client_ip, rate_limit_key_ip_or_unknown, trusted_hops_from_parts, RateLimitKey,
34    TrustedProxyHops, X_FORWARDED_FOR, X_REAL_IP,
35};
36pub use database::DatabasePing;
37pub use discovery::DiscoveryService;
38pub use execution_context::{ExecutionContext, HostType, HttpExecutionArguments};
39pub use guard::{CanActivate, GuardError};
40pub use metadata::MetadataRegistry;
41pub use module_ref::ModuleRef;
42pub use pipe::{HttpPipeTransform, PipeTransform};
43pub use platform::{AxumHttpEngine, HttpServerEngine};
44pub use route_registry::{OpenApiResponseDesc, OpenApiRouteSpec, RouteInfo, RouteRegistry};
45pub use strategy::{AuthError, AuthStrategy};
46pub use trace::{current_trace_context, parse_traceparent, with_trace_context, TraceContext};
47
48#[cfg(feature = "sse")]
49pub use sse::{serialize_to_event, IntoSseEvent, SseEvent, SseKeepAlive, SseResponse};
50
51type CustomFactoryFn =
52    std::sync::Arc<dyn Fn(&ProviderRegistry) -> Arc<dyn Any + Send + Sync> + Send + Sync>;
53
54/// Provider lifetime semantics (NestJS `Scope.DEFAULT` / `Scope.TRANSIENT` / `Scope.REQUEST` analogues).
55///
56/// Set per type via `#[injectable(scope = "singleton" | "transient" | "request")]` or pass to
57/// [`ProviderRegistry::register_use_factory`]. **Request** scope requires the app to call
58/// [`nestrs::NestApplication::use_request_scope`](https://docs.rs/nestrs/latest/nestrs/struct.NestApplication.html#method.use_request_scope).
59///
60/// **Docs:** mdBook **Fundamentals** in the repository (`docs/src/fundamentals.md`).
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub enum ProviderScope {
63    /// One instance per application container (default).
64    Singleton,
65    /// A new instance is created on every injection site / resolution.
66    Transient,
67    /// One instance per request/task scope (requires request-scope middleware).
68    Request,
69}
70
71/// A lightweight `(name, scope)` view of a registered provider, suitable
72/// for serialization. Returned by [`ProviderRegistry::provider_summaries`].
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub struct ProviderSummary {
75    pub type_name: &'static str,
76    pub scope: ProviderScope,
77}
78
79#[derive(Clone)]
80enum ProviderFactory {
81    InjectableFn(fn(&ProviderRegistry) -> Arc<dyn Any + Send + Sync>),
82    Custom(CustomFactoryFn),
83}
84
85#[derive(Clone)]
86struct ProviderEntry {
87    type_name: &'static str,
88    scope: ProviderScope,
89    factory: ProviderFactory,
90    instance: Arc<OnceLock<Arc<dyn Any + Send + Sync>>>,
91    on_module_init: HookFn,
92    on_module_destroy: HookFn,
93    on_application_bootstrap: HookFn,
94    /// NestJS `beforeApplicationShutdown`: fires before `onApplicationShutdown` /
95    /// `onModuleDestroy` during graceful shutdown.
96    on_before_application_shutdown: HookFn,
97    on_application_shutdown: HookFn,
98}
99
100fn noop_hook<'a>(_registry: &'a ProviderRegistry) -> HookFuture<'a> {
101    Box::pin(async {})
102}
103
104fn create_entry_for_injectable<T: Injectable + Send + Sync + 'static>() -> ProviderEntry {
105    fn factory<T: Injectable + Send + Sync + 'static>(
106        registry: &ProviderRegistry,
107    ) -> Arc<dyn Any + Send + Sync> {
108        T::construct(registry)
109    }
110
111    ProviderEntry {
112        type_name: std::any::type_name::<T>(),
113        scope: T::scope(),
114        factory: ProviderFactory::InjectableFn(factory::<T>),
115        instance: Arc::new(OnceLock::new()),
116        on_module_init: hook_on_module_init::<T>,
117        on_module_destroy: hook_on_module_destroy::<T>,
118        on_application_bootstrap: hook_on_application_bootstrap::<T>,
119        on_before_application_shutdown: hook_on_before_application_shutdown::<T>,
120        on_application_shutdown: hook_on_application_shutdown::<T>,
121    }
122}
123
124pub struct ProviderRegistry {
125    entries: HashMap<TypeId, ProviderEntry>,
126    /// Registration order of providers. Iteration over [`Self::entries`] alone is nondeterministic
127    /// (HashMap), so lifecycle hooks and discovery use this order for stable startup/shutdown.
128    order: Vec<TypeId>,
129}
130
131/// Per-request handle identifying the matched handler (used for metadata lookups).
132#[derive(Clone, Copy, Debug)]
133pub struct HandlerKey(pub &'static str);
134
135impl ProviderRegistry {
136    pub fn new() -> Self {
137        Self {
138            entries: HashMap::new(),
139            order: Vec::new(),
140        }
141    }
142
143    fn insert_entry(&mut self, type_id: TypeId, entry: ProviderEntry) {
144        if !self.entries.contains_key(&type_id) {
145            self.order.push(type_id);
146        }
147        self.entries.insert(type_id, entry);
148    }
149
150    pub fn register<T>(&mut self)
151    where
152        T: Injectable + Send + Sync + 'static,
153    {
154        self.insert_entry(TypeId::of::<T>(), create_entry_for_injectable::<T>());
155    }
156
157    /// NestJS **`useValue`**: register a pre-built singleton without an [`Injectable`] impl.
158    ///
159    /// Lifecycle hooks do **not** run for this registration (any `T` is accepted, so there is no
160    /// hook impl to call). If `T` implements [`ProviderLifecycle`], use
161    /// [`Self::register_use_value_with_lifecycle`] to have the framework drive its hooks.
162    pub fn register_use_value<T: Send + Sync + 'static>(&mut self, value: Arc<T>) {
163        let preset: Arc<dyn Any + Send + Sync> = value;
164        let cell = Arc::new(OnceLock::new());
165        let _ = cell.set(preset.clone());
166        self.insert_entry(
167            TypeId::of::<T>(),
168            ProviderEntry {
169                type_name: std::any::type_name::<T>(),
170                scope: ProviderScope::Singleton,
171                factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
172                instance: cell,
173                on_module_init: noop_hook,
174                on_module_destroy: noop_hook,
175                on_application_bootstrap: noop_hook,
176                on_before_application_shutdown: noop_hook,
177                on_application_shutdown: noop_hook,
178            },
179        );
180    }
181
182    /// NestJS **`useFactory`**: register a provider from a **synchronous** closure `Fn(&ProviderRegistry) -> Arc<T>`.
183    ///
184    /// The closure may call [`Self::get`] for dependencies. For **async** initialization of `T`, keep
185    /// `construct`/`factory` cheap and use [`Injectable::on_module_init`] on `T`, or load **module
186    /// options** with [`ConfigurableModuleBuilder::for_root_async`]. Do **not** block the async
187    /// runtime inside the factory.
188    ///
189    /// Prefer [`Self::register`] when the provider is a normal `#[injectable]` type.
190    ///
191    /// Lifecycle hooks do **not** run for this registration (any `T` is accepted, so there is no
192    /// hook impl to call). If `T` implements [`ProviderLifecycle`], use
193    /// [`Self::register_use_factory_with_lifecycle`] to have the framework drive its hooks.
194    pub fn register_use_factory<T, F>(&mut self, scope: ProviderScope, factory: F)
195    where
196        T: Send + Sync + 'static,
197        F: Fn(&ProviderRegistry) -> Arc<T> + Send + Sync + 'static,
198    {
199        let factory: std::sync::Arc<F> = std::sync::Arc::new(factory);
200        let factory = factory.clone();
201        self.insert_entry(
202            TypeId::of::<T>(),
203            ProviderEntry {
204                type_name: std::any::type_name::<T>(),
205                scope,
206                factory: ProviderFactory::Custom(Arc::new(move |r| {
207                    let v = factory(r);
208                    v as Arc<dyn Any + Send + Sync>
209                })),
210                instance: Arc::new(OnceLock::new()),
211                on_module_init: noop_hook,
212                on_module_destroy: noop_hook,
213                on_application_bootstrap: noop_hook,
214                on_before_application_shutdown: noop_hook,
215                on_application_shutdown: noop_hook,
216            },
217        );
218    }
219
220    /// NestJS **`useValue`** with lifecycle hooks: like [`Self::register_use_value`], but the
221    /// singleton's [`ProviderLifecycle`] hooks are driven by the framework (module init/destroy,
222    /// application bootstrap/shutdown) in the same order as [`Injectable`] hooks.
223    pub fn register_use_value_with_lifecycle<T>(&mut self, value: Arc<T>)
224    where
225        T: ProviderLifecycle + Send + Sync + 'static,
226    {
227        let preset: Arc<dyn Any + Send + Sync> = value;
228        let cell = Arc::new(OnceLock::new());
229        let _ = cell.set(preset.clone());
230        self.insert_entry(
231            TypeId::of::<T>(),
232            ProviderEntry {
233                type_name: std::any::type_name::<T>(),
234                scope: ProviderScope::Singleton,
235                factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
236                instance: cell,
237                on_module_init: lifecycle_on_module_init::<T>,
238                on_module_destroy: lifecycle_on_module_destroy::<T>,
239                on_application_bootstrap: lifecycle_on_application_bootstrap::<T>,
240                on_before_application_shutdown: lifecycle_on_before_application_shutdown::<T>,
241                on_application_shutdown: lifecycle_on_application_shutdown::<T>,
242            },
243        );
244    }
245
246    /// NestJS **`useFactory`** with lifecycle hooks: like [`Self::register_use_factory`], but
247    /// `T`'s [`ProviderLifecycle`] hooks are driven by the framework. Hooks run only while `T`
248    /// is **singleton-scoped** (matching [`Injectable`] providers: request/transient instances
249    /// have no framework-driven lifecycle).
250    pub fn register_use_factory_with_lifecycle<T, F>(&mut self, scope: ProviderScope, factory: F)
251    where
252        T: ProviderLifecycle + Send + Sync + 'static,
253        F: Fn(&ProviderRegistry) -> Arc<T> + Send + Sync + 'static,
254    {
255        let factory: std::sync::Arc<F> = std::sync::Arc::new(factory);
256        let factory = factory.clone();
257        self.insert_entry(
258            TypeId::of::<T>(),
259            ProviderEntry {
260                type_name: std::any::type_name::<T>(),
261                scope,
262                factory: ProviderFactory::Custom(Arc::new(move |r| {
263                    let v = factory(r);
264                    v as Arc<dyn Any + Send + Sync>
265                })),
266                instance: Arc::new(OnceLock::new()),
267                on_module_init: lifecycle_on_module_init::<T>,
268                on_module_destroy: lifecycle_on_module_destroy::<T>,
269                on_application_bootstrap: lifecycle_on_application_bootstrap::<T>,
270                on_before_application_shutdown: lifecycle_on_before_application_shutdown::<T>,
271                on_application_shutdown: lifecycle_on_application_shutdown::<T>,
272            },
273        );
274    }
275
276    /// NestJS **`useClass`**: equivalent to [`Self::register`] for a normal injectable type.
277    #[inline]
278    pub fn register_use_class<T>(&mut self)
279    where
280        T: Injectable + Send + Sync + 'static,
281    {
282        self.register::<T>();
283    }
284
285    /// Override a provider with a concrete instance (testing utility).
286    ///
287    /// This is primarily intended for `TestingModule`-style overrides where you want to replace an
288    /// injectable with a mock instance.
289    ///
290    /// The override preserves the provider's declared scope (`T::scope()`): a request- or
291    /// transient-scoped provider keeps its per-request / per-resolution semantics, and every
292    /// resolution hands out the given instance — an override explicitly targets one concrete
293    /// object, so request-scoped overrides share that instance across requests.
294    pub fn override_provider<T>(&mut self, instance: Arc<T>)
295    where
296        T: Injectable + Send + Sync + 'static,
297    {
298        let preset: Arc<dyn Any + Send + Sync> = instance;
299        let instance_cell = Arc::new(OnceLock::new());
300        let _ = instance_cell.set(preset.clone());
301        let entry = ProviderEntry {
302            type_name: std::any::type_name::<T>(),
303            scope: T::scope(),
304            // A real factory (not a placeholder) so request/transient resolutions
305            // return the override instead of panicking; the preset cell serves
306            // the singleton path directly.
307            factory: ProviderFactory::Custom(Arc::new(move |_| preset.clone())),
308            instance: instance_cell,
309            on_module_init: hook_on_module_init::<T>,
310            on_module_destroy: hook_on_module_destroy::<T>,
311            on_application_bootstrap: hook_on_application_bootstrap::<T>,
312            on_before_application_shutdown: hook_on_before_application_shutdown::<T>,
313            on_application_shutdown: hook_on_application_shutdown::<T>,
314        };
315
316        self.insert_entry(TypeId::of::<T>(), entry);
317    }
318
319    /// Produce an instance for `entry`, or `None` when it cannot be resolved
320    /// here — a `Request`-scoped provider resolved outside any request scope
321    /// (e.g. from a bare `tokio::spawn` background task). This is what makes
322    /// [`Self::try_get`] honor its "returns `None` instead of panicking"
323    /// contract for the scope case as well as the not-registered case.
324    fn produce_any(
325        &self,
326        type_id: TypeId,
327        entry: &ProviderEntry,
328    ) -> Option<Arc<dyn Any + Send + Sync>> {
329        match entry.scope {
330            ProviderScope::Singleton => {
331                let _guard = ConstructionGuard::push(type_id, entry.type_name);
332                Some(
333                    entry
334                        .instance
335                        .get_or_init(|| match &entry.factory {
336                            ProviderFactory::InjectableFn(f) => f(self),
337                            ProviderFactory::Custom(f) => f(self),
338                        })
339                        .clone(),
340                )
341            }
342            ProviderScope::Transient => {
343                let _guard = ConstructionGuard::push(type_id, entry.type_name);
344                Some(match &entry.factory {
345                    ProviderFactory::InjectableFn(f) => f(self),
346                    ProviderFactory::Custom(f) => f(self),
347                })
348            }
349            ProviderScope::Request => {
350                let _guard = ConstructionGuard::push(type_id, entry.type_name);
351                // No scope on this task (bare `tokio::spawn`, a standalone
352                // runtime, lifecycle-hook runners): resolution is impossible,
353                // not a construction failure — surface `None` and let the
354                // caller decide (`try_get`) or name the fix (`get`).
355                REQUEST_SCOPE_CACHE
356                    .try_with(|cell| {
357                        if let Some(existing) = cell.borrow().get(&type_id).cloned() {
358                            return existing;
359                        }
360                        let value = match &entry.factory {
361                            ProviderFactory::InjectableFn(f) => f(self),
362                            ProviderFactory::Custom(f) => f(self),
363                        };
364                        cell.borrow_mut().insert(type_id, value.clone());
365                        value
366                    })
367                    .ok()
368            }
369        }
370    }
371
372    /// Resolves a provider, panicking when it cannot be resolved. Prefer
373    /// [`Self::try_get`] at call sites that can handle absence.
374    ///
375    /// Panics with a distinct, actionable message when a `Request`-scoped
376    /// provider is resolved outside a request scope (e.g. from a background
377    /// task spawned with bare `tokio::spawn`) — run such work through
378    /// [`spawn_with_request_scope`] instead.
379    ///
380    /// When called **during** another provider's construction (inside `construct` or a
381    /// `useFactory` closure), the edge `constructor -> requested` is recorded so lifecycle
382    /// hooks can run in dependency order (see [`Self::run_on_module_init`]).
383    pub fn get<T>(&self) -> Arc<T>
384    where
385        T: Send + Sync + 'static,
386    {
387        self.try_get::<T>().unwrap_or_else(|| {
388            // try_get yields None for two distinct reasons; name the right one.
389            if let Some(entry) = self.entries.get(&TypeId::of::<T>()) {
390                if matches!(entry.scope, ProviderScope::Request) {
391                    panic!(
392                        "Request-scoped provider `{}` requested outside a request scope; \
393                         enable request scope middleware (`use_request_scope`), spawn \
394                         background work with `spawn_with_request_scope`, or use \
395                         `try_get` to handle absence gracefully",
396                        entry.type_name
397                    );
398                }
399            }
400            panic!("Provider `{}` not registered", std::any::type_name::<T>())
401        })
402    }
403
404    /// Fallible resolution: returns `None` instead of panicking when the
405    /// provider cannot be resolved — either it is not registered, or it is
406    /// `Request`-scoped and this task has no request scope (a bare
407    /// `tokio::spawn` background task, a standalone runtime context). Use
408    /// [`spawn_with_request_scope`] to give background work a scope.
409    pub fn try_get<T>(&self) -> Option<Arc<T>>
410    where
411        T: Send + Sync + 'static,
412    {
413        let type_id = TypeId::of::<T>();
414        let entry = self.entries.get(&type_id)?;
415
416        if let Some(parent) =
417            CONSTRUCTION_STACK.with(|stack| stack.borrow().last().map(|(_, id)| *id))
418        {
419            record_provider_dependency(parent, type_id);
420        }
421
422        let any = self.produce_any(type_id, entry)?;
423
424        any.downcast::<T>().ok()
425    }
426
427    /// All registered provider [`TypeId`] keys (NestJS discovery-style introspection), in registration order.
428    pub fn registered_type_ids(&self) -> Vec<TypeId> {
429        self.order.clone()
430    }
431
432    /// Human-readable type names for registered providers (debug / tooling), in registration order.
433    pub fn registered_type_names(&self) -> Vec<&'static str> {
434        self.order
435            .iter()
436            .filter_map(|id| self.entries.get(id).map(|e| e.type_name))
437            .collect()
438    }
439
440    /// Pair of `(type_name, scope)` for every registered provider, in
441    /// registration order. Powers the `nestrs::admin` sidecar's
442    /// `GET /__nestrs/providers` endpoint and `nestrs-mcp`'s
443    /// `get_app_providers` tool.
444    pub fn provider_summaries(&self) -> Vec<ProviderSummary> {
445        self.order
446            .iter()
447            .filter_map(|id| {
448                self.entries.get(id).map(|e| ProviderSummary {
449                    type_name: e.type_name,
450                    scope: e.scope,
451                })
452            })
453            .collect()
454    }
455
456    pub fn absorb(&mut self, other: ProviderRegistry) {
457        let ProviderRegistry { entries, order } = other;
458        let mut leftover = entries;
459        for type_id in order {
460            if let Some(entry) = leftover.remove(&type_id) {
461                self.insert_entry(type_id, entry);
462            }
463        }
464        for (type_id, entry) in leftover {
465            self.insert_entry(type_id, entry);
466        }
467    }
468
469    pub fn absorb_exported(&mut self, mut other: ProviderRegistry, exported: &[TypeId]) {
470        if exported.is_empty() {
471            return;
472        }
473        let allow = exported.iter().copied().collect::<HashSet<_>>();
474        // Preserve the source registry's registration order (every entry is tracked in `order`).
475        for type_id in std::mem::take(&mut other.order) {
476            if allow.contains(&type_id) {
477                if let Some(entry) = other.entries.remove(&type_id) {
478                    self.insert_entry(type_id, entry);
479                }
480            }
481        }
482    }
483
484    /// Like [`Self::absorb_exported`], but clones bindings from `other` so the source registry is kept intact
485    /// (used for lazy modules and shared provider cells).
486    pub fn absorb_exported_from(&mut self, other: &ProviderRegistry, exported: &[TypeId]) {
487        if exported.is_empty() {
488            return;
489        }
490        let allow = exported.iter().copied().collect::<HashSet<_>>();
491        for type_id in &other.order {
492            if allow.contains(type_id) {
493                if let Some(entry) = other.entries.get(type_id) {
494                    self.insert_entry(*type_id, entry.clone());
495                }
496            }
497        }
498    }
499
500    /// Construct all singleton providers (so their lifecycle hooks can run deterministically),
501    /// in registration order.
502    pub fn eager_init_singletons(&self) {
503        for type_id in &self.order {
504            let Some(entry) = self.entries.get(type_id) else {
505                continue;
506            };
507            if entry.scope == ProviderScope::Singleton {
508                let _guard = ConstructionGuard::push(*type_id, entry.type_name);
509                let _ = entry.instance.get_or_init(|| match &entry.factory {
510                    ProviderFactory::InjectableFn(f) => f(self),
511                    ProviderFactory::Custom(f) => f(self),
512                });
513            }
514        }
515    }
516
517    /// Registration-ordered, dependency-sorted [`TypeId`]s of all **singleton** providers.
518    ///
519    /// Ordering: construction dependencies recorded by [`Self::get`] are respected first
520    /// (dependencies initialize before dependents); ties fall back to registration order.
521    /// Providers involved in a hook-time cycle are appended in registration order.
522    fn ordered_singletons(&self) -> Vec<TypeId> {
523        let singletons: HashSet<TypeId> = self
524            .order
525            .iter()
526            .filter(|id| {
527                self.entries
528                    .get(id)
529                    .is_some_and(|e| e.scope == ProviderScope::Singleton)
530            })
531            .copied()
532            .collect();
533
534        // The graph records `constructor -> dependency` (the DEPENDENT first); the
535        // sort needs `dep -> dependent` (dependencies initialize first). Only edges
536        // between registered singletons participate (edges to transient/request types
537        // or types from other registries are ignored).
538        let deps = provider_dep_graph().read().expect("provider dep graph");
539        let mut incoming: HashMap<TypeId, usize> =
540            singletons.iter().map(|id| (*id, 0usize)).collect();
541        let mut adjacency: HashMap<TypeId, Vec<TypeId>> = HashMap::new();
542        for (from, targets) in deps.iter() {
543            if !singletons.contains(from) {
544                continue;
545            }
546            for to in targets {
547                if singletons.contains(to) {
548                    adjacency.entry(*to).or_default().push(*from);
549                    *incoming.entry(*from).or_insert(0) += 1;
550                }
551            }
552        }
553        drop(deps);
554
555        // Kahn's algorithm; among ready nodes pick the earliest registration order for stability.
556        use std::cmp::Reverse;
557        let position: HashMap<&TypeId, usize> = self
558            .order
559            .iter()
560            .enumerate()
561            .map(|(i, id)| (id, i))
562            .collect();
563        let mut ready: std::collections::BinaryHeap<Reverse<usize>> = singletons
564            .iter()
565            .filter(|id| incoming[id] == 0)
566            .map(|id| Reverse(position[id]))
567            .collect();
568
569        let mut sorted = Vec::with_capacity(singletons.len());
570        let mut visited = HashSet::new();
571        while let Some(Reverse(pos)) = ready.pop() {
572            let id = self.order[pos];
573            visited.insert(id);
574            sorted.push(id);
575            if let Some(dependents) = adjacency.get(&id) {
576                for to in dependents {
577                    let e = incoming.get_mut(to).expect("edge target tracked");
578                    *e -= 1;
579                    if *e == 0 && !visited.contains(to) {
580                        ready.push(Reverse(position[to]));
581                    }
582                }
583            }
584        }
585
586        // Cycle fallback: append anything not reached, in registration order.
587        for id in &self.order {
588            if singletons.contains(id) && !visited.contains(id) {
589                sorted.push(*id);
590            }
591        }
592        sorted
593    }
594
595    pub async fn run_on_module_init(&self) {
596        for type_id in self.ordered_singletons() {
597            if let Some(entry) = self.entries.get(&type_id) {
598                (entry.on_module_init)(self).await;
599            }
600        }
601    }
602
603    /// Destroy hooks run in **reverse** initialization order (dependencies torn down after dependents).
604    pub async fn run_on_module_destroy(&self) {
605        for type_id in self.ordered_singletons().into_iter().rev() {
606            if let Some(entry) = self.entries.get(&type_id) {
607                (entry.on_module_destroy)(self).await;
608            }
609        }
610    }
611
612    pub async fn run_on_application_bootstrap(&self) {
613        for type_id in self.ordered_singletons() {
614            if let Some(entry) = self.entries.get(&type_id) {
615                (entry.on_application_bootstrap)(self).await;
616            }
617        }
618    }
619
620    /// NestJS `beforeApplicationShutdown` mirror: fires BEFORE `run_on_application_shutdown` and
621    /// `run_on_module_destroy` during graceful shutdown. Reverse initialization order so that
622    /// providers holding resources release them after their dependents have shut down.
623    pub async fn run_on_before_application_shutdown(&self) {
624        for type_id in self.ordered_singletons().into_iter().rev() {
625            if let Some(entry) = self.entries.get(&type_id) {
626                (entry.on_before_application_shutdown)(self).await;
627            }
628        }
629    }
630
631    /// Shutdown hooks run in **reverse** initialization order (dependencies torn down after dependents).
632    pub async fn run_on_application_shutdown(&self) {
633        for type_id in self.ordered_singletons().into_iter().rev() {
634            if let Some(entry) = self.entries.get(&type_id) {
635                (entry.on_application_shutdown)(self).await;
636            }
637        }
638    }
639}
640
641impl Clone for ProviderRegistry {
642    fn clone(&self) -> Self {
643        Self {
644            entries: self.entries.clone(),
645            order: self.order.clone(),
646        }
647    }
648}
649
650impl Default for ProviderRegistry {
651    fn default() -> Self {
652        Self::new()
653    }
654}
655
656type HookFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
657type HookFn = for<'a> fn(&'a ProviderRegistry) -> HookFuture<'a>;
658
659fn hook_on_module_init<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
660where
661    T: Injectable + Send + Sync + 'static,
662{
663    Box::pin(async move {
664        let v = registry.get::<T>();
665        v.on_module_init().await;
666    })
667}
668
669fn hook_on_module_destroy<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
670where
671    T: Injectable + Send + Sync + 'static,
672{
673    Box::pin(async move {
674        let v = registry.get::<T>();
675        v.on_module_destroy().await;
676    })
677}
678
679fn hook_on_application_bootstrap<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
680where
681    T: Injectable + Send + Sync + 'static,
682{
683    Box::pin(async move {
684        let v = registry.get::<T>();
685        v.on_application_bootstrap().await;
686    })
687}
688
689fn hook_on_before_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
690where
691    T: Injectable + Send + Sync + 'static,
692{
693    Box::pin(async move {
694        let v = registry.get::<T>();
695        v.on_before_application_shutdown().await;
696    })
697}
698
699fn hook_on_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
700where
701    T: Injectable + Send + Sync + 'static,
702{
703    Box::pin(async move {
704        let v = registry.get::<T>();
705        v.on_application_shutdown().await;
706    })
707}
708
709/// [`ProviderLifecycle`] twin of [`hook_on_module_init`]: resolves the provider and drives its
710/// hook. Works for both value (preset cell) and factory (lazily built) singletons.
711fn lifecycle_on_module_init<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
712where
713    T: ProviderLifecycle + Send + Sync + 'static,
714{
715    Box::pin(async move {
716        let v = registry.get::<T>();
717        v.on_module_init().await;
718    })
719}
720
721fn lifecycle_on_module_destroy<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
722where
723    T: ProviderLifecycle + Send + Sync + 'static,
724{
725    Box::pin(async move {
726        let v = registry.get::<T>();
727        v.on_module_destroy().await;
728    })
729}
730
731fn lifecycle_on_application_bootstrap<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
732where
733    T: ProviderLifecycle + Send + Sync + 'static,
734{
735    Box::pin(async move {
736        let v = registry.get::<T>();
737        v.on_application_bootstrap().await;
738    })
739}
740
741fn lifecycle_on_before_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
742where
743    T: ProviderLifecycle + Send + Sync + 'static,
744{
745    Box::pin(async move {
746        let v = registry.get::<T>();
747        v.on_before_application_shutdown().await;
748    })
749}
750
751fn lifecycle_on_application_shutdown<'a, T>(registry: &'a ProviderRegistry) -> HookFuture<'a>
752where
753    T: ProviderLifecycle + Send + Sync + 'static,
754{
755    Box::pin(async move {
756        let v = registry.get::<T>();
757        v.on_application_shutdown().await;
758    })
759}
760
761/// Application service or provider type constructed through the DI container.
762///
763/// **`construct` is synchronous.** Perform async I/O in [`Self::on_module_init`] or after you have
764/// an `Arc<Self>` from the registry. Lifecycle hooks run for **singleton** providers when the
765/// framework drives [`ProviderRegistry::run_on_module_init`] and related methods (see `NestFactory` / `listen`).
766///
767/// **Scopes:** override [`Self::scope`] via `#[injectable(scope = "...")]`.
768///
769/// **Docs:** mdBook **Fundamentals** (`docs/src/fundamentals.md`).
770#[async_trait]
771pub trait Injectable: Send + Sync + 'static {
772    fn construct(registry: &ProviderRegistry) -> Arc<Self>;
773
774    /// Provider scope used when the module registers this type.
775    fn scope() -> ProviderScope {
776        ProviderScope::Singleton
777    }
778
779    async fn on_module_init(&self) {}
780    async fn on_module_destroy(&self) {}
781    async fn on_application_bootstrap(&self) {}
782    /// NestJS `beforeApplicationShutdown`: fires before `on_application_shutdown` /
783    /// `on_module_destroy` during graceful shutdown. Use to flush caches, close
784    /// long-lived connections, or release resources while dependents are still alive.
785    async fn on_before_application_shutdown(&self) {}
786    async fn on_application_shutdown(&self) {}
787}
788
789/// Lifecycle hooks for **value / factory** providers — the NestJS pattern of a
790/// `useValue`/`useFactory` object implementing `OnModuleInit` & friends.
791///
792/// NestJS calls lifecycle interfaces on any provider object that implements them, no matter how
793/// the provider was declared. Rust has no specialization, so
794/// [`ProviderRegistry::register_use_value`] / [`ProviderRegistry::register_use_factory`] — which
795/// accept any `T: Send + Sync + 'static` — cannot detect hook impls and never run hooks. Implement
796/// this trait instead and register through [`ProviderRegistry::register_use_value_with_lifecycle`]
797/// or [`ProviderRegistry::register_use_factory_with_lifecycle`]: the framework then drives the
798/// hooks for **singleton** providers in the same dependency/registration order as [`Injectable`]
799/// hooks (destroy/shutdown hooks reversed).
800///
801/// If `T` already implements [`Injectable`], its own hooks fire when registered via
802/// [`ProviderRegistry::register`] — this trait is only for the custom-provider paths.
803///
804/// **Docs:** mdBook **Fundamentals** in the repository (`docs/src/fundamentals.md`).
805#[async_trait]
806pub trait ProviderLifecycle: Send + Sync + 'static {
807    async fn on_module_init(&self) {}
808    async fn on_module_destroy(&self) {}
809    async fn on_application_bootstrap(&self) {}
810    /// NestJS `beforeApplicationShutdown`: fires before [`Self::on_application_shutdown`] /
811    /// [`Self::on_module_destroy`] during graceful shutdown. Use to flush caches, close
812    /// long-lived connections, or release resources while dependents are still alive.
813    async fn on_before_application_shutdown(&self) {}
814    async fn on_application_shutdown(&self) {}
815}
816
817pub trait Controller {
818    fn register(router: Router, registry: &ProviderRegistry) -> Router;
819}
820
821pub trait Module {
822    fn build() -> (ProviderRegistry, Router);
823
824    fn exports() -> Vec<TypeId> {
825        Vec::new()
826    }
827}
828
829/// Testing-oriented module traversal API.
830///
831/// Unlike [`Module::build`], implementations are expected to register *all* providers and controllers
832/// from the import graph into a shared registry/router, so tests can apply overrides before
833/// controllers are registered.
834pub trait ModuleGraph {
835    fn register_providers(registry: &mut ProviderRegistry);
836    fn register_controllers(router: Router, registry: &ProviderRegistry) -> Router;
837}
838
839/// Runtime-composed module unit for conditional imports (feature flags, env switches, plugins).
840///
841/// Typical constructors: [`Self::from_module`], [`Self::from_parts`], [`Self::lazy`], or builders
842/// such as [`DynamicModuleBuilder`] / [`ConfigurableModuleBuilder`]. Import the resulting value from
843/// `#[module(imports = [...])]` when the macro accepts a `DynamicModule` expression.
844///
845/// **Docs:** mdBook **Fundamentals** (`docs/src/fundamentals.md`).
846pub struct DynamicModule {
847    /// Provider registry for this dynamic module.
848    pub registry: ProviderRegistry,
849    pub router: Router,
850    /// Types exported to importing modules.
851    pub exports: Vec<TypeId>,
852}
853
854impl DynamicModule {
855    /// Builds a dynamic module from a static [`Module`] type.
856    pub fn from_module<M: Module>() -> Self {
857        let (registry, router) = M::build();
858        let exports = <M as Module>::exports();
859        Self {
860            registry,
861            router,
862            exports,
863        }
864    }
865
866    /// Wrap an already-built [`Router`] subtree as a dynamic module.
867    pub fn from_router(router: Router) -> Self {
868        Self {
869            registry: ProviderRegistry::new(),
870            router,
871            exports: Vec::new(),
872        }
873    }
874
875    /// Construct a dynamic module from explicit parts.
876    pub fn from_parts(registry: ProviderRegistry, router: Router, exports: Vec<TypeId>) -> Self {
877        Self {
878            registry,
879            router,
880            exports,
881        }
882    }
883
884    /// NestJS-style **lazy module**: `M::build()` runs at most once per process; imports clone bindings
885    /// so singleton [`ProviderRegistry`] cells stay shared (see [`ProviderRegistry::absorb_exported_from`]).
886    pub fn lazy<M: Module + 'static>() -> Self {
887        static CELL: std::sync::OnceLock<DynamicModule> = std::sync::OnceLock::new();
888        CELL.get_or_init(DynamicModule::from_module::<M>).clone()
889    }
890}
891
892impl Clone for DynamicModule {
893    fn clone(&self) -> Self {
894        Self {
895            registry: self.registry.clone(),
896            router: self.router.clone(),
897            exports: self.exports.clone(),
898        }
899    }
900}
901
902/// Typed runtime options token for configurable modules.
903///
904/// This is intended to be provided via `ConfigurableModuleBuilder` / `DynamicModuleBuilder`
905/// (it panics if requested without an override).
906pub struct ModuleOptions<O, M> {
907    inner: O,
908    _marker: std::marker::PhantomData<fn() -> M>,
909}
910
911impl<O, M> ModuleOptions<O, M> {
912    pub fn new(inner: O) -> Self {
913        Self {
914            inner,
915            _marker: std::marker::PhantomData,
916        }
917    }
918
919    pub fn get(&self) -> &O {
920        &self.inner
921    }
922
923    pub fn into_inner(self) -> O {
924        self.inner
925    }
926}
927
928impl<O, M> std::ops::Deref for ModuleOptions<O, M> {
929    type Target = O;
930
931    fn deref(&self) -> &Self::Target {
932        &self.inner
933    }
934}
935
936#[async_trait]
937impl<O, M> Injectable for ModuleOptions<O, M>
938where
939    O: Send + Sync + 'static,
940    M: 'static,
941{
942    fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
943        panic!(
944            "ModuleOptions requested but no value was provided. Use ConfigurableModuleBuilder / DynamicModuleBuilder to supply module options."
945        );
946    }
947}
948
949type RegistryOverrideFn = Box<dyn FnOnce(&mut ProviderRegistry) + Send>;
950
951/// Builds a [`DynamicModule`] from a static module graph, optionally applying provider overrides
952/// before controllers are registered (useful for configurable modules and testing-like setups).
953pub struct DynamicModuleBuilder<M>
954where
955    M: Module + ModuleGraph,
956{
957    overrides: Vec<RegistryOverrideFn>,
958    _marker: std::marker::PhantomData<M>,
959}
960
961impl<M> DynamicModuleBuilder<M>
962where
963    M: Module + ModuleGraph,
964{
965    pub fn new() -> Self {
966        Self {
967            overrides: Vec::new(),
968            _marker: std::marker::PhantomData,
969        }
970    }
971
972    pub fn override_provider<T>(mut self, instance: Arc<T>) -> Self
973    where
974        T: Injectable + Send + Sync + 'static,
975    {
976        self.overrides
977            .push(Box::new(move |r| r.override_provider::<T>(instance)));
978        self
979    }
980
981    pub fn build(self) -> DynamicModule {
982        let mut registry = ProviderRegistry::new();
983        M::register_providers(&mut registry);
984        for apply in self.overrides {
985            apply(&mut registry);
986        }
987        let router = M::register_controllers(Router::new(), &registry);
988        DynamicModule::from_parts(registry, router, M::exports())
989    }
990}
991
992impl<M> Default for DynamicModuleBuilder<M>
993where
994    M: Module + ModuleGraph,
995{
996    fn default() -> Self {
997        Self::new()
998    }
999}
1000
1001/// Convenience builder for NestJS-like configurable modules (`for_root`, `for_root_async`).
1002pub struct ConfigurableModuleBuilder<O> {
1003    _marker: std::marker::PhantomData<O>,
1004}
1005
1006impl<O> ConfigurableModuleBuilder<O>
1007where
1008    O: Send + Sync + 'static,
1009{
1010    pub fn for_root<M>(options: O) -> DynamicModule
1011    where
1012        M: Module + ModuleGraph + 'static,
1013    {
1014        DynamicModuleBuilder::<M>::new()
1015            .override_provider::<ModuleOptions<O, M>>(Arc::new(ModuleOptions::new(options)))
1016            .build()
1017    }
1018
1019    pub async fn for_root_async<M, F, Fut>(factory: F) -> DynamicModule
1020    where
1021        M: Module + ModuleGraph + 'static,
1022        F: FnOnce() -> Fut,
1023        Fut: Future<Output = O>,
1024    {
1025        let options = factory().await;
1026        Self::for_root::<M>(options)
1027    }
1028}
1029
1030thread_local! {
1031    static MODULE_BUILD_STACK: std::cell::RefCell<Vec<(&'static str, TypeId)>> =
1032        const { std::cell::RefCell::new(Vec::new()) };
1033}
1034
1035/// Internal module build/graph traversal guard (used by `#[module]`-generated code).
1036#[doc(hidden)]
1037pub struct __NestrsModuleBuildGuard {
1038    type_id: TypeId,
1039}
1040
1041impl __NestrsModuleBuildGuard {
1042    pub fn push(type_id: TypeId, type_name: &'static str) -> Self {
1043        let is_cycle = MODULE_BUILD_STACK.with(|stack| {
1044            let mut guard = stack.borrow_mut();
1045            let cycle = guard.iter().any(|(_, id)| *id == type_id);
1046            if !cycle {
1047                guard.push((type_name, type_id));
1048            }
1049            cycle
1050        });
1051
1052        if is_cycle {
1053            __nestrs_panic_circular_module_dependency(type_name);
1054        }
1055
1056        Self { type_id }
1057    }
1058}
1059
1060impl Drop for __NestrsModuleBuildGuard {
1061    fn drop(&mut self) {
1062        MODULE_BUILD_STACK.with(|stack| {
1063            let mut guard = stack.borrow_mut();
1064            if let Some((_, id)) = guard.last() {
1065                if *id == self.type_id {
1066                    guard.pop();
1067                }
1068            }
1069        });
1070    }
1071}
1072
1073#[doc(hidden)]
1074pub fn __nestrs_module_stack_contains(type_id: TypeId) -> bool {
1075    MODULE_BUILD_STACK.with(|stack| stack.borrow().iter().any(|(_, id)| *id == type_id))
1076}
1077
1078#[doc(hidden)]
1079pub fn __nestrs_panic_circular_module_dependency(import_type_name: &'static str) -> ! {
1080    let chain = MODULE_BUILD_STACK.with(|stack| {
1081        stack
1082            .borrow()
1083            .iter()
1084            .map(|(name, _)| *name)
1085            .chain(std::iter::once(import_type_name))
1086            .collect::<Vec<_>>()
1087            .join(" -> ")
1088    });
1089
1090    panic!(
1091        "Circular module dependency detected: {chain}. If intentional, mark the NestJS-style back-edge import with `forward_ref::<T>()` (or `forwardRef` alias in the `#[module]` macro). See the nestrs mdBook chapter **Fundamentals** (`docs/src/fundamentals.md`).",
1092    );
1093}
1094
1095tokio::task_local! {
1096    static REQUEST_SCOPE_CACHE: std::cell::RefCell<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>;
1097}
1098
1099/// Runs `future` inside the request-scoped provider cache (used by request
1100/// middleware and the transport scope installers).
1101///
1102/// **Nesting layers; it never replaces.** When a request scope is already
1103/// active on this task, `future` joins it: values inserted by outer
1104/// middleware (e.g. a `RequestScoped` provider already constructed by a
1105/// guard) stay visible, and anything inserted here lands in the same scope.
1106/// Only when no scope is active does this open a fresh one for `future`.
1107///
1108/// Without the join, every nested installer — `install_transactional_middleware`
1109/// under `request_scope_middleware`, or the GraphQL/WS/MCP scope wrappers on
1110/// an in-request transport — would fork the cache: request-scoped providers
1111/// resolved by outer middleware would be invisible (and re-constructed a
1112/// second time) inside, breaking the one-instance-per-request DI contract.
1113pub async fn with_request_scope<Fut, T>(future: Fut) -> T
1114where
1115    Fut: std::future::Future<Output = T>,
1116{
1117    // `try_with` succeeds only while a `scope(...)` future is being polled
1118    // on this task — i.e. precisely "a request scope is active".
1119    if REQUEST_SCOPE_CACHE.try_with(|_| ()).is_ok() {
1120        return future.await;
1121    }
1122    REQUEST_SCOPE_CACHE
1123        .scope(std::cell::RefCell::new(HashMap::new()), future)
1124        .await
1125}
1126
1127/// Spawns `future` on the ambient tokio runtime with the request scope
1128/// carried over — the supported way to run background work that resolves
1129/// `Request`-scoped providers.
1130///
1131/// **Snapshot semantics.** Task-locals do not cross `tokio::spawn`, so the
1132/// child task cannot share the parent's cache. Instead, the request-scoped
1133/// instances already constructed at spawn time are snapshotted (the `Arc`s
1134/// are cloned; the maps are independent) and installed as the child's
1135/// scope:
1136///
1137/// - The child resolves the **same instances** the request had — including
1138///   any in-flight `TransactionSlot` (nestrs) — so spawned work is a
1139///   continuation of the request, not a new one.
1140/// - Values constructed or inserted **after** the spawn are private to
1141///   whichever side constructed them: the parent does not see the child's
1142///   inserts, and the child does not see providers the parent constructs
1143///   later.
1144/// - Spawned **outside** any request scope (a scheduler, a startup job),
1145///   the child simply gets a fresh empty scope — request-scoped providers
1146///   construct per spawned task and are isolated from every other task.
1147///
1148/// **Not carried:** the ability / principal slots. Row-level authz stays
1149/// deny-closed in the spawned task unless the caller explicitly wraps the
1150/// future with the ability helpers — background work inheriting the
1151/// request's authority should be an explicit decision, not a side effect
1152/// of spawning.
1153pub fn spawn_with_request_scope<F>(future: F) -> tokio::task::JoinHandle<F::Output>
1154where
1155    F: std::future::Future + Send + 'static,
1156    F::Output: Send + 'static,
1157{
1158    let snapshot = REQUEST_SCOPE_CACHE
1159        .try_with(|cell| cell.borrow().clone())
1160        .ok();
1161    tokio::spawn(async move {
1162        match snapshot {
1163            Some(map) => {
1164                REQUEST_SCOPE_CACHE
1165                    .scope(std::cell::RefCell::new(map), future)
1166                    .await
1167            }
1168            None => with_request_scope(future).await,
1169        }
1170    })
1171}
1172
1173/// Look up a value previously inserted into the request scope via
1174/// `request_scope_insert` or by the `RequestScoped<T>` provider. Returns
1175/// `None` outside of a request scope.
1176pub fn request_scope_get(type_id: TypeId) -> Option<Arc<dyn Any + Send + Sync>> {
1177    REQUEST_SCOPE_CACHE
1178        .try_with(|c| c.borrow().get(&type_id).cloned())
1179        .ok()
1180        .flatten()
1181}
1182
1183/// Insert a value into the request scope. Must be called from inside a
1184/// `with_request_scope` future; otherwise it's a no-op.
1185pub fn request_scope_insert(type_id: TypeId, value: Arc<dyn Any + Send + Sync>) {
1186    let _ = REQUEST_SCOPE_CACHE.try_with(|c| {
1187        c.borrow_mut().insert(type_id, value);
1188    });
1189}
1190
1191// ---------------------------------------------------------------------------
1192// Per-task ability slot (type-erased)
1193//
1194// `Ability` itself is defined in `nestrs::policies`. To let transport crates
1195// (GraphQL, MCP, workers) install / read the same per-task ability without
1196// forcing a `nestrs` dependency on them, the slot lives here as a type-erased
1197// `Arc<dyn Any + Send + Sync>`. `nestrs::policies::current_ability()` does
1198// the downcast; transport crates that need a typed read do the same via
1199// their own thin accessor in `nestrs`.
1200// ---------------------------------------------------------------------------
1201
1202tokio::task_local! {
1203    static ABILITY_SLOT: std::cell::RefCell<Option<Arc<dyn Any + Send + Sync>>>;
1204}
1205
1206/// Run `future` with the given type-erased ability installed in the per-task
1207/// ability slot. The caller is responsible for passing an `Arc<Ability>` (or
1208/// any other type they want to read back via `current_ability_erased`).
1209///
1210/// `nestrs::policies::with_ability` is the typed convenience wrapper that
1211/// takes an `Arc<Ability>` directly.
1212pub async fn with_ability_erased<F, T>(ability: Arc<dyn Any + Send + Sync>, future: F) -> T
1213where
1214    F: std::future::Future<Output = T>,
1215{
1216    ABILITY_SLOT
1217        .scope(std::cell::RefCell::new(Some(ability)), future)
1218        .await
1219}
1220
1221/// Read the type-erased ability for the current task, if one was installed
1222/// via `with_ability_erased` (or via `nestrs::policies::with_ability`, which
1223/// writes to the same slot). Returns `None` outside of a scope.
1224pub fn current_ability_erased() -> Option<Arc<dyn Any + Send + Sync>> {
1225    ABILITY_SLOT.try_with(|c| c.borrow().clone()).ok().flatten()
1226}
1227
1228// ---------------------------------------------------------------------------
1229// Per-task principal slot (type-erased)
1230//
1231// Row-level authorization predicates receive the current `Principal`. Like
1232// the ability slot above, the slot lives here type-erased so transport
1233// crates (HTTP authn middleware, WS, GraphQL, MCP) can install / read the
1234// per-task principal without a `nestrs` dependency cycle. The value is a
1235// `nestrs::policies::Principal`; `nestrs::policies::current_principal()`
1236// does the downcast.
1237// ---------------------------------------------------------------------------
1238
1239tokio::task_local! {
1240    static PRINCIPAL_SLOT: std::cell::RefCell<Option<Arc<dyn Any + Send + Sync>>>;
1241}
1242
1243/// Run `future` with the given type-erased principal installed in the
1244/// per-task principal slot. The caller is responsible for passing an
1245/// `Arc<policies::Principal>` (or any other type they want to read back via
1246/// `current_principal_erased`).
1247///
1248/// `nestrs::policies::with_principal` is the typed convenience wrapper that
1249/// takes an `Arc<Principal>` directly.
1250pub async fn with_principal_erased<F, T>(principal: Arc<dyn Any + Send + Sync>, future: F) -> T
1251where
1252    F: std::future::Future<Output = T>,
1253{
1254    PRINCIPAL_SLOT
1255        .scope(std::cell::RefCell::new(Some(principal)), future)
1256        .await
1257}
1258
1259/// Read the type-erased principal for the current task, if one was installed
1260/// via `with_principal_erased` (or via `nestrs::policies::with_principal`,
1261/// which writes to the same slot). Returns `None` outside of a scope.
1262pub fn current_principal_erased() -> Option<Arc<dyn Any + Send + Sync>> {
1263    PRINCIPAL_SLOT
1264        .try_with(|c| c.borrow().clone())
1265        .ok()
1266        .flatten()
1267}
1268
1269thread_local! {
1270    static CONSTRUCTION_STACK: std::cell::RefCell<Vec<(&'static str, TypeId)>> =
1271        const { std::cell::RefCell::new(Vec::new()) };
1272}
1273
1274struct ConstructionGuard {
1275    type_id: TypeId,
1276}
1277
1278impl ConstructionGuard {
1279    fn push(type_id: TypeId, type_name: &'static str) -> Self {
1280        CONSTRUCTION_STACK.with(|stack| {
1281            let mut guard = stack.borrow_mut();
1282            if guard.iter().any(|(_, id)| *id == type_id) {
1283                let chain = guard
1284                    .iter()
1285                    .map(|(name, _)| *name)
1286                    .chain(std::iter::once(type_name))
1287                    .collect::<Vec<_>>()
1288                    .join(" -> ");
1289                panic!(
1290                    "Circular provider dependency detected: {chain}. Break the cycle with lazy construction (`register_use_factory`), split types, defer work to `on_module_init`, or a `forward_ref`-style module import for module graphs. See the nestrs mdBook chapter **Fundamentals** (`docs/src/fundamentals.md`)."
1291                );
1292            }
1293            guard.push((type_name, type_id));
1294        });
1295        Self { type_id }
1296    }
1297}
1298
1299impl Drop for ConstructionGuard {
1300    fn drop(&mut self) {
1301        CONSTRUCTION_STACK.with(|stack| {
1302            let mut guard = stack.borrow_mut();
1303            if let Some((_, id)) = guard.last() {
1304                if *id == self.type_id {
1305                    guard.pop();
1306                }
1307            }
1308        });
1309    }
1310}
1311
1312/// Global construction-dependency graph (`constructor -> dependency`) recorded by
1313/// [`ProviderRegistry::get`] while a provider factory is running. Used to order lifecycle hooks.
1314fn provider_dep_graph() -> &'static RwLock<HashMap<TypeId, Vec<TypeId>>> {
1315    static DEPS: OnceLock<RwLock<HashMap<TypeId, Vec<TypeId>>>> = OnceLock::new();
1316    DEPS.get_or_init(|| RwLock::new(HashMap::new()))
1317}
1318
1319fn record_provider_dependency(from: TypeId, to: TypeId) {
1320    // Hot path: the edge is almost always already recorded — the first
1321    // resolution of a constructor -> dependency pair records it, and every
1322    // later resolution (each request-scoped/transient construction on a
1323    // warm process) only needs this read-locked check, instead of
1324    // contending on the global write lock for every single resolution.
1325    {
1326        let deps = provider_dep_graph().read().expect("provider dep graph");
1327        if let Some(targets) = deps.get(&from) {
1328            if targets.contains(&to) {
1329                return;
1330            }
1331        }
1332    }
1333    let mut deps = provider_dep_graph().write().expect("provider dep graph");
1334    let targets = deps.entry(from).or_default();
1335    if !targets.contains(&to) {
1336        targets.push(to);
1337    }
1338}
1339
1340/// Clears the recorded provider dependency graph.
1341///
1342/// **Available only with the `test-hooks` feature.** For tests; see `STABILITY.md` in the repo root.
1343#[cfg(feature = "test-hooks")]
1344pub fn clear_provider_dependencies_for_tests() {
1345    provider_dep_graph()
1346        .write()
1347        .expect("provider dep graph")
1348        .clear();
1349}
1350
1351type ModuleBuildFn = Box<dyn FnOnce() -> (ProviderRegistry, Router) + Send>;
1352
1353static MODULE_BUILD_CACHE: OnceLock<RwLock<HashMap<TypeId, Arc<OnceLock<DynamicModule>>>>> =
1354    OnceLock::new();
1355
1356fn module_build_cache() -> &'static RwLock<HashMap<TypeId, Arc<OnceLock<DynamicModule>>>> {
1357    MODULE_BUILD_CACHE.get_or_init(|| RwLock::new(HashMap::new()))
1358}
1359
1360/// Memoizes a [`Module::build`] result **process-wide**, keyed by the module type.
1361///
1362/// This makes NestJS-style module-instance sharing the default: when two modules import the same
1363/// shared module, both importers receive bindings cloned from **one** built instance (shared
1364/// singleton cells, one route subtree), instead of each importer rebuilding its own copy.
1365///
1366/// Route conflicts from duplicate registration and split-singleton bugs are thereby avoided;
1367/// `forward_ref` back-edges still skip via the existing module build-stack check before this is reached.
1368///
1369/// # Arguments
1370///
1371/// `build` is the uncached module body (generated by `#[module]`). It runs at most once per
1372/// process per module type.
1373#[doc(hidden)]
1374pub fn __nestrs_memoize_module_build<M: Module + 'static>(
1375    build: ModuleBuildFn,
1376) -> (ProviderRegistry, Router) {
1377    let key = TypeId::of::<M>();
1378    let entry = Arc::clone(
1379        module_build_cache()
1380            .write()
1381            .expect("module build cache")
1382            .entry(key)
1383            .or_insert_with(|| Arc::new(OnceLock::new())),
1384    );
1385    // At most one thread builds `M`; concurrent/reentrant callers block on the cell. Reentrant
1386    // builds (a true cycle) are rejected earlier by the module build-stack circular check inside
1387    // `build`, matching pre-memoization semantics.
1388    let dm = entry.get_or_init(|| {
1389        let (registry, router) = build();
1390        DynamicModule::from_parts(registry, router, <M as Module>::exports())
1391    });
1392    (dm.registry.clone(), dm.router.clone())
1393}
1394
1395/// Clears the process-wide module build cache.
1396///
1397/// **Available only with the `test-hooks` feature.** For tests that rebuild the same module type
1398/// expecting fresh instances; see `STABILITY.md` in the repo root.
1399#[cfg(feature = "test-hooks")]
1400pub fn clear_module_cache_for_tests() {
1401    module_build_cache()
1402        .write()
1403        .expect("module build cache")
1404        .clear();
1405}
1406
1407#[cfg(test)]
1408mod request_scope_tests {
1409    // The request scope must NEST, never fork: `with_request_scope` called
1410    // inside an active scope joins it. Pre-fix it installed a fresh map,
1411    // so the transactional middleware (and the GraphQL/WS/MCP scope
1412    // wrappers on in-request transports) hid every value the outer
1413    // middleware had stashed — and, worse, `ProviderScope::Request`
1414    // resolution would construct a SECOND instance of a provider the
1415    // outer scope already built.
1416
1417    use super::*;
1418    use std::sync::atomic::{AtomicUsize, Ordering};
1419
1420    struct Marker;
1421    struct OtherMarker;
1422
1423    #[tokio::test]
1424    async fn nested_with_request_scope_joins_the_outer_scope() {
1425        with_request_scope(async {
1426            request_scope_insert(
1427                TypeId::of::<Marker>(),
1428                Arc::new(Marker) as Arc<dyn Any + Send + Sync>,
1429            );
1430
1431            // A nested scope opener (transactional middleware, transport
1432            // wrapper) must see the outer value — pre-fix this was None.
1433            with_request_scope(async {
1434                assert!(
1435                    request_scope_get(TypeId::of::<Marker>()).is_some(),
1436                    "nested with_request_scope must not hide outer values"
1437                );
1438                // ...and its inserts land in the SAME scope...
1439                request_scope_insert(
1440                    TypeId::of::<OtherMarker>(),
1441                    Arc::new(OtherMarker) as Arc<dyn Any + Send + Sync>,
1442                );
1443            })
1444            .await;
1445
1446            // ...which the outer block still sees once the nested one ends.
1447            assert!(
1448                request_scope_get(TypeId::of::<OtherMarker>()).is_some(),
1449                "nested inserts must land in the active (outer) scope"
1450            );
1451        })
1452        .await;
1453    }
1454
1455    #[tokio::test]
1456    async fn with_request_scope_opens_a_fresh_scope_when_none_is_active() {
1457        // Sanity both ways: outside any scope, get is None (insert is a
1458        // documented no-op)...
1459        assert!(request_scope_get(TypeId::of::<Marker>()).is_none());
1460        // ...and a top-level with_request_scope establishes one.
1461        with_request_scope(async {
1462            request_scope_insert(
1463                TypeId::of::<Marker>(),
1464                Arc::new(Marker) as Arc<dyn Any + Send + Sync>,
1465            );
1466            assert!(request_scope_get(TypeId::of::<Marker>()).is_some());
1467        })
1468        .await;
1469    }
1470
1471    static REQUEST_PROVIDER_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1472
1473    struct CountedRequestService;
1474
1475    impl Injectable for CountedRequestService {
1476        fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1477            REQUEST_PROVIDER_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1478            Arc::new(Self)
1479        }
1480
1481        fn scope() -> ProviderScope {
1482            ProviderScope::Request
1483        }
1484    }
1485
1486    // Per-test counted types: the tests below run concurrently on the
1487    // multi-thread test harness, and a shared counter would let one test's
1488    // `store(0)` reset another's in-flight construction count.
1489    static SNAPSHOT_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1490
1491    struct SnapshottedService;
1492
1493    impl Injectable for SnapshottedService {
1494        fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1495            SNAPSHOT_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1496            Arc::new(Self)
1497        }
1498
1499        fn scope() -> ProviderScope {
1500            ProviderScope::Request
1501        }
1502    }
1503
1504    static OFF_SCOPE_CONSTRUCTIONS: AtomicUsize = AtomicUsize::new(0);
1505
1506    struct OffScopeService;
1507
1508    impl Injectable for OffScopeService {
1509        fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1510            OFF_SCOPE_CONSTRUCTIONS.fetch_add(1, Ordering::SeqCst);
1511            Arc::new(Self)
1512        }
1513
1514        fn scope() -> ProviderScope {
1515            ProviderScope::Request
1516        }
1517    }
1518
1519    #[tokio::test]
1520    async fn nested_scope_does_not_rebuild_request_providers() {
1521        // The DI-contract regression: a Request-scoped provider resolved
1522        // before a nested scope opener (guard → transactional middleware)
1523        // must be the SAME instance when re-resolved inside it — one
1524        // construction per request, not one per nested wrapper.
1525        REQUEST_PROVIDER_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1526        let mut registry = ProviderRegistry::new();
1527        registry.register::<CountedRequestService>();
1528
1529        with_request_scope(async {
1530            let outer: Arc<CountedRequestService> = registry.get();
1531
1532            let inner: Arc<CountedRequestService> =
1533                with_request_scope(async { registry.get::<CountedRequestService>() }).await;
1534
1535            assert!(
1536                Arc::ptr_eq(&outer, &inner),
1537                "nested with_request_scope must reuse the request-scoped instance"
1538            );
1539            assert_eq!(
1540                REQUEST_PROVIDER_CONSTRUCTIONS.load(Ordering::SeqCst),
1541                1,
1542                "one construction per request, not one per nested scope"
1543            );
1544        })
1545        .await;
1546    }
1547
1548    // --- request scope across spawn (audit #47, v2-review item 2) -----------
1549    //
1550    // A handler that `tokio::spawn`s background work ran that child with NO
1551    // request scope; resolving a Request-scoped provider there panicked —
1552    // and panicked through `try_get` too, violating its documented
1553    // "returns None instead of panicking" contract.
1554
1555    #[tokio::test]
1556    async fn try_get_returns_none_off_scope_instead_of_panicking() {
1557        let mut registry = ProviderRegistry::new();
1558        registry.register::<CountedRequestService>();
1559
1560        // Off-scope: registered, but unresolvable here. `try_get` must honor
1561        // its graceful contract (pre-fix: panic).
1562        assert!(
1563            registry.try_get::<CountedRequestService>().is_none(),
1564            "off-scope Request-scoped resolution is absence, not a panic"
1565        );
1566    }
1567
1568    #[test]
1569    #[should_panic(expected = "spawn_with_request_scope")]
1570    fn get_off_scope_panics_with_the_fix_in_the_message() {
1571        let mut registry = ProviderRegistry::new();
1572        registry.register::<CountedRequestService>();
1573
1574        let _ = registry.get::<CountedRequestService>();
1575    }
1576
1577    #[tokio::test]
1578    async fn spawned_task_sees_the_request_snapshot() {
1579        SNAPSHOT_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1580        let mut registry = ProviderRegistry::new();
1581        registry.register::<SnapshottedService>();
1582
1583        with_request_scope(async {
1584            let outer: Arc<SnapshottedService> = registry.get();
1585
1586            let registry = registry.clone();
1587            let handle = spawn_with_request_scope(async move {
1588                let child: Arc<SnapshottedService> = registry.get();
1589                Arc::ptr_eq(&outer, &child)
1590            });
1591
1592            assert!(
1593                handle.await.unwrap(),
1594                "spawned task must resolve the instance the request had at spawn time"
1595            );
1596            assert_eq!(
1597                SNAPSHOT_CONSTRUCTIONS.load(Ordering::SeqCst),
1598                1,
1599                "snapshot must carry the instance, not re-construct it"
1600            );
1601        })
1602        .await;
1603    }
1604
1605    #[tokio::test]
1606    async fn spawned_task_writes_stay_in_the_child() {
1607        with_request_scope(async {
1608            let handle = spawn_with_request_scope(async {
1609                request_scope_insert(
1610                    TypeId::of::<OtherMarker>(),
1611                    Arc::new(OtherMarker) as Arc<dyn Any + Send + Sync>,
1612                );
1613                assert!(
1614                    request_scope_get(TypeId::of::<OtherMarker>()).is_some(),
1615                    "the child sees its own inserts"
1616                );
1617            });
1618            handle.await.unwrap();
1619
1620            assert!(
1621                request_scope_get(TypeId::of::<OtherMarker>()).is_none(),
1622                "child-scope writes must not leak into the parent request scope"
1623            );
1624        })
1625        .await;
1626    }
1627
1628    #[tokio::test]
1629    async fn spawning_off_scope_gives_each_child_a_fresh_scope() {
1630        OFF_SCOPE_CONSTRUCTIONS.store(0, Ordering::SeqCst);
1631        let mut registry = ProviderRegistry::new();
1632        registry.register::<OffScopeService>();
1633
1634        // Off-scope spawn (a scheduler, a startup job): each spawned task is
1635        // its own unit of work — fresh scope, isolated instances.
1636        let a = {
1637            let registry = registry.clone();
1638            spawn_with_request_scope(async move { registry.get::<OffScopeService>() })
1639        };
1640        let b = {
1641            let registry = registry.clone();
1642            spawn_with_request_scope(async move { registry.get::<OffScopeService>() })
1643        };
1644
1645        let a = a.await.unwrap();
1646        let b = b.await.unwrap();
1647        assert!(
1648            !Arc::ptr_eq(&a, &b),
1649            "off-scope spawns must not share request-scoped instances"
1650        );
1651        assert_eq!(
1652            OFF_SCOPE_CONSTRUCTIONS.load(Ordering::SeqCst),
1653            2,
1654            "one construction per spawned scope"
1655        );
1656    }
1657}
1658
1659#[cfg(test)]
1660mod provider_lifecycle_tests {
1661    // useValue/useFactory providers registered through the *_with_lifecycle
1662    // variants get their ProviderLifecycle hooks driven exactly like
1663    // Injectable hooks: registration order for init/bootstrap, REVERSED for
1664    // shutdown/destroy. The plain register_use_value / register_use_factory
1665    // keep their documented hook-less behavior (opt-in, no bound changes).
1666
1667    use super::*;
1668    use std::sync::Mutex;
1669
1670    type Log = Arc<Mutex<Vec<String>>>;
1671
1672    fn assert_log(log: &Log, expected: &[&str]) {
1673        let got = log.lock().unwrap();
1674        let expected: Vec<String> = expected.iter().map(|s| s.to_string()).collect();
1675        assert_eq!(*got, expected, "hook firing order");
1676    }
1677
1678    /// `Tagged<'A'>` and `Tagged<'B'>` are distinct provider types (one per
1679    /// TypeId) sharing a single hook impl — the const char doubles as the
1680    /// event prefix in the log.
1681    struct Tagged<const TAG: char> {
1682        log: Log,
1683    }
1684
1685    impl<const TAG: char> Tagged<TAG> {
1686        fn record(&self, event: &str) {
1687            self.log.lock().unwrap().push(format!("{TAG}:{event}"));
1688        }
1689    }
1690
1691    #[async_trait]
1692    impl<const TAG: char> ProviderLifecycle for Tagged<TAG> {
1693        async fn on_module_init(&self) {
1694            self.record("init");
1695        }
1696        async fn on_module_destroy(&self) {
1697            self.record("destroy");
1698        }
1699        async fn on_application_bootstrap(&self) {
1700            self.record("bootstrap");
1701        }
1702        async fn on_before_application_shutdown(&self) {
1703            self.record("before_shutdown");
1704        }
1705        async fn on_application_shutdown(&self) {
1706            self.record("shutdown");
1707        }
1708    }
1709
1710    #[tokio::test]
1711    async fn use_value_lifecycle_hooks_fire_in_framework_order() {
1712        let log: Log = Arc::default();
1713        let mut registry = ProviderRegistry::new();
1714        registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'V'> { log: log.clone() }));
1715
1716        registry.run_on_module_init().await;
1717        registry.run_on_application_bootstrap().await;
1718        registry.run_on_before_application_shutdown().await;
1719        registry.run_on_application_shutdown().await;
1720        registry.run_on_module_destroy().await;
1721
1722        assert_log(
1723            &log,
1724            &[
1725                "V:init",
1726                "V:bootstrap",
1727                "V:before_shutdown",
1728                "V:shutdown",
1729                "V:destroy",
1730            ],
1731        );
1732    }
1733
1734    #[tokio::test]
1735    async fn lifecycle_hooks_register_order_init_reverse_destroy() {
1736        let log: Log = Arc::default();
1737        let mut registry = ProviderRegistry::new();
1738        registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'A'> { log: log.clone() }));
1739        registry.register_use_value_with_lifecycle(Arc::new(Tagged::<'B'> { log: log.clone() }));
1740
1741        registry.run_on_module_init().await;
1742        registry.run_on_module_destroy().await;
1743
1744        // Destroy hooks run reversed — dependents tear down before their
1745        // dependencies, matching Injectable providers.
1746        assert_log(&log, &["A:init", "B:init", "B:destroy", "A:destroy"]);
1747    }
1748
1749    #[tokio::test]
1750    async fn use_factory_lifecycle_hooks_fire_on_the_lazily_built_singleton() {
1751        let log: Log = Arc::default();
1752        let mut registry = ProviderRegistry::new();
1753        registry.register_use_factory_with_lifecycle(ProviderScope::Singleton, {
1754            let log = log.clone();
1755            move |_r| {
1756                log.lock().unwrap().push("F:construct".to_string());
1757                Arc::new(Tagged::<'F'> { log: log.clone() })
1758            }
1759        });
1760
1761        // Nothing has resolved F yet — the init hook is the first `get` and
1762        // constructs the singleton itself (the same lazy contract as
1763        // Injectable providers whose first resolution happens in a hook).
1764        registry.run_on_module_init().await;
1765        assert_log(&log, &["F:construct", "F:init"]);
1766
1767        // Later resolutions reuse the SAME singleton the hook saw.
1768        let _v: Arc<Tagged<'F'>> = registry.get();
1769        assert_log(&log, &["F:construct", "F:init"]);
1770    }
1771
1772    #[tokio::test]
1773    async fn plain_use_value_and_use_factory_stay_hook_less() {
1774        // Opt-in contract: types registered through the PLAIN variants never
1775        // run lifecycle hooks, even when they implement ProviderLifecycle
1776        // (back-compat — the fix adds the *_with_lifecycle variants rather
1777        // than changing the plain methods' bounds).
1778        let log: Log = Arc::default();
1779        let mut registry = ProviderRegistry::new();
1780        registry.register_use_value(Arc::new(Tagged::<'V'> { log: log.clone() }));
1781        registry.register_use_factory(ProviderScope::Singleton, {
1782            let log = log.clone();
1783            move |_r| Arc::new(Tagged::<'G'> { log: log.clone() })
1784        });
1785
1786        registry.run_on_module_init().await;
1787        registry.run_on_application_bootstrap().await;
1788        registry.run_on_before_application_shutdown().await;
1789        registry.run_on_application_shutdown().await;
1790        registry.run_on_module_destroy().await;
1791
1792        assert_log(&log, &[]);
1793    }
1794}
1795
1796#[cfg(test)]
1797mod provider_dep_graph_tests {
1798    // Dependency recording sits on the hot path of DI resolution: every
1799    // `registry.get` inside a provider construction records the
1800    // constructor -> dependency edge into the process-global graph. The
1801    // write lock is only needed the FIRST time an edge is seen; repeat
1802    // resolutions (each request-scoped/transient construction on a warm
1803    // process) must take the read-locked fast path instead of contending
1804    // on the global write lock.
1805
1806    use super::*;
1807
1808    struct FromA;
1809    struct ToB;
1810
1811    #[test]
1812    fn record_provider_dependency_is_idempotent_on_repeated_edges() {
1813        let from = TypeId::of::<FromA>();
1814        let to = TypeId::of::<ToB>();
1815        record_provider_dependency(from, to);
1816        // Repeated resolutions of the same edge (the read-locked fast
1817        // path): must not duplicate the target.
1818        record_provider_dependency(from, to);
1819        record_provider_dependency(from, to);
1820        let deps = provider_dep_graph().read().expect("provider dep graph");
1821        let targets = deps.get(&from).expect("edge recorded");
1822        assert_eq!(targets.len(), 1, "repeated edges must not duplicate");
1823        assert_eq!(targets[0], to);
1824    }
1825
1826    #[test]
1827    fn record_provider_dependency_dedupes_racing_first_recordings() {
1828        // Threads that miss the fast path simultaneously (edge not yet
1829        // committed) must not push the edge twice — the write-locked arm
1830        // re-checks before pushing.
1831        let from = TypeId::of::<FromA>();
1832        let to = TypeId::of::<ToB>();
1833        std::thread::scope(|s| {
1834            for _ in 0..16 {
1835                s.spawn(|| record_provider_dependency(from, to));
1836            }
1837        });
1838        let deps = provider_dep_graph().read().expect("provider dep graph");
1839        let targets = deps.get(&from).expect("edge recorded");
1840        assert_eq!(
1841            targets.len(),
1842            1,
1843            "racing recorders must not duplicate an edge"
1844        );
1845    }
1846}
1847
1848#[cfg(test)]
1849mod override_provider_tests {
1850    // override_provider replaces WHAT a provider resolves to — it must not
1851    // silently change the provider's LIFETIME. Pre-fix the override entry
1852    // hardcoded `ProviderScope::Singleton` with an `unreachable!()`
1853    // placeholder factory: overriding a request- or transient-scoped
1854    // provider made it process-global (per-request state bleeding across
1855    // requests, `provider_summaries` reporting the wrong scope), and a
1856    // scope-preserving fix could never have resolved anything.
1857
1858    use super::*;
1859
1860    struct SingletonSvc;
1861
1862    impl Injectable for SingletonSvc {
1863        fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1864            unreachable!("overridden before construction")
1865        }
1866    }
1867
1868    struct RequestSvc;
1869
1870    impl Injectable for RequestSvc {
1871        fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1872            unreachable!("overridden before construction")
1873        }
1874
1875        fn scope() -> ProviderScope {
1876            ProviderScope::Request
1877        }
1878    }
1879
1880    struct TransientSvc;
1881
1882    impl Injectable for TransientSvc {
1883        fn construct(_registry: &ProviderRegistry) -> Arc<Self> {
1884            unreachable!("overridden before construction")
1885        }
1886
1887        fn scope() -> ProviderScope {
1888            ProviderScope::Transient
1889        }
1890    }
1891
1892    fn declared_scope<T: 'static>(registry: &ProviderRegistry) -> ProviderScope {
1893        let name = std::any::type_name::<T>();
1894        registry
1895            .provider_summaries()
1896            .into_iter()
1897            .find(|s| s.type_name == name)
1898            .map(|s| s.scope)
1899            .unwrap_or_else(|| panic!("{name} not registered"))
1900    }
1901
1902    #[test]
1903    fn singleton_override_returns_the_instance_and_keeps_singleton_scope() {
1904        let mut registry = ProviderRegistry::new();
1905        registry.register::<SingletonSvc>();
1906        let mock = Arc::new(SingletonSvc);
1907        registry.override_provider::<SingletonSvc>(mock.clone());
1908
1909        let resolved = registry.get::<SingletonSvc>();
1910        assert!(Arc::ptr_eq(&resolved, &mock), "override instance served");
1911        assert_eq!(
1912            declared_scope::<SingletonSvc>(&registry),
1913            ProviderScope::Singleton,
1914            "singleton overrides stay singleton"
1915        );
1916    }
1917
1918    #[tokio::test]
1919    async fn request_override_keeps_request_scope_and_resolves_to_the_instance() {
1920        let mut registry = ProviderRegistry::new();
1921        registry.register::<RequestSvc>();
1922        let mock = Arc::new(RequestSvc);
1923        registry.override_provider::<RequestSvc>(mock.clone());
1924
1925        // Per-request resolution: two gets inside one request return the
1926        // override, cached in that request's scope.
1927        let first = with_request_scope(async {
1928            let a = registry.get::<RequestSvc>();
1929            let b = registry.get::<RequestSvc>();
1930            assert!(Arc::ptr_eq(&a, &b), "one resolution per request");
1931            a
1932        })
1933        .await;
1934        assert!(Arc::ptr_eq(&first, &mock), "override served inside request");
1935
1936        // A separate request resolves the SAME override — an override
1937        // explicitly targets one concrete instance. (Run sequentially at the
1938        // top level: a nested `with_request_scope` would JOIN the first
1939        // scope, not open a fresh one.)
1940        let second = with_request_scope(async { registry.get::<RequestSvc>() }).await;
1941        assert!(
1942            Arc::ptr_eq(&second, &mock),
1943            "override instance shared across requests"
1944        );
1945
1946        assert_eq!(
1947            declared_scope::<RequestSvc>(&registry),
1948            ProviderScope::Request,
1949            "request overrides keep request scope — no silent Singleton coercion"
1950        );
1951    }
1952
1953    #[test]
1954    fn transient_override_keeps_transient_scope_and_resolves_each_time() {
1955        let mut registry = ProviderRegistry::new();
1956        registry.register::<TransientSvc>();
1957        let mock = Arc::new(TransientSvc);
1958        registry.override_provider::<TransientSvc>(mock.clone());
1959
1960        // Transient = a resolution per injection site; the override hands
1961        // out the one concrete instance each time (pre-fix this path
1962        // panicked via the placeholder factory).
1963        let a = registry.get::<TransientSvc>();
1964        let b = registry.get::<TransientSvc>();
1965        assert!(Arc::ptr_eq(&a, &mock), "first transient resolution served");
1966        assert!(Arc::ptr_eq(&b, &mock), "second transient resolution served");
1967        assert_eq!(
1968            declared_scope::<TransientSvc>(&registry),
1969            ProviderScope::Transient,
1970            "transient overrides keep transient scope"
1971        );
1972    }
1973}