get<T extends Object> method

Option<Resolvable<T>> get<T extends Object>({
  1. Entity groupEntity = const DefaultEntity(),
  2. bool traverse = true,
})

Retrieves a dependency from the container.

Implementation

Option<Resolvable<T>> get<T extends Object>({
  Entity groupEntity = const DefaultEntity(),
  bool traverse = true,
}) {
  assert(T != Object, 'T must be specified and cannot be Object.');
  final g = groupEntity.preferOverDefault(focusGroup);
  final option = getDependency<T>(groupEntity: g, traverse: traverse);
  return switch (option) {
    None() => const None(),
    Some(value: Err(:final error, :final stackTrace)) => Some(
        Sync<T>.err(Err<T>(error, stackTrace: stackTrace)),
      ),
    Some(value: Ok(value: final dep)) => switch (dep.value) {
        Sync<T>() => Some(dep.value),
        Async<T>(value: final fut) => () {
            // Capture the actually-stored slot (by identity) at scheduling
            // time. `dep` is a `.transf<T>()` view returned by
            // `registry.getDependency<T>()` — a fresh wrapper, never the
            // stored instance — so we cannot identity-compare against `dep`
            // directly. The registry slot key is `dep.typeEntity`.
            final slotKey = dep.typeEntity;
            final originalSlot = registry.getSlot(
              slotKey,
              groupEntity: g,
            );
            return Some(
              Async<T>(
                () => fut.then((e) {
                  // Throw on Err — the surrounding Async() absorbs it.
                  final value = switch (e) {
                    Ok(value: final v) => v,
                    Err(:final error, :final stackTrace) =>
                      throw Err<T>(error, stackTrace: stackTrace),
                  };
                  // Memoise the resolved value as Sync, but ONLY when the
                  // registry slot still belongs to the original Dependency
                  // by reference. A concurrent `unregister<T>()` (or
                  // `unregister` + new `register<T>(...)`) between the time
                  // we scheduled the then-callback and the time the async
                  // resolves would otherwise be silently undone — we'd
                  // remove the user's new registration and re-write a stale
                  // Sync slot. Identity comparison detects both scenarios:
                  //  * slot is null  → user unregistered. Skip the swap.
                  //  * slot is other → user re-registered. Skip; their new
                  //    state wins.
                  //  * slot is identical to originalSlot → safe to memoise.
                  final currentSlot = registry.getSlot(
                    slotKey,
                    groupEntity: g,
                  );
                  if (originalSlot != null &&
                      identical(currentSlot, originalSlot)) {
                    registry.removeDependency<T>(groupEntity: g).end();
                    switch (registerDependency<T>(
                      dependency: Dependency<T>(
                        Sync<T>.okValue(value),
                        metadata: dep.metadata,
                      ),
                      checkExisting: false,
                    )) {
                      case Ok():
                        break;
                      case Err(:final error):
                        throw error;
                    }
                  }
                  return value;
                }),
              ),
            );
          }(),
      },
  };
}