@realitycollective/service-framework-iwsdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ /**
2
+ * service-framework-iwsdk — minimal usage example
3
+ *
4
+ * Mirrors the three.js / Babylon.js examples, but for the Meta IWSDK frame
5
+ * source. IWSDK owns the render loop, so instead of a bridge that owns
6
+ * `setAnimationLoop` / `runRenderLoop`, the IWSDK shim is a *passive* frame
7
+ * source pumped from inside an IWSDK ECS system, and visibility is mapped to
8
+ * the manager's focus/pause signals (auto-pause when the headset comes off).
9
+ *
10
+ * The mocks below stand in for `@iwsdk/core` so this snippet runs in any
11
+ * Node.js environment. In a real IWSDK app you pass the real primitives:
12
+ *
13
+ * import { World, createSystem, VisibilityState } from "@iwsdk/core";
14
+ * const { manager, adapter } = startServiceRuntime(world, createEnergyProfile);
15
+ * world.registerSystem(
16
+ * makeServiceBridgeSystem({
17
+ * adapter, manager, world, createSystem,
18
+ * visibleState: VisibilityState.Visible,
19
+ * }),
20
+ * );
21
+ */
22
+
23
+ import {
24
+ createServiceProfile,
25
+ createServiceToken,
26
+ type ServiceProfile,
27
+ } from "@realitycollective/service-framework";
28
+ import {
29
+ IWSDKAdapter,
30
+ SnapshotService,
31
+ makeServiceBridgeSystem,
32
+ startServiceRuntime,
33
+ type CreateSystemLike,
34
+ type IWSDKWorldLike,
35
+ type RuntimeAdapter,
36
+ type ServiceContext,
37
+ type Unsubscribe,
38
+ } from "@realitycollective/service-framework-iwsdk";
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // A leaf service that owns its state and decays energy once per frame.
42
+ // It depends only on RuntimeAdapter — never on @iwsdk/core — so it is portable
43
+ // and unit-testable headless against MockRuntimeAdapter.
44
+ // ---------------------------------------------------------------------------
45
+
46
+ interface EnergySnapshot {
47
+ readonly energy: number;
48
+ }
49
+
50
+ const ENERGY_SERVICE_TOKEN = createServiceToken<EnergyService>("EnergyService");
51
+
52
+ class EnergyService extends SnapshotService<unknown, EnergySnapshot> {
53
+ private unsubscribe?: Unsubscribe;
54
+
55
+ public constructor(
56
+ context: ServiceContext,
57
+ private readonly adapter: RuntimeAdapter,
58
+ ) {
59
+ super(context, { energy: 1 });
60
+ }
61
+
62
+ public override initialize(): void {
63
+ this.unsubscribe = this.adapter.onFrame(({ delta }) => {
64
+ const energy = Math.max(0, this.getSnapshot().energy - delta * 0.1);
65
+ this.updateSnapshot({ energy });
66
+ });
67
+ }
68
+
69
+ public override destroy(): void {
70
+ this.unsubscribe?.();
71
+ }
72
+ }
73
+
74
+ function createEnergyProfile(adapter: IWSDKAdapter): ServiceProfile {
75
+ return createServiceProfile("iwsdk-example", [
76
+ {
77
+ token: ENERGY_SERVICE_TOKEN,
78
+ useFactory: (context) => new EnergyService(context, adapter),
79
+ },
80
+ ]);
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Mock @iwsdk/core primitives — swap for the real ones in an IWSDK app.
85
+ // ---------------------------------------------------------------------------
86
+
87
+ const VISIBLE: string = "visible";
88
+ const HIDDEN: string = "hidden";
89
+
90
+ const world: IWSDKWorldLike<string> = { visibilityState: { value: VISIBLE } };
91
+
92
+ const createSystem: CreateSystemLike = () =>
93
+ class {
94
+ public update(_delta: number, _time: number): void {}
95
+ };
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Bootstrap: stand up the manager + adapter, then wire the bridge system.
99
+ // ---------------------------------------------------------------------------
100
+
101
+ const { manager, adapter } = startServiceRuntime(world, createEnergyProfile);
102
+
103
+ const ServiceBridgeSystem = makeServiceBridgeSystem({
104
+ adapter,
105
+ manager,
106
+ world,
107
+ createSystem,
108
+ visibleState: VISIBLE,
109
+ });
110
+
111
+ // In a real app: world.registerSystem(ServiceBridgeSystem). Here we construct
112
+ // it directly and pump a few frames to simulate IWSDK's loop.
113
+ const bridge = new ServiceBridgeSystem();
114
+
115
+ const energyService = manager.resolve(ENERGY_SERVICE_TOKEN);
116
+ energyService.subscribe((snapshot) => console.log(`energy: ${snapshot.energy.toFixed(3)}`));
117
+
118
+ for (let frame = 1; frame <= 3; frame++) {
119
+ bridge.update(1 / 60, (frame * 1000) / 60);
120
+ }
121
+
122
+ // Headset removed → the bridge stops pumping frames and the manager auto-pauses,
123
+ // so energy stops decaying.
124
+ (world as IWSDKWorldLike<string> & { visibilityState: { value: string } }).visibilityState.value = HIDDEN;
125
+ bridge.update(1 / 60, (4 * 1000) / 60);
126
+
127
+ console.log("Energy decays only while the session is visible.");
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Reality Collective
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # `@realitycollective/service-framework-iwsdk`
2
+
3
+ Meta **IWSDK** (WebXR) frame-source bindings for the [Reality Collective TypeScript Service Framework](https://github.com/realitycollective/com.realitycollective.service-framework.ts).
4
+
5
+ Lets `@realitycollective/service-framework` services run inside the [IWSDK](https://github.com/meta-quest/immersive-web-sdk) engine loop without each project re-implementing the bridge layer. Services written against `BaseService<TConfig>` run unchanged here, on three.js, or on Babylon.js.
6
+
7
+ ---
8
+
9
+ ## How it differs from the render-loop bridges
10
+
11
+ `service-framework-three` and `service-framework-babylon` **own** the loop — the bridge calls `renderer.setAnimationLoop(...)` / `engine.runRenderLoop(...)`. **IWSDK already owns the loop**, the XR session, input and the ECS, so this shim must *not* own one.
12
+
13
+ Instead it is a **passive frame source**: a single IWSDK ECS system (`ServiceBridgeSystem`) is pumped one frame at a time by the engine, fans those frames out to subscribed services via `IWSDKAdapter`, and maps IWSDK's `visibilityState` to the manager's focus/pause signals (auto-pause when the headset comes off).
14
+
15
+ ```
16
+ IWSDK World (render loop, XR session, input, ECS)
17
+ │ world.registerSystem(makeServiceBridgeSystem({ ... }))
18
+
19
+ ServiceBridgeSystem ← the only place the engine touches services
20
+ every update(delta, time):
21
+ • visibilityState → manager.emitFocusChange / emitPauseChange
22
+ • if focused: adapter.emitFrame(time, delta)
23
+
24
+ IWSDKAdapter (RuntimeAdapter) → onFrame fan-out → services
25
+
26
+ ServiceManager → your SnapshotService graph
27
+ ```
28
+
29
+ **Invariant:** services depend only on `RuntimeAdapter`, never on `@iwsdk/core`. That is what keeps them unit-testable headless — swap `IWSDKAdapter` for `MockRuntimeAdapter`.
30
+
31
+ ---
32
+
33
+ ## No hard dependency on `@iwsdk/core`
34
+
35
+ Like the three.js and Babylon.js bridges keep their renderer packages at arm's length, this package **never imports `@iwsdk/core`**. The IWSDK primitives the bridge needs — `createSystem` and the `VisibilityState.Visible` value — are passed in by the consumer (who owns IWSDK). This keeps the package tree-shakeable, version-tolerant across IWSDK `0.4.x`, and trivially mockable in unit tests.
36
+
37
+ `@iwsdk/core` is declared as an **optional peer dependency**.
38
+
39
+ ---
40
+
41
+ ## Quick start
42
+
43
+ ```typescript
44
+ import { createServiceProfile } from "@realitycollective/service-framework";
45
+ import {
46
+ startServiceRuntime,
47
+ makeServiceBridgeSystem,
48
+ } from "@realitycollective/service-framework-iwsdk";
49
+ import { World, createSystem, VisibilityState } from "@iwsdk/core";
50
+
51
+ // 1. Build the service graph from a profile factory: (adapter) => ServiceProfile
52
+ const createProfile = (adapter) =>
53
+ createServiceProfile("my-app", [/* your registrations, wired to `adapter` */]);
54
+
55
+ // 2. Stand up the manager + adapter and start it.
56
+ const { manager, adapter } = startServiceRuntime(world, createProfile);
57
+
58
+ // 3. Register the one ECS system that pumps frames and maps visibility.
59
+ world.registerSystem(
60
+ makeServiceBridgeSystem({
61
+ adapter,
62
+ manager,
63
+ world,
64
+ createSystem,
65
+ visibleState: VisibilityState.Visible,
66
+ }),
67
+ );
68
+ ```
69
+
70
+ Game logic ticks **only while the session is visible/focused** — in the browser / 2D preview the host app shows its own "Enter VR" gate and services stay idle until the player enters VR.
71
+
72
+ ---
73
+
74
+ ## Services own their state: `SnapshotService`
75
+
76
+ `SnapshotService<TConfig, TSnapshot>` is the "services own state" base — one immutable snapshot plus pub/sub. Subscribers receive the current value immediately, then every publish. It has **no IWSDK dependency**.
77
+
78
+ ```typescript
79
+ import { SnapshotService, type ServiceContext, type RuntimeAdapter } from "@realitycollective/service-framework-iwsdk";
80
+
81
+ interface EnergySnapshot { readonly energy: number; }
82
+
83
+ class EnergyService extends SnapshotService<unknown, EnergySnapshot> {
84
+ constructor(context: ServiceContext, private readonly adapter: RuntimeAdapter) {
85
+ super(context, { energy: 1 });
86
+ }
87
+
88
+ override initialize(): void {
89
+ this.adapter.onFrame(({ delta }) => {
90
+ this.updateSnapshot({ energy: Math.max(0, this.getSnapshot().energy - delta * 0.1) });
91
+ });
92
+ }
93
+ }
94
+ ```
95
+
96
+ The presentation layer (or another service) subscribes:
97
+
98
+ ```typescript
99
+ const energy = manager.resolve(ENERGY_SERVICE_TOKEN);
100
+ const unsubscribe = energy.subscribe(({ energy }) => hud.setEnergy(energy));
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Headless testing with `MockRuntimeAdapter`
106
+
107
+ Services run against `MockRuntimeAdapter` with no IWSDK / renderer / headset. Tests drive the loop by calling `emitFrame`:
108
+
109
+ ```typescript
110
+ import { MockRuntimeAdapter } from "@realitycollective/service-framework-iwsdk";
111
+
112
+ const adapter = new MockRuntimeAdapter({ immersive: true });
113
+ const service = new EnergyService(makeContext(), adapter);
114
+ service.initialize();
115
+
116
+ adapter.emitFrame(0, 1 / 72); // one frame
117
+ expect(service.getSnapshot().energy).toBeLessThan(1);
118
+ ```
119
+
120
+ No `@iwsdk/core` import appears anywhere in the test.
121
+
122
+ ---
123
+
124
+ ## Capabilities
125
+
126
+ `AdapterCapabilities` (`immersive`, `handTracking`, `planeDetection`, `passthrough`) is what gating services read (`adapter.getCapabilities()`) or subscribe to (`adapter.onCapabilitiesChange(cb)` — mirrors `onFrame`, so gates don't poll every frame). Refine them with `adapter.setCapabilities({ ... })`.
127
+
128
+ > **One gap still to close:** deriving capabilities from the live `XRSession` (enabled features, blend mode, hand input sources) is intentionally left to the host wiring because it needs IWSDK session internals. Until that is wired, the adapter reports `DEFAULT_CAPABILITIES` (all-false) and gating services behave conservatively. `IWSDKAdapter.getWorld()` exposes the bound world as the source for that derivation, and `onCapabilitiesChange` is the notification channel for when it lands.
129
+
130
+ ---
131
+
132
+ ## API surface
133
+
134
+ | Symbol | Kind | Purpose |
135
+ | --- | --- | --- |
136
+ | `RuntimeAdapter` | interface | The seam services depend on (`onFrame`, `getCapabilities`, `onCapabilitiesChange`). |
137
+ | `FrameInfo` | interface | `{ timestamp, delta }`. |
138
+ | `AdapterCapabilities` / `DEFAULT_CAPABILITIES` | interface / const | XR capability flags; all-false default. |
139
+ | `Unsubscribe` / `FrameListener` / `CapabilitiesListener` | types | Callback / handle aliases. |
140
+ | `IWSDKAdapter` | class | Production adapter; `emitFrame`, `setCapabilities`, `getWorld`. |
141
+ | `MockRuntimeAdapter` | class | Headless adapter; `emitFrame(ts?, delta?)`, `setCapabilities`. |
142
+ | `SnapshotService<C, S>` | abstract class | State-owning base (`subscribe` / `getSnapshot` / `publishSnapshot` / `updateSnapshot`). |
143
+ | `ServiceContext<C>` / `SnapshotListener<S>` | types | Activation-context alias; snapshot callback. |
144
+ | `makeServiceBridgeSystem` | factory | Returns the IWSDK `ServiceBridgeSystem` class. |
145
+ | `ServiceBridgeSystemOptions` | interface | `{ adapter, manager, world, createSystem, visibleState }`. |
146
+ | `startServiceRuntime` / `ServiceRuntime` | function / interface | Bootstraps `{ manager, adapter }` from a profile factory. |
147
+ | `IWSDKWorldLike` / `CreateSystemLike` / … | types | Structural `@iwsdk/core` contracts (no engine import). |
148
+
149
+ ---
150
+
151
+ ## Running tests
152
+
153
+ From the workspace root:
154
+
155
+ ```bash
156
+ npm test
157
+ ```
158
+
159
+ ## Running the example
160
+
161
+ ```bash
162
+ npx tsx packages/service-framework-iwsdk/Examples/main.ts
163
+ ```
164
+
165
+ The example mocks the `@iwsdk/core` primitives so it runs in plain Node.js: it decays an `EnergyService` while the session is "visible", then shows it idling once visibility is lost.
166
+
167
+ ---
168
+
169
+ ## License
170
+
171
+ MIT
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Non-React bootstrap for an IWSDK client: build the adapter, stand up the
3
+ * ServiceManager from the app's profile, and start it. The caller registers
4
+ * {@link makeServiceBridgeSystem} with the IWSDK world to pump per-frame ticks.
5
+ */
6
+ import { ServiceManager } from "@realitycollective/service-framework";
7
+ import type { ServiceProfile } from "@realitycollective/service-framework";
8
+ import { IWSDKAdapter } from "./iwsdk-adapter.js";
9
+ import type { IWSDKWorldLike } from "./iwsdk-host.js";
10
+ export interface ServiceRuntime {
11
+ readonly manager: ServiceManager;
12
+ readonly adapter: IWSDKAdapter;
13
+ }
14
+ /**
15
+ * @param world the IWSDK world (owns the loop / XR session).
16
+ * @param profileFactory builds the app service graph from the adapter, e.g.
17
+ * `(adapter) => createServiceProfile("my-app", [...])`.
18
+ */
19
+ export declare function startServiceRuntime(world: IWSDKWorldLike, profileFactory: (adapter: IWSDKAdapter) => ServiceProfile): ServiceRuntime;
20
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,EACrB,cAAc,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,cAAc,GACxD,cAAc,CAMhB"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Non-React bootstrap for an IWSDK client: build the adapter, stand up the
3
+ * ServiceManager from the app's profile, and start it. The caller registers
4
+ * {@link makeServiceBridgeSystem} with the IWSDK world to pump per-frame ticks.
5
+ */
6
+ import { ServiceManager } from "@realitycollective/service-framework";
7
+ import { IWSDKAdapter } from "./iwsdk-adapter.js";
8
+ /**
9
+ * @param world the IWSDK world (owns the loop / XR session).
10
+ * @param profileFactory builds the app service graph from the adapter, e.g.
11
+ * `(adapter) => createServiceProfile("my-app", [...])`.
12
+ */
13
+ export function startServiceRuntime(world, profileFactory) {
14
+ const adapter = new IWSDKAdapter(world);
15
+ const manager = new ServiceManager();
16
+ manager.initializeProfile(profileFactory(adapter));
17
+ manager.start();
18
+ return { manager, adapter };
19
+ }
20
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQlD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAqB,EACrB,cAAyD;IAEzD,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,cAAc,EAAE,CAAC;IACrC,OAAO,CAAC,iBAAiB,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,KAAK,EAAE,CAAC;IAChB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,12 @@
1
+ export { DEFAULT_CAPABILITIES } from "./runtime-adapter.js";
2
+ export type { AdapterCapabilities, CapabilitiesListener, FrameInfo, FrameListener, RuntimeAdapter, Unsubscribe, } from "./runtime-adapter.js";
3
+ export type { CreateSystemLike, IWSDKSignalLike, IWSDKSystemConstructor, IWSDKSystemLike, IWSDKWorldLike, } from "./iwsdk-host.js";
4
+ export { IWSDKAdapter } from "./iwsdk-adapter.js";
5
+ export { MockRuntimeAdapter } from "./mock-runtime-adapter.js";
6
+ export { SnapshotService } from "./snapshot-service.js";
7
+ export type { ServiceContext, SnapshotListener } from "./snapshot-service.js";
8
+ export { makeServiceBridgeSystem } from "./service-bridge-system.js";
9
+ export type { ServiceBridgeSystemOptions } from "./service-bridge-system.js";
10
+ export { startServiceRuntime } from "./bootstrap.js";
11
+ export type { ServiceRuntime } from "./bootstrap.js";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,YAAY,EACV,mBAAmB,EACnB,oBAAoB,EACpB,SAAS,EACT,aAAa,EACb,cAAc,EACd,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAE9B,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,YAAY,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAE7E,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { DEFAULT_CAPABILITIES } from "./runtime-adapter.js";
2
+ export { IWSDKAdapter } from "./iwsdk-adapter.js";
3
+ export { MockRuntimeAdapter } from "./mock-runtime-adapter.js";
4
+ export { SnapshotService } from "./snapshot-service.js";
5
+ export { makeServiceBridgeSystem } from "./service-bridge-system.js";
6
+ export { startServiceRuntime } from "./bootstrap.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAkB5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so
3
+ * the adapter is a passive fan-out: the `ServiceBridgeSystem` (a normal IWSDK
4
+ * system) calls {@link IWSDKAdapter.emitFrame} once per frame and the adapter
5
+ * notifies subscribed services. Capabilities are refined from the XR session via
6
+ * {@link IWSDKAdapter.setCapabilities}.
7
+ */
8
+ import { type AdapterCapabilities, type CapabilitiesListener, type FrameListener, type RuntimeAdapter, type Unsubscribe } from "./runtime-adapter.js";
9
+ import type { IWSDKWorldLike } from "./iwsdk-host.js";
10
+ export declare class IWSDKAdapter implements RuntimeAdapter {
11
+ private readonly world;
12
+ private readonly frameListeners;
13
+ private readonly capabilitiesListeners;
14
+ private capabilities;
15
+ constructor(world: IWSDKWorldLike);
16
+ onFrame(listener: FrameListener): Unsubscribe;
17
+ getCapabilities(): AdapterCapabilities;
18
+ onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe;
19
+ /**
20
+ * The IWSDK `World` this adapter is bound to. Reserved for capability
21
+ * derivation from the live XR session (see the package README "Capabilities"
22
+ * section) — the one piece still to be wired to the real IWSDK session API.
23
+ */
24
+ getWorld(): IWSDKWorldLike;
25
+ /** Refine capabilities once the XR session reports them; notifies subscribers. */
26
+ setCapabilities(capabilities: Partial<AdapterCapabilities>): void;
27
+ /** Called once per frame by the ECS bridge system. */
28
+ emitFrame(timestamp: number, delta: number): void;
29
+ }
30
+ //# sourceMappingURL=iwsdk-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iwsdk-adapter.d.ts","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EAEzB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,qBAAa,YAAa,YAAW,cAAc;IAK9B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJzC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA4B;IAC3D,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAmC;IACzE,OAAO,CAAC,YAAY,CAA6C;gBAE7B,KAAK,EAAE,cAAc;IAElD,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW;IAO7C,eAAe,IAAI,mBAAmB;IAItC,oBAAoB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,WAAW;IAOxE;;;;OAIG;IACI,QAAQ,IAAI,cAAc;IAIjC,kFAAkF;IAC3E,eAAe,CAAC,YAAY,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAKxE,sDAAsD;IAC/C,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;CAIzD"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * IWSDK implementation of {@link RuntimeAdapter}. IWSDK owns the render loop, so
3
+ * the adapter is a passive fan-out: the `ServiceBridgeSystem` (a normal IWSDK
4
+ * system) calls {@link IWSDKAdapter.emitFrame} once per frame and the adapter
5
+ * notifies subscribed services. Capabilities are refined from the XR session via
6
+ * {@link IWSDKAdapter.setCapabilities}.
7
+ */
8
+ import { DEFAULT_CAPABILITIES, } from "./runtime-adapter.js";
9
+ export class IWSDKAdapter {
10
+ world;
11
+ frameListeners = new Set();
12
+ capabilitiesListeners = new Set();
13
+ capabilities = DEFAULT_CAPABILITIES;
14
+ constructor(world) {
15
+ this.world = world;
16
+ }
17
+ onFrame(listener) {
18
+ this.frameListeners.add(listener);
19
+ return () => {
20
+ this.frameListeners.delete(listener);
21
+ };
22
+ }
23
+ getCapabilities() {
24
+ return this.capabilities;
25
+ }
26
+ onCapabilitiesChange(listener) {
27
+ this.capabilitiesListeners.add(listener);
28
+ return () => {
29
+ this.capabilitiesListeners.delete(listener);
30
+ };
31
+ }
32
+ /**
33
+ * The IWSDK `World` this adapter is bound to. Reserved for capability
34
+ * derivation from the live XR session (see the package README "Capabilities"
35
+ * section) — the one piece still to be wired to the real IWSDK session API.
36
+ */
37
+ getWorld() {
38
+ return this.world;
39
+ }
40
+ /** Refine capabilities once the XR session reports them; notifies subscribers. */
41
+ setCapabilities(capabilities) {
42
+ this.capabilities = { ...this.capabilities, ...capabilities };
43
+ this.capabilitiesListeners.forEach((listener) => listener(this.capabilities));
44
+ }
45
+ /** Called once per frame by the ECS bridge system. */
46
+ emitFrame(timestamp, delta) {
47
+ const frame = { timestamp, delta };
48
+ this.frameListeners.forEach((listener) => listener(frame));
49
+ }
50
+ }
51
+ //# sourceMappingURL=iwsdk-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iwsdk-adapter.js","sourceRoot":"","sources":["../src/iwsdk-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,oBAAoB,GAOrB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,OAAO,YAAY;IAKa;IAJnB,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC1C,qBAAqB,GAAG,IAAI,GAAG,EAAwB,CAAC;IACjE,YAAY,GAAwB,oBAAoB,CAAC;IAEjE,YAAoC,KAAqB;QAArB,UAAK,GAAL,KAAK,CAAgB;IAAG,CAAC;IAEtD,OAAO,CAAC,QAAuB;QACpC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC;IACJ,CAAC;IAEM,eAAe;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAEM,oBAAoB,CAAC,QAA8B;QACxD,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9C,CAAC,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACI,QAAQ;QACb,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,kFAAkF;IAC3E,eAAe,CAAC,YAA0C;QAC/D,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,YAAY,EAAE,CAAC;QAC9D,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,sDAAsD;IAC/C,SAAS,CAAC,SAAiB,EAAE,KAAa;QAC/C,MAAM,KAAK,GAAc,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QAC9C,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7D,CAAC;CACF"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Structural contracts for the slice of `@iwsdk/core` this shim touches.
3
+ *
4
+ * Mirrors the approach the three.js and Babylon.js bridges take with their
5
+ * engine packages: the shim never imports `@iwsdk/core` directly, so it builds
6
+ * and unit-tests with no IWSDK / WebXR / headset present, and any 0.4.x IWSDK
7
+ * works without bumping this package. Consumers pass the real `World`,
8
+ * `createSystem` and `VisibilityState.Visible` value through
9
+ * {@link makeServiceBridgeSystem}.
10
+ */
11
+ /** IWSDK exposes reactive values as `{ value }` signals; the bridge only reads `.value`. */
12
+ export interface IWSDKSignalLike<TValue> {
13
+ readonly value: TValue;
14
+ }
15
+ /** The slice of an IWSDK `World` the bridge reads: the visibility signal. */
16
+ export interface IWSDKWorldLike<TVisibility = unknown> {
17
+ readonly visibilityState: IWSDKSignalLike<TVisibility>;
18
+ }
19
+ /** The per-frame entry point IWSDK invokes on a registered system. */
20
+ export interface IWSDKSystemLike {
21
+ update(delta: number, time: number): void;
22
+ }
23
+ /** Constructor shape of the base class IWSDK's `createSystem` returns. */
24
+ export type IWSDKSystemConstructor = new (...args: unknown[]) => IWSDKSystemLike;
25
+ /**
26
+ * Structural shape of IWSDK's `createSystem` factory. `createSystem(schema)`
27
+ * returns a base system class that the bridge extends; the bridge passes an
28
+ * empty schema because it queries no ECS components.
29
+ */
30
+ export type CreateSystemLike = (schema?: Record<string, unknown>) => IWSDKSystemConstructor;
31
+ //# sourceMappingURL=iwsdk-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iwsdk-host.d.ts","sourceRoot":"","sources":["../src/iwsdk-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,4FAA4F;AAC5F,MAAM,WAAW,eAAe,CAAC,MAAM;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc,CAAC,WAAW,GAAG,OAAO;IACnD,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;CACxD;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,0EAA0E;AAC1E,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,eAAe,CAAC;AAEjF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,sBAAsB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Structural contracts for the slice of `@iwsdk/core` this shim touches.
3
+ *
4
+ * Mirrors the approach the three.js and Babylon.js bridges take with their
5
+ * engine packages: the shim never imports `@iwsdk/core` directly, so it builds
6
+ * and unit-tests with no IWSDK / WebXR / headset present, and any 0.4.x IWSDK
7
+ * works without bumping this package. Consumers pass the real `World`,
8
+ * `createSystem` and `VisibilityState.Visible` value through
9
+ * {@link makeServiceBridgeSystem}.
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=iwsdk-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iwsdk-host.js","sourceRoot":"","sources":["../src/iwsdk-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Headless {@link RuntimeAdapter} for tests. Services run against this with no
3
+ * IWSDK / renderer / headset; tests drive the loop by calling
4
+ * {@link MockRuntimeAdapter.emitFrame} and refine gating with
5
+ * {@link MockRuntimeAdapter.setCapabilities}.
6
+ */
7
+ import { type AdapterCapabilities, type CapabilitiesListener, type FrameListener, type RuntimeAdapter, type Unsubscribe } from "./runtime-adapter.js";
8
+ export declare class MockRuntimeAdapter implements RuntimeAdapter {
9
+ private readonly frameListeners;
10
+ private readonly capabilitiesListeners;
11
+ private capabilities;
12
+ constructor(capabilities?: Partial<AdapterCapabilities>);
13
+ onFrame(listener: FrameListener): Unsubscribe;
14
+ getCapabilities(): AdapterCapabilities;
15
+ onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe;
16
+ /** Refine capabilities in tests; notifies subscribers. */
17
+ setCapabilities(capabilities: Partial<AdapterCapabilities>): void;
18
+ /** Drive a frame in tests. */
19
+ emitFrame(timestamp?: number, delta?: number): void;
20
+ }
21
+ //# sourceMappingURL=mock-runtime-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/mock-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAE9B,qBAAa,kBAAmB,YAAW,cAAc;IACvD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA4B;IAC3D,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAmC;IACzE,OAAO,CAAC,YAAY,CAAsB;gBAEvB,YAAY,GAAE,OAAO,CAAC,mBAAmB,CAAM;IAI3D,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW;IAO7C,eAAe,IAAI,mBAAmB;IAItC,oBAAoB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,WAAW;IAOxE,0DAA0D;IACnD,eAAe,CAAC,YAAY,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAKxE,8BAA8B;IACvB,SAAS,CAAC,SAAS,SAAI,EAAE,KAAK,SAAS,GAAG,IAAI;CAGtD"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Headless {@link RuntimeAdapter} for tests. Services run against this with no
3
+ * IWSDK / renderer / headset; tests drive the loop by calling
4
+ * {@link MockRuntimeAdapter.emitFrame} and refine gating with
5
+ * {@link MockRuntimeAdapter.setCapabilities}.
6
+ */
7
+ import { DEFAULT_CAPABILITIES, } from "./runtime-adapter.js";
8
+ export class MockRuntimeAdapter {
9
+ frameListeners = new Set();
10
+ capabilitiesListeners = new Set();
11
+ capabilities;
12
+ constructor(capabilities = {}) {
13
+ this.capabilities = { ...DEFAULT_CAPABILITIES, ...capabilities };
14
+ }
15
+ onFrame(listener) {
16
+ this.frameListeners.add(listener);
17
+ return () => {
18
+ this.frameListeners.delete(listener);
19
+ };
20
+ }
21
+ getCapabilities() {
22
+ return this.capabilities;
23
+ }
24
+ onCapabilitiesChange(listener) {
25
+ this.capabilitiesListeners.add(listener);
26
+ return () => {
27
+ this.capabilitiesListeners.delete(listener);
28
+ };
29
+ }
30
+ /** Refine capabilities in tests; notifies subscribers. */
31
+ setCapabilities(capabilities) {
32
+ this.capabilities = { ...this.capabilities, ...capabilities };
33
+ this.capabilitiesListeners.forEach((listener) => listener(this.capabilities));
34
+ }
35
+ /** Drive a frame in tests. */
36
+ emitFrame(timestamp = 0, delta = 1 / 72) {
37
+ this.frameListeners.forEach((listener) => listener({ timestamp, delta }));
38
+ }
39
+ }
40
+ //# sourceMappingURL=mock-runtime-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-runtime-adapter.js","sourceRoot":"","sources":["../src/mock-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,oBAAoB,GAMrB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,OAAO,kBAAkB;IACZ,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAC;IAC1C,qBAAqB,GAAG,IAAI,GAAG,EAAwB,CAAC;IACjE,YAAY,CAAsB;IAE1C,YAAmB,eAA6C,EAAE;QAChE,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,oBAAoB,EAAE,GAAG,YAAY,EAAE,CAAC;IACnE,CAAC;IAEM,OAAO,CAAC,QAAuB;QACpC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACvC,CAAC,CAAC;IACJ,CAAC;IAEM,eAAe;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAEM,oBAAoB,CAAC,QAA8B;QACxD,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzC,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9C,CAAC,CAAC;IACJ,CAAC;IAED,0DAA0D;IACnD,eAAe,CAAC,YAA0C;QAC/D,IAAI,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,YAAY,EAAE,CAAC;QAC9D,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,8BAA8B;IACvB,SAAS,CAAC,SAAS,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,GAAG,EAAE;QAC5C,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5E,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Runtime adapter contract (IWSDK-only, minimal frame-source).
3
+ *
4
+ * Trimmed to what an IWSDK-only client needs: a per-frame fan-out and
5
+ * capability flags. IWSDK already owns sessions, input and rendering, so the
6
+ * adapter deliberately does NOT re-abstract those. {@link MockRuntimeAdapter}
7
+ * implements this same interface so services can be unit-tested headless.
8
+ */
9
+ export type Unsubscribe = () => void;
10
+ export interface FrameInfo {
11
+ /** Frame timestamp in milliseconds (IWSDK system `time`). */
12
+ readonly timestamp: number;
13
+ /** Seconds elapsed since the previous frame (IWSDK system `delta`). */
14
+ readonly delta: number;
15
+ }
16
+ /** XR capabilities services gate on (e.g. passthrough requires `immersive`). */
17
+ export interface AdapterCapabilities {
18
+ readonly immersive: boolean;
19
+ readonly handTracking: boolean;
20
+ readonly planeDetection: boolean;
21
+ readonly passthrough: boolean;
22
+ }
23
+ export declare const DEFAULT_CAPABILITIES: AdapterCapabilities;
24
+ export type FrameListener = (frame: FrameInfo) => void;
25
+ export type CapabilitiesListener = (capabilities: AdapterCapabilities) => void;
26
+ export interface RuntimeAdapter {
27
+ /** Subscribe to per-frame updates; returns an unsubscribe handle. */
28
+ onFrame(listener: FrameListener): Unsubscribe;
29
+ /** Current XR capabilities (may change once a session is established). */
30
+ getCapabilities(): AdapterCapabilities;
31
+ /**
32
+ * Subscribe to capability changes; returns an unsubscribe handle. Mirrors
33
+ * {@link RuntimeAdapter.onFrame} so gating services can react to a session
34
+ * coming online instead of polling {@link RuntimeAdapter.getCapabilities}
35
+ * every frame.
36
+ */
37
+ onCapabilitiesChange(listener: CapabilitiesListener): Unsubscribe;
38
+ }
39
+ //# sourceMappingURL=runtime-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-adapter.d.ts","sourceRoot":"","sources":["../src/runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC,MAAM,WAAW,SAAS;IACxB,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B;AAED,eAAO,MAAM,oBAAoB,EAAE,mBAKlC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;AACvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,YAAY,EAAE,mBAAmB,KAAK,IAAI,CAAC;AAE/E,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,OAAO,CAAC,QAAQ,EAAE,aAAa,GAAG,WAAW,CAAC;IAC9C,0EAA0E;IAC1E,eAAe,IAAI,mBAAmB,CAAC;IACvC;;;;;OAKG;IACH,oBAAoB,CAAC,QAAQ,EAAE,oBAAoB,GAAG,WAAW,CAAC;CACnE"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Runtime adapter contract (IWSDK-only, minimal frame-source).
3
+ *
4
+ * Trimmed to what an IWSDK-only client needs: a per-frame fan-out and
5
+ * capability flags. IWSDK already owns sessions, input and rendering, so the
6
+ * adapter deliberately does NOT re-abstract those. {@link MockRuntimeAdapter}
7
+ * implements this same interface so services can be unit-tested headless.
8
+ */
9
+ export const DEFAULT_CAPABILITIES = {
10
+ immersive: false,
11
+ handTracking: false,
12
+ planeDetection: false,
13
+ passthrough: false,
14
+ };
15
+ //# sourceMappingURL=runtime-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-adapter.js","sourceRoot":"","sources":["../src/runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH,MAAM,CAAC,MAAM,oBAAoB,GAAwB;IACvD,SAAS,EAAE,KAAK;IAChB,YAAY,EAAE,KAAK;IACnB,cAAc,EAAE,KAAK;IACrB,WAAW,EAAE,KAAK;CACnB,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The single ECS-services bridge. A normal IWSDK system that, each frame:
3
+ * - maps IWSDK `visibilityState` to manager focus/pause (auto-pause when the
4
+ * headset is removed), and
5
+ * - drives the adapter's per-frame fan-out via {@link IWSDKAdapter.emitFrame}.
6
+ *
7
+ * Game logic is only ticked while the session is visible/focused: in the
8
+ * browser / 2D preview the host app shows its own gate (e.g. an "Enter VR"
9
+ * overlay) and the services stay idle until the player enters VR.
10
+ *
11
+ * This is the only place the engine loop touches the service layer; services
12
+ * themselves never see IWSDK. The IWSDK primitives (`createSystem` and the
13
+ * `VisibilityState.Visible` value) are injected so this package never imports
14
+ * `@iwsdk/core` — mirroring how the three.js / Babylon.js bridges keep their
15
+ * engine packages at arm's length.
16
+ */
17
+ import type { ServiceManager } from "@realitycollective/service-framework";
18
+ import type { IWSDKAdapter } from "./iwsdk-adapter.js";
19
+ import type { CreateSystemLike, IWSDKWorldLike } from "./iwsdk-host.js";
20
+ export interface ServiceBridgeSystemOptions<TVisibility = unknown> {
21
+ /** The passive frame source fanned out to services. */
22
+ readonly adapter: IWSDKAdapter;
23
+ /** The service manager whose focus/pause signals are driven by visibility. */
24
+ readonly manager: ServiceManager;
25
+ /** The IWSDK world whose `visibilityState` is read each frame. */
26
+ readonly world: IWSDKWorldLike<TVisibility>;
27
+ /** IWSDK's `createSystem` factory (from `@iwsdk/core`). */
28
+ readonly createSystem: CreateSystemLike;
29
+ /**
30
+ * The `VisibilityState` value that means the session is visible/focused
31
+ * (IWSDK `VisibilityState.Visible`). The bridge ticks services only while
32
+ * `world.visibilityState.value === visibleState`.
33
+ */
34
+ readonly visibleState: TVisibility;
35
+ }
36
+ /**
37
+ * Builds the IWSDK `ServiceBridgeSystem` class. Register the returned class with
38
+ * the world (`world.registerSystem(makeServiceBridgeSystem({ ... }))`); IWSDK
39
+ * then calls its `update(delta, time)` once per frame.
40
+ */
41
+ export declare function makeServiceBridgeSystem<TVisibility>(options: ServiceBridgeSystemOptions<TVisibility>): {
42
+ new (...args: unknown[]): {
43
+ update(delta: number, time: number): void;
44
+ };
45
+ };
46
+ //# sourceMappingURL=service-bridge-system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-bridge-system.d.ts","sourceRoot":"","sources":["../src/service-bridge-system.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAC3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAExE,MAAM,WAAW,0BAA0B,CAAC,WAAW,GAAG,OAAO;IAC/D,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IAC5C,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EACjD,OAAO,EAAE,0BAA0B,CAAC,WAAW,CAAC;;sBAMhB,MAAM,QAAQ,MAAM,GAAG,IAAI;;EAgB5D"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Builds the IWSDK `ServiceBridgeSystem` class. Register the returned class with
3
+ * the world (`world.registerSystem(makeServiceBridgeSystem({ ... }))`); IWSDK
4
+ * then calls its `update(delta, time)` once per frame.
5
+ */
6
+ export function makeServiceBridgeSystem(options) {
7
+ const { adapter, manager, world, createSystem, visibleState } = options;
8
+ let lastFocused;
9
+ return class ServiceBridgeSystem extends createSystem({}) {
10
+ update(delta, time) {
11
+ const focused = world.visibilityState.value === visibleState;
12
+ if (focused !== lastFocused) {
13
+ lastFocused = focused;
14
+ manager.emitFocusChange(focused);
15
+ manager.emitPauseChange({ paused: !focused });
16
+ }
17
+ // Only run game logic while visible/focused; idle in the 2D/browser
18
+ // preview and while the headset is removed (visible-blurred).
19
+ if (focused) {
20
+ adapter.emitFrame(time, delta);
21
+ }
22
+ }
23
+ };
24
+ }
25
+ //# sourceMappingURL=service-bridge-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-bridge-system.js","sourceRoot":"","sources":["../src/service-bridge-system.ts"],"names":[],"mappings":"AAqCA;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAgD;IAEhD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IACxE,IAAI,WAAgC,CAAC;IAErC,OAAO,MAAM,mBAAoB,SAAQ,YAAY,CAAC,EAAE,CAAC;QACvC,MAAM,CAAC,KAAa,EAAE,IAAY;YAChD,MAAM,OAAO,GAAG,KAAK,CAAC,eAAe,CAAC,KAAK,KAAK,YAAY,CAAC;YAE7D,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;gBAC5B,WAAW,GAAG,OAAO,CAAC;gBACtB,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;gBACjC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YAChD,CAAC;YAED,oEAAoE;YACpE,8DAA8D;YAC9D,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `SnapshotService` base — the "services own state" pattern. A snapshot service
3
+ * owns one immutable state object and a pub/sub list: consumers (other services,
4
+ * or the ECS presentation layer) subscribe and receive the current value
5
+ * immediately, then every subsequent publish.
6
+ *
7
+ * It has no IWSDK dependency — only `@realitycollective/service-framework` — so
8
+ * services that extend it stay unit-testable headless against
9
+ * {@link MockRuntimeAdapter}.
10
+ */
11
+ import { BaseService } from "@realitycollective/service-framework";
12
+ /**
13
+ * The activation-context type a service constructor receives. Aliased here so
14
+ * every service uses one consistent, framework-correct shape (matches the
15
+ * `useFactory(context)` parameter the ServiceManager passes).
16
+ */
17
+ export type ServiceContext<TConfig = unknown> = ConstructorParameters<typeof BaseService<TConfig>>[0];
18
+ export type SnapshotListener<TSnapshot> = (snapshot: TSnapshot) => void;
19
+ export declare abstract class SnapshotService<TConfig, TSnapshot> extends BaseService<TConfig> {
20
+ protected snapshot: TSnapshot;
21
+ private readonly listeners;
22
+ protected constructor(context: ServiceContext<TConfig>, initialSnapshot: TSnapshot);
23
+ getSnapshot(): TSnapshot;
24
+ subscribe(listener: SnapshotListener<TSnapshot>): () => void;
25
+ protected publishSnapshot(snapshot: TSnapshot): void;
26
+ protected updateSnapshot(partial: Partial<TSnapshot>): void;
27
+ }
28
+ //# sourceMappingURL=snapshot-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-service.d.ts","sourceRoot":"","sources":["../src/snapshot-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAEnE;;;;GAIG;AACH,MAAM,MAAM,cAAc,CAAC,OAAO,GAAG,OAAO,IAC1C,qBAAqB,CAAC,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,MAAM,MAAM,gBAAgB,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,SAAS,KAAK,IAAI,CAAC;AAExE,8BAAsB,eAAe,CAAC,OAAO,EAAE,SAAS,CAAE,SAAQ,WAAW,CAAC,OAAO,CAAC;IACpF,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;IAEpE,SAAS,aAAa,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,SAAS;IAK3E,WAAW,IAAI,SAAS;IAIxB,SAAS,CAAC,QAAQ,EAAE,gBAAgB,CAAC,SAAS,CAAC,GAAG,MAAM,IAAI;IAQnE,SAAS,CAAC,eAAe,CAAC,QAAQ,EAAE,SAAS,GAAG,IAAI;IAKpD,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI;CAG5D"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * `SnapshotService` base — the "services own state" pattern. A snapshot service
3
+ * owns one immutable state object and a pub/sub list: consumers (other services,
4
+ * or the ECS presentation layer) subscribe and receive the current value
5
+ * immediately, then every subsequent publish.
6
+ *
7
+ * It has no IWSDK dependency — only `@realitycollective/service-framework` — so
8
+ * services that extend it stay unit-testable headless against
9
+ * {@link MockRuntimeAdapter}.
10
+ */
11
+ import { BaseService } from "@realitycollective/service-framework";
12
+ export class SnapshotService extends BaseService {
13
+ snapshot;
14
+ listeners = new Set();
15
+ constructor(context, initialSnapshot) {
16
+ super(context);
17
+ this.snapshot = initialSnapshot;
18
+ }
19
+ getSnapshot() {
20
+ return this.snapshot;
21
+ }
22
+ subscribe(listener) {
23
+ this.listeners.add(listener);
24
+ listener(this.snapshot);
25
+ return () => {
26
+ this.listeners.delete(listener);
27
+ };
28
+ }
29
+ publishSnapshot(snapshot) {
30
+ this.snapshot = snapshot;
31
+ this.listeners.forEach((listener) => listener(this.snapshot));
32
+ }
33
+ updateSnapshot(partial) {
34
+ this.publishSnapshot({ ...this.snapshot, ...partial });
35
+ }
36
+ }
37
+ //# sourceMappingURL=snapshot-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-service.js","sourceRoot":"","sources":["../src/snapshot-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAYnE,MAAM,OAAgB,eAAoC,SAAQ,WAAoB;IAC1E,QAAQ,CAAY;IACb,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAC;IAEpE,YAAsB,OAAgC,EAAE,eAA0B;QAChF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,eAAe,CAAC;IAClC,CAAC;IAEM,WAAW;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAEM,SAAS,CAAC,QAAqC;QACpD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC,CAAC;IACJ,CAAC;IAES,eAAe,CAAC,QAAmB;QAC3C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChE,CAAC;IAES,cAAc,CAAC,OAA2B;QAClD,IAAI,CAAC,eAAe,CAAC,EAAE,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACzD,CAAC;CACF"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@realitycollective/service-framework-iwsdk",
3
+ "version": "1.0.0",
4
+ "description": "Meta IWSDK (WebXR) frame-source bindings for the Reality Collective TypeScript Service Framework.",
5
+ "author": "Reality Collective",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "Examples",
19
+ "README.md"
20
+ ],
21
+ "keywords": [
22
+ "service-framework",
23
+ "iwsdk",
24
+ "webxr",
25
+ "meta",
26
+ "xr",
27
+ "realitycollective",
28
+ "typescript"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/realitycollective/com.realitycollective.service-framework.ts.git"
33
+ },
34
+ "homepage": "https://github.com/realitycollective/com.realitycollective.service-framework.ts#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/realitycollective/com.realitycollective.service-framework.ts/issues"
37
+ },
38
+ "peerDependencies": {
39
+ "@iwsdk/core": ">=0.4.0 <0.5.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "@iwsdk/core": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "dependencies": {
47
+ "@realitycollective/service-framework": "^1.0.0"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc -p tsconfig.build.json",
51
+ "typecheck": "tsc -p tsconfig.json --noEmit"
52
+ }
53
+ }