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.