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