@phreshos/client 0.1.1 → 0.1.3

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
@@ -114,16 +114,10 @@ Promises confirm that the current Client host accepted and applied the local
114
114
  draft; they do not enter server authority or emit Window events.
115
115
 
116
116
  An `under` or `over` Client may own one authoritative host-rendered Surface.
117
- The capability remains silent until Program code explicitly reads, changes, or
118
- subscribes to it:
117
+ The capability is command-only: Program code may change or remove it, but
118
+ cannot read or subscribe to its server-owned state:
119
119
 
120
120
  ```ts
121
- const initial = await window.surface.snapshot()
122
-
123
- const stop = window.surface.subscribe("change", surface => {
124
- // Future replacements only; subscriptions do not replay `initial`.
125
- })
126
-
127
121
  await window.surface.set({
128
122
  opacity: 0.65,
129
123
  radius: "large",
@@ -133,14 +127,14 @@ await window.surface.set({
133
127
  await window.surface.remove()
134
128
  ```
135
129
 
136
- The server stores the target beside its Window and broadcasts complete future
137
- replacements. `set()` with no settings creates a sharp, fully opaque Surface.
130
+ The server stores the target beside its Window and delivers it internally to
131
+ the desktop. `set()` with no settings creates a sharp, fully opaque Surface.
138
132
  Opacity is a finite number from `0` through `1`; zero retains the Surface node.
139
133
  Radius accepts a nonnegative pixel number, a Theme-derived `ScaleLevel`, or
140
134
  `"full"`. Only `remove()` restores exact `null` and immediately removes the
141
135
  node. The optional transaction uses milliseconds and a stable named or cubic
142
136
  Bézier easing; the desktop performs the motion, honors reduced motion, and does
143
- not replay a stored transaction when restoring an initial snapshot. The sharp
137
+ does not replay a stored transaction when restoring desktop state. The sharp
144
138
  container follows the iframe geometry while the independently rounded Surface
145
139
  neither clips nor masks Client content. `window` and `wallpaper` layers reject
146
140
  the capability.
package/dist/channel.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { visibleEndpoint } from "./domain.js";
2
2
  import Events from "./events.js";
3
3
  import wire from "./wire.js";
4
+ import { disableCurrentService, enableCurrentService } from "./service.js";
4
5
  class ClientChannel extends Events {
5
6
  constructor() {
6
7
  super((event, listener, impossible) => wire.on("end-end", event, value => listener(message(value)), null, impossible), observer => wire.onAll("end-end", (event, value) => {
@@ -11,6 +12,8 @@ class ClientChannel extends Events {
11
12
  publish(event, payload = undefined) {
12
13
  wire.send("end-host", "emit", event, payload);
13
14
  }
15
+ async enableService(name) { await enableCurrentService(name); }
16
+ async disableService() { await disableCurrentService(); }
14
17
  }
15
18
  function message(value) {
16
19
  const raw = value;
package/dist/current.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type { Channel as CoreChannel } from "@phreshos/core";
2
- import { Server, type Endpoint, type Process, type Program, type Window } from "./domain.js";
2
+ import { Client, Server, type Endpoint, type Process, type Program, type Window } from "./domain.js";
3
3
  /** The current Process's canonical Server handle. */
4
4
  export type CurrentServer<Events extends object = {}> = Server<Events>;
5
5
  /** Current Client context: its inbound Channel, owner hierarchy, and paired Server. */
6
- export interface Current<Events extends object = {}> extends CoreChannel<Events, Endpoint | null> {
6
+ export interface Current<Events extends object = {}> extends CoreChannel<Events, Endpoint | null>, Pick<Client, "service"> {
7
7
  /** The same Server handle exposed by the current Process. */
8
8
  readonly server: CurrentServer;
9
9
  /** Presentation capability of this current Client. */
package/dist/current.js CHANGED
@@ -2,6 +2,7 @@ import { channel } from "./channel.js";
2
2
  import Deadline from "./deadline.js";
3
3
  import { Client, Server, ServerTrafficHandle, TrafficHandle, bindEvents, endpointEvents, process, program, window as windowHandle } from "./domain.js";
4
4
  import wire from "./wire.js";
5
+ import { endpointService } from "./service.js";
5
6
  const ServerBase = Server;
6
7
  const ClientBase = Client;
7
8
  class CurrentServerHandle extends ServerBase {
@@ -20,6 +21,7 @@ class CurrentServerHandle extends ServerBase {
20
21
  }
21
22
  async start() { await wire.request(["start-endpoint", undefined, "server"]); }
22
23
  async stop() { await wire.request(["stop-endpoint", undefined, "server"]); }
24
+ service() { return endpointService(null, "server"); }
23
25
  async waitReady(timeout) { await wire.request(["wait-ready"], timeout); }
24
26
  async ask(event, payload = undefined) {
25
27
  return this.askWithin(undefined, event, payload);
@@ -75,6 +77,7 @@ class CurrentClientHandle extends ClientBase {
75
77
  }
76
78
  async start(overrides = {}) { await wire.request(["start-endpoint", undefined, "client", overrides]); }
77
79
  async stop() { await wire.request(["stop-endpoint", undefined, "client"]); }
80
+ service() { return endpointService(null, "client"); }
78
81
  }
79
82
  currentClient = new CurrentClientHandle(owner);
80
83
  class ClientCurrent {
@@ -97,6 +100,7 @@ class ClientCurrent {
97
100
  return answer[0];
98
101
  }
99
102
  async stop() { await wire.request(["stop-current"]); }
103
+ service() { return endpointService(null, "client"); }
100
104
  }
101
105
  function bindChannel(target, source) {
102
106
  Object.assign(target, {
@@ -104,7 +108,9 @@ function bindChannel(target, source) {
104
108
  subscribe: source.subscribe.bind(source),
105
109
  waitFor: source.waitFor.bind(source),
106
110
  events: source.events.bind(source),
107
- observe: source.observe.bind(source)
111
+ observe: source.observe.bind(source),
112
+ enableService: source.enableService.bind(source),
113
+ disableService: source.disableService.bind(source)
108
114
  });
109
115
  }
110
116
  /** Inbound events, owner hierarchy, and paired Server for the current Client. */
package/dist/domain.js CHANGED
@@ -4,6 +4,7 @@ import Deadline from "./deadline.js";
4
4
  import HandleRegistry from "./handle-registry.js";
5
5
  import { area, sql, store } from "./storage.js";
6
6
  import wire from "./wire.js";
7
+ import { endpointService } from "./service.js";
7
8
  const handles = new HandleRegistry();
8
9
  const ProgramBase = CoreProgram;
9
10
  const ProcessBase = CoreProcess;
@@ -160,6 +161,7 @@ class ServerHandle extends ServerBase {
160
161
  }
161
162
  async start() { await wire.request(["start-endpoint", this.owner.address, "server"]); }
162
163
  async stop() { await wire.request(["stop-endpoint", this.owner.address, "server"]); }
164
+ service() { return endpointService(this.owner.address, "server"); }
163
165
  async waitReady(timeout) { await wire.request(["wait-ready", this.owner.address], timeout); }
164
166
  async ask(event, payload = undefined) {
165
167
  return this.askWithin(undefined, event, payload);
@@ -202,6 +204,7 @@ class ClientHandle extends ClientBase {
202
204
  }
203
205
  async start(overrides = {}) { await wire.request(["start-endpoint", this.owner.address, "client", overrides]); }
204
206
  async stop() { await wire.request(["stop-endpoint", this.owner.address, "client"]); }
207
+ service() { return endpointService(this.owner.address, "client"); }
205
208
  }
206
209
  class WindowHandle extends Events {
207
210
  target;
@@ -230,30 +233,11 @@ class WindowHandle extends Events {
230
233
  async changeTitle(title) { await wire.request(["changeTitle", await this.target(), title]); }
231
234
  async raise() { await wire.request(["raise", await this.target()]); }
232
235
  }
233
- class WindowSurfaceHandle extends Events {
236
+ class WindowSurfaceHandle {
234
237
  target;
235
238
  constructor(target) {
236
- super((event, listener, impossible) => {
237
- if (event !== "change") {
238
- impossible?.(new Error(`A Window Surface has no "${String(event)}" event`));
239
- return () => undefined;
240
- }
241
- return deferred(target, subject => wire.on("host-end", "surface", (...values) => {
242
- const message = unscoped(subject, values);
243
- if (message)
244
- listener(message[0]);
245
- }, subject, impossible), impossible);
246
- }, observer => deferred(target, subject => wire.on("host-end", "surface", (...values) => {
247
- const message = unscoped(subject, values);
248
- if (message)
249
- observer("change", message[0]);
250
- }, subject)));
251
239
  this.target = target;
252
240
  }
253
- async snapshot() {
254
- const answer = await wire.request(["window", await this.target()]);
255
- return answer[0].surface;
256
- }
257
241
  async set(settings = {}) { await wire.request(["surfaceSet", await this.target(), settings]); }
258
242
  async remove() { await wire.request(["surfaceRemove", await this.target()]); }
259
243
  }
package/dist/host.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Permissions, ServedFile, Theme, ThemeProperties } from "@phreshos/core";
1
+ import type { ClientServiceHandler, Permissions, ServedFile, ServerServiceHandler, Theme, ThemeProperties } from "@phreshos/core";
2
2
  import { type HostPointer } from "./pointer.js";
3
3
  import { type HostSurface } from "./surface.js";
4
4
  /** Desktop capabilities structurally available to a Client endpoint. */
@@ -15,6 +15,18 @@ export interface Host {
15
15
  serve(value: unknown): Promise<ServedFile>;
16
16
  /** Performs an unrestricted server-side fetch on behalf of this Client. */
17
17
  fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
18
+ /** Returns a stable handle for one exact Server service identity. */
19
+ service<ServiceEvents extends object = {}>(key: {
20
+ program: string;
21
+ endpoint: "server";
22
+ name: string;
23
+ }): ServerServiceHandler<ServiceEvents>;
24
+ /** Returns a stable handle for one exact Client service identity. */
25
+ service<ServiceEvents extends object = {}>(key: {
26
+ program: string;
27
+ endpoint: "client";
28
+ name: string;
29
+ }): ClientServiceHandler<ServiceEvents>;
18
30
  }
19
31
  /** Desktop capabilities available to this Client. */
20
32
  export declare const host: Host;
package/dist/host.js CHANGED
@@ -4,6 +4,7 @@ import wire from "./wire.js";
4
4
  import ClientPermissions from "./permissions.js";
5
5
  import ClientPointer, {} from "./pointer.js";
6
6
  import ClientSurface, {} from "./surface.js";
7
+ import { service as serviceHandle } from "./service.js";
7
8
  class ClientHost {
8
9
  theme = new ClientTheme();
9
10
  surface = new ClientSurface();
@@ -79,6 +80,9 @@ class ClientHost {
79
80
  });
80
81
  return response;
81
82
  }
83
+ service(key) {
84
+ return serviceHandle(key);
85
+ }
82
86
  }
83
87
  function requestHeaders(normalized, supplied) {
84
88
  if (!supplied)
package/dist/main.d.ts CHANGED
@@ -3,5 +3,6 @@ export { type HostPointer, type PointerEvents, type PointerPosition } from "./po
3
3
  export { type HostSurface, type Surface, type SurfaceEvents } from "./surface.js";
4
4
  export { current, type Current, type CurrentServer } from "./current.js";
5
5
  export { type Channel, type ChannelCapture, type ChannelEvents, type ChannelMessage } from "./channel.js";
6
+ export { ClientServiceHandler, ServerServiceHandler, ServiceHandler, type ClientServiceChannel, type ServerServiceChannel, type ServiceChannel, type ServiceKey, type ServiceLifecycleEvents } from "@phreshos/core";
6
7
  export { Client, Endpoint, Process, Program, Server, type Window, type AnswerCapture, type AnswerMessage, type AnswerObserver, type AskCapture, type AskMessage, type AskObserver, type ClientTraffic, type EndpointTraffic, type ServerTraffic, type TrafficCapture, type TrafficEvents, type TrafficMessage } from "./domain.js";
7
- export type { Askable, Capture, Captures, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permissions, Position, ProgramArea, ProgramEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermissions, Timeoutable, Theme, ThemeEvents, ThemeProperties, Value, WritableTheme, WindowEvents, WindowLayer, WindowState, WindowSurface, WindowSurfaceEasing, WindowSurfaceEvents, WindowSurfaceSettings, WindowSurfaceTransaction } from "@phreshos/core";
8
+ export type { Askable, Capture, Captures, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permissions, Position, ProgramArea, ProgramEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermissions, Timeoutable, Theme, ThemeEvents, ThemeProperties, Value, WritableTheme, WindowEvents, WindowLayer, WindowState, WindowSurface, WindowSurfaceEasing, WindowSurfaceSettings, WindowSurfaceTransaction } from "@phreshos/core";
package/dist/main.js CHANGED
@@ -3,4 +3,5 @@ export {} from "./pointer.js";
3
3
  export {} from "./surface.js";
4
4
  export { current } from "./current.js";
5
5
  export {} from "./channel.js";
6
+ export { ClientServiceHandler, ServerServiceHandler, ServiceHandler } from "@phreshos/core";
6
7
  export { Client, Endpoint, Process, Program, Server } from "./domain.js";
@@ -0,0 +1,14 @@
1
+ import { type ClientServiceHandler, type ServerServiceHandler, type ServiceHandler, type ServiceKey } from "@phreshos/core";
2
+ import type { HandleAddress } from "./domain.js";
3
+ export declare function service<EventsMap extends object = {}>(key: ServiceKey & {
4
+ endpoint: "server";
5
+ }): ServerServiceHandler<EventsMap>;
6
+ export declare function service<EventsMap extends object = {}>(key: ServiceKey & {
7
+ endpoint: "client";
8
+ }): ClientServiceHandler<EventsMap>;
9
+ export declare function service(key: ServiceKey): ServiceHandler;
10
+ export declare function enableCurrentService(name: string): Promise<void>;
11
+ export declare function disableCurrentService(): Promise<void>;
12
+ export declare function endpointService<EventsMap extends object = {}>(target: HandleAddress | null, endpoint: "server"): Promise<ServerServiceHandler<EventsMap> | null>;
13
+ export declare function endpointService<EventsMap extends object = {}>(target: HandleAddress | null, endpoint: "client"): Promise<ClientServiceHandler<EventsMap> | null>;
14
+ export declare function endpointService(target: HandleAddress | null, endpoint: "server" | "client"): Promise<ServiceHandler | null>;
@@ -0,0 +1,122 @@
1
+ import { ClientServiceHandler as CoreClientServiceHandler, ServerServiceHandler as CoreServerServiceHandler, isServiceKey } from "@phreshos/core";
2
+ import Deadline from "./deadline.js";
3
+ import Events from "./events.js";
4
+ import HandleRegistry from "./handle-registry.js";
5
+ import wire from "./wire.js";
6
+ const handles = new HandleRegistry();
7
+ const ServerServiceBase = CoreServerServiceHandler;
8
+ const ClientServiceBase = CoreClientServiceHandler;
9
+ class ServerChannelHandle extends Events {
10
+ key;
11
+ constructor(key) {
12
+ super(...serviceEvents(key, "channel"));
13
+ this.key = key;
14
+ }
15
+ publish(event, payload = undefined) {
16
+ wire.send("end-host", "service-send", this.key, event, payload);
17
+ }
18
+ async ask(event, payload = undefined) {
19
+ return await this.askWithin(new Deadline(), event, payload);
20
+ }
21
+ timeout(milliseconds) {
22
+ return {
23
+ ask: (event, payload = undefined) => {
24
+ return this.askWithin(new Deadline(milliseconds), event, payload);
25
+ }
26
+ };
27
+ }
28
+ async askWithin(deadline, event, payload) {
29
+ const identity = await wire.identity();
30
+ const address = `client:${identity.process}:${crypto.randomUUID()}`;
31
+ const question = crypto.randomUUID();
32
+ const waiting = wire.expectWithin(address, deadline);
33
+ wire.send("end-host", "service-ask", this.key, address, question, event, payload);
34
+ try {
35
+ return await waiting;
36
+ }
37
+ finally {
38
+ wire.forget(address);
39
+ }
40
+ }
41
+ }
42
+ class ClientChannelHandle extends Events {
43
+ constructor(key) {
44
+ super(...serviceEvents(key, "channel"));
45
+ }
46
+ }
47
+ class ServerHandler extends ServerServiceBase {
48
+ key;
49
+ program;
50
+ endpoint = "server";
51
+ name;
52
+ channel;
53
+ constructor(key) {
54
+ super();
55
+ this.key = key;
56
+ this.program = key.program;
57
+ this.name = key.name;
58
+ this.channel = new ServerChannelHandle(key);
59
+ bindEvents(this, new Events(...serviceEvents(key, "lifecycle")));
60
+ }
61
+ async disabled() {
62
+ const answer = await wire.request(["service-disabled", this.key]);
63
+ return answer[0];
64
+ }
65
+ }
66
+ class ClientHandler extends ClientServiceBase {
67
+ key;
68
+ program;
69
+ endpoint = "client";
70
+ name;
71
+ channel;
72
+ constructor(key) {
73
+ super();
74
+ this.key = key;
75
+ this.program = key.program;
76
+ this.name = key.name;
77
+ this.channel = new ClientChannelHandle(key);
78
+ bindEvents(this, new Events(...serviceEvents(key, "lifecycle")));
79
+ }
80
+ async disabled() {
81
+ const answer = await wire.request(["service-disabled", this.key]);
82
+ return answer[0];
83
+ }
84
+ }
85
+ export function service(key) {
86
+ if (!isServiceKey(key))
87
+ throw new Error("A complete service key is required");
88
+ const normalized = Object.freeze({ program: key.program, endpoint: key.endpoint, name: key.name });
89
+ const identity = JSON.stringify([normalized.program, normalized.endpoint, normalized.name]);
90
+ return handles.obtain(`service:${identity}`, () => {
91
+ return normalized.endpoint === "server"
92
+ ? new ServerHandler(normalized)
93
+ : new ClientHandler(normalized);
94
+ });
95
+ }
96
+ export async function enableCurrentService(name) {
97
+ await wire.request(["enable-service", name]);
98
+ }
99
+ export async function disableCurrentService() {
100
+ await wire.request(["disable-service"]);
101
+ }
102
+ export async function endpointService(target, endpoint) {
103
+ const answer = await wire.request(["endpoint-service", target, endpoint]);
104
+ return answer[0] ? service(answer[0]) : null;
105
+ }
106
+ function serviceEvents(key, scope) {
107
+ return [
108
+ (event, listener) => wire.followService(key, scope, event, listener),
109
+ (observer) => wire.followService(key, scope, null, (event, payload) => {
110
+ if (typeof event === "string")
111
+ observer(event, payload);
112
+ })
113
+ ];
114
+ }
115
+ function bindEvents(target, events) {
116
+ Object.assign(target, {
117
+ subscribe: events.subscribe.bind(events),
118
+ waitFor: events.waitFor.bind(events),
119
+ events: events.events.bind(events),
120
+ observe: events.observe.bind(events)
121
+ });
122
+ }
package/dist/wire.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Cleanup } from "@phreshos/core";
1
+ import type { Cleanup, ServiceKey } from "@phreshos/core";
2
2
  import Deadline from "./deadline.js";
3
3
  import type { HandleAddress } from "./domain.js";
4
4
  type Handler = (...values: unknown[]) => unknown;
@@ -32,6 +32,8 @@ declare class Wire {
32
32
  observe(target: HandleAddress | null, half: "server" | "client", kind: TrafficKind, event: string | null, handler: Handler, impossible?: Failure): Cleanup;
33
33
  /** Follow destinationless events emitted by one Endpoint. */
34
34
  follow(target: HandleAddress | null, half: "server" | "client", event: string | null, handler: Handler, impossible?: Failure): Cleanup;
35
+ /** Follow one exact service lifecycle or application event route. */
36
+ followService(key: ServiceKey, scope: "lifecycle" | "channel", event: string | null, handler: Handler): Cleanup;
35
37
  samplePointer(movementX: number, movementY: number): void;
36
38
  private register;
37
39
  private unregister;
package/dist/wire.js CHANGED
@@ -194,6 +194,16 @@ class Wire {
194
194
  this.send("end-host", "unfollow", subscription);
195
195
  });
196
196
  }
197
+ /** Follow one exact service lifecycle or application event route. */
198
+ followService(key, scope, event, handler) {
199
+ const subscription = crypto.randomUUID();
200
+ const stop = this.on("service-event", subscription, handler);
201
+ this.send("end-host", "service-follow", subscription, key, scope, event);
202
+ return once(() => {
203
+ stop();
204
+ this.send("end-host", "service-unfollow", subscription);
205
+ });
206
+ }
197
207
  samplePointer(movementX, movementY) {
198
208
  this.send("end-host", "pointerSample", movementX, movementY);
199
209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/client",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "The SDK used by a Program's client endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -46,10 +46,10 @@
46
46
  "prepack": "node --run build"
47
47
  },
48
48
  "peerDependencies": {
49
- "@phreshos/core": "^0.1.1"
49
+ "@phreshos/core": "^0.1.3"
50
50
  },
51
51
  "devDependencies": {
52
- "@phreshos/core": "^0.1.1",
52
+ "@phreshos/core": "^0.1.3",
53
53
  "typescript": "^6.0.3"
54
54
  }
55
55
  }