Skip to main content

@realitycollective/service-framework

The core runtime of the Reality Collective Service Framework for TypeScript - dependency injection, service lifecycle, events, schedulers and configuration, for browser and app runtimes.

npm install @realitycollective/service-framework

A TypeScript-first implementation of the same architecture as the Reality Collective Unity Service Framework: services are registered into a profile, resolved by token, and driven by a scheduler that the host environment owns.

What it provides

AreaDetail
Service managerRegistration, dependency-ordered start/stop, resolution by token, wait-for-service
Base servicesBaseService and BaseServiceModule - lifecycle hooks with typed configuration
TokenscreateServiceToken<T>() - type-safe resolution with no string keys at the call site
SchedulerNamed channels such as renderTick that services subscribe to. Your app decides what drives them: a timer, a render loop, or manual ticks
EventsAn in-framework event service for service-to-service messaging
ConfigurationProfile-based configuration with environment awareness
Runtime adapterRuntimeAdapter - the host seam a service depends on: a per-frame fan-out, XR capability flags, and an optional session facet (request, end, state and visibility). Host bindings implement it
Session featuresSessionRequestOptions.requiredFeatures and optionalFeatures - WebXR feature strings for one request, merged over the host's own defaults by mergeSessionInit(init, options). An app that swaps mode mid-session needs them, because the host's defaults were chosen for the mode it is leaving
Capability derivationderiveCapabilities(session) - reads immersive, handTracking, planeDetection, passthrough and environmentBlendMode off a live XR session. Shared by every host binding, so the same session reports the same flags under IWSDK and three.js. Its input, CapabilitySessionLike, is structural: no WebXR types, no DOM
State-owning servicesSnapshotService<TConfig, TSnapshot> - one immutable snapshot plus pub/sub; subscribers get the current value immediately, then every publish
Headless testingMockRuntimeAdapter - drives frames, capabilities and session lifecycle with no engine, no WebXR and no headset
Adapter conformanceruntimeAdapterContractCases() - the checks every RuntimeAdapter must pass, shipped as data rather than as a test file, so an adapter written outside this repository can prove it conforms

EnvironmentDescriptor here means the platform environment - the host's name and its capability strings, such as "dom" or "render-loop" - and is not the same thing as EnvironmentSpec in @realitycollective/webxr-environment, which describes the visual environment of sky, fog and lighting. An app can hold both at once, so the two names are worth keeping apart.

Usage

import {
ManualScheduler,
ServiceManager,
createServiceProfile,
} from "@realitycollective/service-framework";

const scheduler = new ManualScheduler();
const manager = new ServiceManager({ scheduler });

manager.initializeProfile(createServiceProfile("my-app", [/* your service registrations */]));
manager.start();

The host decides what drives the scheduler - a timer, a render loop, or an XR frame source.

Host bindings

The core is host-agnostic. Add exactly one binding for your runtime:

PackageHost
service-framework-reactReact provider and hooks
service-framework-threethree.js render loop, plus a WebXR runtime adapter for any page that owns its renderer
service-framework-babylonBabylon.js render loop
service-framework-iwsdkMeta IWSDK (WebXR) frame source
service-framework-clientReact + three.js, already wired together

Writing your own binding

An adapter for a host nobody has covered yet has to behave exactly as the bindings above do, or a service that passes its unit tests stops behaving the same way on a headset. runtimeAdapterContractCases() is the set of checks that says so, shipped as data rather than as a test file so it can run in your repository under your own runner. Each case returns silently on success and throws a plain Error describing the failure otherwise:

import { runtimeAdapterContractCases } from "@realitycollective/service-framework";

for (const contractCase of runtimeAdapterContractCases()) {
it(contractCase.name, () => contractCase.run(makeSubject()));
}

makeSubject() returns a RuntimeAdapterSubject: your adapter, plus a RuntimeAdapterDriver that pushes a frame, sets capability flags and - if your host owns sessions - starts and ends one. Build a fresh subject per case, because the session cases drive a session through its whole lifecycle. Some cases are asynchronous, so the runner has to await what run returns. An adapter with no session facet passes the session cases without running them, since session is optional.

Live examples

Documentation

Architecture, service authoring and consumption patterns are documented in the repository, including a Unity-to-web migration guide. A runnable example ships in this package's Examples/ folder.

License

MIT - see LICENSE.

Classes

ClassDescription
BaseEventService-
BaseService-
BaseServiceModule-
EnvironmentDescriptorThe platform environment a service is running in: a name plus the capability strings the host offers, such as "dom" or "render-loop". Configuration profiles gate on it, so a service can register differently on a browser page and in a headless test.
ManualScheduler-
MockRuntimeAdapter-
ServiceManager-
ServiceToken-
SnapshotService-
TimerScheduler-

Interfaces

InterfaceDescription
AdapterCapabilitiesXR capabilities services gate on (e.g. passthrough requires immersive).
CapabilityInputSourceLikeOne entry of XRSession.inputSources; hand is set for a tracked hand.
CapabilitySessionLikeThe slice of a live XR session capability derivation reads. Every member is optional, so a host that reports less than a full XRSession still fits.
DependencyGraphNode-
FocusChangeContext-
FrameInfo-
IEnvironmentDescriptor-
IEventService-
IScheduler-
IService-
IServiceModule-
LifecycleContext-
PauseChangeContext-
RuntimeAdapter-
RuntimeAdapterContractCaseOne check a RuntimeAdapter implementation must pass. run returns silently on success and throws an Error describing the failure otherwise, so any test runner can host it. Some cases are asynchronous, so a runner must await whatever run returns.
RuntimeAdapterDriverHow a case drives the adapter's host, whatever that host happens to be.
RuntimeAdapterSubjectOne adapter plus the means to drive its host, as a case receives it.
SchedulerEventMap-
ServiceActivationContext-
ServiceDiagnostics-
ServiceModuleRegistration-
ServiceProfile-
ServiceRegistration-
ServiceSnapshot-
SessionFacetOptional session lifecycle facet. Present on adapters whose host owns an XR session; absent on hosts that do not (a plain render loop, for instance). Check adapter.session before using it.
SessionInitLikeThe two members of an XRSessionInit this merge touches.
SessionRequestOptions-
TimerSchedulerOptions-

Type Aliases

Type AliasDescription
CapabilitiesListener-
EnvironmentBlendModeThe three blend modes WebXR defines for XRSession.environmentBlendMode.
EventHandler-
FrameListener-
RuntimeAdapterFacet-
SchedulerChannel-
SchedulerHandler-
ServiceClass-
ServiceContextThe activation-context type a service constructor receives. Aliased here so every service uses one consistent, framework-correct shape (matches the useFactory(context) parameter the ServiceManager passes).
ServiceFactory-
SessionFailureReasonWhy a session request did not produce a session.
SessionModeThe session kinds a host can be asked for; the WebXR session mode strings.
SessionResultThe outcome of SessionFacet.request. A request never rejects: a host that cannot start a session is a normal runtime condition, not a bug, so the caller gets a result to branch on rather than an exception to catch.
SessionStateWhere the host is in the session lifecycle.
SessionVisibilitySession visibility as the host reports it. non-immersive means the page is running in 2D with no session at all, which WebXR itself has no value for.
SnapshotListener-
TelemetryLevelSeverity of a record. Sinks route on this rather than parsing the name.
TelemetryLogA telemetry emitter. Supply one to ServiceManager to receive the framework's own records; every service then reaches the same emitter through logEvent.
UnsubscribeRuntime adapter contract - the seam between a host runtime and services.

Variables

VariableDescription
DEFAULT_CAPABILITIES-
DEFAULT_SESSION_TIMEOUT_MSHow long SessionFacet.request waits before reporting a timeout.
NO_OP_TELEMETRY_LOGThe emitter used when no telemetry is configured. One shared function for the whole application, so an unconfigured manager allocates nothing per call and a consumer can identity-compare against it to detect the default.
RUNTIME_ADAPTER_FACETSEvery optional facet a RuntimeAdapter can carry. A conformance test walks this list, so an adapter that grows a facet without implementing it in the mock fails the suite rather than being found by a consumer.

Functions

FunctionDescription
createBrowserEnvironment-
createEnvironmentDescriptor-
createServiceProfile-
createServiceToken-
deriveCapabilitiesDerive the capability flags from a live session.
mergeSessionInitFold a request's features into the host's XRSessionInit.
runtimeAdapterContractCasesThe whole suite, in a stable order.