@phreshos/node 0.1.0 → 0.1.2

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/dist/system.js CHANGED
@@ -1,33 +1,92 @@
1
- import { ClientServiceHandler as CoreClientServiceHandler, ServerServiceHandler as CoreServerServiceHandler } from "@phreshos/core";
1
+ import { Client as CoreClient, ClientServiceHandler as CoreClientServiceHandler, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, ServerServiceHandler as CoreServerServiceHandler } from "@phreshos/core";
2
2
  import { homedir } from "node:os";
3
+ import { gatewayAddress } from "./address.js";
3
4
  import Events from "./events.js";
5
+ import HandleRegistry from "./handle-registry.js";
6
+ import { resolveHome } from "./home.js";
4
7
  import { filesystemStorage } from "./storage.js";
8
+ import { openConnection, request, streamProgram } from "./transport.js";
5
9
  import Uploads from "./uploads.js";
6
- /** Build the exact shared System contract over an owner-local Gateway transport. */
7
- export function gatewaySystem(transport) {
8
- return new GatewaySystem(transport);
9
- }
10
- class GatewaySystem {
11
- transport;
10
+ const ProgramBase = CoreProgram;
11
+ const ProcessBase = CoreProcess;
12
+ const ServerBase = CoreServer;
13
+ const ClientBase = CoreClient;
14
+ /** One connected owner-local implementation of the shared System contract. */
15
+ export class System {
16
+ connection;
17
+ home;
18
+ address;
12
19
  storage = filesystemStorage(homedir(), "the native home directory");
13
20
  appearance;
14
21
  program;
15
22
  process;
16
23
  uploads;
17
- constructor(transport) {
18
- this.transport = transport;
19
- this.appearance = new GatewayAppearance(transport);
24
+ transport;
25
+ closed = false;
26
+ lifetime = new AbortController();
27
+ handles = new HandleRegistry();
28
+ constructor(home, address, connection) {
29
+ this.connection = connection;
30
+ this.home = home;
31
+ this.address = address;
32
+ this.transport = {
33
+ control: (value, signal) => request(address, "system", value, this.signal(signal)),
34
+ api: (value, signal) => request(address, "api", value, this.signal(signal)),
35
+ lifecycle: (value, signal) => streamProgram(address, value, this.signal(signal))
36
+ };
37
+ this.appearance = new SystemAppearance(this.transport);
20
38
  this.program = new ProgramRegistry(this);
21
39
  this.process = new ProcessRegistry(this);
22
- this.uploads = new Uploads(request => transport.api(request));
40
+ this.uploads = new Uploads(value => this.transport.api(value));
41
+ }
42
+ /** Connect to the System selected by argument, environment, or owner default. */
43
+ static async connect(home) {
44
+ const resolved = resolveHome(home);
45
+ const address = gatewayAddress(resolved);
46
+ return new System(resolved, address, await openConnection(address));
47
+ }
48
+ /** Atomically replace one runtime Program without touching its installed form. */
49
+ async forceCreateProgram(source) {
50
+ this.requireConnected();
51
+ for await (const event of this.transport.lifecycle({ word: "force-create", program: source })) {
52
+ if (event.event === "created")
53
+ return this.programHandle(required(event.program));
54
+ }
55
+ throw new Error("The System did not confirm the created Program");
56
+ }
57
+ /** Close this owner connection and abort every attached operation it owns. */
58
+ async disconnect() {
59
+ if (this.closed)
60
+ return;
61
+ this.closed = true;
62
+ this.lifetime.abort(new Error("This System connection is closed"));
63
+ this.connection.destroy();
64
+ this.handles.clear();
23
65
  }
24
66
  service(key) {
67
+ this.requireConnected();
25
68
  return key.endpoint === "server"
26
69
  ? new ServerService(this, key)
27
70
  : new ClientService(this, key);
28
71
  }
72
+ programHandle(snapshot) {
73
+ const handle = this.handles.obtain(`program:${snapshot.reference}`, () => new ProgramHandle(this, snapshot));
74
+ handle.update(snapshot);
75
+ return handle;
76
+ }
77
+ processHandle(snapshot) {
78
+ return this.handles.obtain(`process:${snapshot.reference}`, () => new ProcessHandle(this, snapshot));
79
+ }
80
+ signal(signal) {
81
+ this.requireConnected();
82
+ return signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal;
83
+ }
84
+ requireConnected() {
85
+ if (this.closed)
86
+ throw new Error("This System connection is closed");
87
+ }
29
88
  }
30
- class GatewayAppearance extends Events {
89
+ class SystemAppearance extends Events {
31
90
  transport;
32
91
  constructor(transport) {
33
92
  super(["change"], (_event, signal) => transport.api({ capability: "appearance", operation: "wait" }, signal));
@@ -56,7 +115,7 @@ class ProgramRegistry extends Events {
56
115
  operation: "list",
57
116
  input: { installedOnly: onlyInstalled, limit: 100, offset }
58
117
  });
59
- programs.push(...page.data.map(snapshot => new ProgramHandle(this.system, snapshot)));
118
+ programs.push(...page.data.map(snapshot => this.system.programHandle(snapshot)));
60
119
  offset += page.data.length;
61
120
  if (!page.truncated || !page.data.length)
62
121
  return programs;
@@ -65,7 +124,7 @@ class ProgramRegistry extends Events {
65
124
  async find(identity) {
66
125
  try {
67
126
  const snapshot = await this.system.transport.control({ capability: "program", operation: "inspect", input: { program: identity } });
68
- return new ProgramHandle(this.system, snapshot);
127
+ return this.system.programHandle(snapshot);
69
128
  }
70
129
  catch (error) {
71
130
  if (unknown(error, "Program"))
@@ -74,57 +133,61 @@ class ProgramRegistry extends Events {
74
133
  }
75
134
  }
76
135
  async create(source) {
77
- let identity = null;
78
136
  for await (const event of this.system.transport.lifecycle({ word: "create", program: source })) {
79
137
  if (event.event === "created")
80
- identity = String(event.identity);
138
+ return this.system.programHandle(required(event.program));
81
139
  }
82
- if (!identity)
83
- throw new Error("The System did not confirm the created Program");
84
- const program = await this.find(identity);
85
- if (!program)
86
- throw new Error("The created Program cannot be found");
87
- return program;
140
+ throw new Error("The System did not confirm the created Program");
88
141
  }
89
142
  async event(value) {
90
143
  const waited = value;
91
144
  if (waited.event === "uninstall") {
92
145
  const payload = waited.payload;
93
- return { program: new ProgramHandle(this.system, required(payload.program)), everythingRemoved: payload.everythingRemoved === true };
146
+ return { program: this.system.programHandle(required(payload.program)), everythingRemoved: payload.everythingRemoved === true };
94
147
  }
95
- return new ProgramHandle(this.system, required(waited.payload));
148
+ return this.system.programHandle(required(waited.payload));
96
149
  }
97
150
  }
98
- class ProgramHandle extends Events {
151
+ class ProgramHandle extends ProgramBase {
99
152
  system;
153
+ reference;
100
154
  identity;
101
- name;
102
- version;
103
- description;
104
- hasAgent;
105
- server;
106
- client;
107
155
  process;
156
+ startup;
157
+ snapshot;
108
158
  constructor(system, snapshot) {
109
- super(["forget", "uninstall"], (event, signal, timeout) => system.transport.control({
110
- capability: "program", operation: "wait", input: { program: snapshot.identity, event, timeout }
111
- }, signal).then(value => value.payload));
159
+ super();
112
160
  this.system = system;
161
+ this.snapshot = snapshot;
162
+ bindEvents(this, new Events(["forget", "uninstall"], (event, signal, timeout) => system.transport.control({
163
+ capability: "program", operation: "wait", input: { program: snapshot.identity, event, timeout }
164
+ }, signal).then(value => value.payload)));
165
+ this.reference = snapshot.reference;
113
166
  this.identity = snapshot.identity;
114
- this.name = snapshot.name;
115
- this.version = snapshot.version;
116
- this.description = snapshot.description;
117
- this.hasAgent = snapshot.hasAgent;
118
- this.server = snapshot.server ? Object.freeze({ start: snapshot.server.start }) : null;
119
- this.client = snapshot.client ? Object.freeze({
120
- start: snapshot.client.start,
121
- title: snapshot.client.title,
122
- size: snapshot.client.size,
123
- position: snapshot.client.position,
124
- layer: snapshot.client.layer,
125
- minimize: snapshot.client.minimize
126
- }) : null;
127
167
  this.process = new ProgramProcesses(system, this);
168
+ this.startup = new ProgramStartup(system, this);
169
+ }
170
+ get name() { return this.snapshot.name; }
171
+ get version() { return this.snapshot.version; }
172
+ get description() { return this.snapshot.description; }
173
+ get hasAgent() { return this.snapshot.hasAgent; }
174
+ get server() {
175
+ return this.snapshot.server ? Object.freeze({ start: this.snapshot.server.start }) : null;
176
+ }
177
+ get client() {
178
+ return this.snapshot.client ? Object.freeze({
179
+ start: this.snapshot.client.start,
180
+ title: this.snapshot.client.title,
181
+ size: this.snapshot.client.size,
182
+ position: this.snapshot.client.position,
183
+ layer: this.snapshot.client.layer,
184
+ minimize: this.snapshot.client.minimize
185
+ }) : null;
186
+ }
187
+ update(snapshot) {
188
+ if (snapshot.reference !== this.reference)
189
+ throw new Error("A Program handle cannot become another Program");
190
+ this.snapshot = snapshot;
128
191
  }
129
192
  async agent() {
130
193
  if (!this.hasAgent)
@@ -133,15 +196,49 @@ class ProgramHandle extends Events {
133
196
  return typeof value.content === "string" ? value.content : null;
134
197
  }
135
198
  async installed() {
136
- const value = await this.system.transport.control({ capability: "program", operation: "inspect", input: { program: this.identity } });
137
- if (typeof value.installed !== "boolean")
138
- throw new Error("The System returned no Program installation state");
139
- return value.installed;
199
+ for await (const event of this.system.transport.lifecycle({ word: "installed", handle: this.address() })) {
200
+ if (event.event === "installedState")
201
+ return event.installed === true;
202
+ }
203
+ throw new Error("The System returned no Program installation state");
140
204
  }
141
- install() { return command(this.system, { word: "install-existing", identity: this.identity }); }
142
- uninstall(everything = false) { return command(this.system, { word: "uninstall-existing", identity: this.identity, everything }); }
205
+ install() { return command(this.system, { word: "install-existing", handle: this.address() }); }
206
+ uninstall(everything = false) { return command(this.system, { word: "uninstall-existing", handle: this.address(), everything }); }
143
207
  async forget() {
144
- for await (const _event of this.system.transport.lifecycle({ word: "forget", identity: this.identity })) { /* consume completion */ }
208
+ for await (const _event of this.system.transport.lifecycle({ word: "forget", handle: this.address() })) { /* consume completion */ }
209
+ }
210
+ address() { return Object.freeze({ identity: this.identity, reference: this.reference }); }
211
+ }
212
+ class ProgramStartup {
213
+ system;
214
+ program;
215
+ constructor(system, program) {
216
+ this.system = system;
217
+ this.program = program;
218
+ }
219
+ async get() {
220
+ for await (const event of this.system.transport.lifecycle({
221
+ word: "startup", handle: this.program.address(), operation: "get"
222
+ })) {
223
+ if (event.event === "startup")
224
+ return event.launch;
225
+ }
226
+ throw new Error("The System returned no Program startup state");
227
+ }
228
+ async enable(launch = {}) {
229
+ await this.change("enable", launch);
230
+ }
231
+ async disable() {
232
+ await this.change("disable");
233
+ }
234
+ async change(operation, launch) {
235
+ for await (const event of this.system.transport.lifecycle({
236
+ word: "startup", handle: this.program.address(), operation, launch
237
+ })) {
238
+ if (event.event === "startup")
239
+ return;
240
+ }
241
+ throw new Error("The System did not confirm the Program startup change");
145
242
  }
146
243
  }
147
244
  class ProgramProcesses extends Events {
@@ -161,13 +258,62 @@ class ProgramProcesses extends Events {
161
258
  const found = (await this.list()).find(process => process.identity === identityOrName || process.name === identityOrName);
162
259
  return found ?? null;
163
260
  }
164
- create(launch = {}) { return createProcess(this.system, "create", this.program.identity, launch); }
165
- findOrCreate(launch) { return createProcess(this.system, "findOrCreate", this.program.identity, launch); }
261
+ create(launch = {}) { return this.createExact("create-process", launch); }
262
+ async *run(launch = {}, options = {}) {
263
+ let process = null;
264
+ for await (const event of this.system.transport.lifecycle({
265
+ word: "run-process",
266
+ handle: this.program.address(),
267
+ launch
268
+ }, options.signal)) {
269
+ if (event.event === "started") {
270
+ process = this.system.processHandle(required(event.process));
271
+ yield Object.freeze({ event: "started", process });
272
+ }
273
+ else if (event.event === "output") {
274
+ if ((event.stream !== "stdout" && event.stream !== "stderr") || typeof event.text !== "string") {
275
+ throw new Error("The System returned an invalid Process output event");
276
+ }
277
+ yield Object.freeze({
278
+ event: "output",
279
+ stream: event.stream,
280
+ text: event.text
281
+ });
282
+ }
283
+ else if (event.event === "exited") {
284
+ if (!process)
285
+ throw new Error("The System ended a Process before confirming its start");
286
+ const value = event.exit;
287
+ if (!value
288
+ || (value.status !== "exited" && value.status !== "signaled")
289
+ || (value.code !== null && typeof value.code !== "number")
290
+ || (value.signal !== null && typeof value.signal !== "string")) {
291
+ throw new Error("The System returned an invalid Process exit event");
292
+ }
293
+ const exit = Object.freeze({
294
+ status: value.status,
295
+ code: value.code,
296
+ signal: value.signal
297
+ });
298
+ yield Object.freeze({ event: "exited", process, exit });
299
+ }
300
+ else
301
+ throw new Error("The System returned an unknown Process run event");
302
+ }
303
+ }
304
+ findOrCreate(launch) { return this.createExact("find-or-create-process", launch); }
166
305
  async exitAll() {
167
306
  const processes = await this.list();
168
307
  await Promise.all(processes.map(process => process.exit()));
169
308
  return processes.map(process => process.identity);
170
309
  }
310
+ async createExact(word, launch) {
311
+ for await (const event of this.system.transport.lifecycle({ word, handle: this.program.address(), launch })) {
312
+ if (event.event === "createdProcess")
313
+ return this.system.processHandle(required(event.process));
314
+ }
315
+ throw new Error("The System did not confirm the created Process");
316
+ }
171
317
  }
172
318
  class ProcessRegistry extends Events {
173
319
  system;
@@ -181,7 +327,7 @@ class ProcessRegistry extends Events {
181
327
  async find(identity) {
182
328
  try {
183
329
  const snapshot = await this.system.transport.control({ capability: "process", operation: "inspect", input: { process: identity } });
184
- return new ProcessHandle(this.system, snapshot);
330
+ return this.system.processHandle(snapshot);
185
331
  }
186
332
  catch (error) {
187
333
  if (unknown(error, "Process"))
@@ -190,7 +336,7 @@ class ProcessRegistry extends Events {
190
336
  }
191
337
  }
192
338
  }
193
- class ProcessHandle extends Events {
339
+ class ProcessHandle extends ProcessBase {
194
340
  system;
195
341
  snapshot;
196
342
  identity;
@@ -199,26 +345,28 @@ class ProcessHandle extends Events {
199
345
  server;
200
346
  client;
201
347
  constructor(system, snapshot) {
202
- super(["endpointStart", "endpointStop", "exit"], (event, signal, timeout) => system.transport.control({
203
- capability: "process", operation: "wait", input: { process: snapshot.identity, event, timeout }
204
- }, signal).then(value => processEvent(system, value)));
348
+ super();
205
349
  this.system = system;
206
350
  this.snapshot = snapshot;
351
+ bindEvents(this, new Events(["endpointStart", "endpointStop", "exit"], (event, signal, timeout) => system.transport.control({
352
+ capability: "process", operation: "wait", input: { process: snapshot.identity, event, timeout }
353
+ }, signal).then(value => processEvent(system, value))));
207
354
  this.identity = snapshot.identity;
208
355
  this.name = snapshot.name;
209
356
  this.startedAt = new Date(snapshot.startedAt);
210
357
  this.server = new ServerEndpoint(system, this);
211
358
  this.client = new ClientEndpoint(system, this);
212
359
  }
213
- program() { return new ProgramHandle(this.system, required(this.snapshot.programSnapshot, this.snapshot.program)); }
360
+ program() { return this.system.programHandle(required(this.snapshot.programSnapshot, this.snapshot.program)); }
214
361
  async exit() {
215
362
  await this.system.transport.control({ capability: "process", operation: "exit", input: { process: this.identity } });
216
363
  }
217
364
  async exited() { return await this.system.process.find(this.identity) === null; }
218
365
  }
219
- class EndpointHandle extends Events {
366
+ class EndpointOperations extends Events {
220
367
  system;
221
368
  owner;
369
+ endpoint;
222
370
  constructor(system, owner, endpoint) {
223
371
  super([], (event, signal, timeout) => event === null
224
372
  ? system.transport.api({ capability: "endpoint", operation: "wait", process: owner.identity, endpoint, event, timeout }, signal)
@@ -227,13 +375,14 @@ class EndpointHandle extends Events {
227
375
  }, signal).then(value => value.payload));
228
376
  this.system = system;
229
377
  this.owner = owner;
378
+ this.endpoint = endpoint;
230
379
  }
231
380
  process() { return Promise.resolve(this.owner); }
232
381
  async exists() {
233
382
  const value = await this.inspect();
234
383
  return value.running;
235
384
  }
236
- async start() { await this.operation("start"); }
385
+ async start(client) { await this.operation("start", client); }
237
386
  async stop() { await this.operation("stop"); }
238
387
  async service() {
239
388
  const key = await this.system.transport.api({ capability: "endpoint", operation: "service", process: this.owner.identity, endpoint: this.endpoint });
@@ -255,9 +404,23 @@ class EndpointHandle extends Events {
255
404
  } });
256
405
  }
257
406
  }
258
- class ServerEndpoint extends EndpointHandle {
407
+ class ServerEndpoint extends ServerBase {
408
+ system;
409
+ owner;
259
410
  endpoint = "server";
260
- constructor(system, owner) { super(system, owner, "server"); }
411
+ base;
412
+ constructor(system, owner) {
413
+ super();
414
+ this.system = system;
415
+ this.owner = owner;
416
+ this.base = new EndpointOperations(system, owner, "server");
417
+ bindEvents(this, this.base);
418
+ }
419
+ process() { return this.base.process(); }
420
+ exists() { return this.base.exists(); }
421
+ start() { return this.base.start(); }
422
+ stop() { return this.base.stop(); }
423
+ publish(event, payload) { return this.base.publish(event, payload); }
261
424
  async ask(event, payload) {
262
425
  return await this.system.transport.control({ capability: "endpoint", operation: "ask", input: {
263
426
  process: this.owner.identity, endpoint: "server", event, payload
@@ -272,22 +435,29 @@ class ServerEndpoint extends EndpointHandle {
272
435
  await this.system.transport.control({ capability: "endpoint", operation: "waitReady", input: { process: this.owner.identity, endpoint: "server", timeout } });
273
436
  }
274
437
  async service() {
275
- return await super.service();
438
+ return await this.base.service();
276
439
  }
277
440
  }
278
- class ClientEndpoint extends EndpointHandle {
441
+ class ClientEndpoint extends ClientBase {
279
442
  endpoint = "client";
280
443
  window;
444
+ base;
281
445
  constructor(system, owner) {
282
- super(system, owner, "client");
283
- this.window = new GatewayWindow(system, owner);
284
- }
285
- async start(overrides) { await this.operation("start", overrides); }
446
+ super();
447
+ this.base = new EndpointOperations(system, owner, "client");
448
+ bindEvents(this, this.base);
449
+ this.window = new SystemWindow(system, owner);
450
+ }
451
+ process() { return this.base.process(); }
452
+ exists() { return this.base.exists(); }
453
+ start(overrides) { return this.base.start(overrides); }
454
+ stop() { return this.base.stop(); }
455
+ publish(event, payload) { return this.base.publish(event, payload); }
286
456
  async service() {
287
- return await super.service();
457
+ return await this.base.service();
288
458
  }
289
459
  }
290
- class GatewayWindow extends Events {
460
+ class SystemWindow extends Events {
291
461
  system;
292
462
  process;
293
463
  constructor(system, process) {
@@ -387,16 +557,12 @@ async function listProcesses(system, program) {
387
557
  let offset = 0;
388
558
  while (true) {
389
559
  const page = await system.transport.control({ capability: "process", operation: "list", input: { program, limit: 100, offset } });
390
- processes.push(...page.data.map(snapshot => new ProcessHandle(system, snapshot)));
560
+ processes.push(...page.data.map(snapshot => system.processHandle(snapshot)));
391
561
  offset += page.data.length;
392
562
  if (!page.truncated || !page.data.length)
393
563
  return processes;
394
564
  }
395
565
  }
396
- async function createProcess(system, operation, program, launch) {
397
- const snapshot = await system.transport.control({ capability: "process", operation, input: { program, launch } });
398
- return new ProcessHandle(system, snapshot);
399
- }
400
566
  async function* command(system, request) {
401
567
  for await (const event of system.transport.lifecycle(request)) {
402
568
  if (event.event === "output")
@@ -411,19 +577,22 @@ function processEvent(system, value) {
411
577
  const payload = waited.payload;
412
578
  if (waited.event === "exit" && payload)
413
579
  return {
414
- process: new ProcessHandle(system, required(payload.processSnapshot, String(payload.process ?? ""))),
580
+ process: system.processHandle(required(payload.processSnapshot, String(payload.process ?? ""))),
415
581
  status: payload.status,
416
582
  code: payload.code,
417
583
  signal: payload.signal
418
584
  };
419
585
  if ((waited.event === "endpointStart" || waited.event === "endpointStop") && payload?.processSnapshot) {
420
- const process = new ProcessHandle(system, payload.processSnapshot);
586
+ const process = system.processHandle(payload.processSnapshot);
421
587
  return payload.endpoint === "client" ? process.client : process.server;
422
588
  }
423
589
  if (payload && typeof payload.identity === "string")
424
- return new ProcessHandle(system, payload);
590
+ return system.processHandle(payload);
425
591
  return payload;
426
592
  }
593
+ function bindEvents(target, events) {
594
+ Object.assign(target, eventsOf(events));
595
+ }
427
596
  function eventsOf(events) {
428
597
  return {
429
598
  subscribe: events.subscribe,
@@ -439,3 +608,8 @@ function required(value, identity = "") {
439
608
  return value;
440
609
  throw new Error(`The System returned no ${identity ? `${identity} ` : ""}snapshot`);
441
610
  }
611
+ export const Program = CoreProgram;
612
+ export const Process = CoreProcess;
613
+ export const Endpoint = CoreEndpoint;
614
+ export const Server = CoreServer;
615
+ export const Client = CoreClient;
@@ -1,11 +1,11 @@
1
1
  import { type Socket } from "node:net";
2
- export interface GatewayEvent {
2
+ export interface TransportEvent {
3
3
  event?: string;
4
4
  [key: string]: unknown;
5
5
  }
6
- /** Open and retain one owner-local Gateway connection. */
6
+ /** Open and retain one owner-local System connection. */
7
7
  export declare function openConnection(path: string): Promise<Socket>;
8
8
  /** Execute one short authoritative System-control request. */
9
9
  export declare function request(path: string, target: "api" | "system", request: unknown, signal?: AbortSignal): Promise<unknown>;
10
10
  /** Stream one Program lifecycle operation until the System completes it. */
11
- export declare function streamProgram(path: string, request: unknown, signal?: AbortSignal): AsyncGenerator<GatewayEvent, void, unknown>;
11
+ export declare function streamProgram(path: string, request: unknown, signal?: AbortSignal): AsyncGenerator<TransportEvent, void, unknown>;
package/dist/transport.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { connect } from "node:net";
2
- /** Open and retain one owner-local Gateway connection. */
2
+ /** Open and retain one owner-local System connection. */
3
3
  export function openConnection(path) {
4
4
  return new Promise((resolve, reject) => {
5
5
  const socket = connect(path);
@@ -39,7 +39,7 @@ export function request(path, target, request, signal) {
39
39
  outcome = JSON.parse(buffer.slice(0, boundary));
40
40
  }
41
41
  catch {
42
- return finish(() => reject(new Error("The System returned an invalid Gateway response")));
42
+ return finish(() => reject(new Error("The System returned an invalid response")));
43
43
  }
44
44
  if (outcome.success)
45
45
  finish(() => resolve(outcome.result));
@@ -47,7 +47,7 @@ export function request(path, target, request, signal) {
47
47
  finish(() => reject(new Error(outcome.error)));
48
48
  });
49
49
  socket.on("error", () => finish(() => reject(unavailable(path))));
50
- socket.on("close", () => finish(() => reject(new Error("The System closed the Gateway request without an answer"))));
50
+ socket.on("close", () => finish(() => reject(new Error("The System closed the request without an answer"))));
51
51
  if (signal?.aborted)
52
52
  cancel();
53
53
  });
@@ -95,19 +95,25 @@ export function streamProgram(path, request, signal) {
95
95
  wake = null;
96
96
  });
97
97
  return (async function* () {
98
- while (true) {
99
- if (events.length) {
100
- yield events.shift();
101
- continue;
98
+ try {
99
+ while (true) {
100
+ if (events.length) {
101
+ yield events.shift();
102
+ continue;
103
+ }
104
+ if (failure)
105
+ throw failure;
106
+ if (ended)
107
+ return;
108
+ await new Promise(resolve => { wake = resolve; });
102
109
  }
103
- if (failure)
104
- throw failure;
105
- if (ended)
106
- return;
107
- await new Promise(resolve => { wake = resolve; });
110
+ }
111
+ finally {
112
+ signal?.removeEventListener("abort", cancel);
113
+ socket.destroy();
108
114
  }
109
115
  })();
110
116
  }
111
117
  function unavailable(path) {
112
- return new Error(`No System Gateway is listening at ${path} — start PhreshOS first`);
118
+ return new Error(`No System is listening at ${path} — start PhreshOS first`);
113
119
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phreshos/node",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Node.js access to PhreshOS and Program projects.",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -32,7 +32,7 @@
32
32
  "prepack": "node --run build"
33
33
  },
34
34
  "dependencies": {
35
- "@phreshos/core": "^0.1.21",
35
+ "@phreshos/core": "^0.1.23",
36
36
  "adm-zip": "^0.6.0",
37
37
  "jiti": "^2.7.0"
38
38
  },
@@ -1,29 +0,0 @@
1
- import type { ClientDevelopment } from "@phreshos/core";
2
- import type { GatewayEvent } from "./transport.js";
3
- /** One client development server owned by a Gateway development run. */
4
- export declare class DevelopmentClient {
5
- private readonly child;
6
- private readonly output;
7
- private stopped;
8
- private result;
9
- private outputWaiter;
10
- private readonly completion;
11
- constructor(command: string, directory: string);
12
- drain(): GatewayEvent[];
13
- exited(): Promise<CommandExit>;
14
- exitResult(): CommandExit | null;
15
- outputAvailable(): Promise<void>;
16
- stop(): Promise<void>;
17
- endingWasRequested(): boolean;
18
- private push;
19
- }
20
- /** Refuse to claim a URL already served by an unrelated process. */
21
- export declare function assertAvailable(url: string): Promise<void>;
22
- /** Wait until a development Client can be loaded by a sandboxed Program iframe. */
23
- export declare function waitForDevelopmentClient(config: ClientDevelopment, client?: DevelopmentClient, signal?: AbortSignal): AsyncGenerator<GatewayEvent, void, unknown>;
24
- export declare function commandFailure(exit: CommandExit): Error;
25
- export interface CommandExit {
26
- code: number | null;
27
- signal: NodeJS.Signals | null;
28
- error: Error | null;
29
- }