@phreshos/react 0.1.15 → 0.1.17
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.
- package/README.md +73 -144
- package/dist/context-provider.d.ts +24 -28
- package/dist/context-provider.js +37 -67
- package/dist/event-result.d.ts +1 -1
- package/dist/live-snapshot.d.ts +1 -1
- package/dist/live-state.d.ts +1 -1
- package/dist/main.d.ts +2 -2
- package/dist/main.js +1 -1
- package/dist/system-provider.d.ts +28 -20
- package/dist/system-provider.js +43 -61
- package/dist/use-process-state.d.ts +1 -1
- package/dist/use-program-state.d.ts +1 -1
- package/dist/use-service-state.d.ts +3 -3
- package/dist/use-service-state.js +3 -3
- package/dist/use-subscribe-answers.d.ts +1 -1
- package/dist/use-subscribe-asks.d.ts +1 -1
- package/dist/use-subscribe.d.ts +1 -1
- package/dist/use-window-state.d.ts +1 -1
- package/package.json +4 -6
package/README.md
CHANGED
|
@@ -1,162 +1,91 @@
|
|
|
1
1
|
# `@phreshos/react`
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
authority and does not define Program, Process, Endpoint, Client, Server, or
|
|
5
|
-
Window objects.
|
|
6
|
-
|
|
7
|
-
## Package status
|
|
8
|
-
|
|
9
|
-
This package is one component of a larger architecture that is still under
|
|
10
|
-
active testing. The architecture's components will be released in stages as
|
|
11
|
-
their contracts and integrations are verified.
|
|
12
|
-
|
|
13
|
-
`@phreshos/react` is not intended to be used on its own. It adapts the Client
|
|
14
|
-
SDK to React and therefore requires both `@phreshos/client` and React as peer
|
|
15
|
-
dependencies.
|
|
16
|
-
|
|
17
|
-
It provides two kinds of adapter:
|
|
18
|
-
|
|
19
|
-
- `ContextProvider` resolves exactly the runtime values named by its required
|
|
20
|
-
`provide` prop, then exposes them synchronously through `useProgram()`,
|
|
21
|
-
`useProcess()`, and `useParent()`.
|
|
22
|
-
- `SystemProvider` subscribes before reading exactly the system values named by its
|
|
23
|
-
required `provide` prop. `useSystemAppearance()`, `useDesktopPreferences()`,
|
|
24
|
-
`useDesktopSize()`, and `usePointerPosition()` expose those values synchronously
|
|
25
|
-
after resolution.
|
|
26
|
-
- `useSubscribe()` owns named or all-event registrations for one mounted React
|
|
27
|
-
consumer.
|
|
28
|
-
- `useProgramState(program)`, `useProcessState(process)`,
|
|
29
|
-
`useServiceState(service)`, and `useWindowState(window)` explicitly compose
|
|
30
|
-
existing reads with future live events for one mounted consumer. They add no
|
|
31
|
-
state operation to another SDK.
|
|
32
|
-
- `useSubscribeAsks()` adapts an Endpoint traffic surface's question
|
|
33
|
-
subscription, while `useSubscribeAnswers()` adapts a Server traffic
|
|
34
|
-
surface's answer subscription.
|
|
35
|
-
|
|
36
|
-
Window needs no React resolution: `context.window` is already a synchronous,
|
|
37
|
-
silent capability object. `ContextProvider` therefore has no `window` selection
|
|
38
|
-
and the React SDK exposes no pass-through `useWindow()` hook.
|
|
39
|
-
|
|
40
|
-
The domain state hooks return only mutable state; identity and immutable
|
|
41
|
-
metadata remain on the supplied handle:
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
useProgramState(program)
|
|
45
|
-
// { installed, processes } | undefined
|
|
46
|
-
|
|
47
|
-
useProcessState(process)
|
|
48
|
-
// { exited, serverExists, clientExists } | undefined
|
|
49
|
-
|
|
50
|
-
useServiceState(service)
|
|
51
|
-
// { enabled } | undefined
|
|
52
|
-
|
|
53
|
-
useWindowState(window)
|
|
54
|
-
// WindowState | undefined
|
|
55
|
-
```
|
|
3
|
+
Runtime-neutral React adapters for PhreshOS contracts.
|
|
56
4
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`undefined` means only that the initial explicit reads are pending. Each hook
|
|
62
|
-
opens its live subscriptions before those reads, then applies any intervening
|
|
63
|
-
events so an older result cannot overwrite newer state. If an initial read
|
|
64
|
-
rejects, the hook throws that original value during render for the nearest
|
|
65
|
-
React error boundary. It does not invent a fallback, retry, wait for a stopped
|
|
66
|
-
Client, or use an old Window snapshot as the fallback for that rejection. State
|
|
67
|
-
exists only while the hook is mounted and every registration is cleaned up on
|
|
68
|
-
unmount.
|
|
69
|
-
|
|
70
|
-
The Window capability outlives a stopped Client, but live Window state does
|
|
71
|
-
not. A component that spans Client lifecycle should use `useProcessState()` to
|
|
72
|
-
mount its `useWindowState()` child only while `clientExists` is true. Calling
|
|
73
|
-
the Window hook while the Client is absent lets the existing Window reads
|
|
74
|
-
reject normally.
|
|
75
|
-
|
|
76
|
-
The ordinary-event and traffic hooks retain the latest value returned by their
|
|
77
|
-
projector. A named `useSubscribe()` may omit that callback; its default
|
|
78
|
-
projector is `message => message`, so the hook retains the latest message
|
|
79
|
-
unchanged. The all-event form receives a correlated capture:
|
|
5
|
+
The React SDK receives explicit Core handles and live sources. It does not
|
|
6
|
+
initialize transport, import the Client SDK, access the browser, or define
|
|
7
|
+
domain state.
|
|
80
8
|
|
|
81
|
-
|
|
82
|
-
import { system } from "@phreshos/client"
|
|
83
|
-
import { useSubscribe } from "@phreshos/react"
|
|
9
|
+
## Installation
|
|
84
10
|
|
|
85
|
-
|
|
11
|
+
| Package manager | Command |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| npm | `npm install @phreshos/react` |
|
|
14
|
+
| pnpm | `pnpm add @phreshos/react` |
|
|
15
|
+
| Bun | `bun add @phreshos/react` |
|
|
16
|
+
| Yarn | `yarn add @phreshos/react` |
|
|
86
17
|
|
|
87
|
-
|
|
88
|
-
if (capture.event === "resize") return capture.message
|
|
89
|
-
})
|
|
90
|
-
```
|
|
18
|
+
`@phreshos/core` and React are peer dependencies.
|
|
91
19
|
|
|
92
|
-
|
|
93
|
-
subscribes before requesting each selected snapshot and prevents an older read
|
|
94
|
-
from overwriting a newer event:
|
|
20
|
+
## Providers
|
|
95
21
|
|
|
96
22
|
```tsx
|
|
97
|
-
import {
|
|
23
|
+
import { ContextProvider, SystemProvider } from "@phreshos/react"
|
|
24
|
+
|
|
25
|
+
<SystemProvider
|
|
26
|
+
appearance={system.appearance}
|
|
27
|
+
desktopSurface={system.desktop.surface}
|
|
28
|
+
desktopPointer={system.desktop.pointer}
|
|
29
|
+
desktopPreferences={system.desktop.preferences}
|
|
30
|
+
>
|
|
31
|
+
<ContextProvider
|
|
32
|
+
program={() => context.program()}
|
|
33
|
+
process={() => context.process()}
|
|
34
|
+
parent={() => context.process().then(process => process.parent())}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</ContextProvider>
|
|
38
|
+
</SystemProvider>
|
|
39
|
+
```
|
|
98
40
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const desktop = useDesktopSize()
|
|
41
|
+
Each provider accepts explicitly selected sources and resolves only those
|
|
42
|
+
values. A provider requires at least one source; mounting it does not fetch an
|
|
43
|
+
entire runtime implicitly.
|
|
103
44
|
|
|
104
|
-
|
|
105
|
-
}
|
|
45
|
+
## Hooks
|
|
106
46
|
|
|
107
|
-
|
|
108
|
-
return <SystemProvider provide={["appearance", "desktopPreferences", "desktopSize"]} fallback={<p>Loading…</p>}>
|
|
109
|
-
<Content />
|
|
110
|
-
</SystemProvider>
|
|
111
|
-
}
|
|
112
|
-
```
|
|
47
|
+
Context hooks expose the supplied handles:
|
|
113
48
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
permission, and resolution fails unless the Program already holds `pointer`.
|
|
118
|
-
The provider renders its optional fallback, or `null`, until every selected
|
|
119
|
-
value resolves. `useDesktopPreferences()` is a pure state adapter. The System
|
|
120
|
-
communicates its effective scheme through the Client iframe, while each Client
|
|
121
|
-
HTML document declares which schemes it supports.
|
|
49
|
+
- `useProgram()`
|
|
50
|
+
- `useProcess()`
|
|
51
|
+
- `useParent()`
|
|
122
52
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
)
|
|
147
|
-
}
|
|
53
|
+
System hooks expose the supplied live snapshots:
|
|
54
|
+
|
|
55
|
+
- `useSystemAppearance()`
|
|
56
|
+
- `useDesktopSurface()`
|
|
57
|
+
- `useDesktopPointer()`
|
|
58
|
+
- `useDesktopPreferences()`
|
|
59
|
+
|
|
60
|
+
State hooks compose one explicit read with future events:
|
|
61
|
+
|
|
62
|
+
- `useProgramState()`
|
|
63
|
+
- `useProcessState()`
|
|
64
|
+
- `useServiceState()`
|
|
65
|
+
- `useWindowState()`
|
|
66
|
+
|
|
67
|
+
`useSubscribe()`, `useSubscribeAsks()`, and `useSubscribeAnswers()` own
|
|
68
|
+
their mounted registrations and clean them up on unmount. Unresolved initial
|
|
69
|
+
reads remain `undefined`; the adapters do not invent fallback domain state.
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
bun install --frozen-lockfile
|
|
75
|
+
bun run verify
|
|
148
76
|
```
|
|
149
77
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
78
|
+
`verify` checks the contracts, tests the adapters in React, builds the package,
|
|
79
|
+
and validates its public artifact.
|
|
80
|
+
|
|
81
|
+
## Repository boundary
|
|
82
|
+
|
|
83
|
+
This repository owns React lifecycle adaptation only. Core owns the contracts,
|
|
84
|
+
runtime SDKs provide the sources, and React UI owns visual components.
|
|
85
|
+
|
|
86
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the repository workflow and
|
|
87
|
+
[SECURITY.md](SECURITY.md) for private vulnerability reporting.
|
|
154
88
|
|
|
155
|
-
|
|
156
|
-
longer consumes it. Projectors may return a value or Promise; only the latest
|
|
157
|
-
invocation may update the hook state. Projector failures are never converted
|
|
158
|
-
into communication, logged, or suppressed by the SDK; they remain local to the
|
|
159
|
-
React application.
|
|
89
|
+
## License
|
|
160
90
|
|
|
161
|
-
|
|
162
|
-
copy into an application.
|
|
91
|
+
Licensed under the [MIT License](LICENSE). Copyright © 2026 Zohayr SLILEH.
|
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
import type { Process, Program } from "@phreshos/core";
|
|
3
|
+
/** Resolves only the runtime handles supplied by the surrounding runtime. */
|
|
4
|
+
export default function ContextProvider({ children, fallback, parent, process, program }: ContextProviderProperties): string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").FunctionComponentElement<import("react").ProviderProps<Partial<Readonly<{
|
|
5
|
+
program: Program;
|
|
6
|
+
process: Process;
|
|
7
|
+
parent: Process | null;
|
|
8
|
+
}>> | null>> | null;
|
|
9
|
+
/** Returns the current Program supplied by the nearest provider. */
|
|
10
|
+
export declare function useProgram<Handle extends Program = Program>(): Handle;
|
|
11
|
+
/** Returns the current Process supplied by the nearest provider. */
|
|
12
|
+
export declare function useProcess<Handle extends Process = Process>(): Handle;
|
|
13
|
+
/** Returns the current Process's supplied visible parent. */
|
|
14
|
+
export declare function useParent<Handle extends Process = Process>(): Handle | null;
|
|
15
|
+
/** A stable handle or resolver supplied by one runtime integration. */
|
|
16
|
+
export type ContextSource<Value> = Value | (() => Value | PromiseLike<Value>);
|
|
16
17
|
/** Properties accepted by ContextProvider. */
|
|
17
|
-
export
|
|
18
|
-
/** Content rendered after every selected runtime handle has resolved. */
|
|
18
|
+
export type ContextProviderProperties = Readonly<{
|
|
19
19
|
readonly children: ReactNode;
|
|
20
|
-
/** Content rendered while selected handles or optional Server readiness resolve. */
|
|
21
20
|
readonly fallback?: ReactNode;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*/
|
|
28
|
-
readonly waitServer?: boolean | number;
|
|
29
|
-
}
|
|
30
|
-
type SelectedContextProviderProperties = Omit<ContextProviderProperties, "provide" | "waitServer"> & Readonly<{
|
|
31
|
-
selection: readonly ContextProvisionName[];
|
|
32
|
-
waitServer: boolean | number;
|
|
21
|
+
}> & AtLeastOne<ContextSources>;
|
|
22
|
+
type ContextSources = Readonly<{
|
|
23
|
+
readonly parent?: ContextSource<Process | null>;
|
|
24
|
+
readonly process?: ContextSource<Process>;
|
|
25
|
+
readonly program?: ContextSource<Program>;
|
|
33
26
|
}>;
|
|
27
|
+
type AtLeastOne<Values> = {
|
|
28
|
+
[Name in keyof Values]-?: Required<Pick<Values, Name>> & Partial<Omit<Values, Name>>;
|
|
29
|
+
}[keyof Values];
|
|
34
30
|
export {};
|
package/dist/context-provider.js
CHANGED
|
@@ -1,93 +1,63 @@
|
|
|
1
1
|
import { createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
|
|
2
|
-
import { context } from "@phreshos/client";
|
|
3
|
-
const provisionNames = ["program", "process", "parent"];
|
|
4
2
|
const RuntimeContext = createContext(null);
|
|
5
|
-
/** Resolves only the runtime handles
|
|
6
|
-
export default function ContextProvider({ children, fallback = null,
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
fallback,
|
|
13
|
-
key: `${selectionKey}\0${String(waitServer)}`,
|
|
14
|
-
selection,
|
|
15
|
-
waitServer
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
function SelectedContextProvider({ children, fallback, selection, waitServer }) {
|
|
19
|
-
const [resolution, setResolution] = useState({ status: "pending" });
|
|
3
|
+
/** Resolves only the runtime handles supplied by the surrounding runtime. */
|
|
4
|
+
export default function ContextProvider({ children, fallback = null, parent, process, program }) {
|
|
5
|
+
const sources = useMemo(() => ({ parent, process, program }), [parent, process, program]);
|
|
6
|
+
const [resolution, setResolution] = useState({ status: "pending", sources });
|
|
7
|
+
if (parent === undefined && process === undefined && program === undefined) {
|
|
8
|
+
throw new Error("ContextProvider requires at least one runtime handle");
|
|
9
|
+
}
|
|
20
10
|
useEffect(() => {
|
|
21
11
|
let active = true;
|
|
22
|
-
|
|
23
|
-
void resolveContext(selection, waitServer).then(value => {
|
|
12
|
+
void resolveSources(sources).then(value => {
|
|
24
13
|
if (active)
|
|
25
|
-
setResolution({ status: "ready", value });
|
|
14
|
+
setResolution({ status: "ready", sources, value });
|
|
26
15
|
}, error => {
|
|
27
16
|
if (active)
|
|
28
|
-
setResolution({ status: "error", error });
|
|
17
|
+
setResolution({ status: "error", sources, error });
|
|
29
18
|
});
|
|
30
19
|
return () => { active = false; };
|
|
31
|
-
}, [
|
|
32
|
-
if (resolution.status === "pending")
|
|
20
|
+
}, [sources]);
|
|
21
|
+
if (resolution.sources !== sources || resolution.status === "pending")
|
|
33
22
|
return fallback;
|
|
34
23
|
if (resolution.status === "error")
|
|
35
24
|
throw resolution.error;
|
|
36
25
|
return createElement(RuntimeContext.Provider, { value: resolution.value }, children);
|
|
37
26
|
}
|
|
38
|
-
async function
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
serverReady
|
|
45
|
-
]);
|
|
46
|
-
return {
|
|
47
|
-
provided: new Set(selection),
|
|
48
|
-
values: Object.fromEntries(entries)
|
|
49
|
-
};
|
|
27
|
+
async function resolveSources(sources) {
|
|
28
|
+
const entries = await Promise.all(contextNames.flatMap(name => {
|
|
29
|
+
const source = sources[name];
|
|
30
|
+
return source === undefined ? [] : [resolveSource(name, source)];
|
|
31
|
+
}));
|
|
32
|
+
return Object.fromEntries(entries);
|
|
50
33
|
}
|
|
51
|
-
async function
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
case "process": return [name, await context.process()];
|
|
55
|
-
case "parent": return [name, await context.parent()];
|
|
56
|
-
}
|
|
34
|
+
async function resolveSource(name, source) {
|
|
35
|
+
const value = typeof source === "function" ? source() : source;
|
|
36
|
+
return [name, await value];
|
|
57
37
|
}
|
|
58
|
-
/** Returns the current Program
|
|
38
|
+
/** Returns the current Program supplied by the nearest provider. */
|
|
59
39
|
export function useProgram() {
|
|
60
40
|
return useProvided("program");
|
|
61
41
|
}
|
|
62
|
-
/** Returns the current Process
|
|
42
|
+
/** Returns the current Process supplied by the nearest provider. */
|
|
63
43
|
export function useProcess() {
|
|
64
44
|
return useProvided("process");
|
|
65
45
|
}
|
|
66
|
-
/** Returns the current Process's
|
|
46
|
+
/** Returns the current Process's supplied visible parent. */
|
|
67
47
|
export function useParent() {
|
|
68
48
|
return useProvided("parent");
|
|
69
49
|
}
|
|
70
50
|
function useProvided(name) {
|
|
71
|
-
const
|
|
72
|
-
if (!
|
|
73
|
-
throw new Error(
|
|
74
|
-
if (!
|
|
75
|
-
throw new Error(
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
throw new Error(`ContextProvider cannot provide "${String(name)}"`);
|
|
85
|
-
if (selected.has(name))
|
|
86
|
-
throw new Error(`ContextProvider's provide prop selects "${name}" more than once`);
|
|
87
|
-
selected.add(name);
|
|
88
|
-
}
|
|
89
|
-
return [...selected];
|
|
90
|
-
}
|
|
91
|
-
function hookName(name) {
|
|
92
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
93
|
-
}
|
|
51
|
+
const context = useContext(RuntimeContext);
|
|
52
|
+
if (!context)
|
|
53
|
+
throw new Error(`${hookNames[name]} must be used inside ContextProvider`);
|
|
54
|
+
if (!(name in context))
|
|
55
|
+
throw new Error(`${hookNames[name]} requires ContextProvider's ${name} prop`);
|
|
56
|
+
return context[name];
|
|
57
|
+
}
|
|
58
|
+
const contextNames = ["program", "process", "parent"];
|
|
59
|
+
const hookNames = {
|
|
60
|
+
parent: "useParent",
|
|
61
|
+
process: "useProcess",
|
|
62
|
+
program: "useProgram"
|
|
63
|
+
};
|
package/dist/event-result.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Cleanup } from "@phreshos/
|
|
1
|
+
import type { Cleanup } from "@phreshos/core";
|
|
2
2
|
/** A persistent SDK registration consumed by the React adapter. */
|
|
3
3
|
export type Connect<Message> = (receive: (message: Message) => void) => Cleanup;
|
|
4
4
|
/** Retains only the latest projected event value for one mounted hook. */
|
package/dist/live-snapshot.d.ts
CHANGED
package/dist/live-state.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Cleanup } from "@phreshos/
|
|
1
|
+
import type { Cleanup } from "@phreshos/core";
|
|
2
2
|
export type StateReducer<State> = (state: State) => State;
|
|
3
3
|
export type StateFollower<State> = (reduce: (reducer: StateReducer<State>) => void) => Cleanup;
|
|
4
4
|
/** One mounted React snapshot composed from an explicit read and future events. */
|
package/dist/main.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { default as ContextProvider, useParent, useProcess, useProgram, type
|
|
2
|
-
export { default as SystemProvider,
|
|
1
|
+
export { default as ContextProvider, useParent, useProcess, useProgram, type ContextSource, type ContextProviderProperties } from "./context-provider.js";
|
|
2
|
+
export { default as SystemProvider, useDesktopPointer, useDesktopSurface, useDesktopPreferences, useSystemAppearance, type SystemProviderProperties } from "./system-provider.js";
|
|
3
3
|
export { default as useSubscribe } from "./use-subscribe.js";
|
|
4
4
|
export { default as useProgramState, type ProgramState } from "./use-program-state.js";
|
|
5
5
|
export { default as useProcessState, type ProcessState } from "./use-process-state.js";
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { default as ContextProvider, useParent, useProcess, useProgram } from "./context-provider.js";
|
|
2
|
-
export { default as SystemProvider,
|
|
2
|
+
export { default as SystemProvider, useDesktopPointer, useDesktopSurface, useDesktopPreferences, useSystemAppearance } from "./system-provider.js";
|
|
3
3
|
export { default as useSubscribe } from "./use-subscribe.js";
|
|
4
4
|
export { default as useProgramState } from "./use-program-state.js";
|
|
5
5
|
export { default as useProcessState } from "./use-process-state.js";
|
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
export default function SystemProvider(
|
|
2
|
+
import type { Appearance, AppearanceSource, DesktopPointerSnapshot, DesktopPointerSource, DesktopPreferences, DesktopPreferencesSource, DesktopSurfaceSnapshot, DesktopSurfaceSource } from "@phreshos/core";
|
|
3
|
+
import LiveSnapshot from "./live-snapshot.js";
|
|
4
|
+
/** Resolves and follows only the System capabilities supplied by the runtime. */
|
|
5
|
+
export default function SystemProvider({ appearance, children, desktopPointer, desktopPreferences, desktopSurface, fallback }: SystemProviderProperties): string | number | bigint | boolean | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react").FunctionComponentElement<import("react").ProviderProps<SystemStores | null>> | null;
|
|
6
6
|
/** Returns the complete unresolved Appearance and follows authoritative updates. */
|
|
7
7
|
export declare function useSystemAppearance(): Appearance;
|
|
8
|
+
/** Returns the Desktop surface snapshot and follows future resizes. */
|
|
9
|
+
export declare function useDesktopSurface(): DesktopSurfaceSnapshot;
|
|
10
|
+
/** Returns the Desktop pointer snapshot and follows future movement. */
|
|
11
|
+
export declare function useDesktopPointer(): DesktopPointerSnapshot;
|
|
8
12
|
/** Returns all effective Desktop preferences and follows authoritative updates. */
|
|
9
13
|
export declare function useDesktopPreferences(): DesktopPreferences;
|
|
10
|
-
/** Returns the selected desktop size and follows future resize events. */
|
|
11
|
-
export declare function useDesktopSize(): DesktopSize;
|
|
12
|
-
/** Returns the selected Pointer position and follows future move events. */
|
|
13
|
-
export declare function usePointerPosition(): PointerPosition | null;
|
|
14
|
-
/** Values that SystemProvider can request and follow. */
|
|
15
|
-
export type SystemProvisionName = typeof provisionNames[number];
|
|
16
|
-
/** Required non-empty selection of system values. */
|
|
17
|
-
export type SystemProvision = readonly [SystemProvisionName, ...SystemProvisionName[]];
|
|
18
14
|
/** Properties accepted by SystemProvider. */
|
|
19
|
-
export
|
|
20
|
-
/** Content rendered after every selected system value has resolved. */
|
|
15
|
+
export type SystemProviderProperties = Readonly<{
|
|
21
16
|
readonly children: ReactNode;
|
|
22
|
-
/** Content rendered while selected system values resolve. */
|
|
23
17
|
readonly fallback?: ReactNode;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
}> & AtLeastOne<SystemSources>;
|
|
19
|
+
type SystemSources = Readonly<{
|
|
20
|
+
readonly appearance?: AppearanceSource;
|
|
21
|
+
readonly desktopPointer?: DesktopPointerSource;
|
|
22
|
+
readonly desktopPreferences?: DesktopPreferencesSource;
|
|
23
|
+
readonly desktopSurface?: DesktopSurfaceSource;
|
|
29
24
|
}>;
|
|
25
|
+
type SystemValues = Readonly<{
|
|
26
|
+
appearance: Appearance;
|
|
27
|
+
desktopPointer: DesktopPointerSnapshot;
|
|
28
|
+
desktopPreferences: DesktopPreferences;
|
|
29
|
+
desktopSurface: DesktopSurfaceSnapshot;
|
|
30
|
+
}>;
|
|
31
|
+
type SystemName = keyof SystemValues;
|
|
32
|
+
type SystemStores = {
|
|
33
|
+
[Name in SystemName]?: LiveSnapshot<SystemValues[Name]>;
|
|
34
|
+
};
|
|
35
|
+
type AtLeastOne<Values> = {
|
|
36
|
+
[Name in keyof Values]-?: Required<Pick<Values, Name>> & Partial<Omit<Values, Name>>;
|
|
37
|
+
}[keyof Values];
|
|
30
38
|
export {};
|
package/dist/system-provider.js
CHANGED
|
@@ -1,103 +1,85 @@
|
|
|
1
1
|
import { createContext, createElement, useContext, useEffect, useMemo, useState, useSyncExternalStore } from "react";
|
|
2
|
-
import { system } from "@phreshos/client";
|
|
3
2
|
import LiveSnapshot from "./live-snapshot.js";
|
|
4
|
-
const provisionNames = ["appearance", "desktopPreferences", "desktopSize", "pointerPosition"];
|
|
5
3
|
const SystemContext = createContext(null);
|
|
6
|
-
/**
|
|
7
|
-
export default function SystemProvider(
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
4
|
+
/** Resolves and follows only the System capabilities supplied by the runtime. */
|
|
5
|
+
export default function SystemProvider({ appearance, children, desktopPointer, desktopPreferences, desktopSurface, fallback = null }) {
|
|
6
|
+
const stores = useMemo(() => createStores({
|
|
7
|
+
appearance,
|
|
8
|
+
desktopPointer,
|
|
9
|
+
desktopPreferences,
|
|
10
|
+
desktopSurface
|
|
11
|
+
}), [appearance, desktopPointer, desktopPreferences, desktopSurface]);
|
|
12
|
+
const [resolution, setResolution] = useState({ status: "pending", stores });
|
|
13
|
+
if (Object.keys(stores).length === 0)
|
|
14
|
+
throw new Error("SystemProvider requires at least one System capability");
|
|
16
15
|
useEffect(() => {
|
|
17
16
|
let active = true;
|
|
18
|
-
const selected =
|
|
17
|
+
const selected = Object.values(stores);
|
|
19
18
|
void Promise.all(selected.map(store => store.start())).then(() => {
|
|
20
19
|
if (active)
|
|
21
|
-
setResolution({ status: "ready" });
|
|
20
|
+
setResolution({ status: "ready", stores });
|
|
22
21
|
}, error => {
|
|
23
22
|
for (const store of selected)
|
|
24
23
|
store.stop();
|
|
25
24
|
if (active)
|
|
26
|
-
setResolution({ status: "error", error });
|
|
25
|
+
setResolution({ status: "error", stores, error });
|
|
27
26
|
});
|
|
28
27
|
return () => {
|
|
29
28
|
active = false;
|
|
30
29
|
for (const store of selected)
|
|
31
30
|
store.stop();
|
|
32
31
|
};
|
|
33
|
-
}, [
|
|
34
|
-
if (resolution.status === "pending")
|
|
32
|
+
}, [stores]);
|
|
33
|
+
if (resolution.stores !== stores || resolution.status === "pending")
|
|
35
34
|
return fallback;
|
|
36
35
|
if (resolution.status === "error")
|
|
37
36
|
throw resolution.error;
|
|
38
|
-
return createElement(SystemContext.Provider, { value:
|
|
37
|
+
return createElement(SystemContext.Provider, { value: stores }, children);
|
|
39
38
|
}
|
|
40
39
|
/** Returns the complete unresolved Appearance and follows authoritative updates. */
|
|
41
40
|
export function useSystemAppearance() {
|
|
42
41
|
return useProvided("appearance");
|
|
43
42
|
}
|
|
43
|
+
/** Returns the Desktop surface snapshot and follows future resizes. */
|
|
44
|
+
export function useDesktopSurface() {
|
|
45
|
+
return useProvided("desktopSurface");
|
|
46
|
+
}
|
|
47
|
+
/** Returns the Desktop pointer snapshot and follows future movement. */
|
|
48
|
+
export function useDesktopPointer() {
|
|
49
|
+
return useProvided("desktopPointer");
|
|
50
|
+
}
|
|
44
51
|
/** Returns all effective Desktop preferences and follows authoritative updates. */
|
|
45
52
|
export function useDesktopPreferences() {
|
|
46
53
|
return useProvided("desktopPreferences");
|
|
47
54
|
}
|
|
48
|
-
/** Returns the selected desktop size and follows future resize events. */
|
|
49
|
-
export function useDesktopSize() {
|
|
50
|
-
return useProvided("desktopSize");
|
|
51
|
-
}
|
|
52
|
-
/** Returns the selected Pointer position and follows future move events. */
|
|
53
|
-
export function usePointerPosition() {
|
|
54
|
-
return useProvided("pointerPosition");
|
|
55
|
-
}
|
|
56
55
|
function useProvided(name) {
|
|
57
56
|
const context = useContext(SystemContext);
|
|
58
57
|
if (!context)
|
|
59
58
|
throw new Error(`${hookNames[name]} must be used inside SystemProvider`);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const store = context[name];
|
|
60
|
+
if (!store)
|
|
61
|
+
throw new Error(`${hookNames[name]} requires SystemProvider's ${name} prop`);
|
|
63
62
|
return useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot);
|
|
64
63
|
}
|
|
65
|
-
function createStores(
|
|
64
|
+
function createStores(sources) {
|
|
66
65
|
const stores = {};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
stores.pointerPosition = new LiveSnapshot(() => system.pointer.position(), subscriber => system.pointer.subscribe("move", subscriber));
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
66
|
+
const appearance = sources.appearance;
|
|
67
|
+
const surface = sources.desktopSurface;
|
|
68
|
+
const pointer = sources.desktopPointer;
|
|
69
|
+
const preferences = sources.desktopPreferences;
|
|
70
|
+
if (appearance)
|
|
71
|
+
stores.appearance = new LiveSnapshot(() => appearance.snapshot(), subscriber => appearance.subscribe("change", subscriber));
|
|
72
|
+
if (surface)
|
|
73
|
+
stores.desktopSurface = new LiveSnapshot(() => surface.snapshot(), subscriber => surface.subscribe("resize", subscriber));
|
|
74
|
+
if (pointer)
|
|
75
|
+
stores.desktopPointer = new LiveSnapshot(() => pointer.snapshot(), subscriber => pointer.subscribe("move", subscriber));
|
|
76
|
+
if (preferences)
|
|
77
|
+
stores.desktopPreferences = new LiveSnapshot(() => preferences.snapshot(), subscriber => preferences.subscribe("change", subscriber));
|
|
83
78
|
return stores;
|
|
84
79
|
}
|
|
85
|
-
function normalizeProvision(provide) {
|
|
86
|
-
if (!Array.isArray(provide) || provide.length === 0)
|
|
87
|
-
throw new Error("SystemProvider's provide prop must select at least one system value");
|
|
88
|
-
const selected = new Set();
|
|
89
|
-
for (const name of provide) {
|
|
90
|
-
if (!provisionNames.includes(name))
|
|
91
|
-
throw new Error(`SystemProvider cannot provide "${String(name)}"`);
|
|
92
|
-
if (selected.has(name))
|
|
93
|
-
throw new Error(`SystemProvider's provide prop selects "${name}" more than once`);
|
|
94
|
-
selected.add(name);
|
|
95
|
-
}
|
|
96
|
-
return [...selected];
|
|
97
|
-
}
|
|
98
80
|
const hookNames = {
|
|
99
81
|
appearance: "useSystemAppearance",
|
|
82
|
+
desktopPointer: "useDesktopPointer",
|
|
100
83
|
desktopPreferences: "useDesktopPreferences",
|
|
101
|
-
|
|
102
|
-
pointerPosition: "usePointerPosition"
|
|
84
|
+
desktopSurface: "useDesktopSurface"
|
|
103
85
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Service } from "@phreshos/
|
|
2
|
-
/** Live
|
|
1
|
+
import type { Service } from "@phreshos/core";
|
|
2
|
+
/** Live existence of one configured Endpoint service. */
|
|
3
3
|
export type ServiceState = Readonly<{
|
|
4
|
-
|
|
4
|
+
exists: boolean;
|
|
5
5
|
}>;
|
|
6
6
|
/** Explicitly reads and follows one service while this hook is mounted. */
|
|
7
7
|
export default function useServiceState(service: Service): ServiceState | undefined;
|
|
@@ -2,9 +2,9 @@ import { useMemo, useSyncExternalStore } from "react";
|
|
|
2
2
|
import LiveState, { combineCleanups } from "./live-state.js";
|
|
3
3
|
/** Explicitly reads and follows one service while this hook is mounted. */
|
|
4
4
|
export default function useServiceState(service) {
|
|
5
|
-
const state = useMemo(() => new LiveState(async () => ({
|
|
5
|
+
const state = useMemo(() => new LiveState(async () => ({ exists: await service.exists() }), reduce => combineCleanups(service.lifecycle.subscribe("start", () => reduce(current => setExists(current, true))), service.lifecycle.subscribe("stop", () => reduce(current => setExists(current, false))))), [service]);
|
|
6
6
|
return useSyncExternalStore(state.subscribe, state.snapshot, state.snapshot);
|
|
7
7
|
}
|
|
8
|
-
function
|
|
9
|
-
return state.
|
|
8
|
+
function setExists(state, exists) {
|
|
9
|
+
return state.exists === exists ? state : { exists };
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnswerCapture, AnswerSubscriber, Cleanup } from "@phreshos/
|
|
1
|
+
import type { AnswerCapture, AnswerSubscriber, Cleanup } from "@phreshos/core";
|
|
2
2
|
/** Any Server traffic surface capable of subscribing to its outgoing answers. */
|
|
3
3
|
export type AnswerSubscribable = Readonly<{
|
|
4
4
|
/** Subscribes to answers sent by the target Server and returns its cleanup. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AskCapture, AskSubscriber, Cleanup } from "@phreshos/
|
|
1
|
+
import type { AskCapture, AskSubscriber, Cleanup } from "@phreshos/core";
|
|
2
2
|
/** Any Endpoint traffic surface capable of subscribing to its outgoing questions. */
|
|
3
3
|
export type AskSubscribable = Readonly<{
|
|
4
4
|
/** Subscribes to questions sent by the target Endpoint and returns its cleanup. */
|
package/dist/use-subscribe.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Captures, Context, ContextMessage, Endpoint, EventMessage, EventName, Subscribable, SubscribableEvents, SubscribableFallback } from "@phreshos/
|
|
1
|
+
import type { Captures, Context, ContextMessage, Endpoint, EventMessage, EventName, Subscribable, SubscribableEvents, SubscribableFallback } from "@phreshos/core";
|
|
2
2
|
type OpenEvent = string & {};
|
|
3
3
|
type AvailableEvent<Events extends object, Fallback> = EventName<Events> | ([Fallback] extends [never] ? never : OpenEvent);
|
|
4
4
|
type CompatibleEvent<Events extends object, Fallback, Narrowed> = {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { Window, WindowState } from "@phreshos/
|
|
1
|
+
import type { Window, WindowState } from "@phreshos/core";
|
|
2
2
|
/** Explicitly reads and follows one live Client Window while mounted. */
|
|
3
3
|
export default function useWindowState(window: Window): WindowState | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/react",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "React adapters for
|
|
3
|
+
"version": "0.1.17",
|
|
4
|
+
"description": "Runtime-neutral React adapters for PhreshOS contracts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
7
7
|
"types": "dist/main.d.ts",
|
|
@@ -47,13 +47,11 @@
|
|
|
47
47
|
"prepack": "node --run test && node --run build"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@phreshos/
|
|
51
|
-
"@phreshos/core": "^0.1.27",
|
|
50
|
+
"@phreshos/core": "^0.1.31",
|
|
52
51
|
"react": "^19.2.0"
|
|
53
52
|
},
|
|
54
53
|
"devDependencies": {
|
|
55
|
-
"@phreshos/
|
|
56
|
-
"@phreshos/core": "^0.1.27",
|
|
54
|
+
"@phreshos/core": "^0.1.31",
|
|
57
55
|
"@testing-library/react": "^16.3.0",
|
|
58
56
|
"@types/react": "^19.2.18",
|
|
59
57
|
"@types/react-dom": "^19.2.4",
|