@phreshos/client 0.1.25 → 0.1.26

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
@@ -80,7 +80,10 @@ const service = system.service({
80
80
  })
81
81
 
82
82
  await service.waitReady()
83
- if (await service.enabled()) await service.channel.ask("value")
83
+ if (await service.enabled()) await service.ask("value")
84
+
85
+ service.subscribe("changed", message => console.log(message))
86
+ service.lifecycle.subscribe("disable", () => console.log("unavailable"))
84
87
  ```
85
88
 
86
89
  A Client may traverse `Process.parent()` through any number of ancestors in
@@ -98,8 +101,8 @@ into the endpoint. Identity, Theme, Process, Window, readiness, lifecycle, and
98
101
  application values enter only in response to an explicit request or a live
99
102
  registration made by Program code.
100
103
 
101
- `Context` combines navigation into the executing Client's Process with its
102
- Channel. The paired Server is explicitly named as
104
+ `Context` combines communication with navigation into the executing Client's
105
+ Process. The paired Server is explicitly named as
103
106
  `context.server`; its publishing, asking, existence, readiness, start, and stop
104
107
  operations never masquerade as properties of `context`. `context.stop()` stops
105
108
  the executing Client, while complete Process exit remains available only
@@ -131,11 +134,12 @@ them. These are the same domain classes used by the Server SDK, so
131
134
  `ClientTraffic`, is a type-only capability owned by Client and has no
132
135
  independent `instanceof` identity.
133
136
 
134
- The executing Client's Channel is composed directly into `context`. Its
137
+ The executing Client's communication belongs directly to `context`. Its
135
138
  subscription tools receive events addressed to this Client, while `publish()`
136
- emits outward from this Client without choosing a destination. Existence,
137
- readiness, and lifecycle operations belong to `context.server`,
138
- `context.stop()`, or an explicit Endpoint handle.
139
+ emits outward from this Client without choosing a destination. Existence and
140
+ readiness belong to `context.server`; the current Client stops through
141
+ `context.stop()`, and explicit Endpoint handles expose their own
142
+ `endpoint.lifecycle` subscriptions.
139
143
 
140
144
  An Endpoint handle is also a selective source: `endpoint.subscribe()` follows
141
145
  destinationless events emitted by that Endpoint. Its `traffic` property remains
package/dist/context.d.ts CHANGED
@@ -1,9 +1,15 @@
1
- import { type Channel } from "./channel.js";
2
- import { Client, Server, type Process, type Program, type Window } from "./domain.js";
1
+ import type { Context as CoreContext, ContextCapture as CoreContextCapture, ContextEvents as CoreContextEvents, ContextMessage as CoreContextMessage } from "@phreshos/core";
2
+ import { Client, Server, type Endpoint, type Process, type Program, type Window } from "./domain.js";
3
3
  /** The executing Process's canonical Server handle. */
4
4
  export type ContextServer<Events extends object = {}> = Server<Events>;
5
- /** Client runtime context: its inbound Channel, owner hierarchy, and paired Server. */
6
- export interface Context<Events extends object = {}> extends Channel<Events>, Pick<Client, "service"> {
5
+ /** One value addressed to the current Client, with a client-visible sender. */
6
+ export type ContextMessage<Payload = unknown> = CoreContextMessage<Payload, Endpoint | null>;
7
+ /** Applies the client-visible sender envelope to known Context events. */
8
+ export type ContextEvents<Events extends object> = CoreContextEvents<Events, Endpoint | null>;
9
+ /** Every event observable through the current Client Context. */
10
+ export type ContextCapture<Events extends object = {}> = CoreContextCapture<Events, Endpoint | null>;
11
+ /** Client runtime context: inbound communication, owner hierarchy, and paired Server. */
12
+ export interface Context<Events extends object = {}> extends CoreContext<Events, Endpoint | null>, Pick<Client, "service"> {
7
13
  /** The same Server handle exposed by the executing Process. */
8
14
  readonly server: ContextServer;
9
15
  /** Presentation capability of the executing Client. */
package/dist/context.js CHANGED
@@ -1,14 +1,15 @@
1
- import { channel } from "./channel.js";
2
1
  import Deadline from "./deadline.js";
3
- import { Client, Server, ServerTrafficHandle, TrafficHandle, bindEvents, endpointEvents, process, program, window as windowHandle } from "./domain.js";
2
+ import { Client, Server, ServerTrafficHandle, TrafficHandle, bindEvents, endpointLifecycle, endpointEvents, process, program, visibleEndpoint, window as windowHandle } from "./domain.js";
3
+ import Events from "./events.js";
4
4
  import wire from "./wire.js";
5
- import { endpointService } from "./service.js";
5
+ import { disableCurrentService, enableCurrentService, endpointService } from "./service.js";
6
6
  import { currentProgramPermission } from "./permissions.js";
7
7
  const ServerBase = Server;
8
8
  const ClientBase = Client;
9
9
  class ContextServerHandle extends ServerBase {
10
10
  owner;
11
11
  traffic = new ServerTrafficHandle(null, "server");
12
+ lifecycle = endpointLifecycle(currentAddress, "server");
12
13
  constructor(owner) {
13
14
  super();
14
15
  this.owner = owner;
@@ -57,10 +58,8 @@ contextServer = new ContextServerHandle(owner);
57
58
  class ContextClientHandle extends ClientBase {
58
59
  owner;
59
60
  traffic = new TrafficHandle(null, "client");
60
- window = windowHandle(async () => {
61
- const identity = await wire.identity();
62
- return { identity: identity.process, reference: identity.reference };
63
- });
61
+ lifecycle = endpointLifecycle(currentAddress, "client");
62
+ window = windowHandle(currentAddress);
64
63
  constructor(owner) {
65
64
  super();
66
65
  this.owner = owner;
@@ -81,12 +80,15 @@ class ContextClientHandle extends ClientBase {
81
80
  service() { return endpointService(null, "client"); }
82
81
  }
83
82
  contextClient = new ContextClientHandle(owner);
84
- class ClientContext {
83
+ class ClientContext extends Events {
85
84
  server = contextServer;
86
85
  window = contextClient.window;
87
86
  permission = currentProgramPermission;
88
87
  constructor() {
89
- bindChannel(this, channel);
88
+ super((event, listener, impossible) => wire.on("end-end", event, value => listener(contextMessage(value)), null, impossible), (listener, impossible) => wire.onAll("end-end", (event, value) => {
89
+ if (typeof event === "string")
90
+ listener(event, contextMessage(value));
91
+ }, null, impossible));
90
92
  }
91
93
  process() { return owner(); }
92
94
  async parent() {
@@ -103,17 +105,17 @@ class ClientContext {
103
105
  }
104
106
  async stop() { await wire.request(["stop-current"]); }
105
107
  service() { return endpointService(null, "client"); }
108
+ publish(event, payload = undefined) { wire.send("end-host", "emit", event, payload); }
109
+ async enableService(name) { await enableCurrentService(name); }
110
+ async disableService() { await disableCurrentService(); }
106
111
  }
107
- function bindChannel(target, source) {
108
- Object.assign(target, {
109
- publish: source.publish.bind(source),
110
- subscribe: source.subscribe.bind(source),
111
- waitFor: source.waitFor.bind(source),
112
- events: source.events.bind(source),
113
- observe: source.observe.bind(source),
114
- enableService: source.enableService.bind(source),
115
- disableService: source.disableService.bind(source)
116
- });
112
+ async function currentAddress() {
113
+ const identity = await wire.identity();
114
+ return { identity: identity.process, reference: identity.reference };
115
+ }
116
+ function contextMessage(value) {
117
+ const raw = value;
118
+ return { from: visibleEndpoint(raw.from), payload: raw.payload };
117
119
  }
118
120
  /** Inbound events, owner hierarchy, and paired Server for this Client runtime. */
119
121
  export const context = new ClientContext();
package/dist/domain.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, type AnswerCapture as CoreAnswerCapture, type AnswerMessage as CoreAnswerMessage, type AnswerObserver as CoreAnswerObserver, type AskCapture as CoreAskCapture, type AskMessage as CoreAskMessage, type AskObserver as CoreAskObserver, type Cleanup, type ClientTraffic as CoreClientTraffic, type ClientDeclaration, type EndpointTraffic as CoreEndpointTraffic, type Exit, type Launch, type LocalWindow, type Permission, type Position, type ProgramProcess as CoreProgramProcess, type ServerTraffic as CoreServerTraffic, type Size, type TrafficMessage as CoreTrafficMessage, type TrafficCapture as CoreTrafficCapture, type TrafficEvents as CoreTrafficEvents, type Window as CoreWindow, type WindowState } from "@phreshos/core";
1
+ import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, type AnswerCapture as CoreAnswerCapture, type AnswerMessage as CoreAnswerMessage, type AnswerSubscriber as CoreAnswerSubscriber, type AskCapture as CoreAskCapture, type AskMessage as CoreAskMessage, type AskSubscriber as CoreAskSubscriber, type Cleanup, type ClientTraffic as CoreClientTraffic, type ClientDeclaration, type EndpointTraffic as CoreEndpointTraffic, type Exit, type EventOptions, type Launch, type LocalWindow, type Outcome, type Permission, type Position, type ProgramProcess as CoreProgramProcess, type ServerTraffic as CoreServerTraffic, type Size, type TrafficMessage as CoreTrafficMessage, type TrafficCapture as CoreTrafficCapture, type TrafficEvents as CoreTrafficEvents, type Window as CoreWindow, type WindowState } from "@phreshos/core";
2
2
  import Events from "./events.js";
3
3
  export interface HandleAddress {
4
4
  identity: string;
@@ -86,13 +86,13 @@ export type AskMessage<Payload = unknown> = CoreAskMessage<Payload, Server | nul
86
86
  /** One question observable in client-visible Endpoint traffic. */
87
87
  export type AskCapture<Payload = unknown> = CoreAskCapture<Payload, Server | null>;
88
88
  /** Callback that observes questions sent by a client-visible Endpoint. */
89
- export type AskObserver<Payload = unknown> = CoreAskObserver<Payload, Server | null>;
89
+ export type AskSubscriber<Payload = unknown> = CoreAskSubscriber<Payload, Server | null>;
90
90
  /** Answer with an Endpoint destination hidden when it crosses the Client boundary. */
91
91
  export type AnswerMessage<Result = unknown> = CoreAnswerMessage<Result, Endpoint | null>;
92
92
  /** One answer observable in client-visible Server traffic. */
93
93
  export type AnswerCapture<Result = unknown> = CoreAnswerCapture<Result, Endpoint | null>;
94
94
  /** Callback that observes answers sent by a client-visible Server. */
95
- export type AnswerObserver<Result = unknown> = CoreAnswerObserver<Result, Endpoint | null>;
95
+ export type AnswerSubscriber<Result = unknown> = CoreAnswerSubscriber<Result, Endpoint | null>;
96
96
  /** Directed communication originating from one client-visible Endpoint. */
97
97
  export type EndpointTraffic<Events extends object = {}> = CoreEndpointTraffic<Events, Endpoint | null, Server | null>;
98
98
  /** Directed communication originating from one client-visible Server. */
@@ -131,15 +131,35 @@ export declare class TrafficHandle extends Events {
131
131
  protected readonly target: HandleAddress | null;
132
132
  protected readonly kind: "server" | "client";
133
133
  constructor(target: HandleAddress | null, kind: "server" | "client");
134
- observeAsks(observer: (capture: AskCapture) => unknown): Cleanup;
134
+ subscribeAsks(subscriber: (capture: AskCapture) => unknown): Cleanup;
135
+ asks(options?: EventOptions): AsyncIterableIterator<Readonly<{
136
+ event: string;
137
+ questionId: string;
138
+ message: Readonly<{
139
+ to: Server<{}> | null;
140
+ payload: unknown;
141
+ }>;
142
+ }>>;
143
+ private followAsks;
135
144
  }
136
145
  export declare class ServerTrafficHandle extends TrafficHandle {
137
- observeAnswers(observer: (capture: AnswerCapture) => unknown): Cleanup;
146
+ subscribeAnswers(subscriber: (capture: AnswerCapture) => unknown): Cleanup;
147
+ answers(options?: EventOptions): AsyncIterableIterator<Readonly<{
148
+ event: string;
149
+ questionId: string;
150
+ message: Readonly<{
151
+ to: Endpoint<{}> | null;
152
+ outcome: Outcome<unknown>;
153
+ }>;
154
+ }>>;
155
+ private followAnswers;
138
156
  }
139
157
  type WindowTarget = () => Promise<HandleAddress>;
140
158
  export declare function scoped(route: string, subject: string | null, convert: (event: string, values: unknown[]) => unknown): Events;
141
159
  /** Destinationless events originating from one Endpoint handle. */
142
160
  export declare function endpointEvents(target: HandleAddress | null, half: "server" | "client"): Events;
161
+ /** Start and stop transitions belonging directly to one permanent Endpoint. */
162
+ export declare function endpointLifecycle(target: WindowTarget, half: "server" | "client"): Events;
143
163
  export declare function exit(code: unknown, signal: unknown): Exit;
144
164
  export declare function trafficMessage(value: unknown): TrafficMessage;
145
165
  export declare function bindEvents(target: object, events: Events): void;
package/dist/domain.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer } from "@phreshos/core";
2
- import Events from "./events.js";
2
+ import Events, { stream } from "./events.js";
3
3
  import Deadline from "./deadline.js";
4
4
  import HandleRegistry from "./handle-registry.js";
5
5
  import { area, sql, store } from "./storage.js";
@@ -165,38 +165,52 @@ export class TrafficHandle extends Events {
165
165
  target;
166
166
  kind;
167
167
  constructor(target, kind) {
168
- super((event, listener, impossible) => wire.observe(target, kind, "publish", event, value => listener(trafficMessage(value)), impossible), observer => wire.observe(target, kind, "publish", null, (event, value) => {
168
+ super((event, listener, impossible) => wire.observe(target, kind, "publish", event, value => listener(trafficMessage(value)), impossible), (listener, impossible) => wire.observe(target, kind, "publish", null, (event, value) => {
169
169
  if (typeof event === "string")
170
- observer(event, trafficMessage(value));
171
- }));
170
+ listener(event, trafficMessage(value));
171
+ }, impossible));
172
172
  this.target = target;
173
173
  this.kind = kind;
174
174
  }
175
- observeAsks(observer) {
175
+ subscribeAsks(subscriber) {
176
+ return this.followAsks(subscriber);
177
+ }
178
+ asks(options) {
179
+ return stream((subscriber, impossible) => this.followAsks(subscriber, impossible), options);
180
+ }
181
+ followAsks(subscriber, impossible) {
176
182
  return wire.observe(this.target, this.kind, "ask", null, (event, questionId, value) => {
177
183
  if (typeof event !== "string" || typeof questionId !== "string")
178
184
  return;
179
- observer({ event, questionId, message: trafficMessage(value) });
180
- });
185
+ subscriber({ event, questionId, message: trafficMessage(value) });
186
+ }, impossible);
181
187
  }
182
188
  }
183
189
  export class ServerTrafficHandle extends TrafficHandle {
184
- observeAnswers(observer) {
190
+ subscribeAnswers(subscriber) {
191
+ return this.followAnswers(subscriber);
192
+ }
193
+ answers(options) {
194
+ return stream((subscriber, impossible) => this.followAnswers(subscriber, impossible), options);
195
+ }
196
+ followAnswers(subscriber, impossible) {
185
197
  return wire.observe(this.target, "server", "answer", null, (event, questionId, value) => {
186
198
  if (typeof event !== "string" || typeof questionId !== "string")
187
199
  return;
188
200
  const raw = value;
189
- observer({ event, questionId, message: { to: visibleEndpoint(raw.to), outcome: raw.outcome } });
190
- });
201
+ subscriber({ event, questionId, message: { to: visibleEndpoint(raw.to), outcome: raw.outcome } });
202
+ }, impossible);
191
203
  }
192
204
  }
193
205
  class ServerHandle extends ServerBase {
194
206
  owner;
195
207
  traffic;
208
+ lifecycle;
196
209
  constructor(owner) {
197
210
  super();
198
211
  this.owner = owner;
199
212
  this.traffic = new ServerTrafficHandle(owner.address, "server");
213
+ this.lifecycle = endpointLifecycle(() => Promise.resolve(owner.address), "server");
200
214
  bindEvents(this, endpointEvents(owner.address, "server"));
201
215
  }
202
216
  async process() { return this.owner; }
@@ -234,11 +248,13 @@ class ServerHandle extends ServerBase {
234
248
  class ClientHandle extends ClientBase {
235
249
  owner;
236
250
  traffic;
251
+ lifecycle;
237
252
  window;
238
253
  constructor(owner) {
239
254
  super();
240
255
  this.owner = owner;
241
256
  this.traffic = new TrafficHandle(owner.address, "client");
257
+ this.lifecycle = endpointLifecycle(() => Promise.resolve(owner.address), "client");
242
258
  this.window = window(async () => owner.address);
243
259
  bindEvents(this, endpointEvents(owner.address, "client"));
244
260
  }
@@ -328,13 +344,13 @@ function deferredScoped(route, target, convert) {
328
344
  listener(convert(event, message));
329
345
  }, subject, impossible), impossible);
330
346
  },
331
- observer => deferred(target, subject => wire.onAll(route, (event, ...values) => {
347
+ (listener, impossible) => deferred(target, subject => wire.onAll(route, (event, ...values) => {
332
348
  if (typeof event !== "string" || !windowEvent(event))
333
349
  return;
334
350
  const message = unscoped(subject, values);
335
351
  if (message)
336
- observer(event, convert(event, message));
337
- }, subject))
352
+ listener(event, convert(event, message));
353
+ }, subject, impossible), impossible)
338
354
  ];
339
355
  }
340
356
  function windowEvent(event) {
@@ -363,20 +379,41 @@ export function scoped(route, subject, convert) {
363
379
  const message = unscoped(subject, values);
364
380
  if (message)
365
381
  listener(convert(event, message));
366
- }, subject, impossible), observer => wire.onAll(route, (event, ...values) => {
382
+ }, subject, impossible), (listener, impossible) => wire.onAll(route, (event, ...values) => {
367
383
  if (typeof event !== "string")
368
384
  return;
369
385
  const message = unscoped(subject, values);
370
386
  if (message)
371
- observer(event, convert(event, message));
372
- }, subject));
387
+ listener(event, convert(event, message));
388
+ }, subject, impossible));
373
389
  }
374
390
  /** Destinationless events originating from one Endpoint handle. */
375
391
  export function endpointEvents(target, half) {
376
- return new Events((event, listener, impossible) => wire.follow(target, half, event, listener, impossible), observer => wire.follow(target, half, null, (event, payload) => {
392
+ return new Events((event, listener, impossible) => wire.follow(target, half, event, listener, impossible), (listener, impossible) => wire.follow(target, half, null, (event, payload) => {
377
393
  if (typeof event === "string")
378
- observer(event, payload);
379
- }));
394
+ listener(event, payload);
395
+ }, impossible));
396
+ }
397
+ /** Start and stop transitions belonging directly to one permanent Endpoint. */
398
+ export function endpointLifecycle(target, half) {
399
+ return new Events((event, listener, impossible) => deferred(target, subject => wire.on("process-host", endpointLifecycleEvent(event), (...values) => {
400
+ const message = unscoped(subject, values);
401
+ if (message?.[1] === half)
402
+ listener(undefined);
403
+ }, subject, impossible), impossible), (listener, impossible) => deferred(target, subject => wire.onAll("process-host", (event, ...values) => {
404
+ if (event !== "endpointStart" && event !== "endpointStop")
405
+ return;
406
+ const message = unscoped(subject, values);
407
+ if (message?.[1] === half)
408
+ listener(event === "endpointStart" ? "start" : "stop", undefined);
409
+ }, subject, impossible), impossible));
410
+ }
411
+ function endpointLifecycleEvent(event) {
412
+ if (event === "start")
413
+ return "endpointStart";
414
+ if (event === "stop")
415
+ return "endpointStop";
416
+ return event;
380
417
  }
381
418
  function unscoped(subject, values) {
382
419
  if (subject === null)
@@ -426,8 +463,7 @@ export function bindEvents(target, events) {
426
463
  Object.assign(target, {
427
464
  subscribe: events.subscribe.bind(events),
428
465
  waitFor: events.waitFor.bind(events),
429
- events: events.events.bind(events),
430
- observe: events.observe.bind(events)
466
+ events: events.events.bind(events)
431
467
  });
432
468
  }
433
469
  export function program(record) {
package/dist/events.d.ts CHANGED
@@ -2,15 +2,19 @@ import type { Capture, Cleanup, EventOptions } from "@phreshos/core";
2
2
  export declare const defaultTimeout = 10000;
3
3
  type Failure = (error: Error) => void;
4
4
  type Listener = (message: unknown) => unknown;
5
- type Observer = (event: string, message: unknown) => unknown;
5
+ type EveryListener = (event: string, message: unknown) => unknown;
6
+ type Register<Message> = (listener: (message: Message) => unknown, impossible?: Failure) => Cleanup;
6
7
  /** SDK-owned waits, queues and callbacks over boundary forwarding state. */
7
8
  export default class Events {
8
9
  private readonly listen;
9
10
  private readonly listenAll;
10
- constructor(listen: (event: string, listener: Listener, impossible?: Failure) => Cleanup, listenAll: (observer: Observer) => Cleanup);
11
+ constructor(listen: (event: string, listener: Listener, impossible?: Failure) => Cleanup, listenAll: (listener: EveryListener, impossible?: Failure) => Cleanup);
11
12
  subscribe(event: string, subscriber: Listener): Cleanup;
13
+ subscribe(subscriber: (capture: Capture<string, unknown>) => unknown): Cleanup;
12
14
  waitFor(event: string, timeout?: number): Promise<unknown>;
13
15
  events(event: string, options?: EventOptions): AsyncIterableIterator<unknown>;
14
- observe(observer: (capture: Capture<string, unknown>) => unknown): Cleanup;
16
+ events(options?: EventOptions): AsyncIterableIterator<Capture<string, unknown>>;
15
17
  }
18
+ /** Converts one persistent registration into a bounded asynchronous iterator. */
19
+ export declare function stream<Message>(register: Register<Message>, options?: EventOptions): AsyncIterableIterator<Message>;
16
20
  export {};
package/dist/events.js CHANGED
@@ -7,8 +7,10 @@ export default class Events {
7
7
  this.listen = listen;
8
8
  this.listenAll = listenAll;
9
9
  }
10
- subscribe(event, subscriber) {
11
- return this.listen(event, subscriber);
10
+ subscribe(eventOrSubscriber, subscriber) {
11
+ if (typeof eventOrSubscriber === "string")
12
+ return this.listen(eventOrSubscriber, subscriber);
13
+ return this.listenAll((event, message) => eventOrSubscriber({ event, message }));
12
14
  }
13
15
  waitFor(event, timeout = defaultTimeout) {
14
16
  return new Promise((resolve, reject) => {
@@ -26,58 +28,63 @@ export default class Events {
26
28
  stop = this.listen(event, message => finish(() => resolve(message)), error => finish(() => reject(error)));
27
29
  });
28
30
  }
29
- events(event, options = {}) {
30
- const capacity = options.capacity ?? 64;
31
- if (capacity !== Infinity && (!Number.isInteger(capacity) || capacity < 0)) {
32
- throw new Error("An event queue capacity must be a non-negative integer or Infinity");
31
+ events(eventOrOptions = {}, namedOptions = {}) {
32
+ if (typeof eventOrOptions === "string") {
33
+ return stream((listener, impossible) => this.listen(eventOrOptions, listener, impossible), namedOptions);
33
34
  }
34
- const listen = this.listen;
35
- return (async function* () {
36
- const queue = [];
37
- let ended = false;
38
- let failure = null;
39
- let wake = null;
40
- const stop = listen(event, message => {
41
- if (ended || failure)
42
- return;
43
- if (queue.length >= capacity)
44
- failure = new Error(`Event queue exceeded its capacity of ${capacity}`);
45
- else
46
- queue.push(message);
47
- wake?.();
48
- wake = null;
49
- }, error => {
50
- if (ended || failure)
51
- return;
52
- failure = error;
53
- wake?.();
54
- wake = null;
55
- });
56
- const abort = () => {
57
- ended = true;
58
- wake?.();
59
- wake = null;
60
- };
61
- options.signal?.addEventListener("abort", abort, { once: true });
62
- try {
63
- while (!ended) {
64
- if (queue.length) {
65
- yield queue.shift();
66
- continue;
67
- }
68
- if (failure)
69
- throw failure;
70
- await new Promise(resolve => { wake = resolve; });
71
- }
72
- }
73
- finally {
74
- ended = true;
75
- stop();
76
- options.signal?.removeEventListener("abort", abort);
77
- }
78
- })();
35
+ return stream((listener, impossible) => this.listenAll((event, message) => listener({ event, message }), impossible), eventOrOptions);
79
36
  }
80
- observe(observer) {
81
- return this.listenAll((event, message) => observer({ event, message }));
37
+ }
38
+ /** Converts one persistent registration into a bounded asynchronous iterator. */
39
+ export function stream(register, options = {}) {
40
+ const capacity = options.capacity ?? 64;
41
+ if (capacity !== Infinity && (!Number.isInteger(capacity) || capacity < 0)) {
42
+ throw new Error("An event queue capacity must be a non-negative integer or Infinity");
82
43
  }
44
+ return (async function* () {
45
+ const queue = [];
46
+ let ended = false;
47
+ let failure = null;
48
+ let wake = null;
49
+ const stop = register(message => {
50
+ if (ended || failure)
51
+ return;
52
+ if (queue.length >= capacity)
53
+ failure = new Error(`Event queue exceeded its capacity of ${capacity}`);
54
+ else
55
+ queue.push(message);
56
+ wake?.();
57
+ wake = null;
58
+ }, error => {
59
+ if (ended || failure)
60
+ return;
61
+ failure = error;
62
+ wake?.();
63
+ wake = null;
64
+ });
65
+ const abort = () => {
66
+ ended = true;
67
+ wake?.();
68
+ wake = null;
69
+ };
70
+ options.signal?.addEventListener("abort", abort, { once: true });
71
+ if (options.signal?.aborted)
72
+ abort();
73
+ try {
74
+ while (!ended) {
75
+ if (queue.length) {
76
+ yield queue.shift();
77
+ continue;
78
+ }
79
+ if (failure)
80
+ throw failure;
81
+ await new Promise(resolve => { wake = resolve; });
82
+ }
83
+ }
84
+ finally {
85
+ ended = true;
86
+ stop();
87
+ options.signal?.removeEventListener("abort", abort);
88
+ }
89
+ })();
83
90
  }
package/dist/main.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  export { system, type System } from "./system.js";
2
2
  export { type SystemPointer, type PointerEvents, type PointerPosition } from "./pointer.js";
3
3
  export { type DesktopEvents, type DesktopSize, type SystemDesktop } from "./desktop.js";
4
- export { context, type Context, type ContextServer } from "./context.js";
5
- export { type Channel, type ChannelCapture, type ChannelEvents, type ChannelMessage } from "./channel.js";
6
- export { ClientService, ServerService, Service, type ClientServiceChannel, type ServerServiceChannel, type ServiceChannel, type ServiceKey, type ServiceLifecycleEvents } from "@phreshos/core";
7
- export { Client, Endpoint, Process, Program, Server, type Window, type ProgramProcess, 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";
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, Permission, Position, Storage, ProgramEvents, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, SystemUploads, Upload, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermission, Timeoutable, Appearance, AppearanceEvents, AppearanceSource, AppearanceSurface, AnimationsPreference, DesktopPreferences, DesktopPreferencesEvents, DesktopPreferencesUpdate, SystemDesktopPreferences, ThemedValue, Theme, ThemePreference, Value, WindowEvents, WindowGeometry, WindowLayer, WindowState, Easing, LocalWindow, LocalWindowSurface, VisibilityTransition, Transaction } from "@phreshos/core";
4
+ export { context, type Context, type ContextCapture, type ContextEvents, type ContextMessage, type ContextServer } from "./context.js";
5
+ export { ClientService, ServerService, Service, type ServiceKey, type ServiceLifecycle, type ServiceLifecycleEvents } from "@phreshos/core";
6
+ export { Client, Endpoint, Process, Program, Server, type Window, type ProgramProcess, type AnswerCapture, type AnswerMessage, type AnswerSubscriber, type AskCapture, type AskMessage, type AskSubscriber, type ClientTraffic, type EndpointTraffic, type ServerTraffic, type TrafficCapture, type TrafficEvents, type TrafficMessage } from "./domain.js";
7
+ export type { Askable, Capture, Captures, CaptureSubscriber, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EndpointLifecycle, EndpointLifecycleEvents, EntryStat, EventMessage, EventName, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permission, Position, Storage, ProgramEvents, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, SystemUploads, Upload, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermission, Timeoutable, Appearance, AppearanceEvents, AppearanceSource, AppearanceSurface, AnimationsPreference, DesktopPreferences, DesktopPreferencesEvents, DesktopPreferencesUpdate, SystemDesktopPreferences, ThemedValue, Theme, ThemePreference, Value, WindowEvents, WindowGeometry, WindowLayer, WindowState, Easing, LocalWindow, LocalWindowSurface, VisibilityTransition, Transaction } from "@phreshos/core";
package/dist/main.js CHANGED
@@ -2,6 +2,5 @@ export { system } from "./system.js";
2
2
  export {} from "./pointer.js";
3
3
  export {} from "./desktop.js";
4
4
  export { context } from "./context.js";
5
- export {} from "./channel.js";
6
5
  export { ClientService, ServerService, Service } from "@phreshos/core";
7
6
  export { Client, Endpoint, Process, Program, Server } from "./domain.js";
package/dist/service.js CHANGED
@@ -6,11 +6,16 @@ import wire from "./wire.js";
6
6
  const handles = new HandleRegistry();
7
7
  const ServerServiceBase = CoreServerService;
8
8
  const ClientServiceBase = CoreClientService;
9
- class ServerChannelHandle extends Events {
9
+ class ServerHandler extends ServerServiceBase {
10
10
  key;
11
+ name;
12
+ lifecycle;
11
13
  constructor(key) {
12
- super(...serviceEvents(key, "channel"));
14
+ super();
13
15
  this.key = key;
16
+ this.name = key.name;
17
+ this.lifecycle = new Events(...serviceEvents(key, "lifecycle"));
18
+ bindEvents(this, new Events(...serviceEvents(key, "events")));
14
19
  }
15
20
  publish(event, payload = undefined) {
16
21
  wire.send("end-host", "service-send", this.key, event, payload);
@@ -25,6 +30,13 @@ class ServerChannelHandle extends Events {
25
30
  }
26
31
  };
27
32
  }
33
+ async enabled() {
34
+ const answer = await wire.request(["service-enabled", this.key]);
35
+ return answer[0];
36
+ }
37
+ async waitReady(timeout) {
38
+ await wire.request(["service-wait-ready", this.key, timeout], timeout);
39
+ }
28
40
  async askWithin(deadline, event, payload) {
29
41
  const identity = await wire.identity();
30
42
  const address = `client:${identity.process}:${crypto.randomUUID()}`;
@@ -39,40 +51,16 @@ class ServerChannelHandle extends Events {
39
51
  }
40
52
  }
41
53
  }
42
- class ClientChannelHandle extends Events {
43
- constructor(key) {
44
- super(...serviceEvents(key, "channel"));
45
- }
46
- }
47
- class ServerHandler extends ServerServiceBase {
48
- key;
49
- name;
50
- channel;
51
- constructor(key) {
52
- super();
53
- this.key = key;
54
- this.name = key.name;
55
- this.channel = new ServerChannelHandle(key);
56
- bindEvents(this, new Events(...serviceEvents(key, "lifecycle")));
57
- }
58
- async enabled() {
59
- const answer = await wire.request(["service-enabled", this.key]);
60
- return answer[0];
61
- }
62
- async waitReady(timeout) {
63
- await wire.request(["service-wait-ready", this.key, timeout], timeout);
64
- }
65
- }
66
54
  class ClientHandler extends ClientServiceBase {
67
55
  key;
68
56
  name;
69
- channel;
57
+ lifecycle;
70
58
  constructor(key) {
71
59
  super();
72
60
  this.key = key;
73
61
  this.name = key.name;
74
- this.channel = new ClientChannelHandle(key);
75
- bindEvents(this, new Events(...serviceEvents(key, "lifecycle")));
62
+ this.lifecycle = new Events(...serviceEvents(key, "lifecycle"));
63
+ bindEvents(this, new Events(...serviceEvents(key, "events")));
76
64
  }
77
65
  async enabled() {
78
66
  const answer = await wire.request(["service-enabled", this.key]);
@@ -106,9 +94,9 @@ export async function endpointService(target, endpoint) {
106
94
  function serviceEvents(key, scope) {
107
95
  return [
108
96
  (event, listener) => wire.followService(key, scope, event, listener),
109
- (observer) => wire.followService(key, scope, null, (event, payload) => {
97
+ (listener) => wire.followService(key, scope, null, (event, payload) => {
110
98
  if (typeof event === "string")
111
- observer(event, payload);
99
+ listener(event, payload);
112
100
  })
113
101
  ];
114
102
  }
@@ -116,7 +104,6 @@ function bindEvents(target, events) {
116
104
  Object.assign(target, {
117
105
  subscribe: events.subscribe.bind(events),
118
106
  waitFor: events.waitFor.bind(events),
119
- events: events.events.bind(events),
120
- observe: events.observe.bind(events)
107
+ events: events.events.bind(events)
121
108
  });
122
109
  }
package/dist/wire.d.ts CHANGED
@@ -32,12 +32,12 @@ declare class Wire {
32
32
  expectWithin(question: string, deadline: Deadline): Promise<unknown>;
33
33
  forget(question: string): void;
34
34
  on(route: string, event: string, handler: Handler, subject?: string | null, impossible?: Failure): Cleanup;
35
- onAll(route: string, handler: Handler, subject?: string | null): Cleanup;
35
+ onAll(route: string, handler: Handler, subject?: string | null, impossible?: Failure): Cleanup;
36
36
  observe(target: HandleAddress | null, half: "server" | "client", kind: TrafficKind, event: string | null, handler: Handler, impossible?: Failure): Cleanup;
37
37
  /** Follow destinationless events emitted by one Endpoint. */
38
38
  follow(target: HandleAddress | null, half: "server" | "client", event: string | null, handler: Handler, impossible?: Failure): Cleanup;
39
39
  /** Follow one exact service lifecycle or application event route. */
40
- followService(key: ServiceKey, scope: "lifecycle" | "channel", event: string | null, handler: Handler): Cleanup;
40
+ followService(key: ServiceKey, scope: "lifecycle" | "events", event: string | null, handler: Handler): Cleanup;
41
41
  samplePointer(movementX: number, movementY: number): void;
42
42
  private register;
43
43
  private unregister;
package/dist/wire.js CHANGED
@@ -221,11 +221,11 @@ class Wire {
221
221
  this.unregister(subscription);
222
222
  });
223
223
  }
224
- onAll(route, handler, subject = null) {
224
+ onAll(route, handler, subject = null, impossible) {
225
225
  const handlers = this.every.get(route) ?? new Set();
226
226
  handlers.add(handler);
227
227
  this.every.set(route, handlers);
228
- const subscription = this.register("publish", route, null, subject);
228
+ const subscription = this.register("publish", route, null, subject, impossible);
229
229
  return once(() => {
230
230
  handlers.delete(handler);
231
231
  if (!handlers.size)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/client",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "The SDK used by a Program's client endpoint.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -47,13 +47,13 @@
47
47
  "prepack": "node --run build"
48
48
  },
49
49
  "peerDependencies": {
50
- "@phreshos/core": "^0.1.24"
50
+ "@phreshos/core": "^0.1.26"
51
51
  },
52
52
  "dependencies": {
53
53
  "@msgpack/msgpack": "^3.1.3"
54
54
  },
55
55
  "devDependencies": {
56
- "@phreshos/core": "^0.1.24",
56
+ "@phreshos/core": "^0.1.26",
57
57
  "typescript": "^6.0.3"
58
58
  }
59
59
  }
package/dist/channel.d.ts DELETED
@@ -1,12 +0,0 @@
1
- import type { Channel as CoreChannel, ChannelCapture as CoreChannelCapture, ChannelEvents as CoreChannelEvents, ChannelMessage as CoreChannelMessage } from "@phreshos/core";
2
- import { type Endpoint } from "./domain.js";
3
- /** One value addressed to the current Client, with a client-visible sender. */
4
- export type ChannelMessage<Payload = unknown> = CoreChannelMessage<Payload, Endpoint | null>;
5
- /** Applies the client-visible sender envelope to known Channel events. */
6
- export type ChannelEvents<Events extends object> = CoreChannelEvents<Events, Endpoint | null>;
7
- /** Every event observable through the current Client's Channel. */
8
- export type ChannelCapture<Events extends object = {}> = CoreChannelCapture<Events, Endpoint | null>;
9
- /** Events explicitly accepted by the current Client. */
10
- export interface Channel<Events extends object = {}> extends CoreChannel<Events, Endpoint | null> {
11
- }
12
- export declare const channel: Channel;
package/dist/channel.js DELETED
@@ -1,22 +0,0 @@
1
- import { visibleEndpoint } from "./domain.js";
2
- import Events from "./events.js";
3
- import wire from "./wire.js";
4
- import { disableCurrentService, enableCurrentService } from "./service.js";
5
- class ClientChannel extends Events {
6
- constructor() {
7
- super((event, listener, impossible) => wire.on("end-end", event, value => listener(message(value)), null, impossible), observer => wire.onAll("end-end", (event, value) => {
8
- if (typeof event === "string")
9
- observer(event, message(value));
10
- }));
11
- }
12
- publish(event, payload = undefined) {
13
- wire.send("end-host", "emit", event, payload);
14
- }
15
- async enableService(name) { await enableCurrentService(name); }
16
- async disableService() { await disableCurrentService(); }
17
- }
18
- function message(value) {
19
- const raw = value;
20
- return { from: visibleEndpoint(raw.from), payload: raw.payload };
21
- }
22
- export const channel = new ClientChannel();