@phreshos/node 0.1.17 → 0.1.19

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
@@ -56,6 +56,12 @@ bun run verify
56
56
  `verify` checks the types, builds the package, runs the connection and Project
57
57
  tests, and validates the published package shape independently.
58
58
 
59
+ `check` performs static checks, `build` creates distributable output, and `test`
60
+ runs Vitest assertions from `tests/`. Run `build` before testing built artifacts.
61
+ `verify` runs `check`, `build`, and `test` in order. Operational tooling belongs
62
+ in `scripts/`; tests and their fixtures belong in `tests/`. Verification uses
63
+ the committed dependency graph without local package substitutions.
64
+
59
65
  ## Related repositories
60
66
 
61
67
  - [`@phreshos/core`](https://github.com/PhreshOS/core) owns the shared System
package/dist/events.d.ts CHANGED
@@ -1,14 +1,15 @@
1
- import type { Cleanup, EventOptions, Subscribable } from "@phreshos/core";
1
+ import { subscribableDefinition, type Cleanup, type EventOptions, type Subscribable, type SubscribableDefinition } from "@phreshos/core";
2
2
  type Failure = (error: Error) => void;
3
3
  type Register<Message> = (subscriber: (message: Message) => unknown, impossible?: Failure) => Cleanup;
4
4
  type Subscribe = (event: string | null, subscriber: (message: unknown) => unknown, impossible?: Failure) => Cleanup;
5
5
  /** Adapts one live representation source into the shared Subscribable contract. */
6
- export default class Events<Definitions extends object, Fallback = never> {
6
+ export default class Events<Definitions extends object, Fallback = never> implements Subscribable<Definitions, Fallback> {
7
7
  private readonly names;
8
8
  private readonly register;
9
+ readonly [subscribableDefinition]?: SubscribableDefinition<Definitions, Fallback>;
9
10
  constructor(names: readonly string[], register: Subscribe);
10
11
  readonly subscribe: Subscribable<Definitions, Fallback>["subscribe"];
11
- readonly waitFor: Subscribable<Definitions, Fallback>["waitFor"];
12
+ readonly wait: Subscribable<Definitions, Fallback>["wait"];
12
13
  readonly events: Subscribable<Definitions, Fallback>["events"];
13
14
  private listen;
14
15
  }
package/dist/events.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { subscribableDefinition } from "@phreshos/core";
1
2
  /** Adapts one live representation source into the shared Subscribable contract. */
2
3
  export default class Events {
3
4
  names;
@@ -19,7 +20,7 @@ export default class Events {
19
20
  eventOrSubscriber({ event: capture.event, message: capture.payload });
20
21
  });
21
22
  });
22
- waitFor = ((event, timeout = 10_000) => new Promise((resolve, reject) => {
23
+ wait = ((event, timeout = 10_000) => new Promise((resolve, reject) => {
23
24
  let stop = () => undefined;
24
25
  const timer = setTimeout(() => {
25
26
  stop();
@@ -0,0 +1,3 @@
1
+ import type { Network } from "@phreshos/core";
2
+ /** Networking shares the lifetime of its owning System connection. */
3
+ export default function network(signal: () => AbortSignal): Network;
@@ -0,0 +1,13 @@
1
+ import websocket from "./websocket.js";
2
+ /** Networking shares the lifetime of its owning System connection. */
3
+ export default function network(signal) {
4
+ return {
5
+ async fetch(input, init) {
6
+ const request = new Request(input, init);
7
+ return fetch(request, { signal: AbortSignal.any([request.signal, signal()]) });
8
+ },
9
+ websocket(url, protocols) {
10
+ return websocket(url, protocols, signal());
11
+ }
12
+ };
13
+ }
package/dist/project.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isRelativeValue, layers, } from "@phreshos/core";
1
+ import { isRelativeValue, parseLaunch, layers, } from "@phreshos/core";
2
2
  import AdmZip from "adm-zip";
3
3
  import { createHash } from "node:crypto";
4
4
  import { spawn } from "node:child_process";
@@ -61,34 +61,47 @@ export class Project {
61
61
  if (mode === "development" && !config.server?.development && !config.client?.development) {
62
62
  throw new Error("Nothing here says how this Program is developed");
63
63
  }
64
- return {
64
+ const definition = {
65
65
  identity: config.identity,
66
66
  name: config.name,
67
67
  version: config.version,
68
68
  description: config.description,
69
+ options: config.options,
70
+ startup: config.startup,
71
+ categories: config.categories,
72
+ keywords: config.keywords,
73
+ website: config.website,
69
74
  icon: config.icon && resolve(this.directory, config.icon),
70
75
  agent: config.agent && resolve(this.directory, config.agent),
71
- storage: resolve(this.directory, "storage"),
72
- ...server && { server: {
73
- location: resolve(this.directory, server.location),
74
- start: server.start,
75
- service: server.service,
76
- installCommand: config.server?.installCommand,
77
- uninstallCommand: config.server?.uninstallCommand,
78
- ...serverExecution(server)
79
- } },
80
- ...client && { client: {
81
- location: /^https?:\/\//i.test(client.location) ? client.location : resolve(this.directory, client.location),
82
- start: client.start,
83
- service: client.service,
84
- title: config.client?.title,
85
- size: config.client?.size,
86
- position: config.client?.position,
87
- layer: config.client?.layer,
88
- minimize: config.client?.minimize,
89
- permissions: config.client?.permissions
90
- } }
76
+ storage: resolve(this.directory, "storage")
91
77
  };
78
+ const serverDefinition = server ? {
79
+ location: resolve(this.directory, server.location),
80
+ start: server.start,
81
+ service: server.service,
82
+ installCommand: config.server?.installCommand,
83
+ uninstallCommand: config.server?.uninstallCommand,
84
+ ...serverExecution(server)
85
+ } : null;
86
+ const clientDefinition = client ? {
87
+ location: /^https?:\/\//i.test(client.location) ? client.location : resolve(this.directory, client.location),
88
+ start: client.start,
89
+ service: client.service,
90
+ title: config.client?.title,
91
+ size: config.client?.size,
92
+ position: config.client?.position,
93
+ layer: config.client?.layer,
94
+ minimize: config.client?.minimize,
95
+ maximize: config.client?.maximize,
96
+ permissions: config.client?.permissions
97
+ } : null;
98
+ if (serverDefinition && clientDefinition)
99
+ return { ...definition, server: serverDefinition, client: clientDefinition };
100
+ if (serverDefinition)
101
+ return { ...definition, server: serverDefinition };
102
+ if (clientDefinition)
103
+ return { ...definition, client: clientDefinition };
104
+ throw new Error("A Program must define a Server, a Client, or both");
92
105
  }
93
106
  /** Run the optional author-owned production build command. */
94
107
  async build() {
@@ -208,6 +221,9 @@ function clientHalf(half, mode, developmentUrl) {
208
221
  : declared;
209
222
  }
210
223
  function validateConfig(config) {
224
+ parseLaunch({ options: config.options });
225
+ if (config.startup !== undefined && typeof config.startup !== "boolean")
226
+ parseLaunch(config.startup);
211
227
  if (typeof config.identity !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(config.identity)) {
212
228
  throw new Error("A Program's identity must be kebab-case");
213
229
  }
@@ -304,6 +320,8 @@ function packageDefinition(config, version) {
304
320
  name: config.name,
305
321
  version,
306
322
  description: config.description,
323
+ options: config.options,
324
+ startup: config.startup,
307
325
  icon: config.icon ? "icon.png" : undefined,
308
326
  agent: config.agent ? "agent.md" : undefined,
309
327
  categories: config.categories,
@@ -326,6 +344,7 @@ function packageDefinition(config, version) {
326
344
  position: config.client.position,
327
345
  layer: config.client.layer,
328
346
  minimize: config.client.minimize,
347
+ maximize: config.client.maximize,
329
348
  permissions: config.client.permissions
330
349
  } }
331
350
  };
@@ -1,32 +1,17 @@
1
- import type { Appearance, ClientDeclaration, EndpointDeclaration, ServiceKey, WindowGeometry, WindowLayer } from "@phreshos/core";
1
+ import { type Appearance, type ProcessSnapshot, type ProgramSnapshot, type ServiceKey, type WindowState as CoreWindowState } from "@phreshos/core";
2
2
  import type { GatewayConnection } from "./transport.js";
3
- export interface ProgramState {
4
- reference: string;
5
- identity: string;
6
- assetId: string;
3
+ export type ProgramState = ProgramSnapshot & Readonly<{
7
4
  installed: boolean;
8
- name: string;
9
- version: string | null;
10
- description: string | null;
11
- hasAgent: boolean;
12
- server: EndpointDeclaration | null;
13
- client: ClientDeclaration | null;
14
- }
15
- export interface WindowState {
16
- title: string;
17
- position: WindowGeometry["position"];
18
- size: WindowGeometry["size"];
5
+ }>;
6
+ export type WindowState = Omit<CoreWindowState, "front"> & Readonly<{
19
7
  depth: number;
20
- minimized: boolean;
21
- layer: WindowLayer;
22
- location: string;
23
- }
8
+ }>;
24
9
  export interface ProcessIdentityState {
25
10
  reference: string;
26
11
  identity: string;
27
12
  name: string | null;
28
13
  program: string;
29
- options: Record<string, string>;
14
+ options: ProcessSnapshot["options"];
30
15
  startedAt: Date;
31
16
  }
32
17
  export interface ProcessState extends ProcessIdentityState {
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { parseProgramSnapshot, parseAppearance } from "@phreshos/core";
2
3
  const maximumStreamQueue = 256;
3
4
  /** A connection-owned, live representation of the authoritative System model. */
4
5
  export default class SystemRepresentation {
@@ -13,7 +14,7 @@ export default class SystemRepresentation {
13
14
  this.connection = connection;
14
15
  const session = ownerSession(connection.session);
15
16
  this.authorization = session.authorization;
16
- this.appearance = session.linkManager.appearance.value;
17
+ this.appearance = parseAppearance(session.linkManager.appearance.value);
17
18
  for (const [, value] of session.authManager.programManager.programs) {
18
19
  const program = programState(value);
19
20
  this.programs.set(program.identity, program);
@@ -133,7 +134,7 @@ export default class SystemRepresentation {
133
134
  followModel(appearance) {
134
135
  const subscribe = (event, listener) => this.release.push(this.connection.subscribe(event, listener));
135
136
  subscribe(`property-update:${appearance}`, value => {
136
- this.appearance = value;
137
+ this.appearance = parseAppearance(value);
137
138
  this.emit("appearance", this.appearance);
138
139
  });
139
140
  subscribe("/auth/program/create", value => this.arriveProgram("create", value));
@@ -148,7 +149,7 @@ export default class SystemRepresentation {
148
149
  subscribe("/auth/process/client-stop", (identity, value) => this.changeEndpoint(identity, "client", value, false));
149
150
  subscribe("/auth/process/client-access", (identity, value) => this.changeEndpoint(identity, "client", value));
150
151
  subscribe("/auth/process/exited", (value, code, signal) => this.exitProcess(value, code, signal));
151
- for (const event of ["move", "resize", "geometry", "change-title", "raise", "minimize"]) {
152
+ for (const event of ["move", "resize", "geometry", "change-title", "raise", "minimize", "maximize"]) {
152
153
  subscribe(`/auth/process/${event}`, value => this.changeWindow(event, value));
153
154
  }
154
155
  }
@@ -253,21 +254,10 @@ function ownerSession(value) {
253
254
  };
254
255
  }
255
256
  function programState(value) {
256
- if (!record(value) || typeof value.reference !== "string" || typeof value.identity !== "string" || typeof value.assetId !== "string" || typeof value.name !== "string") {
257
- throw new Error("The System returned an invalid Program");
258
- }
259
- return {
260
- reference: value.reference,
261
- identity: value.identity,
262
- assetId: value.assetId,
263
- installed: value.installed === true,
264
- name: value.name,
265
- version: typeof value.version === "string" ? value.version : null,
266
- description: typeof value.description === "string" ? value.description : null,
267
- hasAgent: value.hasAgent === true,
268
- server: value.server,
269
- client: value.client
270
- };
257
+ const parsed = parseProgramSnapshot(value);
258
+ if (parsed.installed === undefined)
259
+ throw new Error("The System returned a Program without installation state");
260
+ return { ...parsed, installed: parsed.installed };
271
261
  }
272
262
  function processState(value) {
273
263
  const identity = processIdentityState(value);
@@ -307,7 +297,7 @@ export function processIdentityState(value) {
307
297
  };
308
298
  }
309
299
  function windowState(value) {
310
- if (!record(value) || typeof value.title !== "string" || typeof value.location !== "string" || typeof value.depth !== "number" || typeof value.minimized !== "boolean") {
300
+ if (!record(value) || typeof value.title !== "string" || typeof value.depth !== "number" || typeof value.minimized !== "boolean" || typeof value.maximized !== "boolean") {
311
301
  throw new Error("The System returned an invalid Window");
312
302
  }
313
303
  return {
@@ -316,8 +306,8 @@ function windowState(value) {
316
306
  size: value.size,
317
307
  depth: value.depth,
318
308
  minimized: value.minimized,
319
- layer: value.layer,
320
- location: value.location
309
+ maximized: value.maximized,
310
+ layer: value.layer
321
311
  };
322
312
  }
323
313
  function windowMessage(event, process) {
@@ -332,6 +322,8 @@ function windowMessage(event, process) {
332
322
  return window.title;
333
323
  if (event === "minimize")
334
324
  return window.minimized;
325
+ if (event === "maximize")
326
+ return window.maximized;
335
327
  return true;
336
328
  }
337
329
  function camel(value) { return value === "change-title" ? "changeTitle" : value; }
package/dist/system.d.ts CHANGED
@@ -11,8 +11,7 @@ export declare class System implements CoreSystem {
11
11
  readonly program: SystemProgram;
12
12
  readonly process: SystemProcess;
13
13
  readonly uploads: SystemUploads;
14
- fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
15
- websocket(url: string | URL, protocols?: string | string[]): Promise<WebSocket>;
14
+ readonly network: import("@phreshos/core").Network;
16
15
  shell(command: string, options?: ShellOptions): AsyncGenerator<import("@phreshos/core").ShellEvent, void, void>;
17
16
  private constructor();
18
17
  /** Connect to the System selected by argument, environment, or owner default. */
package/dist/system.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ClientEndpoint as CoreClientEndpoint, ClientService as CoreClientService, Process as CoreProcess, Program as CoreProgram, ServerEndpoint as CoreServerEndpoint, ServerService as CoreServerService, isServiceKey, parseClientPermissions } from "@phreshos/core";
1
+ import { ClientEndpoint as CoreClientEndpoint, ClientService as CoreClientService, Process as CoreProcess, Program as CoreProgram, ServerEndpoint as CoreServerEndpoint, ServerService as CoreServerService, isServiceKey, parseEndpointReference } from "@phreshos/core";
2
2
  import { homedir } from "node:os";
3
3
  import { gatewayAddress } from "./address.js";
4
4
  import Events from "./events.js";
@@ -11,13 +11,9 @@ import SystemRepresentation, { processIdentityState } from "./representation.js"
11
11
  import { GatewayConnection, openConnection } from "./transport.js";
12
12
  import Uploads from "./uploads.js";
13
13
  import shell from "./shell.js";
14
- import websocket from "./websocket.js";
14
+ import network from "./network.js";
15
15
  const systems = new WeakMap();
16
16
  const processSnapshots = new WeakMap();
17
- const ProgramBase = CoreProgram;
18
- const ProcessBase = CoreProcess;
19
- const ServerEndpointBase = CoreServerEndpoint;
20
- const ClientEndpointBase = CoreClientEndpoint;
21
17
  /** One connected owner-local implementation of the shared System contract. */
22
18
  export class System {
23
19
  storage;
@@ -25,13 +21,7 @@ export class System {
25
21
  program;
26
22
  process;
27
23
  uploads;
28
- async fetch(input, init) {
29
- const request = new Request(input, init);
30
- return await fetch(request, { signal: connectedSignal(this, request.signal) });
31
- }
32
- websocket(url, protocols) {
33
- return websocket(url, protocols, connectedSignal(this));
34
- }
24
+ network = network(() => connectedSignal(this));
35
25
  async *shell(command, options = {}) {
36
26
  yield* shell(command, { ...options, signal: connectedSignal(this, options.signal) });
37
27
  }
@@ -157,8 +147,11 @@ class ProgramRegistry extends Events {
157
147
  return event === "uninstall" ? { program, everything: values[1] === true } : program;
158
148
  }
159
149
  }
160
- class ProgramHandle extends ProgramBase {
150
+ class ProgramHandle extends CoreProgram {
161
151
  system;
152
+ subscribe;
153
+ wait;
154
+ events;
162
155
  reference;
163
156
  identity;
164
157
  data;
@@ -177,11 +170,14 @@ class ProgramHandle extends ProgramBase {
177
170
  this.reference = snapshot.reference;
178
171
  this.identity = snapshot.identity;
179
172
  const address = this.address();
180
- bindEvents(this, new Events(["forget", "uninstall"], (event, subscriber) => {
173
+ const events = new Events(["forget", "uninstall"], (event, subscriber) => {
181
174
  if (event === null)
182
175
  throw new Error("Program events are named");
183
176
  return representation(system).on(`program:${this.reference}:${event}`, (...values) => subscriber(values[0]));
184
- }));
177
+ });
178
+ this.subscribe = events.subscribe;
179
+ this.wait = events.wait;
180
+ this.events = events.events;
185
181
  representation(system).on(`program:${this.reference}:change`, value => this.update(value));
186
182
  const call = (event, ...values) => representation(system).call(event, ...values);
187
183
  this.data = filesystemStorage(() => programStoragePath(system, address, "data"), `Program "${this.identity}" data`, () => connectedSignal(system));
@@ -204,18 +200,7 @@ class ProgramHandle extends ProgramBase {
204
200
  service: this.snapshot.server.service
205
201
  }) : null;
206
202
  }
207
- get client() {
208
- return this.snapshot.client ? Object.freeze({
209
- start: this.snapshot.client.start,
210
- service: this.snapshot.client.service,
211
- title: this.snapshot.client.title,
212
- size: this.snapshot.client.size,
213
- position: this.snapshot.client.position,
214
- layer: this.snapshot.client.layer,
215
- minimize: this.snapshot.client.minimize,
216
- permissions: parseClientPermissions(this.snapshot.client.permissions)
217
- }) : null;
218
- }
203
+ get client() { return this.snapshot.client; }
219
204
  update(snapshot) {
220
205
  if (snapshot.reference !== this.reference)
221
206
  throw new Error("A Program handle cannot become another Program");
@@ -358,8 +343,11 @@ class ProcessRegistry extends Events {
358
343
  return process ? processHandle(this.system, process) : null;
359
344
  }
360
345
  }
361
- class ProcessHandle extends ProcessBase {
346
+ class ProcessHandle extends CoreProcess {
362
347
  system;
348
+ subscribe;
349
+ wait;
350
+ events;
363
351
  identity;
364
352
  name;
365
353
  startedAt;
@@ -369,7 +357,10 @@ class ProcessHandle extends ProcessBase {
369
357
  super();
370
358
  this.system = system;
371
359
  processSnapshots.set(this, snapshot);
372
- bindEvents(this, new Events(["exit"], (_event, subscriber) => (representation(system).on(`process:${snapshot.reference}:exit`, value => subscriber(value)))));
360
+ const events = new Events(["exit"], (_event, subscriber) => (representation(system).on(`process:${snapshot.reference}:exit`, value => subscriber(value))));
361
+ this.subscribe = events.subscribe;
362
+ this.wait = events.wait;
363
+ this.events = events.events;
373
364
  this.identity = snapshot.identity;
374
365
  this.name = snapshot.name;
375
366
  this.startedAt = new Date(snapshot.startedAt);
@@ -389,7 +380,10 @@ class ProcessHandle extends ProcessBase {
389
380
  });
390
381
  return value === null ? null : processHandle(this.system, processIdentityState(value));
391
382
  }
392
- async option(name) { return processState(this.system, this).options[name]; }
383
+ async options(name) {
384
+ const options = processState(this.system, this).options;
385
+ return name === undefined ? Object.freeze({ ...options }) : options[name];
386
+ }
393
387
  async exit() {
394
388
  await representation(this.system).call("/process/exit", this.identity);
395
389
  }
@@ -432,9 +426,12 @@ class EndpointOperations extends Events {
432
426
  await representation(this.system).call(`/process/endpoint/${operation}`, this.owner.identity, this.endpoint, launch);
433
427
  }
434
428
  }
435
- class ServerEndpointHandle extends ServerEndpointBase {
429
+ class ServerEndpointHandle extends CoreServerEndpoint {
436
430
  system;
437
431
  owner;
432
+ subscribe;
433
+ wait;
434
+ events;
438
435
  endpoint = "server";
439
436
  traffic;
440
437
  lifecycle;
@@ -446,7 +443,9 @@ class ServerEndpointHandle extends ServerEndpointBase {
446
443
  this.base = new EndpointOperations(system, owner, "server");
447
444
  this.traffic = new ServerTrafficHandle(representation(system), owner.identity, "server", value => endpointFromReference(system, value));
448
445
  this.lifecycle = this.base.lifecycle;
449
- bindEvents(this, this.base);
446
+ this.subscribe = this.base.subscribe;
447
+ this.wait = this.base.wait;
448
+ this.events = this.base.events;
450
449
  }
451
450
  process() { return this.base.process(); }
452
451
  exists() { return this.base.exists(); }
@@ -454,7 +453,7 @@ class ServerEndpointHandle extends ServerEndpointBase {
454
453
  isService() { return this.base.isService(); }
455
454
  start(launch) { return this.base.start(launch); }
456
455
  stop() { return this.base.stop(); }
457
- publish(event, payload) { return this.base.publish(event, payload); }
456
+ publish = (event, payload) => this.base.publish(event, payload);
458
457
  async ask(event, payload) {
459
458
  return await this.askWithin(event, payload, 10_000);
460
459
  }
@@ -465,7 +464,10 @@ class ServerEndpointHandle extends ServerEndpointBase {
465
464
  return representation(this.system).call("/process/endpoint/ask", this.owner.identity, event, payload, timeout);
466
465
  }
467
466
  }
468
- class ClientEndpointHandle extends ClientEndpointBase {
467
+ class ClientEndpointHandle extends CoreClientEndpoint {
468
+ subscribe;
469
+ wait;
470
+ events;
469
471
  endpoint = "client";
470
472
  traffic;
471
473
  lifecycle;
@@ -476,7 +478,9 @@ class ClientEndpointHandle extends ClientEndpointBase {
476
478
  this.base = new EndpointOperations(system, owner, "client");
477
479
  this.traffic = new EndpointTrafficHandle(representation(system), owner.identity, "client", value => endpointFromReference(system, value));
478
480
  this.lifecycle = this.base.lifecycle;
479
- bindEvents(this, this.base);
481
+ this.subscribe = this.base.subscribe;
482
+ this.wait = this.base.wait;
483
+ this.events = this.base.events;
480
484
  this.window = new SystemWindow(system, owner);
481
485
  }
482
486
  process() { return this.base.process(); }
@@ -485,13 +489,13 @@ class ClientEndpointHandle extends ClientEndpointBase {
485
489
  isService() { return this.base.isService(); }
486
490
  start(launch) { return this.base.start(launch); }
487
491
  stop() { return this.base.stop(); }
488
- publish(event, payload) { return this.base.publish(event, payload); }
492
+ publish = (event, payload) => this.base.publish(event, payload);
489
493
  }
490
494
  class SystemWindow extends Events {
491
495
  system;
492
496
  process;
493
497
  constructor(system, process) {
494
- super(["move", "resize", "geometry", "minimize", "changeTitle", "front"], (event, subscriber) => {
498
+ super(["move", "resize", "geometry", "minimize", "maximize", "changeTitle", "front"], (event, subscriber) => {
495
499
  if (event === null)
496
500
  throw new Error("Window events are named");
497
501
  return representation(system).on(`window:${process.identity}:${event}`, subscriber);
@@ -503,13 +507,14 @@ class SystemWindow extends Events {
503
507
  async position() { return (await this.snapshot()).position; }
504
508
  async size() { return (await this.snapshot()).size; }
505
509
  async minimized() { return (await this.snapshot()).minimized; }
510
+ async maximized() { return (await this.snapshot()).maximized; }
506
511
  async front() { return frontWindow(this.system, this.process); }
507
512
  async layer() { return (await this.snapshot()).layer; }
508
- async location() { return (await this.snapshot()).location; }
509
513
  async move(position) { await this.change("move", position); }
510
514
  async resize(size) { await this.change("resize", size); }
511
515
  async setGeometry(geometry) { await this.change("geometry", geometry); }
512
516
  async minimize(minimized = true) { await this.change("minimize", minimized); }
517
+ async maximize(maximized = true) { await this.change("maximize", maximized); }
513
518
  async changeTitle(title) { await this.change("change-title", title); }
514
519
  async raise() { await this.change("raise"); }
515
520
  snapshot() {
@@ -545,6 +550,9 @@ class ServerServiceHandle extends CoreServerService {
545
550
  system;
546
551
  key;
547
552
  lifecycle;
553
+ subscribe;
554
+ wait;
555
+ events;
548
556
  base;
549
557
  constructor(system, key) {
550
558
  super();
@@ -552,9 +560,12 @@ class ServerServiceHandle extends CoreServerService {
552
560
  this.key = key;
553
561
  this.base = new ServiceBase(system, key);
554
562
  this.lifecycle = this.base.lifecycle;
555
- bindEvents(this, new Events([], (event, subscriber, impossible) => representation(system).follow({
563
+ const events = new Events([], (event, subscriber, impossible) => representation(system).follow({
556
564
  scope: "service", key, kind: "events", event
557
- }, (_received, payload) => subscriber(payload), impossible)));
565
+ }, (_received, payload) => subscriber(payload), impossible));
566
+ this.subscribe = events.subscribe;
567
+ this.wait = events.wait;
568
+ this.events = events.events;
558
569
  }
559
570
  exists() { return this.base.exists(); }
560
571
  waitReady(timeout) { return this.base.waitReady(timeout); }
@@ -568,14 +579,20 @@ class ServerServiceHandle extends CoreServerService {
568
579
  }
569
580
  class ClientServiceHandle extends CoreClientService {
570
581
  lifecycle;
582
+ subscribe;
583
+ wait;
584
+ events;
571
585
  base;
572
586
  constructor(system, key) {
573
587
  super();
574
588
  this.base = new ServiceBase(system, key);
575
589
  this.lifecycle = this.base.lifecycle;
576
- bindEvents(this, new Events([], (event, subscriber, impossible) => representation(system).follow({
590
+ const events = new Events([], (event, subscriber, impossible) => representation(system).follow({
577
591
  scope: "service", key, kind: "events", event
578
- }, (_received, payload) => subscriber(payload), impossible)));
592
+ }, (_received, payload) => subscriber(payload), impossible));
593
+ this.subscribe = events.subscribe;
594
+ this.wait = events.wait;
595
+ this.events = events.events;
579
596
  }
580
597
  exists() { return this.base.exists(); }
581
598
  waitReady(timeout) { return this.base.waitReady(timeout); }
@@ -611,16 +628,6 @@ function programProcessEvent(system, event, values) {
611
628
  const process = processHandle(system, required(values[0]));
612
629
  return event === "exit" ? { process, ...values[1] } : process;
613
630
  }
614
- function bindEvents(target, events) {
615
- Object.assign(target, eventsOf(events));
616
- }
617
- function eventsOf(events) {
618
- return {
619
- subscribe: events.subscribe,
620
- waitFor: events.waitFor,
621
- events: events.events
622
- };
623
- }
624
631
  function chronological(left, right) { return left.startedAt.getTime() - right.startedAt.getTime(); }
625
632
  async function programStoragePath(system, handle, area) {
626
633
  const value = await representation(system).call("/program/area", handle, area, "path", []);
@@ -629,10 +636,9 @@ async function programStoragePath(system, handle, area) {
629
636
  return value;
630
637
  }
631
638
  function endpointFromReference(system, value) {
632
- const reference = value;
633
- if (!reference || (reference.kind !== "server" && reference.kind !== "client") || typeof reference.process?.identity !== "string") {
634
- throw new Error("The System returned an invalid Endpoint reference");
635
- }
639
+ if (value === null)
640
+ return null;
641
+ const reference = parseEndpointReference(value);
636
642
  const owner = processHandle(system, required(representation(system).processes.get(reference.process.identity), reference.process.identity));
637
643
  return reference.kind === "server" ? owner.server : owner.client;
638
644
  }
package/dist/traffic.d.ts CHANGED
@@ -2,7 +2,7 @@ import type { AnswerSubscriber, AskSubscriber, Cleanup, Endpoint, EventOptions,
2
2
  import Events from "./events.js";
3
3
  import type SystemRepresentation from "./representation.js";
4
4
  type Kind = "publish" | "ask" | "answer";
5
- type ResolveEndpoint = (value: unknown) => Endpoint;
5
+ type ResolveEndpoint = (value: unknown) => Endpoint | null;
6
6
  /** Directed traffic originating from one canonical Endpoint. */
7
7
  export declare class EndpointTrafficHandle<Definitions extends object = {}> extends Events<TrafficEvents<Definitions>, never> {
8
8
  private readonly representation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/node",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Node.js access to PhreshOS and Program projects.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -31,9 +31,8 @@
31
31
  "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
32
32
  "check": "tsc --noEmit && tsc -p tsconfig.test.json",
33
33
  "build": "node --run clean && tsc --noEmit false --outDir dist --rootDir source",
34
- "test": "node --run build && node --test tests/*.test.mjs",
35
- "verify:package": "node scripts/verify-package.mjs",
36
- "verify": "node --run check && node --run test && node --run verify:package",
34
+ "test": "vitest run --project default",
35
+ "verify": "node --run check && node --run build && node --run test",
37
36
  "prepack": "node --run build"
38
37
  },
39
38
  "dependencies": {
@@ -43,12 +42,13 @@
43
42
  "jiti": "^2.7.0"
44
43
  },
45
44
  "peerDependencies": {
46
- "@phreshos/core": "^0.1.42"
45
+ "@phreshos/core": "^0.1.44"
47
46
  },
48
47
  "devDependencies": {
49
- "@phreshos/core": "^0.1.42",
48
+ "@phreshos/core": "^0.1.44",
50
49
  "@types/adm-zip": "^0.5.8",
51
50
  "@types/node": "^26.2.0",
52
- "typescript": "^6.0.3"
51
+ "typescript": "^6.0.3",
52
+ "vitest": "^4.1.10"
53
53
  }
54
54
  }