Guide · @staticflux/core
Core & dependency injection
The fabric every package builds on: typed tokens, units that declare what they require and provide, and a hierarchical injector that knows the difference between “created” and “ready”.
#Tokens and units
A token is a typed identity: token('pkg/name').type<T>() for one value, .multi<T>() for a contribution list (routes, menu items, telemetry sinks). The string id is namespaced, type-checked and used for devtools — there are no string lookups at runtime.
A unit declares provides, scope, deps and a synchronous create. Optional init makes it ready asynchronously (single-flight, cached, retried by retry), refreshOn re-runs it when a token or the app epoch refreshes, and critical: true makes boot wait for it.
import { err, toSfError, token, unit } from '@staticflux/core';
export const DocRuntimeToken = token('app/doc-runtime').type<DocRuntimeHolder>();
export const docRuntimeUnit = unit({
id: 'app/doc-runtime-unit',
provides: DocRuntimeToken,
scope: 'app',
deps: { required: { client: DataClientToken, supervisor: SupervisorToken } },
// Capture deps in create (synchronous, cheap, no I/O) …
create: ({ client, supervisor }, ctx): DocRuntimeHolder => {
const holder: DocRuntimeHolder = {
current: undefined,
async load(signal) {
const chunk = await import('./doc-runtime.js'); // heavy code stays off the boot path
if (signal.aborted) return err(toSfError(signal.reason));
holder.current = chunk.createRuntime({ client, telemetry: ctx.telemetry, scope: ctx.scope });
return supervisor.register(holder.current.subsystem);
},
};
return holder;
},
// … use them in init, through the instance. init makes the unit ready (or fails it).
init: (holder, ctx) => holder.load(ctx.signal),
retry: { attempts: 3 },
});
#Three kinds of dependency
| Kind | You receive | Semantics | Default init |
|---|---|---|---|
required | T | Must be ready before this unit is created (components call it render: placeholder until ready) | eager |
lazy | DepHandle<T> | Arrives any time; reactive state / value; whenReady() | on-arrival |
functional | FunctionalDep<T> | Resolved and initialized on first get(); prewarm() on intent | on-demand |
Override timing per dependency: { token, init: 'idle' | 'on-visible' | 'on-intent' | … }. Hooks let a consumer act on a dependency inside a scope that is disposed when the consumer goes away or the dependency refreshes:
create: ({ presence }, ctx) => {
const count = box(0);
// re-runs against the fresh instance after every refresh, in a fresh child scope
ctx.onReady(presence, (feed, scope) => {
scope.defer(feed.subscribe((n) => count.set(n)));
});
return { count };
},
#Scopes, levels and the injector tree
Scope levels run from longest- to shortest-lived: app → session → workspace → route → feature → view → component. injector.child(level, name, units) creates a child injector; a longer-lived unit can never hold a shorter-lived one (SF-DI-002), and cycles are rejected with the path (SF-DI-004). Logging out disposes the session injector and everything in it.
Every instance owns a Scope (ctx.scope): defer cleanups, use disposables, listen to events, child scopes, and a signal that aborts when the scope is disposed.
#Overrides and variants
import { bind, variant } from '@staticflux/core';
const units = [
rankerUnit, // package default
bind(fastRankerUnit, { precedence: 'app', source: 'app config' }), // app override
variant({ of: RankerToken, unit: rankerBUnit, when: () => cohort === 'B' }), // experiment
];
// precedence: experiment > workspace > app > default — and every override shows in devtools
In apps, experiments come from flags through @staticflux/config’s createFlagBinder().variants(...), so feature code never reads a flag.
#Compile-time completeness
import { composeUnits, defineModule } from '@staticflux/core';
export const appUnits = composeUnits([docsUnit, dataClientUnit /* missing: transport */]);
// ✗ Property '__missingProviders' is missing … "data/transport"
// a package or feature module declares what its host must provide
export const docsModule = defineModule({ id: 'docs/module', units: [docsUnit], external: [DataClientToken] });
#Results, Tasks and machines
Result<T, E>withok/err/attempt/attemptAsync; errors areSfErrors fromdefineError(code, kind, summary, fix).Task<T>=(signal) => Promise<Result<T>>, composed withretry,timeout,createSingleFlight,createLimiter,createCircuitBreaker,settleAll,anySignal,sleep.- Every lifecycle is
defineMachine({ id, initial, states, transitions, terminal })run bycreateMachine— reactive state,can(event), invalid transitions returnSF-CORE-010instead of corrupting state. - Telemetry:
createTelemetry({ defaultLevel, levels })with levelsoff | critical | standard | verbose | debugper subsystem, spans, events and typedmetric.*declarations. - Reactivity:
box,derived,effect,watch,batch,untracked— MobX 7 behind a thin port.
AGENTS.md on GitHub: @staticflux/core · @staticflux/testkit