register<T extends Object> method
Resolvable<T>
register<T extends Object>(
- FutureOr<
T> value, { - Option<
TOnRegisterCallback< onRegister = const None(),T> > - Option<
TOnUnregisterCallback< onUnregister = const None(),T> > - Entity groupEntity = const DefaultEntity(),
- bool enableUntilExactlyK = false,
Registers a dependency with the container.
Implementation
Resolvable<T> register<T extends Object>(
FutureOr<T> value, {
Option<TOnRegisterCallback<T>> onRegister = const None(),
Option<TOnUnregisterCallback<T>> onUnregister = const None(),
Entity groupEntity = const DefaultEntity(),
bool enableUntilExactlyK = false,
}) {
assert(T != Object, 'T must be specified and cannot be Object.');
assert(
value is! Future || T != FutureOr,
'register<$T>: registering a Future where T is FutureOr is ambiguous. '
'Use Resolvable<T> or unwrap the Future before registering.',
);
final g = groupEntity.preferOverDefault(focusGroup);
// Existence check FIRST. If a slot for [T] already exists in this
// container's [g] group, reject the registration BEFORE constructing
// the wrapping Resolvable — building it runs the `onRegister` side
// effects (init() etc.), and we must NOT run those for a registration
// we'll reject (audit-pass-3 finding: previously a duplicate-register
// call fired init() on a service that would never be reachable,
// leaking the resource).
switch (getDependency<T>(groupEntity: g, traverse: false)) {
case Some():
return Sync<T>.err(Err('Dependency already registered.'));
case None():
break;
}
final metadata = DependencyMetadata(
index: Some(_indexIncrementer++),
groupEntity: g,
onUnregister: onUnregister.map((cb) => (e) => cb(e.transf())),
);
// Wrap the onRegister invocation so that a synchronous throw is
// captured into the Resolvable instead of escaping out of `register()`.
// Without this, a sync-throwing onRegister blows the call stack
// mid-`register` and the caller has no Resolvable-shaped handle to the
// failure — unacceptable for medical-grade code where every callback
// site must be uniformly catchable.
final a = Resolvable(
() => consec(value, (e) {
return consec(_safeOnRegister<T>(onRegister, e), (_) => e);
}),
);
final b = registerDependency<T>(
dependency: Dependency(a, metadata: Some(metadata)),
// Slot was already free-checked above; skip the redundant probe.
checkExisting: false,
);
switch (b) {
case Err<Dependency<T>> err:
return Sync.err(err.transfErr<T>());
case Ok():
break;
}
if (value is! ReservedSafeCompleter<T>) {
// Used for until*. Walks ANY pending ReservedSafeCompleter (regardless
// of its type parameter) and lets each completer's own captured
// type-check decide whether `value` matches — see `_maybeFinish` for
// why filtering by `<T>` here is unsafe under dart2js release.
_maybeFinish<Object>(value: value, g: g);
// Used for untilT and untilK. Disabled by default to improve performance.
if (enableUntilExactlyK) {
(this as SupportsMixinK).maybeFinishK<T>(g: g);
}
}
// We just succeeded at `registerDependency` above, so `get<T>` MUST find
// the slot we wrote. Pattern-match instead of `.unwrap()` so the
// theoretical "should never happen" branch is explicit: if a concurrent
// unregister somehow raced and wiped the slot, return an Err Resolvable
// instead of throwing — callers awaiting the returned Resolvable then
// see a normal Err on their chain.
return switch (get<T>(groupEntity: groupEntity)) {
Some(value: final r) => r,
None() => Sync<T>.err(
Err('register<$T>: post-register lookup returned None '
'(slot was concurrently removed).'),
),
};
}