mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-23 21:13:35 +00:00
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:
@@ -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.
@@ -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(
|
||||||
|
|||||||
@@ -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 a cycle is found.
|
||||||
/// Throws [CircularDependencyException] if circular dependency is detected.
|
|
||||||
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 ?? [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,49 +63,38 @@ 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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_globalResolutionStack.add(dependencyKey);
|
_globalResolutionStack.add(dependencyKey);
|
||||||
_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,
|
||||||
@@ -99,20 +102,12 @@ class GlobalCycleDetector {
|
|||||||
T Function() action,
|
T Function() action,
|
||||||
) {
|
) {
|
||||||
final dependencyKey = _createDependencyKeyFromType(dependencyType, named, scopeId);
|
final dependencyKey = _createDependencyKeyFromType(dependencyType, 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)
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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].
|
///
|
||||||
///
|
/// Extend [Module] to declaratively group related [Binding] definitions,
|
||||||
/// RU: The Module class is the basis for custom modules.
|
/// then install your module(s) into a [Scope] for dependency resolution.
|
||||||
/// This class is needed to initialize [Scope].
|
///
|
||||||
|
/// 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,24 +231,22 @@ 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 {
|
||||||
result = withGlobalCycleDetection<T>(T, named, () {
|
result = withGlobalCycleDetection<T>(T, named, () {
|
||||||
return _resolveWithLocalDetection<T>(named: named, params: params);
|
return _resolveWithLocalDetection<T>(named: named, params: params);
|
||||||
});
|
});
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
14
pubspec.lock
14
pubspec.lock
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user