register<T extends Object> method

Resolvable<T> register<T extends Object>(
  1. FutureOr<T> value, {
  2. Option<TOnRegisterCallback<T>> onRegister = const None(),
  3. Option<TOnUnregisterCallback<T>> onUnregister = const None(),
  4. Entity groupEntity = const DefaultEntity(),
  5. bool enableUntilExactlyK = false,
})
inherited

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);

  // Check for existing slot BEFORE building the Resolvable — wrapping it
  // runs the onRegister side effects (init()), which must not fire for a
  // registration we'll reject.
  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 onRegister so a sync throw lands as Err on the returned
  // Resolvable instead of escaping out of `register()`.
  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>) {
    _maybeFinish<Object>(value: value, g: g);
    if (enableUntilExactlyK) {
      (this as SupportsMixinK).maybeFinishK<T>(g: g);
    }
  }
  // We just wrote the slot — `get<T>` should find it unless a concurrent
  // unregister raced. Return Err in that case rather than throw.
  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).'),
      ),
  };
}