@phreshos/react 0.1.13 → 0.1.15

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 CHANGED
@@ -23,15 +23,15 @@ It provides two kinds of adapter:
23
23
  required `provide` prop. `useSystemAppearance()`, `useDesktopPreferences()`,
24
24
  `useDesktopSize()`, and `usePointerPosition()` expose those values synchronously
25
25
  after resolution.
26
- - `useSubscribe()` and `useObserve()` own persistent ordinary-event
27
- registrations for one mounted React consumer.
26
+ - `useSubscribe()` owns named or all-event registrations for one mounted React
27
+ consumer.
28
28
  - `useProgramState(program)`, `useProcessState(process)`,
29
29
  `useServiceState(service)`, and `useWindowState(window)` explicitly compose
30
30
  existing reads with future live events for one mounted consumer. They add no
31
31
  state operation to another SDK.
32
- - `useObserveAsks()` adapts an Endpoint traffic surface's question observation,
33
- while `useObserveAnswers()` adapts a Server traffic surface's answer
34
- observation.
32
+ - `useSubscribeAsks()` adapts an Endpoint traffic surface's question
33
+ subscription, while `useSubscribeAnswers()` adapts a Server traffic
34
+ surface's answer subscription.
35
35
 
36
36
  Window needs no React resolution: `context.window` is already a synchronous,
37
37
  silent capability object. `ContextProvider` therefore has no `window` selection
@@ -73,15 +73,20 @@ mount its `useWindowState()` child only while `clientExists` is true. Calling
73
73
  the Window hook while the Client is absent lets the existing Window reads
74
74
  reject normally.
75
75
 
76
- The ordinary-event and traffic hooks retain the latest value returned by their projector.
77
- `useSubscribe()` may omit that callback; its default projector is
78
- `message => message`, so the hook retains the latest message unchanged.
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:
79
80
 
80
81
  ```tsx
81
82
  import { system } from "@phreshos/client"
82
83
  import { useSubscribe } from "@phreshos/react"
83
84
 
84
85
  const desktop = useSubscribe(system.desktop, "resize")
86
+
87
+ const latest = useSubscribe(system.desktop, capture => {
88
+ if (capture.event === "resize") return capture.message
89
+ })
85
90
  ```
86
91
 
87
92
  System reads are asynchronous while subscriptions are live-only. `SystemProvider`
package/dist/main.d.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  export { default as ContextProvider, useParent, useProcess, useProgram, type ContextProvision, type ContextProvisionName, type ContextProviderProperties } from "./context-provider.js";
2
2
  export { default as SystemProvider, usePointerPosition, useDesktopSize, useDesktopPreferences, useSystemAppearance, type SystemProvision, type SystemProvisionName, type SystemProviderProperties } from "./system-provider.js";
3
3
  export { default as useSubscribe } from "./use-subscribe.js";
4
- export { default as useObserve } from "./use-observe.js";
5
4
  export { default as useProgramState, type ProgramState } from "./use-program-state.js";
6
5
  export { default as useProcessState, type ProcessState } from "./use-process-state.js";
7
6
  export { default as useServiceState, type ServiceState } from "./use-service-state.js";
8
7
  export { default as useWindowState } from "./use-window-state.js";
9
- export { default as useObserveAsks, type AskObservable } from "./use-observe-asks.js";
10
- export { default as useObserveAnswers, type AnswerObservable } from "./use-observe-answers.js";
8
+ export { default as useSubscribeAsks, type AskSubscribable } from "./use-subscribe-asks.js";
9
+ export { default as useSubscribeAnswers, type AnswerSubscribable } from "./use-subscribe-answers.js";
package/dist/main.js CHANGED
@@ -1,10 +1,9 @@
1
1
  export { default as ContextProvider, useParent, useProcess, useProgram } from "./context-provider.js";
2
2
  export { default as SystemProvider, usePointerPosition, useDesktopSize, useDesktopPreferences, useSystemAppearance } from "./system-provider.js";
3
3
  export { default as useSubscribe } from "./use-subscribe.js";
4
- export { default as useObserve } from "./use-observe.js";
5
4
  export { default as useProgramState } from "./use-program-state.js";
6
5
  export { default as useProcessState } from "./use-process-state.js";
7
6
  export { default as useServiceState } from "./use-service-state.js";
8
7
  export { default as useWindowState } from "./use-window-state.js";
9
- export { default as useObserveAsks } from "./use-observe-asks.js";
10
- export { default as useObserveAnswers } from "./use-observe-answers.js";
8
+ export { default as useSubscribeAsks } from "./use-subscribe-asks.js";
9
+ export { default as useSubscribeAnswers } from "./use-subscribe-answers.js";
@@ -9,15 +9,6 @@ export default function useProcessState(process) {
9
9
  process.client.exists()
10
10
  ]);
11
11
  return { exited, serverExists, clientExists };
12
- }, reduce => combineCleanups(process.subscribe("endpointStart", endpoint => reduce(current => endpointPresence(current, process, endpoint, true))), process.subscribe("endpointStop", endpoint => reduce(current => endpointPresence(current, process, endpoint, false))), process.subscribe("exit", () => reduce(current => ({ ...current, exited: true, serverExists: false, clientExists: false }))))), [process]);
12
+ }, reduce => combineCleanups(process.server.lifecycle.subscribe("start", () => reduce(current => current.serverExists ? current : { ...current, serverExists: true })), process.server.lifecycle.subscribe("stop", () => reduce(current => current.serverExists ? { ...current, serverExists: false } : current)), process.client.lifecycle.subscribe("start", () => reduce(current => current.clientExists ? current : { ...current, clientExists: true })), process.client.lifecycle.subscribe("stop", () => reduce(current => current.clientExists ? { ...current, clientExists: false } : current)), process.subscribe("exit", () => reduce(current => ({ ...current, exited: true, serverExists: false, clientExists: false }))))), [process]);
13
13
  return useSyncExternalStore(state.subscribe, state.snapshot, state.snapshot);
14
14
  }
15
- function endpointPresence(state, process, endpoint, exists) {
16
- if (endpoint === process.server) {
17
- return state.serverExists === exists ? state : { ...state, serverExists: exists };
18
- }
19
- if (endpoint === process.client) {
20
- return state.clientExists === exists ? state : { ...state, clientExists: exists };
21
- }
22
- return state;
23
- }
@@ -2,7 +2,7 @@ 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 () => ({ enabled: await service.enabled() }), reduce => combineCleanups(service.subscribe("enable", () => reduce(current => setEnabled(current, true))), service.subscribe("disable", () => reduce(current => setEnabled(current, false))))), [service]);
5
+ const state = useMemo(() => new LiveState(async () => ({ enabled: await service.enabled() }), reduce => combineCleanups(service.lifecycle.subscribe("enable", () => reduce(current => setEnabled(current, true))), service.lifecycle.subscribe("disable", () => reduce(current => setEnabled(current, false))))), [service]);
6
6
  return useSyncExternalStore(state.subscribe, state.snapshot, state.snapshot);
7
7
  }
8
8
  function setEnabled(state, enabled) {
@@ -0,0 +1,8 @@
1
+ import type { AnswerCapture, AnswerSubscriber, Cleanup } from "@phreshos/client";
2
+ /** Any Server traffic surface capable of subscribing to its outgoing answers. */
3
+ export type AnswerSubscribable = Readonly<{
4
+ /** Subscribes to answers sent by the target Server and returns its cleanup. */
5
+ subscribeAnswers<Result = unknown>(subscriber: AnswerSubscriber<Result>): Cleanup;
6
+ }>;
7
+ /** Subscribes to outgoing answers while mounted and retains the latest projection. */
8
+ export default function useSubscribeAnswers<Answer, Result>(target: AnswerSubscribable, project: (capture: AnswerCapture<Answer>) => Result): Awaited<Result> | undefined;
@@ -0,0 +1,7 @@
1
+ import { useCallback } from "react";
2
+ import useEventResult from "./event-result.js";
3
+ /** Subscribes to outgoing answers while mounted and retains the latest projection. */
4
+ export default function useSubscribeAnswers(target, project) {
5
+ const connect = useCallback((receive) => target.subscribeAnswers(receive), [target]);
6
+ return useEventResult(connect, project);
7
+ }
@@ -0,0 +1,8 @@
1
+ import type { AskCapture, AskSubscriber, Cleanup } from "@phreshos/client";
2
+ /** Any Endpoint traffic surface capable of subscribing to its outgoing questions. */
3
+ export type AskSubscribable = Readonly<{
4
+ /** Subscribes to questions sent by the target Endpoint and returns its cleanup. */
5
+ subscribeAsks<Payload = unknown>(subscriber: AskSubscriber<Payload>): Cleanup;
6
+ }>;
7
+ /** Subscribes to outgoing questions while mounted and retains the latest projection. */
8
+ export default function useSubscribeAsks<Payload, Result>(target: AskSubscribable, project: (capture: AskCapture<Payload>) => Result): Awaited<Result> | undefined;
@@ -0,0 +1,7 @@
1
+ import { useCallback } from "react";
2
+ import useEventResult from "./event-result.js";
3
+ /** Subscribes to outgoing questions while mounted and retains the latest projection. */
4
+ export default function useSubscribeAsks(target, project) {
5
+ const connect = useCallback((receive) => target.subscribeAsks(receive), [target]);
6
+ return useEventResult(connect, project);
7
+ }
@@ -1,4 +1,4 @@
1
- import type { Channel, ChannelMessage, Endpoint, EventMessage, EventName, Subscribable, SubscribableEvents, SubscribableFallback } from "@phreshos/client";
1
+ import type { Captures, Context, ContextMessage, Endpoint, EventMessage, EventName, Subscribable, SubscribableEvents, SubscribableFallback } from "@phreshos/client";
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> = {
@@ -11,17 +11,16 @@ type SubscribableTarget = Readonly<{
11
11
  }>;
12
12
  type TargetEvent<Target> = AvailableEvent<SubscribableEvents<Target>, SubscribableFallback<Target>>;
13
13
  type TargetMessage<Target, Event extends string> = EventMessage<SubscribableEvents<Target>, SubscribableFallback<Target>, Event>;
14
- /**
15
- * Subscribes while mounted and retains the latest message or projected result.
16
- *
17
- * The target supplies known message types. An explicit generic or callback
18
- * annotation may narrow that type, but cannot replace it incompatibly.
19
- */
14
+ type TargetCapture<Target> = Captures<SubscribableEvents<Target>, SubscribableFallback<Target>>;
15
+ /** Subscribes while mounted and retains the latest named message. */
20
16
  export default function useSubscribe<Target extends SubscribableTarget, Event extends TargetEvent<Target>>(target: Target, event: Event): TargetMessage<Target, Event> | undefined;
21
17
  export default function useSubscribe<Narrowed = unknown>(target: Endpoint, event: string): Narrowed | undefined;
22
- export default function useSubscribe<Narrowed extends ChannelMessage<unknown> = ChannelMessage<unknown>>(target: Channel, event: string): Narrowed | undefined;
18
+ export default function useSubscribe<Narrowed extends ContextMessage<unknown> = ContextMessage<unknown>>(target: Context, event: string): Narrowed | undefined;
19
+ /** Subscribes while mounted and retains the latest projected named message. */
23
20
  export default function useSubscribe<Target extends SubscribableTarget, Event extends TargetEvent<Target>, Result>(target: Target, event: Event, project: (message: TargetMessage<Target, Event>) => Result): Awaited<Result> | undefined;
24
21
  export default function useSubscribe<Narrowed, Result = unknown>(target: Endpoint, event: string, project: (message: Narrowed) => Result): Awaited<Result> | undefined;
25
- export default function useSubscribe<Narrowed extends ChannelMessage<unknown>, Result = unknown>(target: Channel, event: string, project: (message: Narrowed) => Result): Awaited<Result> | undefined;
22
+ export default function useSubscribe<Narrowed extends ContextMessage<unknown>, Result = unknown>(target: Context, event: string, project: (message: Narrowed) => Result): Awaited<Result> | undefined;
26
23
  export default function useSubscribe<Narrowed, Events extends object, Fallback, Result>(target: Subscribable<Events, Fallback>, event: CompatibleEvent<Events, Fallback, Narrowed>, project: (message: Narrowed) => Result): Awaited<Result> | undefined;
24
+ /** Subscribes across every event and retains the latest projected capture. */
25
+ export default function useSubscribe<Target extends SubscribableTarget, Result>(target: Target, project: (capture: TargetCapture<Target>) => Result): Awaited<Result> | undefined;
27
26
  export {};
@@ -1,11 +1,17 @@
1
1
  import { useCallback } from "react";
2
2
  import useEventResult from "./event-result.js";
3
- export default function useSubscribe(target, event, project) {
3
+ export default function useSubscribe(target, eventOrProject, namedProject) {
4
+ const event = typeof eventOrProject === "string" ? eventOrProject : null;
4
5
  const connect = useCallback((receive) => {
6
+ if (event === null) {
7
+ const subscribe = target.subscribe;
8
+ return subscribe(receive);
9
+ }
5
10
  const subscribe = target.subscribe;
6
11
  return subscribe(event, receive);
7
12
  }, [target, event]);
8
- return useEventResult(connect, project ?? identity);
13
+ const project = event === null ? eventOrProject : namedProject ?? identity;
14
+ return useEventResult(connect, project);
9
15
  }
10
16
  function identity(message) {
11
17
  return message;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/react",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "React adapters for the Client SDK.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -47,13 +47,13 @@
47
47
  "prepack": "node --run test && node --run build"
48
48
  },
49
49
  "peerDependencies": {
50
- "@phreshos/client": "^0.1.25",
51
- "@phreshos/core": "^0.1.24",
50
+ "@phreshos/client": "^0.1.27",
51
+ "@phreshos/core": "^0.1.27",
52
52
  "react": "^19.2.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@phreshos/client": "^0.1.25",
56
- "@phreshos/core": "^0.1.24",
55
+ "@phreshos/client": "^0.1.27",
56
+ "@phreshos/core": "^0.1.27",
57
57
  "@testing-library/react": "^16.3.0",
58
58
  "@types/react": "^19.2.18",
59
59
  "@types/react-dom": "^19.2.4",
@@ -1,11 +0,0 @@
1
- import type { AnswerCapture, AnswerObserver, Cleanup } from "@phreshos/client";
2
- /** Any Server traffic surface capable of observing its outgoing answers. */
3
- export type AnswerObservable = Readonly<{
4
- /** Observes answers sent by the target Server and returns its cleanup. */
5
- observeAnswers<Result = unknown>(observer: AnswerObserver<Result>): Cleanup;
6
- }>;
7
- /**
8
- * Observes answers in one Server's traffic while mounted and retains the
9
- * latest projected result.
10
- */
11
- export default function useObserveAnswers<Answer, Result>(target: AnswerObservable, project: (capture: AnswerCapture<Answer>) => Result): Awaited<Result> | undefined;
@@ -1,10 +0,0 @@
1
- import { useCallback } from "react";
2
- import useEventResult from "./event-result.js";
3
- /**
4
- * Observes answers in one Server's traffic while mounted and retains the
5
- * latest projected result.
6
- */
7
- export default function useObserveAnswers(target, project) {
8
- const connect = useCallback((receive) => target.observeAnswers(receive), [target]);
9
- return useEventResult(connect, project);
10
- }
@@ -1,11 +0,0 @@
1
- import type { AskCapture, AskObserver, Cleanup } from "@phreshos/client";
2
- /** Any Endpoint traffic surface capable of observing its outgoing questions. */
3
- export type AskObservable = Readonly<{
4
- /** Observes questions sent by the target Endpoint and returns its cleanup. */
5
- observeAsks<Payload = unknown>(observer: AskObserver<Payload>): Cleanup;
6
- }>;
7
- /**
8
- * Observes questions in one Endpoint's traffic while mounted and retains the
9
- * latest projected result.
10
- */
11
- export default function useObserveAsks<Payload, Result>(target: AskObservable, project: (capture: AskCapture<Payload>) => Result): Awaited<Result> | undefined;
@@ -1,10 +0,0 @@
1
- import { useCallback } from "react";
2
- import useEventResult from "./event-result.js";
3
- /**
4
- * Observes questions in one Endpoint's traffic while mounted and retains the
5
- * latest projected result.
6
- */
7
- export default function useObserveAsks(target, project) {
8
- const connect = useCallback((receive) => target.observeAsks(receive), [target]);
9
- return useEventResult(connect, project);
10
- }
@@ -1,3 +0,0 @@
1
- import type { Captures, Subscribable } from "@phreshos/client";
2
- /** Observes all events while mounted and retains the latest projected result. */
3
- export default function useObserve<Events extends object, Fallback, Result>(target: Subscribable<Events, Fallback>, project: (capture: Captures<Events, Fallback>) => Result): Awaited<Result> | undefined;
@@ -1,7 +0,0 @@
1
- import { useCallback } from "react";
2
- import useEventResult from "./event-result.js";
3
- /** Observes all events while mounted and retains the latest projected result. */
4
- export default function useObserve(target, project) {
5
- const connect = useCallback((receive) => target.observe(receive), [target]);
6
- return useEventResult(connect, project);
7
- }