orbital_core
Core contracts, debug primitives and lifecycle vocabulary for the Orbital ecosystem.
What this package contains
orbital_core is the framework-agnostic layer shared by every other Orbital
package. It exports:
OrbitalBinding,OrbitalBindingType,OrbitalDependencyRefandOrbitalKeyOrbitalResolverandOrbitalScopeOrbitalLifecycleOrbitalLogger,OrbitalLogEventand logger presets/config- debug snapshots,
OrbitalDebugBridgeand service-extension contracts - deterministic runtime exceptions such as duplicate, missing, cycle and not-ready failures
This package contains no concrete dependency graph implementation and no Flutter routing runtime.
Binding model
Orbital supports six creation strategies:
singletonsingletonAsynclazySingletonlazySingletonAsyncfactoryfactoryAsync
Use async bindings only when that binding's own factory needs await.
Use dependsOn to describe readiness order, not constructor injection:
OrbitalBinding.singletonAsync<ApiClient>((resolver) async {
return ApiClient.connect();
}),
OrbitalBinding.singleton<HomeController>(
(resolver) => HomeController(
resolver.get<ApiClient>(),
),
dependsOn: [OrbitalDependencyRef.of<ApiClient>()],
),
The factory still reads values through OrbitalResolver.
Resolver, scope and lifecycle contracts
OrbitalResolver exposes:
get<T>()getAsync<T>()getOrNull<T>()isRegistered<T>()isReady<T>()
OrbitalScope extends that contract with:
createChild(...)register(...)initialize()dispose()
OrbitalLifecycle is optional and gives framework-managed objects three hooks:
onInit()onReady()onDispose()
It also exposes addDisposer(action), which registers cleanup that runs
automatically on disposal (in LIFO order) without overriding onDispose().
Disposer failures are reported rather than thrown, so one failing cleanup does
not block the rest; a runRegisteredDisposers(onDisposerError:) reporter that
itself throws is isolated so it cannot abort the remaining drain either.
After disposal has completed, isDisposed is true and addDisposer becomes
a no-op: a cleanup registered after the run could never run, so it is dropped
rather than leaked. The concrete runtime typically handles this for you — in
orbital_injector, a listen on an already-disposed owner returns a cancelled
handle instead of attaching a live subscription.
The execution order and recovery semantics are implemented by the concrete
runtime, typically orbital_injector.
Logging and debug contracts
OrbitalLogger provides stable event names plus readable formatted output.
orbital_core also owns the runtime debug model consumed by
orbital_devtools:
- scope snapshots
- router snapshots
- health summaries
- service-extension names
OrbitalDebugBridge.enable(...)
OrbitalDebugBridge is a singleton. isEnabled reports whether one is active,
and instance returns it so callers can reuse a bridge another component
(e.g. OrbitalRouter's auto-enable) already created — the first enabler owns
the singleton, and a later enable(...) with a different logger/appId throws
rather than silently replacing it.
This split keeps runtime packages independent from the DevTools UI while still sharing one debug protocol.
Sensitive data in logs
The default logger preset is normal, which omits dependency read/register
tracing. Verbose logging can include route URIs, query parameters, error
messages and stack traces. Treat verbose output and debug snapshots as
development diagnostics: do not forward them to remote logging services without
reviewing or redacting application-specific secrets such as tokens, emails or
customer identifiers.
For production telemetry, provide a custom OrbitalLoggerAdapter and redact
event.details before export. Keep OrbitalLoggerConfig.minimal() or a
category-limited custom config for environments where URLs or query strings may
contain sensitive data.
More detail
See doc.md for binding semantics, readiness rules, lifecycle contracts, logger behavior and debug integration.