docs(api): improve all DI core code documentation with English dartdoc and examples

- Full English API/class/method documentation for core CherryPick classes:
  * Binding<T>
  * BindingResolver<T>
  * CycleDetector and CycleDetectionMixin
  * GlobalCycleDetector and GlobalCycleDetectionMixin
  * Factory<T>
  * Module
  * Scope
- Each public and private method is now documented in clear DartDoc style with usages
- Added code samples for modules, scope, subscopes, DI resolve/async, cycle detection
- Russian comments completely replaced with English for consistency
- NO logic or API changes, documentation and devex improvement only

Also updated: pubspec.lock for workspace/example folders (auto by dependency changes)
This commit is contained in:
Sergey Penkovsky
2025-08-12 15:38:15 +03:00
parent 900cd68663
commit 9312ef46ea
11 changed files with 428 additions and 265 deletions

View File

@@ -47,7 +47,7 @@ packages:
path: "../cherrypick" path: "../cherrypick"
relative: true relative: true
source: path source: path
version: "3.0.0-dev.7" version: "3.0.0-dev.8"
collection: collection:
dependency: transitive dependency: transitive
description: description:

Binary file not shown.

View File

@@ -13,41 +13,70 @@
import 'dart:async'; import 'dart:async';
/// Represents a direct instance or an async instance ([T] or [Future<T>]).
/// Used for both direct and async bindings.
///
/// Example:
/// ```dart
/// Instance<String> sync = "hello";
/// Instance<MyApi> async = Future.value(MyApi());
/// ```
typedef Instance<T> = FutureOr<T>; typedef Instance<T> = FutureOr<T>;
/// RU: Синхронный или асинхронный провайдер без параметров, возвращающий [T] или [Future<T>]. /// Provider function type for synchronous or asynchronous, parameterless creation of [T].
/// ENG: Synchronous or asynchronous provider without parameters, returning [T] or [Future<T>]. /// Can return [T] or [Future<T>].
///
/// Example:
/// ```dart
/// Provider<MyService> provider = () => MyService();
/// Provider<Api> asyncProvider = () async => await Api.connect();
/// ```
typedef Provider<T> = FutureOr<T> Function(); typedef Provider<T> = FutureOr<T> Function();
/// RU: Провайдер с динамическим параметром, возвращающий [T] или [Future<T>] в зависимости от реализации. /// Provider function type that accepts a dynamic parameter, for factory/parametrized injection.
/// ENG: Provider with dynamic parameter, returning [T] or [Future<T>] depending on implementation. /// Returns [T] or [Future<T>].
///
/// Example:
/// ```dart
/// ProviderWithParams<User> provider = (params) => User(params["name"]);
/// ```
typedef ProviderWithParams<T> = FutureOr<T> Function(dynamic); typedef ProviderWithParams<T> = FutureOr<T> Function(dynamic);
/// RU: Абстрактный интерфейс для классов, которые разрешают зависимости типа [T]. /// Abstract interface for dependency resolvers used by [Binding].
/// ENG: Abstract interface for classes that resolve dependencies of type [T]. /// Defines how to resolve instances of type [T].
///
/// You usually don't use this directly; it's used internally for advanced/low-level DI.
abstract class BindingResolver<T> { abstract class BindingResolver<T> {
/// RU: Синхронное разрешение зависимости с параметром [params]. /// Synchronously resolves the dependency, optionally taking parameters (for factory cases).
/// ENG: Synchronous resolution of the dependency with [params]. /// Throws if implementation does not support sync resolution.
T? resolveSync([dynamic params]); T? resolveSync([dynamic params]);
/// RU: Асинхронное разрешение зависимости с параметром [params]. /// Asynchronously resolves the dependency, optionally taking parameters (for factory cases).
/// ENG: Asynchronous resolution of the dependency with [params]. /// If instance is already a [Future], returns it directly.
Future<T>? resolveAsync([dynamic params]); Future<T>? resolveAsync([dynamic params]);
/// RU: Помечает текущий резолвер как синглтон — результат будет закеширован. /// Marks this resolver as singleton: instance(s) will be cached and reused inside the scope.
/// ENG: Marks this resolver as singleton — result will be cached.
void toSingleton(); void toSingleton();
/// Returns true if this resolver is marked as singleton.
bool get isSingleton; bool get isSingleton;
} }
/// RU: Резолвер, оборачивающий конкретный экземпляр [T] (или Future<T>), без вызова провайдера. /// Concrete resolver for direct instance ([T] or [Future<T>]). No provider is called.
/// ENG: Resolver that wraps a concrete instance of [T] (or Future<T>), without provider invocation. ///
/// Used for [Binding.toInstance].
/// Supports both sync and async resolution; sync will throw if underlying instance is [Future].
/// Examples:
/// ```dart
/// var resolver = InstanceResolver("hello");
/// resolver.resolveSync(); // == "hello"
/// var asyncResolver = InstanceResolver(Future.value(7));
/// asyncResolver.resolveAsync(); // Future<int>
/// ```
class InstanceResolver<T> implements BindingResolver<T> { class InstanceResolver<T> implements BindingResolver<T> {
final Instance<T> _instance; final Instance<T> _instance;
/// RU: Создаёт резолвер, оборачивающий значение [instance]. /// Wraps the given instance (sync or async) in a resolver.
/// ENG: Creates a resolver that wraps the given [instance].
InstanceResolver(this._instance); InstanceResolver(this._instance);
@override @override
@@ -62,7 +91,6 @@ class InstanceResolver<T> implements BindingResolver<T> {
@override @override
Future<T> resolveAsync([_]) { Future<T> resolveAsync([_]) {
if (_instance is Future<T>) return _instance; if (_instance is Future<T>) return _instance;
return Future.value(_instance); return Future.value(_instance);
} }
@@ -73,8 +101,23 @@ class InstanceResolver<T> implements BindingResolver<T> {
bool get isSingleton => true; bool get isSingleton => true;
} }
/// RU: Резолвер, оборачивающий провайдер, с возможностью синглтон-кеширования. /// Resolver for provider functions (sync/async/factory), with optional singleton caching.
/// ENG: Resolver that wraps a provider, with optional singleton caching. /// Used for [Binding.toProvide], [Binding.toProvideWithParams], [Binding.singleton].
///
/// Examples:
/// ```dart
/// // No param, sync:
/// var r = ProviderResolver((_) => 5, withParams: false);
/// r.resolveSync(); // == 5
/// // With param:
/// var rp = ProviderResolver((p) => p * 2, withParams: true);
/// rp.resolveSync(2); // == 4
/// // Singleton:
/// r.toSingleton();
/// // Async:
/// var ra = ProviderResolver((_) async => await Future.value(10), withParams: false);
/// await ra.resolveAsync(); // == 10
/// ```
class ProviderResolver<T> implements BindingResolver<T> { class ProviderResolver<T> implements BindingResolver<T> {
final ProviderWithParams<T> _provider; final ProviderWithParams<T> _provider;
final bool _withParams; final bool _withParams;
@@ -82,8 +125,7 @@ class ProviderResolver<T> implements BindingResolver<T> {
FutureOr<T>? _cache; FutureOr<T>? _cache;
bool _singleton = false; bool _singleton = false;
/// RU: Создаёт резолвер из произвольной функции [raw], поддерживающей ноль или один параметр. /// Creates a resolver from [provider], optionally accepting dynamic params.
/// ENG: Creates a resolver from arbitrary function [raw], supporting zero or one parameter.
ProviderResolver( ProviderResolver(
ProviderWithParams<T> provider, { ProviderWithParams<T> provider, {
required bool withParams, required bool withParams,
@@ -93,16 +135,13 @@ class ProviderResolver<T> implements BindingResolver<T> {
@override @override
T resolveSync([dynamic params]) { T resolveSync([dynamic params]) {
_checkParams(params); _checkParams(params);
final result = _cache ?? _provider(params); final result = _cache ?? _provider(params);
if (result is T) { if (result is T) {
if (_singleton) { if (_singleton) {
_cache ??= result; _cache ??= result;
} }
return result; return result;
} }
throw StateError( throw StateError(
'Provider [$_provider] return Future<$T>. Use resolveAsync() instead.', 'Provider [$_provider] return Future<$T>. Use resolveAsync() instead.',
); );
@@ -111,14 +150,11 @@ class ProviderResolver<T> implements BindingResolver<T> {
@override @override
Future<T> resolveAsync([dynamic params]) { Future<T> resolveAsync([dynamic params]) {
_checkParams(params); _checkParams(params);
final result = _cache ?? _provider(params); final result = _cache ?? _provider(params);
final target = result is Future<T> ? result : Future<T>.value(result); final target = result is Future<T> ? result : Future<T>.value(result);
if (_singleton) { if (_singleton) {
_cache ??= target; _cache ??= target;
} }
return target; return target;
} }
@@ -130,8 +166,7 @@ class ProviderResolver<T> implements BindingResolver<T> {
@override @override
bool get isSingleton => _singleton; bool get isSingleton => _singleton;
/// RU: Проверяет, был ли передан параметр, если провайдер требует его. /// Throws if params required but not supplied.
/// ENG: Checks if parameter is passed when the provider expects it.
void _checkParams(dynamic params) { void _checkParams(dynamic params) {
if (_withParams && params == null) { if (_withParams && params == null) {
throw StateError( throw StateError(

View File

@@ -14,16 +14,20 @@
import 'dart:collection'; import 'dart:collection';
import 'package:cherrypick/src/observer.dart'; import 'package:cherrypick/src/observer.dart';
/// RU: Исключение, выбрасываемое при обнаружении циклической зависимости. /// Exception thrown when a circular dependency is detected during dependency resolution.
/// ENG: Exception thrown when a circular dependency is detected. ///
/// Contains a [message] and the [dependencyChain] showing the resolution cycle.
///
/// Example diagnostic:
/// ```
/// CircularDependencyException: Circular dependency detected for A
/// Dependency chain: A -> B -> C -> A
/// ```
class CircularDependencyException implements Exception { class CircularDependencyException implements Exception {
final String message; final String message;
final List<String> dependencyChain; final List<String> dependencyChain;
CircularDependencyException(this.message, this.dependencyChain) { CircularDependencyException(this.message, this.dependencyChain);
// DEBUG
}
@override @override
String toString() { String toString() {
@@ -32,8 +36,26 @@ class CircularDependencyException implements Exception {
} }
} }
/// RU: Детектор циклических зависимостей для CherryPick DI контейнера. /// Circular dependency detector for CherryPick DI containers.
/// ENG: Circular dependency detector for CherryPick DI container. ///
/// Tracks dependency resolution chains to detect and prevent infinite recursion caused by cycles.
/// Whenever a resolve chain re-enters a started dependency, a [CircularDependencyException] is thrown with the full chain.
///
/// This class is used internally, but you can interact with it through [CycleDetectionMixin].
///
/// Example usage (pseudocode):
/// ```dart
/// final detector = CycleDetector(observer: myObserver);
/// try {
/// detector.startResolving<A>();
/// // ... resolving A which depends on B, etc
/// detector.startResolving<B>();
/// detector.startResolving<A>(); // BOOM: throws exception
/// } finally {
/// detector.finishResolving<B>();
/// detector.finishResolving<A>();
/// }
/// ```
class CycleDetector { class CycleDetector {
final CherryPickObserver _observer; final CherryPickObserver _observer;
final Set<String> _resolutionStack = HashSet<String>(); final Set<String> _resolutionStack = HashSet<String>();
@@ -41,10 +63,9 @@ class CycleDetector {
CycleDetector({required CherryPickObserver observer}) : _observer = observer; CycleDetector({required CherryPickObserver observer}) : _observer = observer;
/// RU: Начинает отслеживание разрешения зависимости. /// Starts tracking dependency resolution for type [T] and optional [named] qualifier.
/// ENG: Starts tracking dependency resolution.
/// ///
/// Throws [CircularDependencyException] if circular dependency is detected. /// Throws [CircularDependencyException] if a cycle is found.
void startResolving<T>({String? named}) { void startResolving<T>({String? named}) {
final dependencyKey = _createDependencyKey<T>(named); final dependencyKey = _createDependencyKey<T>(named);
_observer.onDiagnostic( _observer.onDiagnostic(
@@ -57,26 +78,19 @@ class CycleDetector {
if (_resolutionStack.contains(dependencyKey)) { if (_resolutionStack.contains(dependencyKey)) {
final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey); final cycleStartIndex = _resolutionHistory.indexOf(dependencyKey);
final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey); final cycle = _resolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
_observer.onCycleDetected( _observer.onCycleDetected(cycle);
cycle, _observer.onError('Cycle detected for $dependencyKey', null, null);
);
_observer.onError(
'Cycle detected for $dependencyKey',
null,
null,
);
throw CircularDependencyException( throw CircularDependencyException(
'Circular dependency detected for $dependencyKey', 'Circular dependency detected for $dependencyKey',
cycle, cycle,
); );
} }
_resolutionStack.add(dependencyKey); _resolutionStack.add(dependencyKey);
_resolutionHistory.add(dependencyKey); _resolutionHistory.add(dependencyKey);
} }
/// RU: Завершает отслеживание разрешения зависимости. /// Stops tracking dependency resolution for type [T] and optional [named] qualifier.
/// ENG: Finishes tracking dependency resolution. /// Should always be called after [startResolving], including for errors.
void finishResolving<T>({String? named}) { void finishResolving<T>({String? named}) {
final dependencyKey = _createDependencyKey<T>(named); final dependencyKey = _createDependencyKey<T>(named);
_observer.onDiagnostic( _observer.onDiagnostic(
@@ -84,15 +98,13 @@ class CycleDetector {
details: {'event': 'finishResolving'}, details: {'event': 'finishResolving'},
); );
_resolutionStack.remove(dependencyKey); _resolutionStack.remove(dependencyKey);
// Удаляем из истории только если это последний элемент // Only remove from history if it's the last one
if (_resolutionHistory.isNotEmpty && if (_resolutionHistory.isNotEmpty && _resolutionHistory.last == dependencyKey) {
_resolutionHistory.last == dependencyKey) {
_resolutionHistory.removeLast(); _resolutionHistory.removeLast();
} }
} }
/// RU: Очищает все состояние детектора. /// Clears all resolution state and resets the cycle detector.
/// ENG: Clears all detector state.
void clear() { void clear() {
_observer.onDiagnostic( _observer.onDiagnostic(
'CycleDetector clear', 'CycleDetector clear',
@@ -105,33 +117,45 @@ class CycleDetector {
_resolutionHistory.clear(); _resolutionHistory.clear();
} }
/// RU: Проверяет, находится ли зависимость в процессе разрешения. /// Returns true if dependency [T] (and [named], if specified) is being resolved right now.
/// ENG: Checks if dependency is currently being resolved.
bool isResolving<T>({String? named}) { bool isResolving<T>({String? named}) {
final dependencyKey = _createDependencyKey<T>(named); final dependencyKey = _createDependencyKey<T>(named);
return _resolutionStack.contains(dependencyKey); return _resolutionStack.contains(dependencyKey);
} }
/// RU: Возвращает текущую цепочку разрешения зависимостей. /// Gets the current dependency resolution chain (for diagnostics or debugging).
/// ENG: Returns current dependency resolution chain.
List<String> get currentResolutionChain => List.unmodifiable(_resolutionHistory); List<String> get currentResolutionChain => List.unmodifiable(_resolutionHistory);
/// RU: Создает уникальный ключ для зависимости. /// Returns a unique string key for type [T] (+name).
/// ENG: Creates unique key for dependency.
String _createDependencyKey<T>(String? named) { String _createDependencyKey<T>(String? named) {
final typeName = T.toString(); final typeName = T.toString();
return named != null ? '$typeName@$named' : typeName; return named != null ? '$typeName@$named' : typeName;
} }
} }
/// RU: Миксин для добавления поддержки обнаружения циклических зависимостей. /// Mixin for adding circular dependency detection support to custom DI containers/classes.
/// ENG: Mixin for adding circular dependency detection support. ///
/// Fields:
/// - `observer`: must be implemented by your class (used for diagnostics and error reporting)
///
/// Example usage:
/// ```dart
/// class MyContainer with CycleDetectionMixin {
/// @override
/// CherryPickObserver get observer => myObserver;
/// }
///
/// final c = MyContainer();
/// c.enableCycleDetection();
/// c.withCycleDetection(String, null, () {
/// // ... dependency resolution code
/// });
/// ```
mixin CycleDetectionMixin { mixin CycleDetectionMixin {
CycleDetector? _cycleDetector; CycleDetector? _cycleDetector;
CherryPickObserver get observer; CherryPickObserver get observer;
/// RU: Включает обнаружение циклических зависимостей. /// Turns on circular dependency detection for this class/container.
/// ENG: Enables circular dependency detection.
void enableCycleDetection() { void enableCycleDetection() {
_cycleDetector = CycleDetector(observer: observer); _cycleDetector = CycleDetector(observer: observer);
observer.onDiagnostic( observer.onDiagnostic(
@@ -143,8 +167,7 @@ mixin CycleDetectionMixin {
); );
} }
/// RU: Отключает обнаружение циклических зависимостей. /// Shuts off detection and clears any cycle history for this container.
/// ENG: Disables circular dependency detection.
void disableCycleDetection() { void disableCycleDetection() {
_cycleDetector?.clear(); _cycleDetector?.clear();
observer.onDiagnostic( observer.onDiagnostic(
@@ -157,12 +180,17 @@ mixin CycleDetectionMixin {
_cycleDetector = null; _cycleDetector = null;
} }
/// RU: Проверяет, включено ли обнаружение циклических зависимостей. /// Returns true if detection is currently enabled.
/// ENG: Checks if circular dependency detection is enabled.
bool get isCycleDetectionEnabled => _cycleDetector != null; bool get isCycleDetectionEnabled => _cycleDetector != null;
/// RU: Выполняет действие с отслеживанием циклических зависимостей. /// Executes [action] while tracking for circular DI cycles for [dependencyType] and [named].
/// ENG: Executes action with circular dependency tracking. ///
/// Throws [CircularDependencyException] if a dependency cycle is detected.
///
/// Example:
/// ```dart
/// withCycleDetection(String, 'api', () => resolveApi());
/// ```
T withCycleDetection<T>( T withCycleDetection<T>(
Type dependencyType, Type dependencyType,
String? named, String? named,
@@ -180,14 +208,8 @@ mixin CycleDetectionMixin {
final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey); final cycleStartIndex = _cycleDetector!._resolutionHistory.indexOf(dependencyKey);
final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex) final cycle = _cycleDetector!._resolutionHistory.sublist(cycleStartIndex)
..add(dependencyKey); ..add(dependencyKey);
observer.onCycleDetected( observer.onCycleDetected(cycle);
cycle, observer.onError('Cycle detected for $dependencyKey', null, null);
);
observer.onError(
'Cycle detected for $dependencyKey',
null,
null,
);
throw CircularDependencyException( throw CircularDependencyException(
'Circular dependency detected for $dependencyKey', 'Circular dependency detected for $dependencyKey',
cycle, cycle,
@@ -208,8 +230,7 @@ mixin CycleDetectionMixin {
} }
} }
/// RU: Возвращает текущую цепочку разрешения зависимостей. /// Gets the current active dependency resolution chain.
/// ENG: Returns current dependency resolution chain.
List<String> get currentResolutionChain => List<String> get currentResolutionChain =>
_cycleDetector?.currentResolutionChain ?? []; _cycleDetector?.currentResolutionChain ?? [];
} }

View File

@@ -12,6 +12,28 @@
// //
import 'package:cherrypick/src/scope.dart'; import 'package:cherrypick/src/scope.dart';
/// Abstract factory interface for creating objects of type [T] using a [Scope].
///
/// Can be implemented for advanced dependency injection scenarios where
/// the resolution requires contextual information from the DI [Scope].
///
/// Often used to supply complex objects, runtime-bound services,
/// or objects depending on dynamic configuration.
///
/// Example usage:
/// ```dart
/// class MyServiceFactory implements Factory<MyService> {
/// @override
/// MyService createInstance(Scope scope) {
/// final db = scope.resolve<Database>(named: "main");
/// return MyService(db);
/// }
/// }
///
/// // Usage in a module:
/// bind<MyService>().toProvide(() => MyServiceFactory().createInstance(scope));
/// ```
abstract class Factory<T> { abstract class Factory<T> {
/// Implement this to provide an instance of [T], with access to the resolving [scope].
T createInstance(Scope scope); T createInstance(Scope scope);
} }

View File

@@ -15,33 +15,47 @@ import 'dart:collection';
import 'package:cherrypick/cherrypick.dart'; import 'package:cherrypick/cherrypick.dart';
/// RU: Глобальный детектор циклических зависимостей для всей иерархии скоупов. /// GlobalCycleDetector detects and prevents circular dependencies across an entire DI scope hierarchy.
/// ENG: Global circular dependency detector for entire scope hierarchy. ///
/// This is particularly important for modular/feature-based applications
/// where subscopes can introduce indirect cycles that span different scopes.
///
/// The detector tracks resolution chains and throws [CircularDependencyException]
/// when a cycle is found, showing the full chain (including scope context).
///
/// Example usage via [GlobalCycleDetectionMixin]:
/// ```dart
/// class MyScope with GlobalCycleDetectionMixin { /* ... */ }
///
/// final scope = MyScope();
/// scope.setScopeId('feature');
/// scope.enableGlobalCycleDetection();
///
/// scope.withGlobalCycleDetection(String, null, () {
/// // ... resolve dependencies here, will detect cross-scope cycles
/// });
/// ```
class GlobalCycleDetector { class GlobalCycleDetector {
static GlobalCycleDetector? _instance; static GlobalCycleDetector? _instance;
final CherryPickObserver _observer; final CherryPickObserver _observer;
// Глобальный стек разрешения зависимостей // Global set and chain history for all resolutions
final Set<String> _globalResolutionStack = HashSet<String>(); final Set<String> _globalResolutionStack = HashSet<String>();
// История разрешения для построения цепочки зависимостей
final List<String> _globalResolutionHistory = []; final List<String> _globalResolutionHistory = [];
// Карта активных детекторов по скоупам // Map of active detectors for subscopes (rarely used directly)
final Map<String, CycleDetector> _scopeDetectors = HashMap<String, CycleDetector>(); final Map<String, CycleDetector> _scopeDetectors = HashMap<String, CycleDetector>();
GlobalCycleDetector._internal({required CherryPickObserver observer}): _observer = observer; GlobalCycleDetector._internal({required CherryPickObserver observer}): _observer = observer;
/// RU: Получить единственный экземпляр глобального детектора. /// Returns the singleton global detector instance, initializing it if needed.
/// ENG: Get singleton instance of global detector.
static GlobalCycleDetector get instance { static GlobalCycleDetector get instance {
_instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver); _instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver);
return _instance!; return _instance!;
} }
/// RU: Сбросить глобальный детектор (полезно для тестов). /// Reset internal state (useful for testing).
/// ENG: Reset global detector (useful for tests).
static void reset() { static void reset() {
_instance?._globalResolutionStack.clear(); _instance?._globalResolutionStack.clear();
_instance?._globalResolutionHistory.clear(); _instance?._globalResolutionHistory.clear();
@@ -49,24 +63,16 @@ class GlobalCycleDetector {
_instance = null; _instance = null;
} }
/// RU: Начать отслеживание разрешения зависимости в глобальном контексте. /// Start tracking resolution of dependency [T] with optional [named] and [scopeId].
/// ENG: Start tracking dependency resolution in global context. /// Throws [CircularDependencyException] on cycle.
void startGlobalResolving<T>({String? named, String? scopeId}) { void startGlobalResolving<T>({String? named, String? scopeId}) {
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId); final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
if (_globalResolutionStack.contains(dependencyKey)) { if (_globalResolutionStack.contains(dependencyKey)) {
// Найдена глобальная циклическая зависимость
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey); final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey); final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
_observer.onCycleDetected( _observer.onCycleDetected(cycle, scopeName: scopeId);
cycle, _observer.onError('Global circular dependency detected for $dependencyKey', null, null);
scopeName: scopeId,
);
_observer.onError(
'Global circular dependency detected for $dependencyKey',
null,
null,
);
throw CircularDependencyException( throw CircularDependencyException(
'Global circular dependency detected for $dependencyKey', 'Global circular dependency detected for $dependencyKey',
cycle, cycle,
@@ -77,21 +83,18 @@ class GlobalCycleDetector {
_globalResolutionHistory.add(dependencyKey); _globalResolutionHistory.add(dependencyKey);
} }
/// RU: Завершить отслеживание разрешения зависимости в глобальном контексте. /// Finish tracking a dependency. Should always be called after [startGlobalResolving].
/// ENG: Finish tracking dependency resolution in global context.
void finishGlobalResolving<T>({String? named, String? scopeId}) { void finishGlobalResolving<T>({String? named, String? scopeId}) {
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId); final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
_globalResolutionStack.remove(dependencyKey); _globalResolutionStack.remove(dependencyKey);
// Удаляем из истории только если это последний элемент if (_globalResolutionHistory.isNotEmpty && _globalResolutionHistory.last == dependencyKey) {
if (_globalResolutionHistory.isNotEmpty &&
_globalResolutionHistory.last == dependencyKey) {
_globalResolutionHistory.removeLast(); _globalResolutionHistory.removeLast();
} }
} }
/// RU: Выполнить действие с глобальным отслеживанием циклических зависимостей. /// Internally execute [action] with global cycle detection for [dependencyType], [named], [scopeId].
/// ENG: Execute action with global circular dependency tracking. /// Throws [CircularDependencyException] on cycle.
T withGlobalCycleDetection<T>( T withGlobalCycleDetection<T>(
Type dependencyType, Type dependencyType,
String? named, String? named,
@@ -102,17 +105,9 @@ class GlobalCycleDetector {
if (_globalResolutionStack.contains(dependencyKey)) { if (_globalResolutionStack.contains(dependencyKey)) {
final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey); final cycleStartIndex = _globalResolutionHistory.indexOf(dependencyKey);
final cycle = _globalResolutionHistory.sublist(cycleStartIndex) final cycle = _globalResolutionHistory.sublist(cycleStartIndex)..add(dependencyKey);
..add(dependencyKey); _observer.onCycleDetected(cycle, scopeName: scopeId);
_observer.onCycleDetected( _observer.onError('Global circular dependency detected for $dependencyKey', null, null);
cycle,
scopeName: scopeId,
);
_observer.onError(
'Global circular dependency detected for $dependencyKey',
null,
null,
);
throw CircularDependencyException( throw CircularDependencyException(
'Global circular dependency detected for $dependencyKey', 'Global circular dependency detected for $dependencyKey',
cycle, cycle,
@@ -126,38 +121,32 @@ class GlobalCycleDetector {
return action(); return action();
} finally { } finally {
_globalResolutionStack.remove(dependencyKey); _globalResolutionStack.remove(dependencyKey);
if (_globalResolutionHistory.isNotEmpty && if (_globalResolutionHistory.isNotEmpty && _globalResolutionHistory.last == dependencyKey) {
_globalResolutionHistory.last == dependencyKey) {
_globalResolutionHistory.removeLast(); _globalResolutionHistory.removeLast();
} }
} }
} }
/// RU: Получить детектор для конкретного скоупа. /// Get per-scope detector (not usually needed by consumers).
/// ENG: Get detector for specific scope.
CycleDetector getScopeDetector(String scopeId) { CycleDetector getScopeDetector(String scopeId) {
return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver)); return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver));
} }
/// RU: Удалить детектор для скоупа. /// Remove detector for a given scope.
/// ENG: Remove detector for scope.
void removeScopeDetector(String scopeId) { void removeScopeDetector(String scopeId) {
_scopeDetectors.remove(scopeId); _scopeDetectors.remove(scopeId);
} }
/// RU: Проверить, находится ли зависимость в процессе глобального разрешения. /// Returns true if dependency [T] is currently being resolved in the global scope.
/// ENG: Check if dependency is currently being resolved globally.
bool isGloballyResolving<T>({String? named, String? scopeId}) { bool isGloballyResolving<T>({String? named, String? scopeId}) {
final dependencyKey = _createDependencyKeyFromType(T, named, scopeId); final dependencyKey = _createDependencyKeyFromType(T, named, scopeId);
return _globalResolutionStack.contains(dependencyKey); return _globalResolutionStack.contains(dependencyKey);
} }
/// RU: Получить текущую глобальную цепочку разрешения зависимостей. /// Get current global dependency resolution chain (for debugging or diagnostics).
/// ENG: Get current global dependency resolution chain.
List<String> get globalResolutionChain => List.unmodifiable(_globalResolutionHistory); List<String> get globalResolutionChain => List.unmodifiable(_globalResolutionHistory);
/// RU: Очистить все состояние детектора. /// Clears all global and per-scope state in this detector.
/// ENG: Clear all detector state.
void clear() { void clear() {
_globalResolutionStack.clear(); _globalResolutionStack.clear();
_globalResolutionHistory.clear(); _globalResolutionHistory.clear();
@@ -167,14 +156,7 @@ class GlobalCycleDetector {
void _detectorClear(detector) => detector.clear(); void _detectorClear(detector) => detector.clear();
/// RU: Создать уникальный ключ для зависимости с учетом скоупа. /// Creates a unique dependency key string including scope and name (for diagnostics/cycle checks).
/// ENG: Create unique key for dependency including scope.
//String _createDependencyKey<T>(String? named, String? scopeId) {
// return _createDependencyKeyFromType(T, named, scopeId);
//}
/// RU: Создать уникальный ключ для зависимости по типу с учетом скоупа.
/// ENG: Create unique key for dependency by type including scope.
String _createDependencyKeyFromType(Type type, String? named, String? scopeId) { String _createDependencyKeyFromType(Type type, String? named, String? scopeId) {
final typeName = type.toString(); final typeName = type.toString();
final namePrefix = named != null ? '@$named' : ''; final namePrefix = named != null ? '@$named' : '';
@@ -183,40 +165,53 @@ class GlobalCycleDetector {
} }
} }
/// RU: Улучшенный миксин для глобального обнаружения циклических зависимостей. /// Enhanced mixin for global circular dependency detection, to be mixed into
/// ENG: Enhanced mixin for global circular dependency detection. /// DI scopes or containers that want cross-scope protection.
///
/// Typical usage pattern:
/// ```dart
/// class MySubscope with GlobalCycleDetectionMixin { ... }
///
/// final scope = MySubscope();
/// scope.setScopeId('user_profile');
/// scope.enableGlobalCycleDetection();
///
/// scope.withGlobalCycleDetection(UserService, null, () {
/// // ... resolve user service and friends, auto-detects global cycles
/// });
/// ```
mixin GlobalCycleDetectionMixin { mixin GlobalCycleDetectionMixin {
String? _scopeId; String? _scopeId;
bool _globalCycleDetectionEnabled = false; bool _globalCycleDetectionEnabled = false;
/// RU: Установить идентификатор скоупа для глобального отслеживания. /// Set the scope's unique identifier for global tracking (should be called at scope initialization).
/// ENG: Set scope identifier for global tracking.
void setScopeId(String scopeId) { void setScopeId(String scopeId) {
_scopeId = scopeId; _scopeId = scopeId;
} }
/// RU: Получить идентификатор скоупа. /// Get the scope's id, if configured.
/// ENG: Get scope identifier.
String? get scopeId => _scopeId; String? get scopeId => _scopeId;
/// RU: Включить глобальное обнаружение циклических зависимостей. /// Enable global cross-scope circular dependency detection.
/// ENG: Enable global circular dependency detection.
void enableGlobalCycleDetection() { void enableGlobalCycleDetection() {
_globalCycleDetectionEnabled = true; _globalCycleDetectionEnabled = true;
} }
/// RU: Отключить глобальное обнаружение циклических зависимостей. /// Disable global cycle detection (no cycle checks will be performed globally).
/// ENG: Disable global circular dependency detection.
void disableGlobalCycleDetection() { void disableGlobalCycleDetection() {
_globalCycleDetectionEnabled = false; _globalCycleDetectionEnabled = false;
} }
/// RU: Проверить, включено ли глобальное обнаружение циклических зависимостей. /// Returns true if global cycle detection is currently enabled for this scope/container.
/// ENG: Check if global circular dependency detection is enabled.
bool get isGlobalCycleDetectionEnabled => _globalCycleDetectionEnabled; bool get isGlobalCycleDetectionEnabled => _globalCycleDetectionEnabled;
/// RU: Выполнить действие с глобальным отслеживанием циклических зависимостей. /// Executes [action] with global cycle detection for [dependencyType] and [named].
/// ENG: Execute action with global circular dependency tracking. /// Throws [CircularDependencyException] if a cycle is detected.
///
/// Example:
/// ```dart
/// withGlobalCycleDetection(UserService, null, () => resolveUser());
/// ```
T withGlobalCycleDetection<T>( T withGlobalCycleDetection<T>(
Type dependencyType, Type dependencyType,
String? named, String? named,
@@ -234,8 +229,7 @@ mixin GlobalCycleDetectionMixin {
); );
} }
/// RU: Получить текущую глобальную цепочку разрешения зависимостей. /// Access the current global dependency resolution chain for diagnostics.
/// ENG: Get current global dependency resolution chain.
List<String> get globalResolutionChain => List<String> get globalResolutionChain =>
GlobalCycleDetector.instance.globalResolutionChain; GlobalCycleDetector.instance.globalResolutionChain;
} }

View File

@@ -15,39 +15,71 @@ import 'dart:collection';
import 'package:cherrypick/src/binding.dart'; import 'package:cherrypick/src/binding.dart';
import 'package:cherrypick/src/scope.dart'; import 'package:cherrypick/src/scope.dart';
/// RU: Класс Module является основой для пользовательских модулей. /// Represents a DI module—a reusable group of dependency bindings.
/// Этот класс нужен для инициализации [Scope].
/// ///
/// RU: The Module class is the basis for custom modules. /// Extend [Module] to declaratively group related [Binding] definitions,
/// This class is needed to initialize [Scope]. /// then install your module(s) into a [Scope] for dependency resolution.
///
/// Modules make it easier to organize your DI configuration for features, layers,
/// infrastructure, or integration, and support modular app architecture.
///
/// Usage example:
/// ```dart
/// class AppModule extends Module {
/// @override
/// void builder(Scope currentScope) {
/// bind<NetworkService>().toProvide(() => NetworkService());
/// bind<AuthService>().toProvide(() => AuthService(currentScope.resolve<NetworkService>()));
/// bind<Config>().toInstance(Config.dev());
/// }
/// }
///
/// // Installing the module into the root DI scope:
/// final rootScope = CherryPick.openRootScope();
/// rootScope.installModules([AppModule()]);
/// ```
///
/// Combine several modules and submodules to implement scalable architectures.
/// ///
abstract class Module { abstract class Module {
final Set<Binding> _bindingSet = HashSet(); final Set<Binding> _bindingSet = HashSet();
/// RU: Метод добавляет в коллекцию модуля [Binding] экземпляр. /// Begins the declaration of a new binding within this module.
/// ///
/// ENG: The method adds an instance to the collection of the [Binding] module. /// Typically used within [builder] to register all needed dependency bindings.
/// ///
/// return [Binding<T>] /// Example:
/// ```dart
/// bind<Api>().toProvide(() => MockApi());
/// bind<Config>().toInstance(Config.dev());
/// ```
Binding<T> bind<T>() { Binding<T> bind<T>() {
final binding = Binding<T>(); final binding = Binding<T>();
_bindingSet.add(binding); _bindingSet.add(binding);
return binding; return binding;
} }
/// RU: Метод возвращает коллекцию [Binding] экземпляров. /// Returns the set of all [Binding] instances registered in this module.
/// ///
/// ENG: The method returns a collection of [Binding] instances. /// This is typically used internally by [Scope] during module installation,
/// /// but can also be used for diagnostics or introspection.
/// return [Set<Binding>]
Set<Binding> get bindingSet => _bindingSet; Set<Binding> get bindingSet => _bindingSet;
/// RU: Абстрактный метод для инициализации пользовательских экземпляров. /// Abstract method where all dependency bindings are registered.
/// В этом методе осуществляется конфигурация зависимостей.
/// ///
/// ENG: Abstract method for initializing custom instances. /// Override this method in your custom module subclass to declare
/// This method configures dependencies. /// all dependency bindings to be provided by this module.
/// ///
/// return [void] /// The provided [currentScope] can be used for resolving other dependencies,
/// accessing configuration, or controlling binding behavior dynamically.
///
/// Example (with dependency chaining):
/// ```dart
/// @override
/// void builder(Scope currentScope) {
/// bind<ApiClient>().toProvide(() => RestApi());
/// bind<UserRepo>().toProvide(() => UserRepo(currentScope.resolve<ApiClient>()));
/// }
/// ```
void builder(Scope currentScope); void builder(Scope currentScope);
} }

View File

@@ -21,6 +21,37 @@ import 'package:cherrypick/src/module.dart';
import 'package:cherrypick/src/observer.dart'; import 'package:cherrypick/src/observer.dart';
// import 'package:cherrypick/src/log_format.dart'; // import 'package:cherrypick/src/log_format.dart';
/// Represents a DI scope (container) for modules, subscopes,
/// and dependency resolution (sync/async) in CherryPick.
///
/// Scopes provide hierarchical DI: you can resolve dependencies from parents,
/// override or isolate modules, and manage scope-specific singletons.
///
/// Each scope:
/// - Can install modules ([installModules]) that define [Binding]s
/// - Supports parent-child scope tree (see [openSubScope])
/// - Can resolve dependencies synchronously ([resolve]) or asynchronously ([resolveAsync])
/// - Cleans up resources for [Disposable] objects (see [dispose])
/// - Detects dependency cycles (local and global, if enabled)
///
/// Example usage:
/// ```dart
/// final rootScope = CherryPick.openRootScope();
/// rootScope.installModules([AppModule()]);
///
/// // Synchronous resolution:
/// final auth = rootScope.resolve<AuthService>();
///
/// // Asynchronous resolution:
/// final db = await rootScope.resolveAsync<Database>();
///
/// // Open a child scope (for a feature, page, or test):
/// final userScope = rootScope.openSubScope('user');
/// userScope.installModules([UserModule()]);
///
/// // Proper resource cleanup (calls dispose() on tracked objects)
/// await CherryPick.closeRootScope();
/// ```
class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin { class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
final Scope? _parentScope; final Scope? _parentScope;
@@ -32,11 +63,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
/// COLLECTS all resolved instances that implement [Disposable]. /// COLLECTS all resolved instances that implement [Disposable].
final Set<Disposable> _disposables = HashSet(); final Set<Disposable> _disposables = HashSet();
/// RU: Метод возвращает родительский [Scope]. /// Returns the parent [Scope] if present, or null if this is the root scope.
///
/// ENG: The method returns the parent [Scope].
///
/// return [Scope]
Scope? get parentScope => _parentScope; Scope? get parentScope => _parentScope;
final Map<String, Scope> _scopeMap = HashMap(); final Map<String, Scope> _scopeMap = HashMap();
@@ -61,8 +88,9 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
final Map<Object, Map<String?, BindingResolver>> _bindingResolvers = {}; final Map<Object, Map<String?, BindingResolver>> _bindingResolvers = {};
/// RU: Генерирует уникальный идентификатор для скоупа. /// Generates a unique identifier string for this scope instance.
/// ENG: Generates unique identifier for scope. ///
/// Used internally for diagnostics, logging and global scope tracking.
String _generateScopeId() { String _generateScopeId() {
final random = Random(); final random = Random();
final timestamp = DateTime.now().millisecondsSinceEpoch; final timestamp = DateTime.now().millisecondsSinceEpoch;
@@ -70,16 +98,20 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return 'scope_${timestamp}_$randomPart'; return 'scope_${timestamp}_$randomPart';
} }
/// RU: Метод открывает дочерний (дополнительный) [Scope]. /// Opens a named child [Scope] (subscope) as a descendant of the current scope.
/// ///
/// ENG: The method opens child (additional) [Scope]. /// Subscopes inherit modules and DI context from their parent, but can override or extend bindings.
/// Useful for feature-isolation, screens, request/transaction lifetimes, and test separation.
/// ///
/// return [Scope] /// Example:
/// ```dart
/// final featureScope = rootScope.openSubScope('feature');
/// featureScope.installModules([FeatureModule()]);
/// final dep = featureScope.resolve<MyDep>();
/// ```
Scope openSubScope(String name) { Scope openSubScope(String name) {
if (!_scopeMap.containsKey(name)) { if (!_scopeMap.containsKey(name)) {
final childScope = Scope(this, observer: observer); // Наследуем observer вниз по иерархии final childScope = Scope(this, observer: observer);
// print removed (trace)
// Наследуем настройки обнаружения циклических зависимостей
if (isCycleDetectionEnabled) { if (isCycleDetectionEnabled) {
childScope.enableCycleDetection(); childScope.enableCycleDetection();
} }
@@ -101,16 +133,19 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return _scopeMap[name]!; return _scopeMap[name]!;
} }
/// RU: Метод закрывает дочерний (дополнительный) [Scope] асинхронно. /// Asynchronously closes and disposes a named child [Scope] (subscope).
/// ///
/// ENG: The method closes child (additional) [Scope] asynchronously. /// Ensures all [Disposable] objects and internal modules
/// in the subscope are properly cleaned up. Also removes any global cycle detectors associated with the subscope.
/// ///
/// return [Future<void>] /// Example:
/// ```dart
/// await rootScope.closeSubScope('feature');
/// ```
Future<void> closeSubScope(String name) async { Future<void> closeSubScope(String name) async {
final childScope = _scopeMap[name]; final childScope = _scopeMap[name];
if (childScope != null) { if (childScope != null) {
await childScope.dispose(); // асинхронный вызов await childScope.dispose();
// Очищаем детектор для дочернего скоупа
if (childScope.scopeId != null) { if (childScope.scopeId != null) {
GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!); GlobalCycleDetector.instance.removeScopeDetector(childScope.scopeId!);
} }
@@ -129,11 +164,15 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
_scopeMap.remove(name); _scopeMap.remove(name);
} }
/// RU: Метод инициализирует пользовательские модули в [Scope]. /// Installs a list of custom [Module]s into the [Scope].
/// ///
/// ENG: The method initializes custom modules in [Scope]. /// Each module registers bindings and configuration for dependencies.
/// After calling this, bindings are immediately available for resolve/tryResolve.
/// ///
/// return [Scope] /// Example:
/// ```dart
/// rootScope.installModules([AppModule(), NetworkModule()]);
/// ```
Scope installModules(List<Module> modules) { Scope installModules(List<Module> modules) {
_modulesList.addAll(modules); _modulesList.addAll(modules);
if (modules.isNotEmpty) { if (modules.isNotEmpty) {
@@ -153,7 +192,7 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
}, },
); );
module.builder(this); module.builder(this);
// После builder: для всех новых биндингов // Associate bindings with this scope's observer
for (final binding in module.bindingSet) { for (final binding in module.bindingSet) {
binding.observer = observer; binding.observer = observer;
binding.logAllDeferred(); binding.logAllDeferred();
@@ -163,11 +202,15 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return this; return this;
} }
/// RU: Метод удаляет пользовательские модули из [Scope]. /// Removes all installed [Module]s and their bindings from this [Scope].
/// ///
/// ENG: This method removes custom modules from [Scope]. /// Typically used in tests or when resetting app configuration/runtime environment.
/// Note: this does not dispose resolved [Disposable]s (call [dispose] for that).
/// ///
/// return [Scope] /// Example:
/// ```dart
/// testScope.dropModules();
/// ```
Scope dropModules() { Scope dropModules() {
if (_modulesList.isNotEmpty) { if (_modulesList.isNotEmpty) {
observer.onModulesRemoved( observer.onModulesRemoved(
@@ -188,20 +231,18 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return this; return this;
} }
/// RU: Возвращает найденную зависимость, определенную параметром типа [T]. /// Resolves a dependency of type [T], optionally by name and with params.
/// Выдает [StateError], если зависимость не может быть разрешена.
/// Если вы хотите получить [null], если зависимость не может быть найдена,
/// то используйте вместо этого [tryResolve]
/// return - возвращает объект типа [T] или [StateError]
/// ///
/// ENG: Returns the found dependency specified by the type parameter [T]. /// Throws [StateError] if the dependency cannot be resolved. (Use [tryResolve] for fallible lookup).
/// Throws [StateError] if the dependency cannot be resolved. /// Resolves from installed modules or recurses up the parent scope chain.
/// If you want to get [null] if the dependency cannot be found then use [tryResolve] instead
/// return - returns an object of type [T] or [StateError]
/// ///
/// Example:
/// ```dart
/// final logger = scope.resolve<Logger>();
/// final special = scope.resolve<Service>(named: 'special');
/// ```
T resolve<T>({String? named, dynamic params}) { T resolve<T>({String? named, dynamic params}) {
observer.onInstanceRequested(T.toString(), T, scopeName: scopeId); observer.onInstanceRequested(T.toString(), T, scopeName: scopeId);
// Используем глобальное отслеживание, если включено
T result; T result;
if (isGlobalCycleDetectionEnabled) { if (isGlobalCycleDetectionEnabled) {
try { try {
@@ -232,8 +273,9 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return result; return result;
} }
/// RU: Разрешение с локальным детектором циклических зависимостей. /// Resolves [T] using the local cycle detector for this scope.
/// ENG: Resolution with local circular dependency detector. /// Throws [StateError] if not found or cycle is detected.
/// Used internally by [resolve].
T _resolveWithLocalDetection<T>({String? named, dynamic params}) { T _resolveWithLocalDetection<T>({String? named, dynamic params}) {
return withCycleDetection<T>(T, named, () { return withCycleDetection<T>(T, named, () {
var resolved = _tryResolveInternal<T>(named: named, params: params); var resolved = _tryResolveInternal<T>(named: named, params: params);
@@ -262,11 +304,16 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
}); });
} }
/// RU: Возвращает найденную зависимость типа [T] или null, если она не может быть найдена. /// Attempts to resolve a dependency of type [T], optionally by name and with params.
/// ENG: Returns found dependency of type [T] or null if it cannot be found.
/// ///
/// Returns the resolved dependency, or `null` if not found.
/// Does not throw if missing (unlike [resolve]).
///
/// Example:
/// ```dart
/// final maybeDb = scope.tryResolve<Database>();
/// ```
T? tryResolve<T>({String? named, dynamic params}) { T? tryResolve<T>({String? named, dynamic params}) {
// Используем глобальное отслеживание, если включено
T? result; T? result;
if (isGlobalCycleDetectionEnabled) { if (isGlobalCycleDetectionEnabled) {
result = withGlobalCycleDetection<T?>(T, named, () { result = withGlobalCycleDetection<T?>(T, named, () {
@@ -279,8 +326,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return result; return result;
} }
/// RU: Попытка разрешения с локальным детектором циклических зависимостей. /// Attempts to resolve [T] using the local cycle detector. Returns null if not found or cycle.
/// ENG: Try resolution with local circular dependency detector. /// Used internally by [tryResolve].
T? _tryResolveWithLocalDetection<T>({String? named, dynamic params}) { T? _tryResolveWithLocalDetection<T>({String? named, dynamic params}) {
if (isCycleDetectionEnabled) { if (isCycleDetectionEnabled) {
return withCycleDetection<T?>(T, named, () { return withCycleDetection<T?>(T, named, () {
@@ -291,29 +338,25 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
} }
} }
/// RU: Внутренний метод для разрешения зависимостей без проверки циклических зависимостей. /// Locates and resolves [T] without cycle detection (direct lookup).
/// ENG: Internal method for dependency resolution without circular dependency checking. /// Returns null if not found. Used internally.
T? _tryResolveInternal<T>({String? named, dynamic params}) { T? _tryResolveInternal<T>({String? named, dynamic params}) {
final resolver = _findBindingResolver<T>(named); final resolver = _findBindingResolver<T>(named);
// 1 - Try from own modules; 2 - Fallback to parent
// 1 Поиск зависимости по всем модулям текущего скоупа
return resolver?.resolveSync(params) ?? return resolver?.resolveSync(params) ??
// 2 Поиск зависимостей в родительском скоупе
_parentScope?.tryResolve(named: named, params: params); _parentScope?.tryResolve(named: named, params: params);
} }
/// RU: Асинхронно возвращает найденную зависимость, определенную параметром типа [T]. /// Asynchronously resolves a dependency of type [T].
/// Выдает [StateError], если зависимость не может быть разрешена.
/// Если хотите получить [null], если зависимость не может быть найдена, используйте [tryResolveAsync].
/// return - возвращает объект типа [T] or [StateError]
/// ///
/// ENG: Asynchronously returns the found dependency specified by the type parameter [T]. /// Throws [StateError] if not found. (Use [tryResolveAsync] for a fallible async resolve.)
/// Throws [StateError] if the dependency cannot be resolved.
/// If you want to get [null] if the dependency cannot be found, use [tryResolveAsync] instead.
/// return - returns an object of type [T] or [StateError]
/// ///
/// Example:
/// ```dart
/// final db = await scope.resolveAsync<Database>();
/// final special = await scope.resolveAsync<Service>(named: "special");
/// ```
Future<T> resolveAsync<T>({String? named, dynamic params}) async { Future<T> resolveAsync<T>({String? named, dynamic params}) async {
// Используем глобальное отслеживание, если включено
T result; T result;
if (isGlobalCycleDetectionEnabled) { if (isGlobalCycleDetectionEnabled) {
result = await withGlobalCycleDetection<Future<T>>(T, named, () async { result = await withGlobalCycleDetection<Future<T>>(T, named, () async {
@@ -326,8 +369,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return result; return result;
} }
/// RU: Асинхронное разрешение с локальным детектором циклических зависимостей. /// Resolves [T] asynchronously using local cycle detector. Throws if not found.
/// ENG: Async resolution with local circular dependency detector. /// Internal implementation for async [resolveAsync].
Future<T> _resolveAsyncWithLocalDetection<T>({String? named, dynamic params}) async { Future<T> _resolveAsyncWithLocalDetection<T>({String? named, dynamic params}) async {
return withCycleDetection<Future<T>>(T, named, () async { return withCycleDetection<Future<T>>(T, named, () async {
var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params); var resolved = await _tryResolveAsyncInternal<T>(named: named, params: params);
@@ -356,8 +399,14 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
}); });
} }
/// Attempts to asynchronously resolve a dependency of type [T].
/// Returns the dependency or null if not present (never throws).
///
/// Example:
/// ```dart
/// final user = await scope.tryResolveAsync<User>();
/// ```
Future<T?> tryResolveAsync<T>({String? named, dynamic params}) async { Future<T?> tryResolveAsync<T>({String? named, dynamic params}) async {
// Используем глобальное отслеживание, если включено
T? result; T? result;
if (isGlobalCycleDetectionEnabled) { if (isGlobalCycleDetectionEnabled) {
result = await withGlobalCycleDetection<Future<T?>>(T, named, () async { result = await withGlobalCycleDetection<Future<T?>>(T, named, () async {
@@ -370,8 +419,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
return result; return result;
} }
/// RU: Асинхронная попытка разрешения с локальным детектором циклических зависимостей. /// Attempts to resolve [T] asynchronously using local cycle detector. Returns null if missing.
/// ENG: Async try resolution with local circular dependency detector. /// Internal implementation for async [tryResolveAsync].
Future<T?> _tryResolveAsyncWithLocalDetection<T>({String? named, dynamic params}) async { Future<T?> _tryResolveAsyncWithLocalDetection<T>({String? named, dynamic params}) async {
if (isCycleDetectionEnabled) { if (isCycleDetectionEnabled) {
return withCycleDetection<Future<T?>>(T, named, () async { return withCycleDetection<Future<T?>>(T, named, () async {
@@ -382,21 +431,21 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
} }
} }
/// RU: Внутренний метод для асинхронного разрешения зависимостей без проверки циклических зависимостей. /// Direct async resolution for [T] without cycle check. Returns null if missing. Internal use only.
/// ENG: Internal method for async dependency resolution without circular dependency checking.
Future<T?> _tryResolveAsyncInternal<T>({String? named, dynamic params}) async { Future<T?> _tryResolveAsyncInternal<T>({String? named, dynamic params}) async {
final resolver = _findBindingResolver<T>(named); final resolver = _findBindingResolver<T>(named);
// 1 - Try from own modules; 2 - Fallback to parent
// 1 Поиск зависимости по всем модулям текущего скоупа
return resolver?.resolveAsync(params) ?? return resolver?.resolveAsync(params) ??
// 2 Поиск зависимостей в родительском скоупе
_parentScope?.tryResolveAsync(named: named, params: params); _parentScope?.tryResolveAsync(named: named, params: params);
} }
/// Looks up the [BindingResolver] for [T] and [named] within this scope.
/// Returns null if none found. Internal use only.
BindingResolver<T>? _findBindingResolver<T>(String? named) => BindingResolver<T>? _findBindingResolver<T>(String? named) =>
_bindingResolvers[T]?[named] as BindingResolver<T>?; _bindingResolvers[T]?[named] as BindingResolver<T>?;
// Индексируем все bindingи после каждого installModules/dropModules /// Rebuilds the internal index of all [BindingResolver]s from installed modules.
/// Called after [installModules] and [dropModules]. Internal use only.
void _rebuildResolversIndex() { void _rebuildResolversIndex() {
_bindingResolvers.clear(); _bindingResolvers.clear();
for (var module in _modulesList) { for (var module in _modulesList) {
@@ -408,14 +457,24 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
} }
} }
/// INTERNAL: Tracks Disposable objects /// Tracks resolved [Disposable] instances, to ensure dispose is called automatically.
/// Internal use only.
void _trackDisposable(Object? obj) { void _trackDisposable(Object? obj) {
if (obj is Disposable && !_disposables.contains(obj)) { if (obj is Disposable && !_disposables.contains(obj)) {
_disposables.add(obj); _disposables.add(obj);
} }
} }
/// Calls dispose on all tracked disposables and child scopes recursively (async). /// Asynchronously disposes this [Scope], all tracked [Disposable] objects, and recursively
/// all its child subscopes.
///
/// This method should always be called when a scope is no longer needed
/// to guarantee timely resource cleanup (files, sockets, streams, handles, etc).
///
/// Example:
/// ```dart
/// await myScope.dispose();
/// ```
Future<void> dispose() async { Future<void> dispose() async {
// First dispose children scopes // First dispose children scopes
for (final subScope in _scopeMap.values) { for (final subScope in _scopeMap.values) {

View File

@@ -127,7 +127,7 @@ packages:
path: "../../cherrypick" path: "../../cherrypick"
relative: true relative: true
source: path source: path
version: "3.0.0-dev.7" version: "3.0.0-dev.8"
cherrypick_annotations: cherrypick_annotations:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -141,7 +141,7 @@ packages:
path: "../../cherrypick_flutter" path: "../../cherrypick_flutter"
relative: true relative: true
source: path source: path
version: "1.1.3-dev.7" version: "1.1.3-dev.8"
cherrypick_generator: cherrypick_generator:
dependency: "direct dev" dependency: "direct dev"
description: description:

View File

@@ -175,7 +175,7 @@ packages:
path: "../../cherrypick" path: "../../cherrypick"
relative: true relative: true
source: path source: path
version: "3.0.0-dev.7" version: "3.0.0-dev.8"
cherrypick_annotations: cherrypick_annotations:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -5,23 +5,23 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "76.0.0" version: "73.0.0"
_macros: _macros:
dependency: transitive dependency: transitive
description: dart description: dart
source: sdk source: sdk
version: "0.3.3" version: "0.3.2"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.11.0" version: "6.8.0"
ansi_styles: ansi_styles:
dependency: transitive dependency: transitive
description: description:
@@ -298,10 +298,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: macros name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.3-main.0" version: "0.1.2-main.4"
matcher: matcher:
dependency: transitive dependency: transitive
description: description: