mirror of
https://github.com/pese-git/cherrypick.git
synced 2026-01-24 05:25:19 +00:00
Compare commits
10 Commits
talker_che
...
cherrypick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4c5fd922e | ||
|
|
8870b8ce54 | ||
|
|
298cb65ac8 | ||
|
|
1b9db31c13 | ||
|
|
ca3cd2c8fd | ||
|
|
c91e15319b | ||
|
|
99e662124f | ||
|
|
03f54981f3 | ||
|
|
349efe6ba6 | ||
|
|
c2f0e027b6 |
102
CHANGELOG.md
102
CHANGELOG.md
@@ -3,6 +3,108 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## 2025-08-15
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`cherrypick` - `v3.0.0-dev.10`](#cherrypick---v300-dev10)
|
||||||
|
- [`cherrypick_annotations` - `v1.1.2-dev.1`](#cherrypick_annotations---v112-dev1)
|
||||||
|
- [`cherrypick_flutter` - `v1.1.3-dev.10`](#cherrypick_flutter---v113-dev10)
|
||||||
|
- [`cherrypick_generator` - `v2.0.0-dev.1`](#cherrypick_generator---v200-dev1)
|
||||||
|
- [`talker_cherrypick_logger` - `v1.1.0-dev.5`](#talker_cherrypick_logger---v110-dev5)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `cherrypick` - `v3.0.0-dev.10`
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
|
#### `cherrypick_annotations` - `v1.1.2-dev.1`
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
|
#### `cherrypick_flutter` - `v1.1.3-dev.10`
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
|
#### `cherrypick_generator` - `v2.0.0-dev.1`
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
|
#### `talker_cherrypick_logger` - `v1.1.0-dev.5`
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-08-13
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`talker_cherrypick_logger` - `v1.1.0-dev.4`](#talker_cherrypick_logger---v110-dev4)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `talker_cherrypick_logger` - `v1.1.0-dev.4`
|
||||||
|
|
||||||
|
- **DOCS**(readme): update install instructions to use pub.dev as default method and remove obsolete git example.
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-08-13
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`talker_cherrypick_logger` - `v1.1.0-dev.3`](#talker_cherrypick_logger---v110-dev3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `talker_cherrypick_logger` - `v1.1.0-dev.3`
|
||||||
|
|
||||||
|
|
||||||
|
## 2025-08-13
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Packages with breaking changes:
|
||||||
|
|
||||||
|
- There are no breaking changes in this release.
|
||||||
|
|
||||||
|
Packages with other changes:
|
||||||
|
|
||||||
|
- [`talker_cherrypick_logger` - `v1.1.0-dev.2`](#talker_cherrypick_logger---v110-dev2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `talker_cherrypick_logger` - `v1.1.0-dev.2`
|
||||||
|
|
||||||
|
- Bump "talker_cherrypick_logger" to `1.1.0-dev.2`.
|
||||||
|
|
||||||
|
|
||||||
## 2025-08-13
|
## 2025-08-13
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ class UniversalChainBenchmark<TContainer> extends BenchmarkBase {
|
|||||||
_childDi!.resolve<UniversalService>();
|
_childDi!.resolve<UniversalService>();
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.asyncChain:
|
case UniversalScenario.asyncChain:
|
||||||
throw UnsupportedError('asyncChain supported only in UniversalChainAsyncBenchmark');
|
throw UnsupportedError(
|
||||||
|
'asyncChain supported only in UniversalChainAsyncBenchmark');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,11 @@ class BenchmarkCliRunner {
|
|||||||
if (config.di == 'getit') {
|
if (config.di == 'getit') {
|
||||||
final di = GetItAdapter();
|
final di = GetItAdapter();
|
||||||
if (scenario == UniversalScenario.asyncChain) {
|
if (scenario == UniversalScenario.asyncChain) {
|
||||||
final benchAsync = UniversalChainAsyncBenchmark<GetIt>(di,
|
final benchAsync = UniversalChainAsyncBenchmark<GetIt>(
|
||||||
chainCount: c, nestingDepth: d, mode: mode,
|
di,
|
||||||
|
chainCount: c,
|
||||||
|
nestingDepth: d,
|
||||||
|
mode: mode,
|
||||||
);
|
);
|
||||||
benchResult = await BenchmarkRunner.runAsync(
|
benchResult = await BenchmarkRunner.runAsync(
|
||||||
benchmark: benchAsync,
|
benchmark: benchAsync,
|
||||||
@@ -45,8 +48,12 @@ class BenchmarkCliRunner {
|
|||||||
repeats: config.repeats,
|
repeats: config.repeats,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final benchSync = UniversalChainBenchmark<GetIt>(di,
|
final benchSync = UniversalChainBenchmark<GetIt>(
|
||||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
di,
|
||||||
|
chainCount: c,
|
||||||
|
nestingDepth: d,
|
||||||
|
mode: mode,
|
||||||
|
scenario: scenario,
|
||||||
);
|
);
|
||||||
benchResult = await BenchmarkRunner.runSync(
|
benchResult = await BenchmarkRunner.runSync(
|
||||||
benchmark: benchSync,
|
benchmark: benchSync,
|
||||||
@@ -57,8 +64,12 @@ class BenchmarkCliRunner {
|
|||||||
} else if (config.di == 'riverpod') {
|
} else if (config.di == 'riverpod') {
|
||||||
final di = RiverpodAdapter();
|
final di = RiverpodAdapter();
|
||||||
if (scenario == UniversalScenario.asyncChain) {
|
if (scenario == UniversalScenario.asyncChain) {
|
||||||
final benchAsync = UniversalChainAsyncBenchmark<Map<String, rp.ProviderBase<Object?>>>(di,
|
final benchAsync = UniversalChainAsyncBenchmark<
|
||||||
chainCount: c, nestingDepth: d, mode: mode,
|
Map<String, rp.ProviderBase<Object?>>>(
|
||||||
|
di,
|
||||||
|
chainCount: c,
|
||||||
|
nestingDepth: d,
|
||||||
|
mode: mode,
|
||||||
);
|
);
|
||||||
benchResult = await BenchmarkRunner.runAsync(
|
benchResult = await BenchmarkRunner.runAsync(
|
||||||
benchmark: benchAsync,
|
benchmark: benchAsync,
|
||||||
@@ -66,8 +77,13 @@ class BenchmarkCliRunner {
|
|||||||
repeats: config.repeats,
|
repeats: config.repeats,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final benchSync = UniversalChainBenchmark<Map<String, rp.ProviderBase<Object?>>>(di,
|
final benchSync = UniversalChainBenchmark<
|
||||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
Map<String, rp.ProviderBase<Object?>>>(
|
||||||
|
di,
|
||||||
|
chainCount: c,
|
||||||
|
nestingDepth: d,
|
||||||
|
mode: mode,
|
||||||
|
scenario: scenario,
|
||||||
);
|
);
|
||||||
benchResult = await BenchmarkRunner.runSync(
|
benchResult = await BenchmarkRunner.runSync(
|
||||||
benchmark: benchSync,
|
benchmark: benchSync,
|
||||||
@@ -78,8 +94,11 @@ class BenchmarkCliRunner {
|
|||||||
} else {
|
} else {
|
||||||
final di = CherrypickDIAdapter();
|
final di = CherrypickDIAdapter();
|
||||||
if (scenario == UniversalScenario.asyncChain) {
|
if (scenario == UniversalScenario.asyncChain) {
|
||||||
final benchAsync = UniversalChainAsyncBenchmark<Scope>(di,
|
final benchAsync = UniversalChainAsyncBenchmark<Scope>(
|
||||||
chainCount: c, nestingDepth: d, mode: mode,
|
di,
|
||||||
|
chainCount: c,
|
||||||
|
nestingDepth: d,
|
||||||
|
mode: mode,
|
||||||
);
|
);
|
||||||
benchResult = await BenchmarkRunner.runAsync(
|
benchResult = await BenchmarkRunner.runAsync(
|
||||||
benchmark: benchAsync,
|
benchmark: benchAsync,
|
||||||
@@ -87,8 +106,12 @@ class BenchmarkCliRunner {
|
|||||||
repeats: config.repeats,
|
repeats: config.repeats,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final benchSync = UniversalChainBenchmark<Scope>(di,
|
final benchSync = UniversalChainBenchmark<Scope>(
|
||||||
chainCount: c, nestingDepth: d, mode: mode, scenario: scenario,
|
di,
|
||||||
|
chainCount: c,
|
||||||
|
nestingDepth: d,
|
||||||
|
mode: mode,
|
||||||
|
scenario: scenario,
|
||||||
);
|
);
|
||||||
benchResult = await BenchmarkRunner.runSync(
|
benchResult = await BenchmarkRunner.runSync(
|
||||||
benchmark: benchSync,
|
benchmark: benchSync,
|
||||||
@@ -103,7 +126,11 @@ class BenchmarkCliRunner {
|
|||||||
var median = timings[timings.length ~/ 2];
|
var median = timings[timings.length ~/ 2];
|
||||||
var minVal = timings.first;
|
var minVal = timings.first;
|
||||||
var maxVal = timings.last;
|
var maxVal = timings.last;
|
||||||
var stddev = timings.isEmpty ? 0 : sqrt(timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) / timings.length);
|
var stddev = timings.isEmpty
|
||||||
|
? 0
|
||||||
|
: sqrt(
|
||||||
|
timings.map((x) => pow(x - mean, 2)).reduce((a, b) => a + b) /
|
||||||
|
timings.length);
|
||||||
results.add({
|
results.add({
|
||||||
'benchmark': 'Universal_$bench',
|
'benchmark': 'Universal_$bench',
|
||||||
'chainCount': c,
|
'chainCount': c,
|
||||||
@@ -128,6 +155,7 @@ class BenchmarkCliRunner {
|
|||||||
'json': JsonReport(),
|
'json': JsonReport(),
|
||||||
'markdown': MarkdownReport(),
|
'markdown': MarkdownReport(),
|
||||||
};
|
};
|
||||||
print(reportGenerators[config.format]?.render(results) ?? PrettyReport().render(results));
|
print(reportGenerators[config.format]?.render(results) ??
|
||||||
|
PrettyReport().render(results));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,14 +8,19 @@ import 'package:benchmark_di/scenarios/universal_scenario.dart';
|
|||||||
enum UniversalBenchmark {
|
enum UniversalBenchmark {
|
||||||
/// Simple singleton registration benchmark
|
/// Simple singleton registration benchmark
|
||||||
registerSingleton,
|
registerSingleton,
|
||||||
|
|
||||||
/// Chain of singleton dependencies
|
/// Chain of singleton dependencies
|
||||||
chainSingleton,
|
chainSingleton,
|
||||||
|
|
||||||
/// Chain using factories
|
/// Chain using factories
|
||||||
chainFactory,
|
chainFactory,
|
||||||
|
|
||||||
/// Async chain resolution
|
/// Async chain resolution
|
||||||
chainAsync,
|
chainAsync,
|
||||||
|
|
||||||
/// Named registration benchmark
|
/// Named registration benchmark
|
||||||
named,
|
named,
|
||||||
|
|
||||||
/// Override/child-scope benchmark
|
/// Override/child-scope benchmark
|
||||||
override,
|
override,
|
||||||
}
|
}
|
||||||
@@ -65,23 +70,32 @@ T parseEnum<T>(String value, List<T> values, T defaultValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parses comma-separated integer list from [s].
|
/// Parses comma-separated integer list from [s].
|
||||||
List<int> parseIntList(String s) =>
|
List<int> parseIntList(String s) => s
|
||||||
s.split(',').map((e) => int.tryParse(e.trim()) ?? 0).where((x) => x > 0).toList();
|
.split(',')
|
||||||
|
.map((e) => int.tryParse(e.trim()) ?? 0)
|
||||||
|
.where((x) => x > 0)
|
||||||
|
.toList();
|
||||||
|
|
||||||
/// CLI config describing what and how to benchmark.
|
/// CLI config describing what and how to benchmark.
|
||||||
class BenchmarkCliConfig {
|
class BenchmarkCliConfig {
|
||||||
/// Benchmarks enabled to run (scenarios).
|
/// Benchmarks enabled to run (scenarios).
|
||||||
final List<UniversalBenchmark> benchesToRun;
|
final List<UniversalBenchmark> benchesToRun;
|
||||||
|
|
||||||
/// List of chain counts (parallel, per test).
|
/// List of chain counts (parallel, per test).
|
||||||
final List<int> chainCounts;
|
final List<int> chainCounts;
|
||||||
|
|
||||||
/// List of nesting depths (max chain length, per test).
|
/// List of nesting depths (max chain length, per test).
|
||||||
final List<int> nestDepths;
|
final List<int> nestDepths;
|
||||||
|
|
||||||
/// How many times to repeat each trial.
|
/// How many times to repeat each trial.
|
||||||
final int repeats;
|
final int repeats;
|
||||||
|
|
||||||
/// How many times to warm-up before measuring.
|
/// How many times to warm-up before measuring.
|
||||||
final int warmups;
|
final int warmups;
|
||||||
|
|
||||||
/// Output report format.
|
/// Output report format.
|
||||||
final String format;
|
final String format;
|
||||||
|
|
||||||
/// Name of DI implementation ("cherrypick" or "getit")
|
/// Name of DI implementation ("cherrypick" or "getit")
|
||||||
final String di;
|
final String di;
|
||||||
BenchmarkCliConfig({
|
BenchmarkCliConfig({
|
||||||
@@ -105,7 +119,9 @@ BenchmarkCliConfig parseBenchmarkCli(List<String> args) {
|
|||||||
..addOption('repeat', abbr: 'r', defaultsTo: '2')
|
..addOption('repeat', abbr: 'r', defaultsTo: '2')
|
||||||
..addOption('warmup', abbr: 'w', defaultsTo: '1')
|
..addOption('warmup', abbr: 'w', defaultsTo: '1')
|
||||||
..addOption('format', abbr: 'f', defaultsTo: 'pretty')
|
..addOption('format', abbr: 'f', defaultsTo: 'pretty')
|
||||||
..addOption('di', defaultsTo: 'cherrypick', help: 'DI implementation: cherrypick, getit or riverpod')
|
..addOption('di',
|
||||||
|
defaultsTo: 'cherrypick',
|
||||||
|
help: 'DI implementation: cherrypick, getit or riverpod')
|
||||||
..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');
|
..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');
|
||||||
final result = parser.parse(args);
|
final result = parser.parse(args);
|
||||||
if (result['help'] == true) {
|
if (result['help'] == true) {
|
||||||
|
|||||||
@@ -5,20 +5,32 @@ class CsvReport extends ReportGenerator {
|
|||||||
/// List of all keys/columns to include in the CSV output.
|
/// List of all keys/columns to include in the CSV output.
|
||||||
@override
|
@override
|
||||||
final List<String> keys = [
|
final List<String> keys = [
|
||||||
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us',
|
'benchmark',
|
||||||
'min_us','max_us','trials','timings_us','memory_diff_kb','delta_peak_kb','peak_rss_kb'
|
'chainCount',
|
||||||
|
'nestingDepth',
|
||||||
|
'mean_us',
|
||||||
|
'median_us',
|
||||||
|
'stddev_us',
|
||||||
|
'min_us',
|
||||||
|
'max_us',
|
||||||
|
'trials',
|
||||||
|
'timings_us',
|
||||||
|
'memory_diff_kb',
|
||||||
|
'delta_peak_kb',
|
||||||
|
'peak_rss_kb'
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Renders rows as a CSV table string.
|
/// Renders rows as a CSV table string.
|
||||||
@override
|
@override
|
||||||
String render(List<Map<String, dynamic>> rows) {
|
String render(List<Map<String, dynamic>> rows) {
|
||||||
final header = keys.join(',');
|
final header = keys.join(',');
|
||||||
final lines = rows.map((r) =>
|
final lines = rows
|
||||||
keys.map((k) {
|
.map((r) => keys.map((k) {
|
||||||
final v = r[k];
|
final v = r[k];
|
||||||
if (v is List) return '"${v.join(';')}"';
|
if (v is List) return '"${v.join(';')}"';
|
||||||
return (v ?? '').toString();
|
return (v ?? '').toString();
|
||||||
}).join(',')
|
}).join(','))
|
||||||
).toList();
|
.toList();
|
||||||
return ([header] + lines).join('\n');
|
return ([header] + lines).join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ class JsonReport extends ReportGenerator {
|
|||||||
/// No specific keys; outputs all fields in raw map.
|
/// No specific keys; outputs all fields in raw map.
|
||||||
@override
|
@override
|
||||||
List<String> get keys => [];
|
List<String> get keys => [];
|
||||||
|
|
||||||
/// Renders all result rows as a pretty-printed JSON array.
|
/// Renders all result rows as a pretty-printed JSON array.
|
||||||
@override
|
@override
|
||||||
String render(List<Map<String, dynamic>> rows) {
|
String render(List<Map<String, dynamic>> rows) {
|
||||||
|
|||||||
@@ -7,8 +7,18 @@ class MarkdownReport extends ReportGenerator {
|
|||||||
/// List of columns (keys) to show in the Markdown table.
|
/// List of columns (keys) to show in the Markdown table.
|
||||||
@override
|
@override
|
||||||
final List<String> keys = [
|
final List<String> keys = [
|
||||||
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us',
|
'benchmark',
|
||||||
'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb'
|
'chainCount',
|
||||||
|
'nestingDepth',
|
||||||
|
'mean_us',
|
||||||
|
'median_us',
|
||||||
|
'stddev_us',
|
||||||
|
'min_us',
|
||||||
|
'max_us',
|
||||||
|
'trials',
|
||||||
|
'memory_diff_kb',
|
||||||
|
'delta_peak_kb',
|
||||||
|
'peak_rss_kb'
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Friendly display names for each benchmark type.
|
/// Friendly display names for each benchmark type.
|
||||||
@@ -25,7 +35,18 @@ class MarkdownReport extends ReportGenerator {
|
|||||||
@override
|
@override
|
||||||
String render(List<Map<String, dynamic>> rows) {
|
String render(List<Map<String, dynamic>> rows) {
|
||||||
final headers = [
|
final headers = [
|
||||||
'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)'
|
'Benchmark',
|
||||||
|
'Chain Count',
|
||||||
|
'Depth',
|
||||||
|
'Mean (us)',
|
||||||
|
'Median',
|
||||||
|
'Stddev',
|
||||||
|
'Min',
|
||||||
|
'Max',
|
||||||
|
'N',
|
||||||
|
'ΔRSS(KB)',
|
||||||
|
'ΔPeak(KB)',
|
||||||
|
'PeakRSS(KB)'
|
||||||
];
|
];
|
||||||
final dataRows = rows.map((r) {
|
final dataRows = rows.map((r) {
|
||||||
final readableName = nameMap[r['benchmark']] ?? r['benchmark'];
|
final readableName = nameMap[r['benchmark']] ?? r['benchmark'];
|
||||||
|
|||||||
@@ -7,8 +7,18 @@ class PrettyReport extends ReportGenerator {
|
|||||||
/// List of columns to output in the pretty report.
|
/// List of columns to output in the pretty report.
|
||||||
@override
|
@override
|
||||||
final List<String> keys = [
|
final List<String> keys = [
|
||||||
'benchmark','chainCount','nestingDepth','mean_us','median_us','stddev_us',
|
'benchmark',
|
||||||
'min_us','max_us','trials','memory_diff_kb','delta_peak_kb','peak_rss_kb'
|
'chainCount',
|
||||||
|
'nestingDepth',
|
||||||
|
'mean_us',
|
||||||
|
'median_us',
|
||||||
|
'stddev_us',
|
||||||
|
'min_us',
|
||||||
|
'max_us',
|
||||||
|
'trials',
|
||||||
|
'memory_diff_kb',
|
||||||
|
'delta_peak_kb',
|
||||||
|
'peak_rss_kb'
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Mappings from internal benchmark IDs to display names.
|
/// Mappings from internal benchmark IDs to display names.
|
||||||
@@ -25,7 +35,18 @@ class PrettyReport extends ReportGenerator {
|
|||||||
@override
|
@override
|
||||||
String render(List<Map<String, dynamic>> rows) {
|
String render(List<Map<String, dynamic>> rows) {
|
||||||
final headers = [
|
final headers = [
|
||||||
'Benchmark', 'Chain Count', 'Depth', 'Mean (us)', 'Median', 'Stddev', 'Min', 'Max', 'N', 'ΔRSS(KB)', 'ΔPeak(KB)', 'PeakRSS(KB)'
|
'Benchmark',
|
||||||
|
'Chain Count',
|
||||||
|
'Depth',
|
||||||
|
'Mean (us)',
|
||||||
|
'Median',
|
||||||
|
'Stddev',
|
||||||
|
'Min',
|
||||||
|
'Max',
|
||||||
|
'N',
|
||||||
|
'ΔRSS(KB)',
|
||||||
|
'ΔPeak(KB)',
|
||||||
|
'PeakRSS(KB)'
|
||||||
];
|
];
|
||||||
final header = headers.join('\t');
|
final header = headers.join('\t');
|
||||||
final lines = rows.map((r) {
|
final lines = rows.map((r) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
abstract class ReportGenerator {
|
abstract class ReportGenerator {
|
||||||
/// Renders the given [results] as a formatted string (table, markdown, csv, etc).
|
/// Renders the given [results] as a formatted string (table, markdown, csv, etc).
|
||||||
String render(List<Map<String, dynamic>> results);
|
String render(List<Map<String, dynamic>> results);
|
||||||
|
|
||||||
/// List of output columns/keys included in the export (or [] for auto/all).
|
/// List of output columns/keys included in the export (or [] for auto/all).
|
||||||
List<String> get keys;
|
List<String> get keys;
|
||||||
}
|
}
|
||||||
@@ -7,10 +7,13 @@ import 'package:benchmark_di/benchmarks/universal_chain_async_benchmark.dart';
|
|||||||
class BenchmarkResult {
|
class BenchmarkResult {
|
||||||
/// List of timings for each run (in microseconds).
|
/// List of timings for each run (in microseconds).
|
||||||
final List<num> timings;
|
final List<num> timings;
|
||||||
|
|
||||||
/// Difference in memory (RSS, in KB) after running.
|
/// Difference in memory (RSS, in KB) after running.
|
||||||
final int memoryDiffKb;
|
final int memoryDiffKb;
|
||||||
|
|
||||||
/// Difference between peak RSS and initial RSS (in KB).
|
/// Difference between peak RSS and initial RSS (in KB).
|
||||||
final int deltaPeakKb;
|
final int deltaPeakKb;
|
||||||
|
|
||||||
/// Peak RSS memory observed (in KB).
|
/// Peak RSS memory observed (in KB).
|
||||||
final int peakRssKb;
|
final int peakRssKb;
|
||||||
BenchmarkResult({
|
BenchmarkResult({
|
||||||
@@ -19,6 +22,7 @@ class BenchmarkResult {
|
|||||||
required this.deltaPeakKb,
|
required this.deltaPeakKb,
|
||||||
required this.peakRssKb,
|
required this.peakRssKb,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Computes a BenchmarkResult instance from run timings and memory data.
|
/// Computes a BenchmarkResult instance from run timings and memory data.
|
||||||
factory BenchmarkResult.collect({
|
factory BenchmarkResult.collect({
|
||||||
required List<num> timings,
|
required List<num> timings,
|
||||||
@@ -64,7 +68,8 @@ class BenchmarkRunner {
|
|||||||
rssValues.add(ProcessInfo.currentRss);
|
rssValues.add(ProcessInfo.currentRss);
|
||||||
benchmark.teardown();
|
benchmark.teardown();
|
||||||
}
|
}
|
||||||
return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore);
|
return BenchmarkResult.collect(
|
||||||
|
timings: timings, rssValues: rssValues, memBefore: memBefore);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs an asynchronous benchmark ([UniversalChainAsyncBenchmark]) for a given number of [warmups] and [repeats].
|
/// Runs an asynchronous benchmark ([UniversalChainAsyncBenchmark]) for a given number of [warmups] and [repeats].
|
||||||
@@ -91,6 +96,7 @@ class BenchmarkRunner {
|
|||||||
rssValues.add(ProcessInfo.currentRss);
|
rssValues.add(ProcessInfo.currentRss);
|
||||||
await benchmark.teardown();
|
await benchmark.teardown();
|
||||||
}
|
}
|
||||||
return BenchmarkResult.collect(timings: timings, rssValues: rssValues, memBefore: memBefore);
|
return BenchmarkResult.collect(
|
||||||
|
timings: timings, rssValues: rssValues, memBefore: memBefore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import 'package:benchmark_di/scenarios/universal_service.dart';
|
|||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
import 'di_adapter.dart';
|
import 'di_adapter.dart';
|
||||||
|
|
||||||
|
|
||||||
/// Test module that generates a chain of service bindings for benchmarking.
|
/// Test module that generates a chain of service bindings for benchmarking.
|
||||||
///
|
///
|
||||||
/// Configurable by chain count, nesting depth, binding mode, and scenario
|
/// Configurable by chain count, nesting depth, binding mode, and scenario
|
||||||
@@ -12,10 +11,13 @@ import 'di_adapter.dart';
|
|||||||
class UniversalChainModule extends Module {
|
class UniversalChainModule extends Module {
|
||||||
/// Number of chains to create.
|
/// Number of chains to create.
|
||||||
final int chainCount;
|
final int chainCount;
|
||||||
|
|
||||||
/// Depth of each chain.
|
/// Depth of each chain.
|
||||||
final int nestingDepth;
|
final int nestingDepth;
|
||||||
|
|
||||||
/// How modules are registered (factory/singleton/async).
|
/// How modules are registered (factory/singleton/async).
|
||||||
final UniversalBindingMode bindingMode;
|
final UniversalBindingMode bindingMode;
|
||||||
|
|
||||||
/// Which di scenario to generate (chained, named, etc).
|
/// Which di scenario to generate (chained, named, etc).
|
||||||
final UniversalScenario scenario;
|
final UniversalScenario scenario;
|
||||||
|
|
||||||
@@ -40,7 +42,8 @@ class UniversalChainModule extends Module {
|
|||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvideAsync(() async {
|
.toProvideAsync(() async {
|
||||||
final prev = level > 1
|
final prev = level > 1
|
||||||
? await currentScope.resolveAsync<UniversalService>(named: prevDepName)
|
? await currentScope.resolveAsync<UniversalService>(
|
||||||
|
named: prevDepName)
|
||||||
: null;
|
: null;
|
||||||
return UniversalServiceImpl(
|
return UniversalServiceImpl(
|
||||||
value: depName,
|
value: depName,
|
||||||
@@ -58,13 +61,18 @@ class UniversalChainModule extends Module {
|
|||||||
case UniversalScenario.register:
|
case UniversalScenario.register:
|
||||||
// Simple singleton registration.
|
// Simple singleton registration.
|
||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvide(() => UniversalServiceImpl(value: 'reg', dependency: null))
|
.toProvide(
|
||||||
|
() => UniversalServiceImpl(value: 'reg', dependency: null))
|
||||||
.singleton();
|
.singleton();
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.named:
|
case UniversalScenario.named:
|
||||||
// Named factory registration for two distinct objects.
|
// Named factory registration for two distinct objects.
|
||||||
bind<UniversalService>().toProvide(() => UniversalServiceImpl(value: 'impl1')).withName('impl1');
|
bind<UniversalService>()
|
||||||
bind<UniversalService>().toProvide(() => UniversalServiceImpl(value: 'impl2')).withName('impl2');
|
.toProvide(() => UniversalServiceImpl(value: 'impl1'))
|
||||||
|
.withName('impl1');
|
||||||
|
bind<UniversalService>()
|
||||||
|
.toProvide(() => UniversalServiceImpl(value: 'impl2'))
|
||||||
|
.withName('impl2');
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.chain:
|
case UniversalScenario.chain:
|
||||||
// Chain of nested services, with dependency on previous level by name.
|
// Chain of nested services, with dependency on previous level by name.
|
||||||
@@ -79,7 +87,8 @@ class UniversalChainModule extends Module {
|
|||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvide(() => UniversalServiceImpl(
|
.toProvide(() => UniversalServiceImpl(
|
||||||
value: depName,
|
value: depName,
|
||||||
dependency: currentScope.tryResolve<UniversalService>(named: prevDepName),
|
dependency: currentScope.tryResolve<UniversalService>(
|
||||||
|
named: prevDepName),
|
||||||
))
|
))
|
||||||
.withName(depName)
|
.withName(depName)
|
||||||
.singleton();
|
.singleton();
|
||||||
@@ -88,7 +97,8 @@ class UniversalChainModule extends Module {
|
|||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvide(() => UniversalServiceImpl(
|
.toProvide(() => UniversalServiceImpl(
|
||||||
value: depName,
|
value: depName,
|
||||||
dependency: currentScope.tryResolve<UniversalService>(named: prevDepName),
|
dependency: currentScope.tryResolve<UniversalService>(
|
||||||
|
named: prevDepName),
|
||||||
))
|
))
|
||||||
.withName(depName);
|
.withName(depName);
|
||||||
break;
|
break;
|
||||||
@@ -96,7 +106,9 @@ class UniversalChainModule extends Module {
|
|||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvideAsync(() async => UniversalServiceImpl(
|
.toProvideAsync(() async => UniversalServiceImpl(
|
||||||
value: depName,
|
value: depName,
|
||||||
dependency: await currentScope.resolveAsync<UniversalService>(named: prevDepName),
|
dependency:
|
||||||
|
await currentScope.resolveAsync<UniversalService>(
|
||||||
|
named: prevDepName),
|
||||||
))
|
))
|
||||||
.withName(depName)
|
.withName(depName)
|
||||||
.singleton();
|
.singleton();
|
||||||
@@ -107,14 +119,16 @@ class UniversalChainModule extends Module {
|
|||||||
// Регистрация алиаса без имени (на последний элемент цепочки)
|
// Регистрация алиаса без имени (на последний элемент цепочки)
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
final depName = '${chainCount}_$nestingDepth';
|
||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvide(() => currentScope.resolve<UniversalService>(named: depName))
|
.toProvide(
|
||||||
|
() => currentScope.resolve<UniversalService>(named: depName))
|
||||||
.singleton();
|
.singleton();
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.override:
|
case UniversalScenario.override:
|
||||||
// handled at benchmark level, но алиас нужен прямо в этом scope!
|
// handled at benchmark level, но алиас нужен прямо в этом scope!
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
final depName = '${chainCount}_$nestingDepth';
|
||||||
bind<UniversalService>()
|
bind<UniversalService>()
|
||||||
.toProvide(() => currentScope.resolve<UniversalService>(named: depName))
|
.toProvide(
|
||||||
|
() => currentScope.resolve<UniversalService>(named: depName))
|
||||||
.singleton();
|
.singleton();
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.asyncChain:
|
case UniversalScenario.asyncChain:
|
||||||
@@ -124,7 +138,6 @@ class UniversalChainModule extends Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class CherrypickDIAdapter extends DIAdapter<Scope> {
|
class CherrypickDIAdapter extends DIAdapter<Scope> {
|
||||||
Scope? _scope;
|
Scope? _scope;
|
||||||
final bool _isSubScope;
|
final bool _isSubScope;
|
||||||
@@ -158,7 +171,8 @@ class CherrypickDIAdapter extends DIAdapter<Scope> {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw UnsupportedError('Scenario $scenario not supported by CherrypickDIAdapter');
|
throw UnsupportedError(
|
||||||
|
'Scenario $scenario not supported by CherrypickDIAdapter');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
import 'package:benchmark_di/scenarios/universal_binding_mode.dart';
|
||||||
|
|
||||||
/// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации.
|
/// Универсальная абстракция для DI-адаптера с унифицированной функцией регистрации.
|
||||||
/// Теперь для каждого адаптера задаём строгий generic тип контейнера.
|
/// Теперь для каждого адаптера задаём строгий generic тип контейнера.
|
||||||
typedef Registration<TContainer> = void Function(TContainer);
|
typedef Registration<TContainer> = void Function(TContainer);
|
||||||
|
|||||||
@@ -80,9 +80,11 @@ class GetItAdapter extends DIAdapter<GetIt> {
|
|||||||
getIt.registerSingletonAsync<UniversalService>(
|
getIt.registerSingletonAsync<UniversalService>(
|
||||||
() async {
|
() async {
|
||||||
final prev = level > 1
|
final prev = level > 1
|
||||||
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
? await getIt.getAsync<UniversalService>(
|
||||||
|
instanceName: prevDepName)
|
||||||
: null;
|
: null;
|
||||||
return UniversalServiceImpl(value: depName, dependency: prev);
|
return UniversalServiceImpl(
|
||||||
|
value: depName, dependency: prev);
|
||||||
},
|
},
|
||||||
instanceName: depName,
|
instanceName: depName,
|
||||||
);
|
);
|
||||||
@@ -90,11 +92,16 @@ class GetItAdapter extends DIAdapter<GetIt> {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.register:
|
case UniversalScenario.register:
|
||||||
getIt.registerSingleton<UniversalService>(UniversalServiceImpl(value: 'reg', dependency: null));
|
getIt.registerSingleton<UniversalService>(
|
||||||
|
UniversalServiceImpl(value: 'reg', dependency: null));
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.named:
|
case UniversalScenario.named:
|
||||||
getIt.registerFactory<UniversalService>(() => UniversalServiceImpl(value: 'impl1'), instanceName: 'impl1');
|
getIt.registerFactory<UniversalService>(
|
||||||
getIt.registerFactory<UniversalService>(() => UniversalServiceImpl(value: 'impl2'), instanceName: 'impl2');
|
() => UniversalServiceImpl(value: 'impl1'),
|
||||||
|
instanceName: 'impl1');
|
||||||
|
getIt.registerFactory<UniversalService>(
|
||||||
|
() => UniversalServiceImpl(value: 'impl2'),
|
||||||
|
instanceName: 'impl2');
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.chain:
|
case UniversalScenario.chain:
|
||||||
for (int chain = 1; chain <= chainCount; chain++) {
|
for (int chain = 1; chain <= chainCount; chain++) {
|
||||||
@@ -129,7 +136,8 @@ class GetItAdapter extends DIAdapter<GetIt> {
|
|||||||
() async => UniversalServiceImpl(
|
() async => UniversalServiceImpl(
|
||||||
value: depName,
|
value: depName,
|
||||||
dependency: level > 1
|
dependency: level > 1
|
||||||
? await getIt.getAsync<UniversalService>(instanceName: prevDepName)
|
? await getIt.getAsync<UniversalService>(
|
||||||
|
instanceName: prevDepName)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
instanceName: depName,
|
instanceName: depName,
|
||||||
@@ -143,7 +151,8 @@ class GetItAdapter extends DIAdapter<GetIt> {
|
|||||||
// handled at benchmark level
|
// handled at benchmark level
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (scenario == UniversalScenario.chain || scenario == UniversalScenario.override) {
|
if (scenario == UniversalScenario.chain ||
|
||||||
|
scenario == UniversalScenario.override) {
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
final depName = '${chainCount}_$nestingDepth';
|
||||||
getIt.registerSingleton<UniversalService>(
|
getIt.registerSingleton<UniversalService>(
|
||||||
getIt<UniversalService>(instanceName: depName),
|
getIt<UniversalService>(instanceName: depName),
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ class RiverpodAdapter extends DIAdapter<Map<String, rp.ProviderBase<Object?>>> {
|
|||||||
_parent = parent;
|
_parent = parent;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setupDependencies(void Function(Map<String, rp.ProviderBase<Object?>> container) registration) {
|
void setupDependencies(
|
||||||
|
void Function(Map<String, rp.ProviderBase<Object?>> container)
|
||||||
|
registration) {
|
||||||
_container ??= _parent == null
|
_container ??= _parent == null
|
||||||
? rp.ProviderContainer()
|
? rp.ProviderContainer()
|
||||||
: rp.ProviderContainer(parent: _parent);
|
: rp.ProviderContainer(parent: _parent);
|
||||||
@@ -76,7 +78,8 @@ class RiverpodAdapter extends DIAdapter<Map<String, rp.ProviderBase<Object?>>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Registration<Map<String, rp.ProviderBase<Object?>>> universalRegistration<S extends Enum>({
|
Registration<Map<String, rp.ProviderBase<Object?>>>
|
||||||
|
universalRegistration<S extends Enum>({
|
||||||
required S scenario,
|
required S scenario,
|
||||||
required int chainCount,
|
required int chainCount,
|
||||||
required int nestingDepth,
|
required int nestingDepth,
|
||||||
@@ -86,25 +89,34 @@ class RiverpodAdapter extends DIAdapter<Map<String, rp.ProviderBase<Object?>>> {
|
|||||||
return (providers) {
|
return (providers) {
|
||||||
switch (scenario) {
|
switch (scenario) {
|
||||||
case UniversalScenario.register:
|
case UniversalScenario.register:
|
||||||
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'reg', dependency: null));
|
providers['UniversalService'] = rp.Provider<UniversalService>(
|
||||||
|
(ref) => UniversalServiceImpl(value: 'reg', dependency: null));
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.named:
|
case UniversalScenario.named:
|
||||||
providers['impl1'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'impl1'));
|
providers['impl1'] = rp.Provider<UniversalService>(
|
||||||
providers['impl2'] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(value: 'impl2'));
|
(ref) => UniversalServiceImpl(value: 'impl1'));
|
||||||
|
providers['impl2'] = rp.Provider<UniversalService>(
|
||||||
|
(ref) => UniversalServiceImpl(value: 'impl2'));
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.chain:
|
case UniversalScenario.chain:
|
||||||
for (int chain = 1; chain <= chainCount; chain++) {
|
for (int chain = 1; chain <= chainCount; chain++) {
|
||||||
for (int level = 1; level <= nestingDepth; level++) {
|
for (int level = 1; level <= nestingDepth; level++) {
|
||||||
final prevDepName = '${chain}_${level - 1}';
|
final prevDepName = '${chain}_${level - 1}';
|
||||||
final depName = '${chain}_$level';
|
final depName = '${chain}_$level';
|
||||||
providers[depName] = rp.Provider<UniversalService>((ref) => UniversalServiceImpl(
|
providers[depName] =
|
||||||
|
rp.Provider<UniversalService>((ref) => UniversalServiceImpl(
|
||||||
value: depName,
|
value: depName,
|
||||||
dependency: level > 1 ? ref.watch(providers[prevDepName] as rp.ProviderBase<UniversalService>) : null,
|
dependency: level > 1
|
||||||
|
? ref.watch(providers[prevDepName]
|
||||||
|
as rp.ProviderBase<UniversalService>)
|
||||||
|
: null,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
final depName = '${chainCount}_$nestingDepth';
|
||||||
providers['UniversalService'] = rp.Provider<UniversalService>((ref) => ref.watch(providers[depName] as rp.ProviderBase<UniversalService>));
|
providers['UniversalService'] = rp.Provider<UniversalService>(
|
||||||
|
(ref) => ref.watch(
|
||||||
|
providers[depName] as rp.ProviderBase<UniversalService>));
|
||||||
break;
|
break;
|
||||||
case UniversalScenario.override:
|
case UniversalScenario.override:
|
||||||
// handled at benchmark level
|
// handled at benchmark level
|
||||||
@@ -114,24 +126,31 @@ class RiverpodAdapter extends DIAdapter<Map<String, rp.ProviderBase<Object?>>> {
|
|||||||
for (int level = 1; level <= nestingDepth; level++) {
|
for (int level = 1; level <= nestingDepth; level++) {
|
||||||
final prevDepName = '${chain}_${level - 1}';
|
final prevDepName = '${chain}_${level - 1}';
|
||||||
final depName = '${chain}_$level';
|
final depName = '${chain}_$level';
|
||||||
providers[depName] = rp.FutureProvider<UniversalService>((ref) async {
|
providers[depName] =
|
||||||
|
rp.FutureProvider<UniversalService>((ref) async {
|
||||||
return UniversalServiceImpl(
|
return UniversalServiceImpl(
|
||||||
value: depName,
|
value: depName,
|
||||||
dependency: level > 1
|
dependency: level > 1
|
||||||
? await ref.watch((providers[prevDepName] as rp.FutureProvider<UniversalService>).future) as UniversalService?
|
? await ref.watch((providers[prevDepName]
|
||||||
|
as rp.FutureProvider<UniversalService>)
|
||||||
|
.future) as UniversalService?
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final depName = '${chainCount}_$nestingDepth';
|
final depName = '${chainCount}_$nestingDepth';
|
||||||
providers['UniversalService'] = rp.FutureProvider<UniversalService>((ref) async {
|
providers['UniversalService'] =
|
||||||
return await ref.watch((providers[depName] as rp.FutureProvider<UniversalService>).future);
|
rp.FutureProvider<UniversalService>((ref) async {
|
||||||
|
return await ref.watch(
|
||||||
|
(providers[depName] as rp.FutureProvider<UniversalService>)
|
||||||
|
.future);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw UnsupportedError('Scenario $scenario not supported by RiverpodAdapter');
|
throw UnsupportedError(
|
||||||
|
'Scenario $scenario not supported by RiverpodAdapter');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,16 @@
|
|||||||
enum UniversalScenario {
|
enum UniversalScenario {
|
||||||
/// Single registration.
|
/// Single registration.
|
||||||
register,
|
register,
|
||||||
|
|
||||||
/// Chain of dependencies.
|
/// Chain of dependencies.
|
||||||
chain,
|
chain,
|
||||||
|
|
||||||
/// Named registrations.
|
/// Named registrations.
|
||||||
named,
|
named,
|
||||||
|
|
||||||
/// Child-scope override scenario.
|
/// Child-scope override scenario.
|
||||||
override,
|
override,
|
||||||
|
|
||||||
/// Asynchronous chain scenario.
|
/// Asynchronous chain scenario.
|
||||||
asyncChain,
|
asyncChain,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/// Base interface for any universal service in the benchmarks.
|
/// Base interface for any universal service in the benchmarks.
|
||||||
///
|
///
|
||||||
/// Represents an object in the dependency chain with an identifiable value
|
/// Represents an object in the dependency chain with an identifiable value
|
||||||
@@ -6,6 +5,7 @@
|
|||||||
abstract class UniversalService {
|
abstract class UniversalService {
|
||||||
/// String ID for this service instance (e.g. chain/level info).
|
/// String ID for this service instance (e.g. chain/level info).
|
||||||
final String value;
|
final String value;
|
||||||
|
|
||||||
/// Optional reference to dependency service in the chain.
|
/// Optional reference to dependency service in the chain.
|
||||||
final UniversalService? dependency;
|
final UniversalService? dependency;
|
||||||
UniversalService({required this.value, this.dependency});
|
UniversalService({required this.value, this.dependency});
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ packages:
|
|||||||
path: "../cherrypick"
|
path: "../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.8"
|
version: "3.0.0-dev.9"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 3.0.0-dev.10
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
## 3.0.0-dev.9
|
## 3.0.0-dev.9
|
||||||
|
|
||||||
- **DOCS**(readme): add talker_cherrypick_logger to Additional Modules section.
|
- **DOCS**(readme): add talker_cherrypick_logger to Additional Modules section.
|
||||||
|
|||||||
@@ -36,18 +36,16 @@ class DatabaseModule extends Module {
|
|||||||
class ApiModule extends Module {
|
class ApiModule extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<ApiService>().toProvide(() => ApiService(
|
bind<ApiService>()
|
||||||
currentScope.resolve<DatabaseService>()
|
.toProvide(() => ApiService(currentScope.resolve<DatabaseService>()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserModule extends Module {
|
class UserModule extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<UserService>().toProvide(() => UserService(
|
bind<UserService>()
|
||||||
currentScope.resolve<ApiService>()
|
.toProvide(() => UserService(currentScope.resolve<ApiService>()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,18 +63,16 @@ class CircularServiceB {
|
|||||||
class CircularModuleA extends Module {
|
class CircularModuleA extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<CircularServiceA>().toProvide(() => CircularServiceA(
|
bind<CircularServiceA>().toProvide(
|
||||||
currentScope.resolve<CircularServiceB>()
|
() => CircularServiceA(currentScope.resolve<CircularServiceB>()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CircularModuleB extends Module {
|
class CircularModuleB extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<CircularServiceB>().toProvide(() => CircularServiceB(
|
bind<CircularServiceB>().toProvide(
|
||||||
currentScope.resolve<CircularServiceA>()
|
() => CircularServiceB(currentScope.resolve<CircularServiceA>()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,11 +83,13 @@ void main() {
|
|||||||
print('1. Globally enable cycle detection:');
|
print('1. Globally enable cycle detection:');
|
||||||
|
|
||||||
CherryPick.enableGlobalCycleDetection();
|
CherryPick.enableGlobalCycleDetection();
|
||||||
print('✅ Global cycle detection enabled: ${CherryPick.isGlobalCycleDetectionEnabled}');
|
print(
|
||||||
|
'✅ Global cycle detection enabled: ${CherryPick.isGlobalCycleDetectionEnabled}');
|
||||||
|
|
||||||
// All new scopes will automatically have cycle detection enabled
|
// All new scopes will automatically have cycle detection enabled
|
||||||
final globalScope = CherryPick.openRootScope();
|
final globalScope = CherryPick.openRootScope();
|
||||||
print('✅ Root scope has cycle detection enabled: ${globalScope.isCycleDetectionEnabled}');
|
print(
|
||||||
|
'✅ Root scope has cycle detection enabled: ${globalScope.isCycleDetectionEnabled}');
|
||||||
|
|
||||||
// Install modules without circular dependencies
|
// Install modules without circular dependencies
|
||||||
globalScope.installModules([
|
globalScope.installModules([
|
||||||
@@ -112,7 +110,8 @@ void main() {
|
|||||||
|
|
||||||
// Создаем безопасный скоуп (с автоматически включенным обнаружением)
|
// Создаем безопасный скоуп (с автоматически включенным обнаружением)
|
||||||
final safeScope = CherryPick.openSafeRootScope();
|
final safeScope = CherryPick.openSafeRootScope();
|
||||||
print('✅ Safe scope created with cycle detection: ${safeScope.isCycleDetectionEnabled}');
|
print(
|
||||||
|
'✅ Safe scope created with cycle detection: ${safeScope.isCycleDetectionEnabled}');
|
||||||
|
|
||||||
safeScope.installModules([
|
safeScope.installModules([
|
||||||
DatabaseModule(),
|
DatabaseModule(),
|
||||||
@@ -153,30 +152,37 @@ void main() {
|
|||||||
// Создаем скоуп без обнаружения
|
// Создаем скоуп без обнаружения
|
||||||
// ignore: unused_local_variable
|
// ignore: unused_local_variable
|
||||||
final specificScope = CherryPick.openRootScope();
|
final specificScope = CherryPick.openRootScope();
|
||||||
print(' Detection in root scope: ${CherryPick.isCycleDetectionEnabledForScope()}');
|
print(
|
||||||
|
' Detection in root scope: ${CherryPick.isCycleDetectionEnabledForScope()}');
|
||||||
|
|
||||||
// Включаем обнаружение для конкретного скоупа
|
// Включаем обнаружение для конкретного скоупа
|
||||||
CherryPick.enableCycleDetectionForScope();
|
CherryPick.enableCycleDetectionForScope();
|
||||||
print('✅ Detection enabled for root scope: ${CherryPick.isCycleDetectionEnabledForScope()}');
|
print(
|
||||||
|
'✅ Detection enabled for root scope: ${CherryPick.isCycleDetectionEnabledForScope()}');
|
||||||
|
|
||||||
// Создаем дочерний скоуп
|
// Создаем дочерний скоуп
|
||||||
// ignore: unused_local_variable
|
// ignore: unused_local_variable
|
||||||
final featureScope = CherryPick.openScope(scopeName: 'feature.auth');
|
final featureScope = CherryPick.openScope(scopeName: 'feature.auth');
|
||||||
print(' Detection in feature.auth scope: ${CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.auth')}');
|
print(
|
||||||
|
' Detection in feature.auth scope: ${CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.auth')}');
|
||||||
|
|
||||||
// Включаем обнаружение для дочернего скоупа
|
// Включаем обнаружение для дочернего скоупа
|
||||||
CherryPick.enableCycleDetectionForScope(scopeName: 'feature.auth');
|
CherryPick.enableCycleDetectionForScope(scopeName: 'feature.auth');
|
||||||
print('✅ Detection enabled for feature.auth scope: ${CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.auth')}');
|
print(
|
||||||
|
'✅ Detection enabled for feature.auth scope: ${CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.auth')}');
|
||||||
print('');
|
print('');
|
||||||
|
|
||||||
// Example 5: Creating safe child scopes
|
// Example 5: Creating safe child scopes
|
||||||
print('5. Creating safe child scopes:');
|
print('5. Creating safe child scopes:');
|
||||||
|
|
||||||
final safeFeatureScope = CherryPick.openSafeScope(scopeName: 'feature.payments');
|
final safeFeatureScope =
|
||||||
print('✅ Safe feature scope created: ${safeFeatureScope.isCycleDetectionEnabled}');
|
CherryPick.openSafeScope(scopeName: 'feature.payments');
|
||||||
|
print(
|
||||||
|
'✅ Safe feature scope created: ${safeFeatureScope.isCycleDetectionEnabled}');
|
||||||
|
|
||||||
// You can create a complex hierarchy of scopes
|
// You can create a complex hierarchy of scopes
|
||||||
final complexScope = CherryPick.openSafeScope(scopeName: 'app.feature.auth.login');
|
final complexScope =
|
||||||
|
CherryPick.openSafeScope(scopeName: 'app.feature.auth.login');
|
||||||
print('✅ Complex scope created: ${complexScope.isCycleDetectionEnabled}');
|
print('✅ Complex scope created: ${complexScope.isCycleDetectionEnabled}');
|
||||||
print('');
|
print('');
|
||||||
|
|
||||||
@@ -209,7 +215,8 @@ void main() {
|
|||||||
print('');
|
print('');
|
||||||
|
|
||||||
print('🚀 Production mode:');
|
print('🚀 Production mode:');
|
||||||
print(' CherryPick.disableGlobalCycleDetection(); // Disable for performance');
|
print(
|
||||||
|
' CherryPick.disableGlobalCycleDetection(); // Disable for performance');
|
||||||
print(' final scope = CherryPick.openRootScope(); // Regular scope');
|
print(' final scope = CherryPick.openRootScope(); // Regular scope');
|
||||||
print('');
|
print('');
|
||||||
|
|
||||||
@@ -219,7 +226,8 @@ void main() {
|
|||||||
print('');
|
print('');
|
||||||
|
|
||||||
print('🎯 Feature-specific:');
|
print('🎯 Feature-specific:');
|
||||||
print(' CherryPick.enableCycleDetectionForScope(scopeName: "feature.critical");');
|
print(
|
||||||
|
' CherryPick.enableCycleDetectionForScope(scopeName: "feature.critical");');
|
||||||
print(' // Enable only for critical features');
|
print(' // Enable only for critical features');
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
|||||||
@@ -29,18 +29,16 @@ class OrderService {
|
|||||||
class UserModule extends Module {
|
class UserModule extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<UserService>().toProvide(() => UserService(
|
bind<UserService>()
|
||||||
currentScope.resolve<OrderService>()
|
.toProvide(() => UserService(currentScope.resolve<OrderService>()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OrderModule extends Module {
|
class OrderModule extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<OrderService>().toProvide(() => OrderService(
|
bind<OrderService>()
|
||||||
currentScope.resolve<UserService>()
|
.toProvide(() => OrderService(currentScope.resolve<UserService>()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,9 +101,8 @@ class ImprovedUserModule extends Module {
|
|||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<UserRepository>().singleton().toProvide(() => UserRepository());
|
bind<UserRepository>().singleton().toProvide(() => UserRepository());
|
||||||
bind<ImprovedUserService>().toProvide(() => ImprovedUserService(
|
bind<ImprovedUserService>().toProvide(
|
||||||
currentScope.resolve<UserRepository>()
|
() => ImprovedUserService(currentScope.resolve<UserRepository>()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +112,7 @@ class ImprovedOrderModule extends Module {
|
|||||||
bind<OrderRepository>().singleton().toProvide(() => OrderRepository());
|
bind<OrderRepository>().singleton().toProvide(() => OrderRepository());
|
||||||
bind<ImprovedOrderService>().toProvide(() => ImprovedOrderService(
|
bind<ImprovedOrderService>().toProvide(() => ImprovedOrderService(
|
||||||
currentScope.resolve<OrderRepository>(),
|
currentScope.resolve<OrderRepository>(),
|
||||||
currentScope.resolve<ImprovedUserService>()
|
currentScope.resolve<ImprovedUserService>()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +123,8 @@ void main() {
|
|||||||
print('1. Attempt to create a scope with circular dependencies:');
|
print('1. Attempt to create a scope with circular dependencies:');
|
||||||
try {
|
try {
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
scope.enableCycleDetection(); // Включаем обнаружение циклических зависимостей
|
scope
|
||||||
|
.enableCycleDetection(); // Включаем обнаружение циклических зависимостей
|
||||||
|
|
||||||
scope.installModules([
|
scope.installModules([
|
||||||
UserModule(),
|
UserModule(),
|
||||||
@@ -184,7 +181,6 @@ void main() {
|
|||||||
orderService.createOrder('ORD-001', 'John');
|
orderService.createOrder('ORD-001', 'John');
|
||||||
final orders = orderService.getOrdersForUser('John');
|
final orders = orderService.getOrdersForUser('John');
|
||||||
print('✅ Orders for user John: $orders');
|
print('✅ Orders for user John: $orders');
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('❌ Error: $e');
|
print('❌ Error: $e');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ 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(cycle);
|
_observer.onCycleDetected(cycle);
|
||||||
_observer.onError('Cycle detected for $dependencyKey', null, null);
|
_observer.onError('Cycle detected for $dependencyKey', null, null);
|
||||||
throw CircularDependencyException(
|
throw CircularDependencyException(
|
||||||
@@ -99,7 +100,8 @@ class CycleDetector {
|
|||||||
);
|
);
|
||||||
_resolutionStack.remove(dependencyKey);
|
_resolutionStack.remove(dependencyKey);
|
||||||
// Only remove from history if it's the last one
|
// Only remove from history if it's the last one
|
||||||
if (_resolutionHistory.isNotEmpty && _resolutionHistory.last == dependencyKey) {
|
if (_resolutionHistory.isNotEmpty &&
|
||||||
|
_resolutionHistory.last == dependencyKey) {
|
||||||
_resolutionHistory.removeLast();
|
_resolutionHistory.removeLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +126,8 @@ class CycleDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the current dependency resolution chain (for diagnostics or debugging).
|
/// Gets the current dependency resolution chain (for diagnostics or debugging).
|
||||||
List<String> get currentResolutionChain => List.unmodifiable(_resolutionHistory);
|
List<String> get currentResolutionChain =>
|
||||||
|
List.unmodifiable(_resolutionHistory);
|
||||||
|
|
||||||
/// Returns a unique string key for type [T] (+name).
|
/// Returns a unique string key for type [T] (+name).
|
||||||
String _createDependencyKey<T>(String? named) {
|
String _createDependencyKey<T>(String? named) {
|
||||||
@@ -205,7 +208,8 @@ mixin CycleDetectionMixin {
|
|||||||
: dependencyType.toString();
|
: dependencyType.toString();
|
||||||
|
|
||||||
if (_cycleDetector!._resolutionStack.contains(dependencyKey)) {
|
if (_cycleDetector!._resolutionStack.contains(dependencyKey)) {
|
||||||
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(cycle);
|
observer.onCycleDetected(cycle);
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'package:cherrypick/cherrypick.dart';
|
import 'package:cherrypick/cherrypick.dart';
|
||||||
|
|
||||||
|
|
||||||
/// GlobalCycleDetector detects and prevents circular dependencies across an entire DI scope hierarchy.
|
/// GlobalCycleDetector detects and prevents circular dependencies across an entire DI scope hierarchy.
|
||||||
///
|
///
|
||||||
/// This is particularly important for modular/feature-based applications
|
/// This is particularly important for modular/feature-based applications
|
||||||
@@ -45,13 +44,16 @@ class GlobalCycleDetector {
|
|||||||
final List<String> _globalResolutionHistory = [];
|
final List<String> _globalResolutionHistory = [];
|
||||||
|
|
||||||
// Map of active detectors for subscopes (rarely used directly)
|
// 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;
|
||||||
|
|
||||||
/// Returns the singleton global detector instance, initializing it if needed.
|
/// Returns the singleton global detector instance, initializing it if needed.
|
||||||
static GlobalCycleDetector get instance {
|
static GlobalCycleDetector get instance {
|
||||||
_instance ??= GlobalCycleDetector._internal(observer: CherryPick.globalObserver);
|
_instance ??=
|
||||||
|
GlobalCycleDetector._internal(observer: CherryPick.globalObserver);
|
||||||
return _instance!;
|
return _instance!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,9 +72,11 @@ 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)..add(dependencyKey);
|
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)
|
||||||
|
..add(dependencyKey);
|
||||||
_observer.onCycleDetected(cycle, scopeName: scopeId);
|
_observer.onCycleDetected(cycle, scopeName: scopeId);
|
||||||
_observer.onError('Global circular dependency detected for $dependencyKey', null, null);
|
_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,
|
||||||
@@ -88,7 +92,8 @@ class GlobalCycleDetector {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,13 +106,16 @@ class GlobalCycleDetector {
|
|||||||
String? scopeId,
|
String? scopeId,
|
||||||
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)..add(dependencyKey);
|
final cycle = _globalResolutionHistory.sublist(cycleStartIndex)
|
||||||
|
..add(dependencyKey);
|
||||||
_observer.onCycleDetected(cycle, scopeName: scopeId);
|
_observer.onCycleDetected(cycle, scopeName: scopeId);
|
||||||
_observer.onError('Global circular dependency detected for $dependencyKey', null, null);
|
_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,
|
||||||
@@ -121,7 +129,8 @@ class GlobalCycleDetector {
|
|||||||
return action();
|
return action();
|
||||||
} finally {
|
} finally {
|
||||||
_globalResolutionStack.remove(dependencyKey);
|
_globalResolutionStack.remove(dependencyKey);
|
||||||
if (_globalResolutionHistory.isNotEmpty && _globalResolutionHistory.last == dependencyKey) {
|
if (_globalResolutionHistory.isNotEmpty &&
|
||||||
|
_globalResolutionHistory.last == dependencyKey) {
|
||||||
_globalResolutionHistory.removeLast();
|
_globalResolutionHistory.removeLast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +138,8 @@ class GlobalCycleDetector {
|
|||||||
|
|
||||||
/// Get per-scope detector (not usually needed by consumers).
|
/// Get per-scope detector (not usually needed by consumers).
|
||||||
CycleDetector getScopeDetector(String scopeId) {
|
CycleDetector getScopeDetector(String scopeId) {
|
||||||
return _scopeDetectors.putIfAbsent(scopeId, () => CycleDetector(observer: CherryPick.globalObserver));
|
return _scopeDetectors.putIfAbsent(
|
||||||
|
scopeId, () => CycleDetector(observer: CherryPick.globalObserver));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove detector for a given scope.
|
/// Remove detector for a given scope.
|
||||||
@@ -144,7 +154,8 @@ class GlobalCycleDetector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get current global dependency resolution chain (for debugging or diagnostics).
|
/// Get current global dependency resolution chain (for debugging or diagnostics).
|
||||||
List<String> get globalResolutionChain => List.unmodifiable(_globalResolutionHistory);
|
List<String> get globalResolutionChain =>
|
||||||
|
List.unmodifiable(_globalResolutionHistory);
|
||||||
|
|
||||||
/// Clears all global and per-scope state in this detector.
|
/// Clears all global and per-scope state in this detector.
|
||||||
void clear() {
|
void clear() {
|
||||||
@@ -157,7 +168,8 @@ class GlobalCycleDetector {
|
|||||||
void _detectorClear(detector) => detector.clear();
|
void _detectorClear(detector) => detector.clear();
|
||||||
|
|
||||||
/// Creates a unique dependency key string including scope and name (for diagnostics/cycle checks).
|
/// Creates a unique dependency key string including scope and name (for diagnostics/cycle checks).
|
||||||
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' : '';
|
||||||
final scopePrefix = scopeId != null ? '[$scopeId]' : '';
|
final scopePrefix = scopeId != null ? '[$scopeId]' : '';
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import 'package:cherrypick/src/global_cycle_detector.dart';
|
|||||||
import 'package:cherrypick/src/observer.dart';
|
import 'package:cherrypick/src/observer.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
|
|
||||||
Scope? _rootScope;
|
Scope? _rootScope;
|
||||||
|
|
||||||
/// Global logger for all [Scope]s managed by [CherryPick].
|
/// Global logger for all [Scope]s managed by [CherryPick].
|
||||||
@@ -80,7 +79,8 @@ class CherryPick {
|
|||||||
if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) {
|
if (_globalCycleDetectionEnabled && !_rootScope!.isCycleDetectionEnabled) {
|
||||||
_rootScope!.enableCycleDetection();
|
_rootScope!.enableCycleDetection();
|
||||||
}
|
}
|
||||||
if (_globalCrossScopeCycleDetectionEnabled && !_rootScope!.isGlobalCycleDetectionEnabled) {
|
if (_globalCrossScopeCycleDetectionEnabled &&
|
||||||
|
!_rootScope!.isGlobalCycleDetectionEnabled) {
|
||||||
_rootScope!.enableGlobalCycleDetection();
|
_rootScope!.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
return _rootScope!;
|
return _rootScope!;
|
||||||
@@ -96,7 +96,8 @@ class CherryPick {
|
|||||||
/// ```
|
/// ```
|
||||||
static Future<void> closeRootScope() async {
|
static Future<void> closeRootScope() async {
|
||||||
if (_rootScope != null) {
|
if (_rootScope != null) {
|
||||||
await _rootScope!.dispose(); // Автоматический вызов dispose для rootScope!
|
await _rootScope!
|
||||||
|
.dispose(); // Автоматический вызов dispose для rootScope!
|
||||||
_rootScope = null;
|
_rootScope = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,13 +142,15 @@ class CherryPick {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// CherryPick.enableCycleDetectionForScope(scopeName: 'api.feature');
|
/// CherryPick.enableCycleDetectionForScope(scopeName: 'api.feature');
|
||||||
/// ```
|
/// ```
|
||||||
static void enableCycleDetectionForScope({String scopeName = '', String separator = '.'}) {
|
static void enableCycleDetectionForScope(
|
||||||
|
{String scopeName = '', String separator = '.'}) {
|
||||||
final scope = _getScope(scopeName, separator);
|
final scope = _getScope(scopeName, separator);
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disables cycle detection for a given scope. See [enableCycleDetectionForScope].
|
/// Disables cycle detection for a given scope. See [enableCycleDetectionForScope].
|
||||||
static void disableCycleDetectionForScope({String scopeName = '', String separator = '.'}) {
|
static void disableCycleDetectionForScope(
|
||||||
|
{String scopeName = '', String separator = '.'}) {
|
||||||
final scope = _getScope(scopeName, separator);
|
final scope = _getScope(scopeName, separator);
|
||||||
scope.disableCycleDetection();
|
scope.disableCycleDetection();
|
||||||
}
|
}
|
||||||
@@ -158,7 +161,8 @@ class CherryPick {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.api');
|
/// CherryPick.isCycleDetectionEnabledForScope(scopeName: 'feature.api');
|
||||||
/// ```
|
/// ```
|
||||||
static bool isCycleDetectionEnabledForScope({String scopeName = '', String separator = '.'}) {
|
static bool isCycleDetectionEnabledForScope(
|
||||||
|
{String scopeName = '', String separator = '.'}) {
|
||||||
final scope = _getScope(scopeName, separator);
|
final scope = _getScope(scopeName, separator);
|
||||||
return scope.isCycleDetectionEnabled;
|
return scope.isCycleDetectionEnabled;
|
||||||
}
|
}
|
||||||
@@ -171,7 +175,8 @@ class CherryPick {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// print(CherryPick.getCurrentResolutionChain(scopeName: 'feature.api'));
|
/// print(CherryPick.getCurrentResolutionChain(scopeName: 'feature.api'));
|
||||||
/// ```
|
/// ```
|
||||||
static List<String> getCurrentResolutionChain({String scopeName = '', String separator = '.'}) {
|
static List<String> getCurrentResolutionChain(
|
||||||
|
{String scopeName = '', String separator = '.'}) {
|
||||||
final scope = _getScope(scopeName, separator);
|
final scope = _getScope(scopeName, separator);
|
||||||
return scope.currentResolutionChain;
|
return scope.currentResolutionChain;
|
||||||
}
|
}
|
||||||
@@ -229,14 +234,13 @@ class CherryPick {
|
|||||||
if (nameParts.isEmpty) {
|
if (nameParts.isEmpty) {
|
||||||
throw Exception('Can not open sub scope because scopeName can not split');
|
throw Exception('Can not open sub scope because scopeName can not split');
|
||||||
}
|
}
|
||||||
final scope = nameParts.fold(
|
final scope = nameParts.fold(openRootScope(),
|
||||||
openRootScope(),
|
(Scope previous, String element) => previous.openSubScope(element));
|
||||||
(Scope previous, String element) => previous.openSubScope(element)
|
|
||||||
);
|
|
||||||
if (_globalCycleDetectionEnabled && !scope.isCycleDetectionEnabled) {
|
if (_globalCycleDetectionEnabled && !scope.isCycleDetectionEnabled) {
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
}
|
}
|
||||||
if (_globalCrossScopeCycleDetectionEnabled && !scope.isGlobalCycleDetectionEnabled) {
|
if (_globalCrossScopeCycleDetectionEnabled &&
|
||||||
|
!scope.isGlobalCycleDetectionEnabled) {
|
||||||
scope.enableGlobalCycleDetection();
|
scope.enableGlobalCycleDetection();
|
||||||
}
|
}
|
||||||
return scope;
|
return scope;
|
||||||
@@ -252,21 +256,21 @@ class CherryPick {
|
|||||||
/// CherryPick.closeScope(scopeName: 'network.super.api');
|
/// CherryPick.closeScope(scopeName: 'network.super.api');
|
||||||
/// ```
|
/// ```
|
||||||
@experimental
|
@experimental
|
||||||
static Future<void> closeScope({String scopeName = '', String separator = '.'}) async {
|
static Future<void> closeScope(
|
||||||
|
{String scopeName = '', String separator = '.'}) async {
|
||||||
if (scopeName.isEmpty) {
|
if (scopeName.isEmpty) {
|
||||||
await closeRootScope();
|
await closeRootScope();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final nameParts = scopeName.split(separator);
|
final nameParts = scopeName.split(separator);
|
||||||
if (nameParts.isEmpty) {
|
if (nameParts.isEmpty) {
|
||||||
throw Exception('Can not close sub scope because scopeName can not split');
|
throw Exception(
|
||||||
|
'Can not close sub scope because scopeName can not split');
|
||||||
}
|
}
|
||||||
if (nameParts.length > 1) {
|
if (nameParts.length > 1) {
|
||||||
final lastPart = nameParts.removeLast();
|
final lastPart = nameParts.removeLast();
|
||||||
final scope = nameParts.fold(
|
final scope = nameParts.fold(openRootScope(),
|
||||||
openRootScope(),
|
(Scope previous, String element) => previous.openSubScope(element));
|
||||||
(Scope previous, String element) => previous.openSubScope(element)
|
|
||||||
);
|
|
||||||
await scope.closeSubScope(lastPart);
|
await scope.closeSubScope(lastPart);
|
||||||
} else {
|
} else {
|
||||||
await openRootScope().closeSubScope(nameParts.first);
|
await openRootScope().closeSubScope(nameParts.first);
|
||||||
@@ -316,7 +320,8 @@ class CherryPick {
|
|||||||
/// print('Global cross-scope detection is ON');
|
/// print('Global cross-scope detection is ON');
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
static bool get isGlobalCrossScopeCycleDetectionEnabled => _globalCrossScopeCycleDetectionEnabled;
|
static bool get isGlobalCrossScopeCycleDetectionEnabled =>
|
||||||
|
_globalCrossScopeCycleDetectionEnabled;
|
||||||
|
|
||||||
/// Returns the current global dependency resolution chain (across all scopes).
|
/// Returns the current global dependency resolution chain (across all scopes).
|
||||||
///
|
///
|
||||||
@@ -367,7 +372,8 @@ class CherryPick {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// final featureScope = CherryPick.openGlobalSafeScope(scopeName: 'featureA.api');
|
/// final featureScope = CherryPick.openGlobalSafeScope(scopeName: 'featureA.api');
|
||||||
/// ```
|
/// ```
|
||||||
static Scope openGlobalSafeScope({String scopeName = '', String separator = '.'}) {
|
static Scope openGlobalSafeScope(
|
||||||
|
{String scopeName = '', String separator = '.'}) {
|
||||||
final scope = openScope(scopeName: scopeName, separator: separator);
|
final scope = openScope(scopeName: scopeName, separator: separator);
|
||||||
scope.enableCycleDetection();
|
scope.enableCycleDetection();
|
||||||
scope.enableGlobalCycleDetection();
|
scope.enableGlobalCycleDetection();
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ abstract class CherryPickObserver {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// observer.onInstanceCreated('MyService', MyService, instance, scopeName: 'root');
|
/// observer.onInstanceCreated('MyService', MyService, instance, scopeName: 'root');
|
||||||
/// ```
|
/// ```
|
||||||
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName});
|
void onInstanceCreated(String name, Type type, Object instance,
|
||||||
|
{String? scopeName});
|
||||||
|
|
||||||
/// Called when an instance is disposed (removed from cache and/or finalized).
|
/// Called when an instance is disposed (removed from cache and/or finalized).
|
||||||
///
|
///
|
||||||
@@ -57,7 +58,8 @@ abstract class CherryPickObserver {
|
|||||||
/// ```dart
|
/// ```dart
|
||||||
/// observer.onInstanceDisposed('MyService', MyService, instance, scopeName: 'root');
|
/// observer.onInstanceDisposed('MyService', MyService, instance, scopeName: 'root');
|
||||||
/// ```
|
/// ```
|
||||||
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName});
|
void onInstanceDisposed(String name, Type type, Object instance,
|
||||||
|
{String? scopeName});
|
||||||
|
|
||||||
// === Module events ===
|
// === Module events ===
|
||||||
/// Called when modules are installed into the container.
|
/// Called when modules are installed into the container.
|
||||||
@@ -157,19 +159,23 @@ class PrintCherryPickObserver implements CherryPickObserver {
|
|||||||
print('[request][CherryPick] $name — $type (scope: $scopeName)');
|
print('[request][CherryPick] $name — $type (scope: $scopeName)');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) =>
|
void onInstanceCreated(String name, Type type, Object instance,
|
||||||
print('[create][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
{String? scopeName}) =>
|
||||||
|
print(
|
||||||
|
'[create][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) =>
|
void onInstanceDisposed(String name, Type type, Object instance,
|
||||||
print('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
{String? scopeName}) =>
|
||||||
|
print(
|
||||||
|
'[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onModulesInstalled(List<String> modules, {String? scopeName}) =>
|
void onModulesInstalled(List<String> modules, {String? scopeName}) => print(
|
||||||
print('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
'[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
@override
|
@override
|
||||||
void onModulesRemoved(List<String> modules, {String? scopeName}) =>
|
void onModulesRemoved(List<String> modules, {String? scopeName}) => print(
|
||||||
print('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
'[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onScopeOpened(String name) => print('[scope opened][CherryPick] $name');
|
void onScopeOpened(String name) => print('[scope opened][CherryPick] $name');
|
||||||
@@ -178,8 +184,8 @@ class PrintCherryPickObserver implements CherryPickObserver {
|
|||||||
void onScopeClosed(String name) => print('[scope closed][CherryPick] $name');
|
void onScopeClosed(String name) => print('[scope closed][CherryPick] $name');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onCycleDetected(List<String> chain, {String? scopeName}) =>
|
void onCycleDetected(List<String> chain, {String? scopeName}) => print(
|
||||||
print('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}');
|
'[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onCacheHit(String name, Type type, {String? scopeName}) =>
|
void onCacheHit(String name, Type type, {String? scopeName}) =>
|
||||||
@@ -210,9 +216,11 @@ class SilentCherryPickObserver implements CherryPickObserver {
|
|||||||
@override
|
@override
|
||||||
void onInstanceRequested(String name, Type type, {String? scopeName}) {}
|
void onInstanceRequested(String name, Type type, {String? scopeName}) {}
|
||||||
@override
|
@override
|
||||||
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {}
|
void onInstanceCreated(String name, Type type, Object instance,
|
||||||
|
{String? scopeName}) {}
|
||||||
@override
|
@override
|
||||||
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {}
|
void onInstanceDisposed(String name, Type type, Object instance,
|
||||||
|
{String? scopeName}) {}
|
||||||
@override
|
@override
|
||||||
void onModulesInstalled(List<String> modules, {String? scopeName}) {}
|
void onModulesInstalled(List<String> modules, {String? scopeName}) {}
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
|
|
||||||
final Map<String, Scope> _scopeMap = HashMap();
|
final Map<String, Scope> _scopeMap = HashMap();
|
||||||
|
|
||||||
Scope(this._parentScope, {required CherryPickObserver observer}) : _observer = observer {
|
Scope(this._parentScope, {required CherryPickObserver observer})
|
||||||
|
: _observer = observer {
|
||||||
setScopeId(_generateScopeId());
|
setScopeId(_generateScopeId());
|
||||||
observer.onScopeOpened(scopeId ?? 'NO_ID');
|
observer.onScopeOpened(scopeId ?? 'NO_ID');
|
||||||
observer.onDiagnostic(
|
observer.onDiagnostic(
|
||||||
@@ -87,7 +88,6 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
// индекс для мгновенного поиска binding’ов
|
// индекс для мгновенного поиска binding’ов
|
||||||
final Map<Object, Map<String?, BindingResolver>> _bindingResolvers = {};
|
final Map<Object, Map<String?, BindingResolver>> _bindingResolvers = {};
|
||||||
|
|
||||||
|
|
||||||
/// Generates a unique identifier string for this scope instance.
|
/// Generates a unique identifier string for this scope instance.
|
||||||
///
|
///
|
||||||
/// Used internally for diagnostics, logging and global scope tracking.
|
/// Used internally for diagnostics, logging and global scope tracking.
|
||||||
@@ -280,7 +280,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
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);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId);
|
observer.onInstanceCreated(T.toString(), T, resolved,
|
||||||
|
scopeName: scopeId);
|
||||||
observer.onDiagnostic(
|
observer.onDiagnostic(
|
||||||
'Successfully resolved: $T',
|
'Successfully resolved: $T',
|
||||||
details: {
|
details: {
|
||||||
@@ -360,10 +361,12 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
T result;
|
T result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
result = await withGlobalCycleDetection<Future<T>>(T, named, () async {
|
result = await withGlobalCycleDetection<Future<T>>(T, named, () async {
|
||||||
return await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
return await _resolveAsyncWithLocalDetection<T>(
|
||||||
|
named: named, params: params);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
result = await _resolveAsyncWithLocalDetection<T>(named: named, params: params);
|
result = await _resolveAsyncWithLocalDetection<T>(
|
||||||
|
named: named, params: params);
|
||||||
}
|
}
|
||||||
_trackDisposable(result);
|
_trackDisposable(result);
|
||||||
return result;
|
return result;
|
||||||
@@ -371,11 +374,14 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
|
|
||||||
/// Resolves [T] asynchronously using local cycle detector. Throws if not found.
|
/// Resolves [T] asynchronously using local cycle detector. Throws if not found.
|
||||||
/// Internal implementation for async [resolveAsync].
|
/// 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);
|
||||||
if (resolved != null) {
|
if (resolved != null) {
|
||||||
observer.onInstanceCreated(T.toString(), T, resolved, scopeName: scopeId);
|
observer.onInstanceCreated(T.toString(), T, resolved,
|
||||||
|
scopeName: scopeId);
|
||||||
observer.onDiagnostic(
|
observer.onDiagnostic(
|
||||||
'Successfully async resolved: $T',
|
'Successfully async resolved: $T',
|
||||||
details: {
|
details: {
|
||||||
@@ -410,10 +416,12 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
T? result;
|
T? result;
|
||||||
if (isGlobalCycleDetectionEnabled) {
|
if (isGlobalCycleDetectionEnabled) {
|
||||||
result = await withGlobalCycleDetection<Future<T?>>(T, named, () async {
|
result = await withGlobalCycleDetection<Future<T?>>(T, named, () async {
|
||||||
return await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
return await _tryResolveAsyncWithLocalDetection<T>(
|
||||||
|
named: named, params: params);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
result = await _tryResolveAsyncWithLocalDetection<T>(named: named, params: params);
|
result = await _tryResolveAsyncWithLocalDetection<T>(
|
||||||
|
named: named, params: params);
|
||||||
}
|
}
|
||||||
if (result != null) _trackDisposable(result);
|
if (result != null) _trackDisposable(result);
|
||||||
return result;
|
return result;
|
||||||
@@ -421,7 +429,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
|
|
||||||
/// Attempts to resolve [T] asynchronously using local cycle detector. Returns null if missing.
|
/// Attempts to resolve [T] asynchronously using local cycle detector. Returns null if missing.
|
||||||
/// Internal implementation for async [tryResolveAsync].
|
/// 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 {
|
||||||
return await _tryResolveAsyncInternal<T>(named: named, params: params);
|
return await _tryResolveAsyncInternal<T>(named: named, params: params);
|
||||||
@@ -432,7 +441,8 @@ class Scope with CycleDetectionMixin, GlobalCycleDetectionMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Direct async resolution for [T] without cycle check. Returns null if missing. Internal use only.
|
/// Direct async resolution for [T] without cycle check. Returns null if missing. Internal use only.
|
||||||
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 - Try from own modules; 2 - Fallback to parent
|
||||||
return resolver?.resolveAsync(params) ??
|
return resolver?.resolveAsync(params) ??
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
name: cherrypick
|
name: cherrypick
|
||||||
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
description: Cherrypick is a small dependency injection (DI) library for dart/flutter projects.
|
||||||
version: 3.0.0-dev.9
|
version: 3.0.0-dev.10
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://cherrypick-di.dev/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://cherrypick-di.dev/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
topics:
|
topics:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class DummyModule extends Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class A {}
|
class A {}
|
||||||
|
|
||||||
class B {}
|
class B {}
|
||||||
|
|
||||||
class CyclicModule extends Module {
|
class CyclicModule extends Module {
|
||||||
@@ -52,10 +53,13 @@ void main() {
|
|||||||
throwsA(isA<CircularDependencyException>()),
|
throwsA(isA<CircularDependencyException>()),
|
||||||
);
|
);
|
||||||
// Проверяем, что цикл зафиксирован либо в errors, либо в diagnostics либо cycles
|
// Проверяем, что цикл зафиксирован либо в errors, либо в diagnostics либо cycles
|
||||||
final foundInErrors = observer.errors.any((m) => m.contains('cycle detected'));
|
final foundInErrors =
|
||||||
final foundInDiagnostics = observer.diagnostics.any((m) => m.contains('cycle detected'));
|
observer.errors.any((m) => m.contains('cycle detected'));
|
||||||
|
final foundInDiagnostics =
|
||||||
|
observer.diagnostics.any((m) => m.contains('cycle detected'));
|
||||||
final foundCycleNotified = observer.cycles.isNotEmpty;
|
final foundCycleNotified = observer.cycles.isNotEmpty;
|
||||||
expect(foundInErrors || foundInDiagnostics || foundCycleNotified, isTrue,
|
expect(foundInErrors || foundInDiagnostics || foundCycleNotified, isTrue,
|
||||||
reason: 'Ожидаем хотя бы один лог о цикле! errors: ${observer.errors}\ndiag: ${observer.diagnostics}\ncycles: ${observer.cycles}');
|
reason:
|
||||||
|
'Ожидаем хотя бы один лог о цикле! errors: ${observer.errors}\ndiag: ${observer.diagnostics}\ncycles: ${observer.cycles}');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -15,8 +15,7 @@ class MockObserver implements CherryPickObserver {
|
|||||||
void onWarning(String message, {Object? details}) => warnings.add(message);
|
void onWarning(String message, {Object? details}) => warnings.add(message);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onError(String message, Object? error, StackTrace? stackTrace) =>
|
void onError(String message, Object? error, StackTrace? stackTrace) => errors.add(
|
||||||
errors.add(
|
|
||||||
'$message${error != null ? ' $error' : ''}${stackTrace != null ? '\n$stackTrace' : ''}');
|
'$message${error != null ? ' $error' : ''}${stackTrace != null ? '\n$stackTrace' : ''}');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -30,9 +29,11 @@ class MockObserver implements CherryPickObserver {
|
|||||||
@override
|
@override
|
||||||
void onInstanceRequested(String name, Type type, {String? scopeName}) {}
|
void onInstanceRequested(String name, Type type, {String? scopeName}) {}
|
||||||
@override
|
@override
|
||||||
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {}
|
void onInstanceCreated(String name, Type type, Object instance,
|
||||||
|
{String? scopeName}) {}
|
||||||
@override
|
@override
|
||||||
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {}
|
void onInstanceDisposed(String name, Type type, Object instance,
|
||||||
|
{String? scopeName}) {}
|
||||||
@override
|
@override
|
||||||
void onModulesInstalled(List<String> moduleNames, {String? scopeName}) {}
|
void onModulesInstalled(List<String> moduleNames, {String? scopeName}) {}
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('current implementation limitation - may not detect cross-scope cycles', () {
|
test(
|
||||||
|
'current implementation limitation - may not detect cross-scope cycles',
|
||||||
|
() {
|
||||||
// Этот тест демонстрирует ограничение текущей реализации
|
// Этот тест демонстрирует ограничение текущей реализации
|
||||||
final parentScope = CherryPick.openRootScope();
|
final parentScope = CherryPick.openRootScope();
|
||||||
parentScope.enableCycleDetection();
|
parentScope.enableCycleDetection();
|
||||||
|
|||||||
@@ -52,8 +52,7 @@ void main() {
|
|||||||
throwsA(predicate((e) =>
|
throwsA(predicate((e) =>
|
||||||
e is CircularDependencyException &&
|
e is CircularDependencyException &&
|
||||||
e.dependencyChain.contains('String') &&
|
e.dependencyChain.contains('String') &&
|
||||||
e.dependencyChain.length > 1
|
e.dependencyChain.length > 1)),
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -161,14 +160,16 @@ class ServiceB {
|
|||||||
class CircularModuleA extends Module {
|
class CircularModuleA extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<ServiceA>().toProvide(() => ServiceA(currentScope.resolve<ServiceB>()));
|
bind<ServiceA>()
|
||||||
|
.toProvide(() => ServiceA(currentScope.resolve<ServiceB>()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CircularModuleB extends Module {
|
class CircularModuleB extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<ServiceB>().toProvide(() => ServiceB(currentScope.resolve<ServiceA>()));
|
bind<ServiceB>()
|
||||||
|
.toProvide(() => ServiceB(currentScope.resolve<ServiceA>()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,9 @@ void main() {
|
|||||||
expect(CherryPick.isGlobalCrossScopeCycleDetectionEnabled, isFalse);
|
expect(CherryPick.isGlobalCrossScopeCycleDetectionEnabled, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should automatically enable global cycle detection for new root scope', () {
|
test(
|
||||||
|
'should automatically enable global cycle detection for new root scope',
|
||||||
|
() {
|
||||||
CherryPick.enableGlobalCrossScopeCycleDetection();
|
CherryPick.enableGlobalCrossScopeCycleDetection();
|
||||||
|
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
@@ -45,7 +47,9 @@ void main() {
|
|||||||
expect(scope.isGlobalCycleDetectionEnabled, isTrue);
|
expect(scope.isGlobalCycleDetectionEnabled, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should automatically enable global cycle detection for existing root scope', () {
|
test(
|
||||||
|
'should automatically enable global cycle detection for existing root scope',
|
||||||
|
() {
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
expect(scope.isGlobalCycleDetectionEnabled, isFalse);
|
expect(scope.isGlobalCycleDetectionEnabled, isFalse);
|
||||||
|
|
||||||
@@ -56,15 +60,18 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
group('Global Safe Scope Creation', () {
|
group('Global Safe Scope Creation', () {
|
||||||
test('should create global safe root scope with both detections enabled', () {
|
test('should create global safe root scope with both detections enabled',
|
||||||
|
() {
|
||||||
final scope = CherryPick.openGlobalSafeRootScope();
|
final scope = CherryPick.openGlobalSafeRootScope();
|
||||||
|
|
||||||
expect(scope.isCycleDetectionEnabled, isTrue);
|
expect(scope.isCycleDetectionEnabled, isTrue);
|
||||||
expect(scope.isGlobalCycleDetectionEnabled, isTrue);
|
expect(scope.isGlobalCycleDetectionEnabled, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should create global safe sub-scope with both detections enabled', () {
|
test('should create global safe sub-scope with both detections enabled',
|
||||||
final scope = CherryPick.openGlobalSafeScope(scopeName: 'feature.global');
|
() {
|
||||||
|
final scope =
|
||||||
|
CherryPick.openGlobalSafeScope(scopeName: 'feature.global');
|
||||||
|
|
||||||
expect(scope.isCycleDetectionEnabled, isTrue);
|
expect(scope.isCycleDetectionEnabled, isTrue);
|
||||||
expect(scope.isGlobalCycleDetectionEnabled, isTrue);
|
expect(scope.isGlobalCycleDetectionEnabled, isTrue);
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ void main() {
|
|||||||
expect(CherryPick.isGlobalCycleDetectionEnabled, isFalse);
|
expect(CherryPick.isGlobalCycleDetectionEnabled, isFalse);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should automatically enable cycle detection for new root scope when global is enabled', () {
|
test(
|
||||||
|
'should automatically enable cycle detection for new root scope when global is enabled',
|
||||||
|
() {
|
||||||
CherryPick.enableGlobalCycleDetection();
|
CherryPick.enableGlobalCycleDetection();
|
||||||
|
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
@@ -47,7 +49,9 @@ void main() {
|
|||||||
expect(scope.isCycleDetectionEnabled, isTrue);
|
expect(scope.isCycleDetectionEnabled, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should automatically enable cycle detection for existing root scope when global is enabled', () {
|
test(
|
||||||
|
'should automatically enable cycle detection for existing root scope when global is enabled',
|
||||||
|
() {
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
expect(scope.isCycleDetectionEnabled, isFalse);
|
expect(scope.isCycleDetectionEnabled, isFalse);
|
||||||
|
|
||||||
@@ -56,7 +60,9 @@ void main() {
|
|||||||
expect(scope.isCycleDetectionEnabled, isTrue);
|
expect(scope.isCycleDetectionEnabled, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should automatically disable cycle detection for existing root scope when global is disabled', () {
|
test(
|
||||||
|
'should automatically disable cycle detection for existing root scope when global is disabled',
|
||||||
|
() {
|
||||||
CherryPick.enableGlobalCycleDetection();
|
CherryPick.enableGlobalCycleDetection();
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
expect(scope.isCycleDetectionEnabled, isTrue);
|
expect(scope.isCycleDetectionEnabled, isTrue);
|
||||||
@@ -99,21 +105,25 @@ void main() {
|
|||||||
final scopeName = 'feature.auth';
|
final scopeName = 'feature.auth';
|
||||||
CherryPick.openScope(scopeName: scopeName);
|
CherryPick.openScope(scopeName: scopeName);
|
||||||
|
|
||||||
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), isFalse);
|
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName),
|
||||||
|
isFalse);
|
||||||
|
|
||||||
CherryPick.enableCycleDetectionForScope(scopeName: scopeName);
|
CherryPick.enableCycleDetectionForScope(scopeName: scopeName);
|
||||||
|
|
||||||
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), isTrue);
|
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName),
|
||||||
|
isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should disable cycle detection for specific scope', () {
|
test('should disable cycle detection for specific scope', () {
|
||||||
final scopeName = 'feature.auth';
|
final scopeName = 'feature.auth';
|
||||||
CherryPick.enableCycleDetectionForScope(scopeName: scopeName);
|
CherryPick.enableCycleDetectionForScope(scopeName: scopeName);
|
||||||
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), isTrue);
|
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName),
|
||||||
|
isTrue);
|
||||||
|
|
||||||
CherryPick.disableCycleDetectionForScope(scopeName: scopeName);
|
CherryPick.disableCycleDetectionForScope(scopeName: scopeName);
|
||||||
|
|
||||||
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName), isFalse);
|
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName),
|
||||||
|
isFalse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -134,14 +144,17 @@ void main() {
|
|||||||
// Глобальная настройка отключена
|
// Глобальная настройка отключена
|
||||||
expect(CherryPick.isGlobalCycleDetectionEnabled, isFalse);
|
expect(CherryPick.isGlobalCycleDetectionEnabled, isFalse);
|
||||||
|
|
||||||
final scope = CherryPick.openSafeScope(scopeName: 'feature.independent');
|
final scope =
|
||||||
|
CherryPick.openSafeScope(scopeName: 'feature.independent');
|
||||||
|
|
||||||
expect(scope.isCycleDetectionEnabled, isTrue);
|
expect(scope.isCycleDetectionEnabled, isTrue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Resolution Chain Tracking', () {
|
group('Resolution Chain Tracking', () {
|
||||||
test('should return empty resolution chain for scope without cycle detection', () {
|
test(
|
||||||
|
'should return empty resolution chain for scope without cycle detection',
|
||||||
|
() {
|
||||||
CherryPick.openRootScope();
|
CherryPick.openRootScope();
|
||||||
|
|
||||||
final chain = CherryPick.getCurrentResolutionChain();
|
final chain = CherryPick.getCurrentResolutionChain();
|
||||||
@@ -149,7 +162,9 @@ void main() {
|
|||||||
expect(chain, isEmpty);
|
expect(chain, isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return empty resolution chain for scope with cycle detection but no active resolution', () {
|
test(
|
||||||
|
'should return empty resolution chain for scope with cycle detection but no active resolution',
|
||||||
|
() {
|
||||||
CherryPick.enableCycleDetectionForScope();
|
CherryPick.enableCycleDetectionForScope();
|
||||||
|
|
||||||
final chain = CherryPick.getCurrentResolutionChain();
|
final chain = CherryPick.getCurrentResolutionChain();
|
||||||
@@ -161,14 +176,17 @@ void main() {
|
|||||||
final scopeName = 'feature.tracking';
|
final scopeName = 'feature.tracking';
|
||||||
CherryPick.enableCycleDetectionForScope(scopeName: scopeName);
|
CherryPick.enableCycleDetectionForScope(scopeName: scopeName);
|
||||||
|
|
||||||
final chain = CherryPick.getCurrentResolutionChain(scopeName: scopeName);
|
final chain =
|
||||||
|
CherryPick.getCurrentResolutionChain(scopeName: scopeName);
|
||||||
|
|
||||||
expect(chain, isEmpty); // Пустая, так как нет активного разрешения
|
expect(chain, isEmpty); // Пустая, так как нет активного разрешения
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Integration with Circular Dependencies', () {
|
group('Integration with Circular Dependencies', () {
|
||||||
test('should detect circular dependency with global cycle detection enabled', () {
|
test(
|
||||||
|
'should detect circular dependency with global cycle detection enabled',
|
||||||
|
() {
|
||||||
CherryPick.enableGlobalCycleDetection();
|
CherryPick.enableGlobalCycleDetection();
|
||||||
|
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
@@ -190,7 +208,9 @@ void main() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should not detect circular dependency when cycle detection is disabled', () {
|
test(
|
||||||
|
'should not detect circular dependency when cycle detection is disabled',
|
||||||
|
() {
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
scope.installModules([CircularTestModule()]);
|
scope.installModules([CircularTestModule()]);
|
||||||
|
|
||||||
@@ -205,7 +225,8 @@ void main() {
|
|||||||
test('should handle empty scope name as root scope', () {
|
test('should handle empty scope name as root scope', () {
|
||||||
CherryPick.enableCycleDetectionForScope(scopeName: '');
|
CherryPick.enableCycleDetectionForScope(scopeName: '');
|
||||||
|
|
||||||
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: ''), isTrue);
|
expect(
|
||||||
|
CherryPick.isCycleDetectionEnabledForScope(scopeName: ''), isTrue);
|
||||||
expect(CherryPick.isCycleDetectionEnabledForScope(), isTrue);
|
expect(CherryPick.isCycleDetectionEnabledForScope(), isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -213,14 +234,21 @@ void main() {
|
|||||||
final complexScopeName = 'app.feature.auth.login';
|
final complexScopeName = 'app.feature.auth.login';
|
||||||
CherryPick.enableCycleDetectionForScope(scopeName: complexScopeName);
|
CherryPick.enableCycleDetectionForScope(scopeName: complexScopeName);
|
||||||
|
|
||||||
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: complexScopeName), isTrue);
|
expect(
|
||||||
|
CherryPick.isCycleDetectionEnabledForScope(
|
||||||
|
scopeName: complexScopeName),
|
||||||
|
isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle custom separator', () {
|
test('should handle custom separator', () {
|
||||||
final scopeName = 'app/feature/auth';
|
final scopeName = 'app/feature/auth';
|
||||||
CherryPick.enableCycleDetectionForScope(scopeName: scopeName, separator: '/');
|
CherryPick.enableCycleDetectionForScope(
|
||||||
|
scopeName: scopeName, separator: '/');
|
||||||
|
|
||||||
expect(CherryPick.isCycleDetectionEnabledForScope(scopeName: scopeName, separator: '/'), isTrue);
|
expect(
|
||||||
|
CherryPick.isCycleDetectionEnabledForScope(
|
||||||
|
scopeName: scopeName, separator: '/'),
|
||||||
|
isTrue);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -240,7 +268,9 @@ class CircularServiceB {
|
|||||||
class CircularTestModule extends Module {
|
class CircularTestModule extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope currentScope) {
|
void builder(Scope currentScope) {
|
||||||
bind<CircularServiceA>().toProvide(() => CircularServiceA(currentScope.resolve<CircularServiceB>()));
|
bind<CircularServiceA>().toProvide(
|
||||||
bind<CircularServiceB>().toProvide(() => CircularServiceB(currentScope.resolve<CircularServiceA>()));
|
() => CircularServiceA(currentScope.resolve<CircularServiceB>()));
|
||||||
|
bind<CircularServiceB>().toProvide(
|
||||||
|
() => CircularServiceB(currentScope.resolve<CircularServiceA>()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:cherrypick/cherrypick.dart' show Disposable, Module, Scope, CherryPick;
|
import 'package:cherrypick/cherrypick.dart'
|
||||||
|
show Disposable, Module, Scope, CherryPick;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
import '../mock_logger.dart';
|
import '../mock_logger.dart';
|
||||||
@@ -18,7 +19,9 @@ class AsyncExampleDisposable implements Disposable {
|
|||||||
class AsyncExampleModule extends Module {
|
class AsyncExampleModule extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope scope) {
|
void builder(Scope scope) {
|
||||||
bind<AsyncExampleDisposable>().toProvide(() => AsyncExampleDisposable()).singleton();
|
bind<AsyncExampleDisposable>()
|
||||||
|
.toProvide(() => AsyncExampleDisposable())
|
||||||
|
.singleton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +52,9 @@ class CountingDisposable implements Disposable {
|
|||||||
class ModuleCountingDisposable extends Module {
|
class ModuleCountingDisposable extends Module {
|
||||||
@override
|
@override
|
||||||
void builder(Scope scope) {
|
void builder(Scope scope) {
|
||||||
bind<CountingDisposable>().toProvide(() => CountingDisposable()).singleton();
|
bind<CountingDisposable>()
|
||||||
|
.toProvide(() => CountingDisposable())
|
||||||
|
.singleton();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,8 +104,7 @@ class AsyncModule extends Module {
|
|||||||
.toProvideAsync(() async {
|
.toProvideAsync(() async {
|
||||||
await Future.delayed(Duration(milliseconds: 10));
|
await Future.delayed(Duration(milliseconds: 10));
|
||||||
return AsyncCreatedDisposable();
|
return AsyncCreatedDisposable();
|
||||||
})
|
}).singleton();
|
||||||
.singleton();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +123,8 @@ void main() {
|
|||||||
final scope = Scope(null, observer: observer);
|
final scope = Scope(null, observer: observer);
|
||||||
expect(Scope(scope, observer: observer), isNotNull); // эквивалент
|
expect(Scope(scope, observer: observer), isNotNull); // эквивалент
|
||||||
});
|
});
|
||||||
test('closeSubScope removes subscope so next openSubScope returns new', () async {
|
test('closeSubScope removes subscope so next openSubScope returns new',
|
||||||
|
() async {
|
||||||
final observer = MockObserver();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, observer: observer);
|
final scope = Scope(null, observer: observer);
|
||||||
final subScope = scope.openSubScope("child");
|
final subScope = scope.openSubScope("child");
|
||||||
@@ -181,7 +186,8 @@ void main() {
|
|||||||
});
|
});
|
||||||
test("After dropModules resolves fail", () {
|
test("After dropModules resolves fail", () {
|
||||||
final observer = MockObserver();
|
final observer = MockObserver();
|
||||||
final scope = Scope(null, observer: observer)..installModules([TestModule<int>(value: 5)]);
|
final scope = Scope(null, observer: observer)
|
||||||
|
..installModules([TestModule<int>(value: 5)]);
|
||||||
expect(scope.resolve<int>(), 5);
|
expect(scope.resolve<int>(), 5);
|
||||||
scope.dropModules();
|
scope.dropModules();
|
||||||
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
expect(() => scope.resolve<int>(), throwsA(isA<StateError>()));
|
||||||
@@ -294,7 +300,8 @@ void main() {
|
|||||||
await scope.dispose();
|
await scope.dispose();
|
||||||
expect(t.disposed, isTrue);
|
expect(t.disposed, isTrue);
|
||||||
});
|
});
|
||||||
test('scope.disposeAsync calls dispose on all unique disposables', () async {
|
test('scope.disposeAsync calls dispose on all unique disposables',
|
||||||
|
() async {
|
||||||
final scope = Scope(null, observer: MockObserver());
|
final scope = Scope(null, observer: MockObserver());
|
||||||
scope.installModules([ModuleWithDisposable()]);
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
final t1 = scope.resolve<TestDisposable>();
|
final t1 = scope.resolve<TestDisposable>();
|
||||||
@@ -305,7 +312,8 @@ void main() {
|
|||||||
expect(t1.disposed, isTrue);
|
expect(t1.disposed, isTrue);
|
||||||
expect(t2.disposed, isTrue);
|
expect(t2.disposed, isTrue);
|
||||||
});
|
});
|
||||||
test('calling disposeAsync twice does not throw and not call twice', () async {
|
test('calling disposeAsync twice does not throw and not call twice',
|
||||||
|
() async {
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
scope.installModules([ModuleWithDisposable()]);
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
final t = scope.resolve<TestDisposable>();
|
final t = scope.resolve<TestDisposable>();
|
||||||
@@ -313,7 +321,8 @@ void main() {
|
|||||||
await scope.dispose();
|
await scope.dispose();
|
||||||
expect(t.disposed, isTrue);
|
expect(t.disposed, isTrue);
|
||||||
});
|
});
|
||||||
test('Non-disposable dependency is ignored by scope.disposeAsync', () async {
|
test('Non-disposable dependency is ignored by scope.disposeAsync',
|
||||||
|
() async {
|
||||||
final scope = CherryPick.openRootScope();
|
final scope = CherryPick.openRootScope();
|
||||||
scope.installModules([ModuleWithDisposable()]);
|
scope.installModules([ModuleWithDisposable()]);
|
||||||
final s = scope.resolve<String>();
|
final s = scope.resolve<String>();
|
||||||
@@ -327,7 +336,8 @@ void main() {
|
|||||||
group('Scope/subScope dispose edge cases', () {
|
group('Scope/subScope dispose edge cases', () {
|
||||||
test('Dispose called in closed subScope only', () async {
|
test('Dispose called in closed subScope only', () async {
|
||||||
final root = CherryPick.openRootScope();
|
final root = CherryPick.openRootScope();
|
||||||
final sub = root.openSubScope('feature')..installModules([ModuleCountingDisposable()]);
|
final sub = root.openSubScope('feature')
|
||||||
|
..installModules([ModuleCountingDisposable()]);
|
||||||
final d = sub.resolve<CountingDisposable>();
|
final d = sub.resolve<CountingDisposable>();
|
||||||
expect(d.disposeCount, 0);
|
expect(d.disposeCount, 0);
|
||||||
|
|
||||||
@@ -339,7 +349,8 @@ void main() {
|
|||||||
expect(d.disposeCount, 1);
|
expect(d.disposeCount, 1);
|
||||||
|
|
||||||
// Повторное открытие subScope создает NEW instance (dispose на старый не вызовется снова)
|
// Повторное открытие subScope создает NEW instance (dispose на старый не вызовется снова)
|
||||||
final sub2 = root.openSubScope('feature')..installModules([ModuleCountingDisposable()]);
|
final sub2 = root.openSubScope('feature')
|
||||||
|
..installModules([ModuleCountingDisposable()]);
|
||||||
final d2 = sub2.resolve<CountingDisposable>();
|
final d2 = sub2.resolve<CountingDisposable>();
|
||||||
expect(identical(d, d2), isFalse);
|
expect(identical(d, d2), isFalse);
|
||||||
await root.closeSubScope('feature');
|
await root.closeSubScope('feature');
|
||||||
@@ -347,8 +358,14 @@ void main() {
|
|||||||
});
|
});
|
||||||
test('Dispose for all nested subScopes on root disposeAsync', () async {
|
test('Dispose for all nested subScopes on root disposeAsync', () async {
|
||||||
final root = CherryPick.openRootScope();
|
final root = CherryPick.openRootScope();
|
||||||
root.openSubScope('a').openSubScope('b').installModules([ModuleCountingDisposable()]);
|
root
|
||||||
final d = root.openSubScope('a').openSubScope('b').resolve<CountingDisposable>();
|
.openSubScope('a')
|
||||||
|
.openSubScope('b')
|
||||||
|
.installModules([ModuleCountingDisposable()]);
|
||||||
|
final d = root
|
||||||
|
.openSubScope('a')
|
||||||
|
.openSubScope('b')
|
||||||
|
.resolve<CountingDisposable>();
|
||||||
await root.dispose();
|
await root.dispose();
|
||||||
expect(d.disposeCount, 1);
|
expect(d.disposeCount, 1);
|
||||||
});
|
});
|
||||||
@@ -357,7 +374,8 @@ void main() {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
group('Async disposable (Future test)', () {
|
group('Async disposable (Future test)', () {
|
||||||
test('Async Disposable is awaited on disposeAsync', () async {
|
test('Async Disposable is awaited on disposeAsync', () async {
|
||||||
final scope = CherryPick.openRootScope()..installModules([AsyncExampleModule()]);
|
final scope = CherryPick.openRootScope()
|
||||||
|
..installModules([AsyncExampleModule()]);
|
||||||
final d = scope.resolve<AsyncExampleDisposable>();
|
final d = scope.resolve<AsyncExampleDisposable>();
|
||||||
expect(d.disposed, false);
|
expect(d.disposed, false);
|
||||||
await scope.dispose();
|
await scope.dispose();
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.2-dev.1
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
## 1.1.2-dev.0
|
## 1.1.2-dev.0
|
||||||
|
|
||||||
- **DOCS**(annotations): unify and improve English DartDoc for all DI annotations.
|
- **DOCS**(annotations): unify and improve English DartDoc for all DI annotations.
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
name: cherrypick_annotations
|
name: cherrypick_annotations
|
||||||
description: |
|
description: |
|
||||||
Set of annotations for CherryPick dependency injection library. Enables code generation and declarative DI for Dart & Flutter projects.
|
Set of annotations for CherryPick dependency injection library. Enables code generation and declarative DI for Dart & Flutter projects.
|
||||||
version: 1.1.2-dev.0
|
version: 1.1.2-dev.1
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
homepage: https://cherrypick-di.dev/
|
||||||
|
documentation: https://cherrypick-di.dev/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
|
repository: https://github.com/pese-git/cherrypick/cherrypick_annotations
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
topics:
|
topics:
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 1.1.3-dev.10
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
## 1.1.3-dev.9
|
## 1.1.3-dev.9
|
||||||
|
|
||||||
- **DOCS**(provider): add detailed English API documentation for CherryPickProvider Flutter integration.
|
- **DOCS**(provider): add detailed English API documentation for CherryPickProvider Flutter integration.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
name: cherrypick_flutter
|
name: cherrypick_flutter
|
||||||
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
|
description: "Flutter library that allows access to the root scope through the context using `CherryPickProvider`."
|
||||||
version: 1.1.3-dev.9
|
version: 1.1.3-dev.10
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://cherrypick-di.dev/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://cherrypick-di.dev/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
topics:
|
topics:
|
||||||
@@ -19,7 +19,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cherrypick: ^3.0.0-dev.9
|
cherrypick: ^3.0.0-dev.10
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
## 2.0.0-dev.1
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
## 2.0.0-dev.0
|
## 2.0.0-dev.0
|
||||||
|
|
||||||
> Note: This release has breaking changes.
|
> Note: This release has breaking changes.
|
||||||
|
|||||||
@@ -248,7 +248,6 @@ class _ParsedInjectField {
|
|||||||
/// Name qualifier for named resolution, or null if not set.
|
/// Name qualifier for named resolution, or null if not set.
|
||||||
final String? namedValue;
|
final String? namedValue;
|
||||||
|
|
||||||
|
|
||||||
_ParsedInjectField({
|
_ParsedInjectField({
|
||||||
required this.fieldName,
|
required this.fieldName,
|
||||||
required this.coreType,
|
required this.coreType,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import 'annotation_validator.dart';
|
|||||||
enum BindingType {
|
enum BindingType {
|
||||||
/// Direct instance returned from the method (@instance).
|
/// Direct instance returned from the method (@instance).
|
||||||
instance,
|
instance,
|
||||||
|
|
||||||
/// Provider/factory function (@provide).
|
/// Provider/factory function (@provide).
|
||||||
provide;
|
provide;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ name: cherrypick_generator
|
|||||||
description: |
|
description: |
|
||||||
Source code generator for the cherrypick dependency injection system. Processes annotations to generate binding and module code for Dart & Flutter projects.
|
Source code generator for the cherrypick dependency injection system. Processes annotations to generate binding and module code for Dart & Flutter projects.
|
||||||
|
|
||||||
version: 2.0.0-dev.0
|
version: 2.0.0-dev.1
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
homepage: https://cherrypick-di.dev/
|
||||||
|
documentation: https://cherrypick-di.dev/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
|
repository: https://github.com/pese-git/cherrypick/cherrypick_generator
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
topics:
|
topics:
|
||||||
@@ -18,7 +19,7 @@ environment:
|
|||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
cherrypick_annotations: ^1.1.2-dev.0
|
cherrypick_annotations: ^1.1.2-dev.1
|
||||||
analyzer: ^7.0.0
|
analyzer: ^7.0.0
|
||||||
dart_style: ^3.0.0
|
dart_style: ^3.0.0
|
||||||
build: ^2.4.1
|
build: ^2.4.1
|
||||||
|
|||||||
@@ -244,8 +244,7 @@ void main() {
|
|||||||
final result = bindSpec.generateBind(4);
|
final result = bindSpec.generateBind(4);
|
||||||
expect(
|
expect(
|
||||||
result,
|
result,
|
||||||
equals(
|
equals(" bind<ApiClient>()\n"
|
||||||
" bind<ApiClient>()\n"
|
|
||||||
" .toProvideAsync(() => createApiClient())\n"
|
" .toProvideAsync(() => createApiClient())\n"
|
||||||
" .withName('mainApi')\n"
|
" .withName('mainApi')\n"
|
||||||
" .singleton();"));
|
" .singleton();"));
|
||||||
|
|||||||
@@ -127,28 +127,28 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.8"
|
version: "3.0.0-dev.9"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.1"
|
version: "1.1.2-dev.0"
|
||||||
cherrypick_flutter:
|
cherrypick_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_flutter"
|
path: "../../cherrypick_flutter"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.3-dev.8"
|
version: "1.1.3-dev.9"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.1"
|
version: "2.0.0-dev.0"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:talker_flutter/talker_flutter.dart';
|
import 'package:talker_flutter/talker_flutter.dart';
|
||||||
|
|
||||||
|
|
||||||
import 'domain/repository/post_repository.dart';
|
import 'domain/repository/post_repository.dart';
|
||||||
import 'presentation/bloc/post_bloc.dart';
|
import 'presentation/bloc/post_bloc.dart';
|
||||||
import 'router/app_router.dart';
|
import 'router/app_router.dart';
|
||||||
@@ -14,9 +13,11 @@ part 'app.inject.cherrypick.g.dart';
|
|||||||
class TalkerProvider extends InheritedWidget {
|
class TalkerProvider extends InheritedWidget {
|
||||||
final Talker talker;
|
final Talker talker;
|
||||||
const TalkerProvider({required this.talker, required super.child, super.key});
|
const TalkerProvider({required this.talker, required super.child, super.key});
|
||||||
static Talker of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<TalkerProvider>()!.talker;
|
static Talker of(BuildContext context) =>
|
||||||
|
context.dependOnInheritedWidgetOfExactType<TalkerProvider>()!.talker;
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(TalkerProvider oldWidget) => oldWidget.talker != talker;
|
bool updateShouldNotify(TalkerProvider oldWidget) =>
|
||||||
|
oldWidget.talker != talker;
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ abstract class AppModule extends Module {
|
|||||||
|
|
||||||
@provide()
|
@provide()
|
||||||
@singleton()
|
@singleton()
|
||||||
TalkerDioLogger talkerDioLogger(Talker talker, TalkerDioLoggerSettings settings) => TalkerDioLogger(talker: talker, settings: settings);
|
TalkerDioLogger talkerDioLogger(
|
||||||
|
Talker talker, TalkerDioLoggerSettings settings) =>
|
||||||
|
TalkerDioLogger(talker: talker, settings: settings);
|
||||||
|
|
||||||
@instance()
|
@instance()
|
||||||
int timeout() => 1000;
|
int timeout() => 1000;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ void main() {
|
|||||||
final talker = Talker();
|
final talker = Talker();
|
||||||
final talkerLogger = TalkerCherryPickObserver(talker);
|
final talkerLogger = TalkerCherryPickObserver(talker);
|
||||||
|
|
||||||
|
|
||||||
Bloc.observer = TalkerBlocObserver(talker: talker);
|
Bloc.observer = TalkerBlocObserver(talker: talker);
|
||||||
|
|
||||||
CherryPick.setGlobalObserver(talkerLogger);
|
CherryPick.setGlobalObserver(talkerLogger);
|
||||||
@@ -24,7 +23,10 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Используем safe root scope для гарантии защиты
|
// Используем safe root scope для гарантии защиты
|
||||||
CherryPick.openRootScope().installModules([CoreModule(talker: talker), $AppModule()]);
|
CherryPick.openRootScope()
|
||||||
|
.installModules([CoreModule(talker: talker), $AppModule()]);
|
||||||
|
|
||||||
runApp(MyApp(talker: talker,));
|
runApp(MyApp(
|
||||||
|
talker: talker,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,21 +175,21 @@ packages:
|
|||||||
path: "../../cherrypick"
|
path: "../../cherrypick"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "3.0.0-dev.8"
|
version: "3.0.0-dev.9"
|
||||||
cherrypick_annotations:
|
cherrypick_annotations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_annotations"
|
path: "../../cherrypick_annotations"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.1"
|
version: "1.1.2-dev.0"
|
||||||
cherrypick_generator:
|
cherrypick_generator:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "../../cherrypick_generator"
|
path: "../../cherrypick_generator"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.1.1"
|
version: "2.0.0-dev.0"
|
||||||
cli_launcher:
|
cli_launcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -864,7 +864,7 @@ packages:
|
|||||||
path: "../../talker_cherrypick_logger"
|
path: "../../talker_cherrypick_logger"
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.0.0"
|
version: "1.1.0-dev.3"
|
||||||
talker_dio_logger:
|
talker_dio_logger:
|
||||||
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: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "73.0.0"
|
version: "76.0.0"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.3.2"
|
version: "0.3.3"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.8.0"
|
version: "6.11.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: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2-main.4"
|
version: "0.1.3-main.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
29
talker_cherrypick_logger/.gitignore
vendored
29
talker_cherrypick_logger/.gitignore
vendored
@@ -1,7 +1,26 @@
|
|||||||
# https://dart.dev/guides/libraries/private-files
|
# See https://www.dartlang.org/guides/libraries/private-files
|
||||||
# Created by `dart pub`
|
|
||||||
.dart_tool/
|
|
||||||
|
|
||||||
# Avoid committing pubspec.lock for library packages; see
|
# Files and directories created by pub
|
||||||
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
||||||
|
# If you're building an application, you may want to check-in your pubspec.lock
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
|
|
||||||
|
# Directory created by dartdoc
|
||||||
|
# If you don't generate documentation locally you can remove this line.
|
||||||
|
doc/api/
|
||||||
|
|
||||||
|
# Avoid committing generated Javascript files:
|
||||||
|
*.dart.js
|
||||||
|
*.info.json # Produced by the --dump-info flag.
|
||||||
|
*.js # When generated by dart2js. Don't specify *.js if your
|
||||||
|
# project includes source files written in JavaScript.
|
||||||
|
*.js_
|
||||||
|
*.js.deps
|
||||||
|
*.js.map
|
||||||
|
|
||||||
|
# FVM Version Cache
|
||||||
|
.fvm/
|
||||||
|
|
||||||
|
pubspec_overrides.yaml
|
||||||
@@ -1,3 +1,17 @@
|
|||||||
|
## 1.1.0-dev.5
|
||||||
|
|
||||||
|
- **DOCS**(pub): update homepage and documentation URLs in pubspec.yaml to new official site.
|
||||||
|
|
||||||
|
## 1.1.0-dev.4
|
||||||
|
|
||||||
|
- **DOCS**(readme): update install instructions to use pub.dev as default method and remove obsolete git example.
|
||||||
|
|
||||||
|
## 1.1.0-dev.3
|
||||||
|
|
||||||
|
## 1.1.0-dev.2
|
||||||
|
|
||||||
|
- Bump "talker_cherrypick_logger" to `1.1.0-dev.2`.
|
||||||
|
|
||||||
## 1.1.0-dev.0
|
## 1.1.0-dev.0
|
||||||
|
|
||||||
- **FEAT**(logging): add talker_dio_logger and talker_bloc_logger integration, improve cherrypick logger structure, add UI log screen for DI and network/bloc debug.
|
- **FEAT**(logging): add talker_dio_logger and talker_bloc_logger integration, improve cherrypick logger structure, add UI log screen for DI and network/bloc debug.
|
||||||
|
|||||||
@@ -21,15 +21,14 @@ All CherryPick lifecycle events, instance creations, cache operations, module ac
|
|||||||
|
|
||||||
### 1. Add dependencies
|
### 1. Add dependencies
|
||||||
|
|
||||||
|
Install the package **from [pub.dev](https://pub.dev/packages/talker_cherrypick_logger)**:
|
||||||
|
|
||||||
In your `pubspec.yaml`:
|
In your `pubspec.yaml`:
|
||||||
```yaml
|
```yaml
|
||||||
dependencies:
|
dependencies:
|
||||||
cherrypick: ^latest
|
cherrypick: ^latest
|
||||||
talker: ^latest
|
talker: ^latest
|
||||||
talker_cherrypick_logger:
|
talker_cherrypick_logger: ^latest
|
||||||
git:
|
|
||||||
url: https://github.com/pese-dot-work/cherrypick.git
|
|
||||||
path: talker_cherrypick_logger
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Import the package
|
### 2. Import the package
|
||||||
|
|||||||
@@ -69,26 +69,32 @@ class TalkerCherryPickObserver implements CherryPickObserver {
|
|||||||
|
|
||||||
/// Called when a new instance is created.
|
/// Called when a new instance is created.
|
||||||
@override
|
@override
|
||||||
void onInstanceCreated(String name, Type type, Object instance, {String? scopeName}) {
|
void onInstanceCreated(String name, Type type, Object instance,
|
||||||
talker.info('[create][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
{String? scopeName}) {
|
||||||
|
talker.info(
|
||||||
|
'[create][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when an instance is disposed.
|
/// Called when an instance is disposed.
|
||||||
@override
|
@override
|
||||||
void onInstanceDisposed(String name, Type type, Object instance, {String? scopeName}) {
|
void onInstanceDisposed(String name, Type type, Object instance,
|
||||||
talker.info('[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
{String? scopeName}) {
|
||||||
|
talker.info(
|
||||||
|
'[dispose][CherryPick] $name — $type => $instance (scope: $scopeName)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when modules are installed.
|
/// Called when modules are installed.
|
||||||
@override
|
@override
|
||||||
void onModulesInstalled(List<String> modules, {String? scopeName}) {
|
void onModulesInstalled(List<String> modules, {String? scopeName}) {
|
||||||
talker.info('[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
talker.info(
|
||||||
|
'[modules installed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when modules are removed.
|
/// Called when modules are removed.
|
||||||
@override
|
@override
|
||||||
void onModulesRemoved(List<String> modules, {String? scopeName}) {
|
void onModulesRemoved(List<String> modules, {String? scopeName}) {
|
||||||
talker.info('[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
talker.info(
|
||||||
|
'[modules removed][CherryPick] ${modules.join(', ')} (scope: $scopeName)');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a DI scope is opened.
|
/// Called when a DI scope is opened.
|
||||||
@@ -106,7 +112,8 @@ class TalkerCherryPickObserver implements CherryPickObserver {
|
|||||||
/// Called if the DI container detects a cycle in the dependency graph.
|
/// Called if the DI container detects a cycle in the dependency graph.
|
||||||
@override
|
@override
|
||||||
void onCycleDetected(List<String> chain, {String? scopeName}) {
|
void onCycleDetected(List<String> chain, {String? scopeName}) {
|
||||||
talker.warning('[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}');
|
talker.warning(
|
||||||
|
'[cycle][CherryPick] Detected: ${chain.join(' -> ')}${scopeName != null ? ' (scope: $scopeName)' : ''}');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when an instance is found in the cache.
|
/// Called when an instance is found in the cache.
|
||||||
@@ -136,6 +143,7 @@ class TalkerCherryPickObserver implements CherryPickObserver {
|
|||||||
/// Called for error events with optional stack trace.
|
/// Called for error events with optional stack trace.
|
||||||
@override
|
@override
|
||||||
void onError(String message, Object? error, StackTrace? stackTrace) {
|
void onError(String message, Object? error, StackTrace? stackTrace) {
|
||||||
talker.handle(error ?? '[CherryPick] $message', stackTrace, '[error][CherryPick] $message');
|
talker.handle(error ?? '[CherryPick] $message', stackTrace,
|
||||||
|
'[error][CherryPick] $message');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
name: talker_cherrypick_logger
|
name: talker_cherrypick_logger
|
||||||
description: A starting point for Dart libraries or applications.
|
description: A Talker logger integration for CherryPick DI to observe and log DI events and errors.
|
||||||
version: 1.1.0-dev.0
|
version: 1.1.0-dev.5
|
||||||
homepage: https://pese-git.github.io/cherrypick-site/
|
homepage: https://cherrypick-di.dev/
|
||||||
documentation: https://github.com/pese-git/cherrypick/wiki
|
documentation: https://cherrypick-di.dev/docs/intro
|
||||||
repository: https://github.com/pese-git/cherrypick
|
repository: https://github.com/pese-git/cherrypick
|
||||||
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
issue_tracker: https://github.com/pese-git/cherrypick/issues
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ environment:
|
|||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
talker: ^4.9.3
|
talker: ^4.9.3
|
||||||
cherrypick: ^3.0.0-dev.9
|
cherrypick: ^3.0.0-dev.10
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ void main() {
|
|||||||
test('onInstanceRequested logs info', () {
|
test('onInstanceRequested logs info', () {
|
||||||
observer.onInstanceRequested('A', String, scopeName: 'test');
|
observer.onInstanceRequested('A', String, scopeName: 'test');
|
||||||
final log = talker.history.last;
|
final log = talker.history.last;
|
||||||
expect(log.message, contains('[request][CherryPick] A — String (scope: test)'));
|
expect(log.message,
|
||||||
|
contains('[request][CherryPick] A — String (scope: test)'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('onCycleDetected logs warning', () {
|
test('onCycleDetected logs warning', () {
|
||||||
|
|||||||
Reference in New Issue
Block a user