@phreshos/node 0.1.13 → 0.1.14

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,4 +1,4 @@
1
- import { ClientEndpoint as CoreClientEndpoint, ClientService as CoreClientService, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, ServerEndpoint as CoreServerEndpoint, ServerService as CoreServerService, isServiceKey } from "@phreshos/core";
1
+ import { ClientEndpoint as CoreClientEndpoint, ClientService as CoreClientService, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, ServerEndpoint as CoreServerEndpoint, ServerService as CoreServerService, isServiceKey, parseClientPermissions } from "@phreshos/core";
2
2
  import { homedir } from "node:os";
3
3
  import { gatewayAddress } from "./address.js";
4
4
  import Events from "./events.js";
@@ -7,10 +7,13 @@ import { resolveHome } from "./home.js";
7
7
  import { filesystemStorage, nativeStorage } from "./storage.js";
8
8
  import { programPermissions, programSql, programStore } from "./program-resources.js";
9
9
  import { EndpointTrafficHandle, ServerTrafficHandle } from "./traffic.js";
10
- import { openConnection, request, stream } from "./transport.js";
10
+ import SystemRepresentation, {} from "./representation.js";
11
+ import { GatewayConnection, openConnection } from "./transport.js";
11
12
  import Uploads from "./uploads.js";
12
13
  import shell from "./shell.js";
14
+ import websocket from "./websocket.js";
13
15
  const systems = new WeakMap();
16
+ const processSnapshots = new WeakMap();
14
17
  const ProgramBase = CoreProgram;
15
18
  const ProcessBase = CoreProcess;
16
19
  const ServerEndpointBase = CoreServerEndpoint;
@@ -26,43 +29,40 @@ export class System {
26
29
  const request = new Request(input, init);
27
30
  return await fetch(request, { signal: connectedSignal(this, request.signal) });
28
31
  }
32
+ websocket(url, protocols) {
33
+ return websocket(url, protocols, connectedSignal(this));
34
+ }
29
35
  async *shell(command, options = {}) {
30
36
  yield* shell(command, { ...options, signal: connectedSignal(this, options.signal) });
31
37
  }
32
- constructor(address, connection) {
38
+ constructor(connection) {
33
39
  const lifetime = new AbortController();
34
40
  const handles = new HandleRegistry();
35
- const transport = {
36
- control: (value, signal) => request(address, "system", value, connectedSignal(this, signal)),
37
- api: (value, signal) => request(address, "api", value, connectedSignal(this, signal)),
38
- stream: (target, value, signal) => stream(address, target, value, connectedSignal(this, signal))
39
- };
40
- systems.set(this, { address, connection, handles, lifetime, transport, closed: false });
41
- connection.once("close", () => closeSystem(this, new Error("This System connection is closed")));
41
+ const representation = new SystemRepresentation(connection);
42
+ systems.set(this, { connection, handles, lifetime, representation, closed: false });
43
+ connection.onDisconnect(() => void closeSystem(this, new Error("This System connection is closed")));
42
44
  this.storage = nativeStorage(homedir(), "the native filesystem", () => connectedSignal(this));
43
- this.appearance = new SystemAppearance(transport);
45
+ this.appearance = new SystemAppearance(this);
44
46
  this.program = new ProgramRegistry(this);
45
47
  this.process = new ProcessRegistry(this);
46
- this.uploads = new Uploads(value => transport.api(value), () => connectedSignal(this));
48
+ this.uploads = new Uploads(value => uploadRequest(this, value), () => connectedSignal(this));
49
+ representation.activate();
47
50
  }
48
51
  /** Connect to the System selected by argument, environment, or owner default. */
49
52
  static async connect(home) {
50
53
  const resolved = resolveHome(home);
51
54
  const address = gatewayAddress(resolved);
52
- return new System(address, await openConnection(address));
55
+ return new System(await openConnection(address));
53
56
  }
54
57
  /** Atomically replace one runtime Program without touching its installed form. */
55
58
  async forceCreateProgram(source) {
56
59
  requireConnected(this);
57
- for await (const event of transport(this).stream("program", { word: "force-create", program: source })) {
58
- if (event.event === "created")
59
- return programHandle(this, required(event.program));
60
- }
61
- throw new Error("The System did not confirm the created Program");
60
+ const identity = await representation(this).call("/program/force-create-program", source, "");
61
+ return programHandle(this, required(representation(this).programs.get(identity), identity));
62
62
  }
63
63
  /** Close this owner connection and abort every attached operation it owns. */
64
64
  async disconnect() {
65
- closeSystem(this, new Error("This System connection is closed"));
65
+ await closeSystem(this, new Error("This System connection is closed"));
66
66
  }
67
67
  service(key) {
68
68
  requireConnected(this);
@@ -88,11 +88,12 @@ function systemState(system) {
88
88
  function closeSystem(system, reason) {
89
89
  const state = systemState(system);
90
90
  if (state.closed)
91
- return;
91
+ return Promise.resolve();
92
92
  state.closed = true;
93
93
  state.lifetime.abort(reason);
94
- state.connection.destroy();
94
+ state.representation.close();
95
95
  state.handles.clear();
96
+ return state.connection.disconnect();
96
97
  }
97
98
  function requireConnected(system) {
98
99
  if (systemState(system).closed)
@@ -103,9 +104,9 @@ function connectedSignal(system, signal) {
103
104
  requireConnected(system);
104
105
  return signal ? AbortSignal.any([signal, lifetime]) : lifetime;
105
106
  }
106
- function transport(system) {
107
+ function representation(system) {
107
108
  requireConnected(system);
108
- return systemState(system).transport;
109
+ return systemState(system).representation;
109
110
  }
110
111
  function programHandle(system, snapshot) {
111
112
  const handle = systemState(system).handles.obtain(`program:${snapshot.reference}`, () => new ProgramHandle(system, snapshot));
@@ -116,65 +117,45 @@ function processHandle(system, snapshot) {
116
117
  return systemState(system).handles.obtain(`process:${snapshot.reference}`, () => new ProcessHandle(system, snapshot));
117
118
  }
118
119
  class SystemAppearance extends Events {
119
- transport;
120
- constructor(transport) {
121
- super(["change"], (_event, signal) => transport.api({ capability: "appearance", operation: "wait" }, signal));
122
- this.transport = transport;
120
+ system;
121
+ constructor(system) {
122
+ super(["change"], (_event, subscriber) => representation(system).on("appearance", subscriber));
123
+ this.system = system;
123
124
  }
124
125
  async snapshot() {
125
- return await this.transport.api({ capability: "appearance", operation: "snapshot" });
126
+ return representation(this.system).appearance;
126
127
  }
127
128
  async update(appearance) {
128
- await this.transport.api({ capability: "appearance", operation: "update", value: appearance });
129
+ await representation(this.system).call("/appearance/update", appearance);
129
130
  }
130
131
  }
131
132
  class ProgramRegistry extends Events {
132
133
  system;
133
134
  constructor(system) {
134
- super(["create", "forget", "install", "uninstall"], (event, signal, timeout) => (transport(system).control({ capability: "program", operation: "wait", input: { event, timeout } }, signal)
135
- .then(value => this.event(value))));
135
+ super(["create", "forget", "install", "uninstall"], (event, subscriber) => {
136
+ if (event === null)
137
+ throw new Error("System Program events are named");
138
+ return representation(system).on(`program:${event}`, (...values) => subscriber(this.event(event, values)));
139
+ });
136
140
  this.system = system;
137
141
  }
138
142
  async list(onlyInstalled = false) {
139
- const programs = [];
140
- let offset = 0;
141
- while (true) {
142
- const page = await transport(this.system).control({
143
- capability: "program",
144
- operation: "list",
145
- input: { installedOnly: onlyInstalled, limit: 100, offset }
146
- });
147
- programs.push(...page.data.map(snapshot => programHandle(this.system, snapshot)));
148
- offset += page.data.length;
149
- if (!page.truncated || !page.data.length)
150
- return programs;
151
- }
143
+ return [...representation(this.system).programs.values()]
144
+ .filter(program => !onlyInstalled || program.installed)
145
+ .sort((left, right) => left.identity.localeCompare(right.identity))
146
+ .map(program => programHandle(this.system, program));
152
147
  }
153
148
  async find(identity) {
154
- try {
155
- const snapshot = await transport(this.system).control({ capability: "program", operation: "inspect", input: { program: identity } });
156
- return programHandle(this.system, snapshot);
157
- }
158
- catch (error) {
159
- if (unknown(error, "Program"))
160
- return null;
161
- throw error;
162
- }
149
+ const program = representation(this.system).programs.get(identity);
150
+ return program ? programHandle(this.system, program) : null;
163
151
  }
164
152
  async create(source) {
165
- for await (const event of transport(this.system).stream("program", { word: "create", program: source })) {
166
- if (event.event === "created")
167
- return programHandle(this.system, required(event.program));
168
- }
169
- throw new Error("The System did not confirm the created Program");
153
+ const identity = await representation(this.system).call("/program/create-program", source);
154
+ return programHandle(this.system, required(representation(this.system).programs.get(identity), identity));
170
155
  }
171
- async event(value) {
172
- const waited = value;
173
- if (waited.event === "uninstall") {
174
- const payload = waited.payload;
175
- return { program: programHandle(this.system, required(payload.program)), everything: payload.everything === true };
176
- }
177
- return programHandle(this.system, required(waited.payload));
156
+ event(event, values) {
157
+ const program = programHandle(this.system, required(values[0]));
158
+ return event === "uninstall" ? { program, everything: values[1] === true } : program;
178
159
  }
179
160
  }
180
161
  class ProgramHandle extends ProgramBase {
@@ -197,18 +178,21 @@ class ProgramHandle extends ProgramBase {
197
178
  this.reference = snapshot.reference;
198
179
  this.identity = snapshot.identity;
199
180
  const address = this.address();
200
- bindEvents(this, new Events(["forget", "uninstall"], (event, signal, timeout) => transport(system).api({
201
- capability: "program", operation: "wait", handle: address, event, timeout
202
- }, signal)));
203
- const request = (value) => transport(system).api(value);
181
+ bindEvents(this, new Events(["forget", "uninstall"], (event, subscriber) => {
182
+ if (event === null)
183
+ throw new Error("Program events are named");
184
+ return representation(system).on(`program:${this.reference}:${event}`, (...values) => subscriber(values[0]));
185
+ }));
186
+ representation(system).on(`program:${this.reference}:change`, value => this.update(value));
187
+ const call = (event, ...values) => representation(system).call(event, ...values);
204
188
  this.data = filesystemStorage(() => programStoragePath(system, address, "data"), `Program "${this.identity}" data`, () => connectedSignal(system));
205
189
  this.cache = filesystemStorage(() => programStoragePath(system, address, "cache"), `Program "${this.identity}" cache`, () => connectedSignal(system));
206
- this.store = programStore(request, address);
207
- this.logs = programSql(request, address, "logs");
208
- this.database = programSql(request, address, "database");
190
+ this.store = programStore(call, address);
191
+ this.logs = programSql(call, address, "logs");
192
+ this.database = programSql(call, address, "database");
209
193
  this.process = new ProgramProcesses(system, this);
210
194
  this.startup = new ProgramStartup(system, this);
211
- this.permissions = programPermissions(request, address);
195
+ this.permissions = programPermissions(call, address);
212
196
  }
213
197
  get name() { return this.snapshot.name; }
214
198
  get assetId() { return this.snapshot.assetId; }
@@ -230,7 +214,7 @@ class ProgramHandle extends ProgramBase {
230
214
  position: this.snapshot.client.position,
231
215
  layer: this.snapshot.client.layer,
232
216
  minimize: this.snapshot.client.minimize,
233
- permissions: Object.freeze(Object.fromEntries(Object.entries(this.snapshot.client.permissions).map(([name, values]) => [name, Object.freeze([...values])])))
217
+ permissions: parseClientPermissions(this.snapshot.client.permissions)
234
218
  }) : null;
235
219
  }
236
220
  update(snapshot) {
@@ -239,7 +223,7 @@ class ProgramHandle extends ProgramBase {
239
223
  this.snapshot = snapshot;
240
224
  }
241
225
  async icon(size = "medium") {
242
- const value = await transport(this.system).api({ capability: "program", operation: "icon", handle: this.address(), size });
226
+ const value = await representation(this.system).call("/program/icon", this.address(), size);
243
227
  if (!Array.isArray(value) || value.some(byte => typeof byte !== "number"))
244
228
  throw new Error("The System returned an invalid Program icon");
245
229
  return new Blob([Uint8Array.from(value)], { type: "image/png" });
@@ -247,27 +231,20 @@ class ProgramHandle extends ProgramBase {
247
231
  async agent() {
248
232
  if (!this.hasAgent)
249
233
  return null;
250
- const value = await transport(this.system).api({ capability: "program", operation: "agent", handle: this.address() });
234
+ const value = await representation(this.system).call("/program/agent", this.address());
251
235
  return typeof value === "string" ? value : null;
252
236
  }
253
237
  async installed() {
254
- for await (const event of transport(this.system).stream("program", { word: "installed", handle: this.address() })) {
255
- if (event.event === "installedState")
256
- return event.installed === true;
257
- }
258
- throw new Error("The System returned no Program installation state");
238
+ return this.snapshot.installed;
259
239
  }
260
- install() { return command(this.system, { word: "install-existing", handle: this.address() }); }
261
- uninstall(everything = false) { return command(this.system, { word: "uninstall-existing", handle: this.address(), everything }); }
240
+ install() { return command(this.system, "install", this.address()); }
241
+ uninstall(everything = false) { return command(this.system, "uninstall", this.address(), everything); }
262
242
  async fork(identity) {
263
- for await (const event of transport(this.system).stream("program", { word: "fork", handle: this.address(), identity })) {
264
- if (event.event === "created")
265
- return programHandle(this.system, required(event.program));
266
- }
267
- throw new Error("The System did not confirm the forked Program");
243
+ const created = await representation(this.system).call("/program/fork-program", this.address(), identity);
244
+ return programHandle(this.system, required(representation(this.system).programs.get(created), created));
268
245
  }
269
246
  async forget() {
270
- for await (const _event of transport(this.system).stream("program", { word: "forget", handle: this.address() })) { /* consume completion */ }
247
+ await representation(this.system).call("/program/forget-program", this.address(), "");
271
248
  }
272
249
  address() { return Object.freeze({ identity: this.identity, reference: this.reference }); }
273
250
  }
@@ -279,13 +256,7 @@ class ProgramStartup {
279
256
  this.program = program;
280
257
  }
281
258
  async get() {
282
- for await (const event of transport(this.system).stream("program", {
283
- word: "startup", handle: this.program.address(), operation: "get"
284
- })) {
285
- if (event.event === "startup")
286
- return event.launch;
287
- }
288
- throw new Error("The System returned no Program startup state");
259
+ return await representation(this.system).call("/program/startup", this.program.address(), "get");
289
260
  }
290
261
  async enable(launch = {}) {
291
262
  await this.change("enable", launch);
@@ -294,30 +265,25 @@ class ProgramStartup {
294
265
  await this.change("disable");
295
266
  }
296
267
  async change(operation, launch) {
297
- for await (const event of transport(this.system).stream("program", {
298
- word: "startup", handle: this.program.address(), operation, launch
299
- })) {
300
- if (event.event === "startup")
301
- return;
302
- }
303
- throw new Error("The System did not confirm the Program startup change");
268
+ await representation(this.system).call("/program/startup", this.program.address(), operation, launch);
304
269
  }
305
270
  }
306
271
  class ProgramProcesses extends Events {
307
272
  system;
308
273
  program;
309
274
  constructor(system, program) {
310
- super(["create", "exit"], (event, signal, timeout) => transport(system).api({
311
- capability: "programProcess", operation: "wait", handle: program.address(), event, timeout
312
- }, signal).then(value => programProcessEvent(system, event, value)));
275
+ super(["create", "exit"], (event, subscriber) => {
276
+ if (event === null)
277
+ throw new Error("Program Process events are named");
278
+ return representation(system).on(`program:${program.identity}:process:${event}`, (...values) => (subscriber(programProcessEvent(system, event, values))));
279
+ });
313
280
  this.system = system;
314
281
  this.program = program;
315
282
  }
316
283
  async list() {
317
- const value = await transport(this.system).api({ capability: "programProcess", operation: "list", handle: this.program.address() });
318
- if (!Array.isArray(value))
319
- throw new Error("The System returned an invalid Program Process list");
320
- return value.map(snapshot => processHandle(this.system, snapshot));
284
+ return [...representation(this.system).processes.values()]
285
+ .filter(process => process.program === this.program.identity)
286
+ .map(process => processHandle(this.system, process));
321
287
  }
322
288
  async first() { return (await this.list()).sort(chronological)[0] ?? null; }
323
289
  async last() { return (await this.list()).sort(chronological).at(-1) ?? null; }
@@ -328,13 +294,12 @@ class ProgramProcesses extends Events {
328
294
  create(launch = {}) { return this.createExact("create-process", launch); }
329
295
  async *run(launch = {}, options = {}) {
330
296
  let process = null;
331
- for await (const event of transport(this.system).stream("program", {
332
- word: "run-process",
333
- handle: this.program.address(),
334
- launch
335
- }, options.signal)) {
297
+ for await (const event of representation(this.system).command("run", this.program.address(), launch, options.signal)) {
336
298
  if (event.event === "started") {
337
- process = processHandle(this.system, required(event.process));
299
+ const identity = event.process?.identity;
300
+ if (typeof identity !== "string")
301
+ throw new Error("The System returned an invalid started Process");
302
+ process = processHandle(this.system, required(representation(this.system).processes.get(identity), identity));
338
303
  yield Object.freeze({ event: "started", process });
339
304
  }
340
305
  else if (event.event === "output") {
@@ -370,42 +335,32 @@ class ProgramProcesses extends Events {
370
335
  }
371
336
  findOrCreate(launch) { return this.createExact("find-or-create-process", launch); }
372
337
  async exitAll() {
373
- const processes = await this.list();
374
- await Promise.all(processes.map(process => process.exit()));
375
- return processes.map(process => process.identity);
338
+ return await representation(this.system).call("/process/exit-all", this.program.identity, "");
376
339
  }
377
340
  async createExact(word, launch) {
378
- for await (const event of transport(this.system).stream("program", { word, handle: this.program.address(), launch })) {
379
- if (event.event === "createdProcess")
380
- return processHandle(this.system, required(event.process));
381
- }
382
- throw new Error("The System did not confirm the created Process");
341
+ const route = word === "create-process" ? "/program/create-process" : "/program/find-or-create-process";
342
+ const identity = await representation(this.system).call(route, this.program.address(), launch, null);
343
+ return processHandle(this.system, required(representation(this.system).processes.get(identity), identity));
383
344
  }
384
345
  }
385
346
  class ProcessRegistry extends Events {
386
347
  system;
387
348
  constructor(system) {
388
- super(["create", "exit"], (event, signal, timeout) => transport(system).control({
389
- capability: "process", operation: "wait", input: { event, timeout }
390
- }, signal).then(value => processEvent(system, value)));
349
+ super(["create", "exit"], (event, subscriber) => {
350
+ if (event === null)
351
+ throw new Error("System Process events are named");
352
+ return representation(system).on(`process:${event}`, (...values) => subscriber(processEvent(system, event, values)));
353
+ });
391
354
  this.system = system;
392
355
  }
393
356
  list() { return listProcesses(this.system); }
394
357
  async find(identity) {
395
- try {
396
- const snapshot = await transport(this.system).control({ capability: "process", operation: "inspect", input: { process: identity } });
397
- return processHandle(this.system, snapshot);
398
- }
399
- catch (error) {
400
- if (unknown(error, "Process"))
401
- return null;
402
- throw error;
403
- }
358
+ const process = representation(this.system).processes.get(identity);
359
+ return process ? processHandle(this.system, process) : null;
404
360
  }
405
361
  }
406
362
  class ProcessHandle extends ProcessBase {
407
363
  system;
408
- snapshot;
409
364
  identity;
410
365
  name;
411
366
  startedAt;
@@ -414,30 +369,32 @@ class ProcessHandle extends ProcessBase {
414
369
  constructor(system, snapshot) {
415
370
  super();
416
371
  this.system = system;
417
- this.snapshot = snapshot;
418
- bindEvents(this, new Events(["exit"], (event, signal, timeout) => transport(system).control({
419
- capability: "process", operation: "wait", input: { process: snapshot.identity, event, timeout }
420
- }, signal).then(exactProcessEvent)));
372
+ processSnapshots.set(this, snapshot);
373
+ bindEvents(this, new Events(["exit"], (_event, subscriber) => (representation(system).on(`process:${snapshot.reference}:exit`, value => subscriber(value)))));
421
374
  this.identity = snapshot.identity;
422
375
  this.name = snapshot.name;
423
376
  this.startedAt = new Date(snapshot.startedAt);
424
377
  this.server = new ServerEndpointHandle(system, this);
425
378
  this.client = new ClientEndpointHandle(system, this);
426
379
  }
427
- program() { return programHandle(this.system, required(this.snapshot.programSnapshot, this.snapshot.program)); }
380
+ program() {
381
+ const snapshot = processState(this.system, this);
382
+ return programHandle(this.system, required(representation(this.system).programs.get(snapshot.program), snapshot.program));
383
+ }
428
384
  async parent() {
429
385
  if (!await this.exists())
430
386
  throw new Error(`Process "${this.identity}" no longer exists`);
431
- if (this.snapshot.parent === null)
387
+ const snapshot = processState(this.system, this);
388
+ if (snapshot.parent === null)
432
389
  return null;
433
- const parent = await this.system.process.find(this.snapshot.parent);
390
+ const parent = await this.system.process.find(snapshot.parent.identity);
434
391
  if (!parent)
435
392
  throw new Error("The parent Process no longer exists");
436
393
  return parent;
437
394
  }
438
- async option(name) { return this.snapshot.options[name]; }
395
+ async option(name) { return processState(this.system, this).options[name]; }
439
396
  async exit() {
440
- await transport(this.system).control({ capability: "process", operation: "exit", input: { process: this.identity } });
397
+ await representation(this.system).call("/process/exit", this.identity);
441
398
  }
442
399
  async exited() { return await this.system.process.find(this.identity) === null; }
443
400
  async exists() { return !await this.exited(); }
@@ -448,49 +405,34 @@ class EndpointOperations extends Events {
448
405
  endpoint;
449
406
  lifecycle;
450
407
  constructor(system, owner, endpoint) {
451
- super([], (event, signal, timeout) => event === null
452
- ? transport(system).api({ capability: "endpoint", operation: "wait", process: owner.identity, endpoint, event, timeout }, signal)
453
- : transport(system).control({
454
- capability: "endpoint", operation: "wait", input: { process: owner.identity, endpoint, event, timeout }
455
- }, signal).then(value => value.payload));
408
+ super([], (event, subscriber, impossible) => representation(system).follow({
409
+ scope: "endpoint",
410
+ process: owner.identity,
411
+ endpoint,
412
+ event
413
+ }, (_received, payload) => subscriber(payload), impossible));
456
414
  this.system = system;
457
415
  this.owner = owner;
458
416
  this.endpoint = endpoint;
459
- this.lifecycle = new Events(["start", "stop"], (event, signal, timeout) => {
460
- return waitEndpointLifecycle(system, owner, endpoint, event, signal, timeout);
461
- });
417
+ this.lifecycle = new Events(["start", "stop"], (event, subscriber, impossible) => (endpointLifecycle(system, owner, endpoint, event, subscriber, impossible)));
462
418
  }
463
419
  process() { return Promise.resolve(this.owner); }
464
420
  async exists() {
465
- const value = await this.inspect();
466
- return value.running;
421
+ return endpointState(this.system, this.owner, this.endpoint) !== null;
467
422
  }
468
423
  async start(launch = {}) { await this.operation("start", launch); }
469
424
  async stop() { await this.operation("stop"); }
470
425
  async waitReady(timeout) {
471
- await transport(this.system).control({ capability: "endpoint", operation: "waitReady", input: {
472
- process: this.owner.identity, endpoint: this.endpoint, timeout
473
- } });
426
+ await waitEndpointReady(this.system, this.owner, this.endpoint, timeout);
474
427
  }
475
428
  async isService() {
476
- return await transport(this.system).api({
477
- capability: "endpoint", operation: "isService", process: this.owner.identity, endpoint: this.endpoint
478
- });
429
+ return endpointState(this.system, this.owner, this.endpoint)?.service === true;
479
430
  }
480
431
  publish(event, payload) {
481
- void transport(this.system).control({ capability: "endpoint", operation: "publish", input: {
482
- process: this.owner.identity, endpoint: this.endpoint, event, payload
483
- } });
484
- }
485
- inspect() {
486
- return transport(this.system).control({ capability: "endpoint", operation: "inspect", input: {
487
- process: this.owner.identity, endpoint: this.endpoint
488
- } });
432
+ void representation(this.system).call("/process/endpoint/publish", this.owner.identity, this.endpoint, event, payload);
489
433
  }
490
434
  async operation(operation, launch) {
491
- await transport(this.system).control({ capability: "endpoint", operation, input: {
492
- process: this.owner.identity, endpoint: this.endpoint, ...(launch ? { launch } : {})
493
- } });
435
+ await representation(this.system).call(`/process/endpoint/${operation}`, this.owner.identity, this.endpoint, launch);
494
436
  }
495
437
  }
496
438
  class ServerEndpointHandle extends ServerEndpointBase {
@@ -505,7 +447,7 @@ class ServerEndpointHandle extends ServerEndpointBase {
505
447
  this.system = system;
506
448
  this.owner = owner;
507
449
  this.base = new EndpointOperations(system, owner, "server");
508
- this.traffic = new ServerTrafficHandle((value, signal) => transport(system).api(value, signal), owner.identity, "server", value => endpointFromReference(system, value));
450
+ this.traffic = new ServerTrafficHandle(representation(system), owner.identity, "server", value => endpointFromReference(system, value));
509
451
  this.lifecycle = this.base.lifecycle;
510
452
  bindEvents(this, this.base);
511
453
  }
@@ -517,14 +459,13 @@ class ServerEndpointHandle extends ServerEndpointBase {
517
459
  stop() { return this.base.stop(); }
518
460
  publish(event, payload) { return this.base.publish(event, payload); }
519
461
  async ask(event, payload) {
520
- return await transport(this.system).control({ capability: "endpoint", operation: "ask", input: {
521
- process: this.owner.identity, endpoint: "server", event, payload
522
- } });
462
+ return await this.askWithin(event, payload, 10_000);
523
463
  }
524
464
  timeout(milliseconds) {
525
- return { ask: (event, payload) => transport(this.system).control({
526
- capability: "endpoint", operation: "ask", input: { process: this.owner.identity, endpoint: "server", event, payload, timeout: milliseconds }
527
- }) };
465
+ return { ask: (event, payload) => this.askWithin(event, payload, milliseconds) };
466
+ }
467
+ askWithin(event, payload, timeout) {
468
+ return representation(this.system).call("/process/endpoint/ask", this.owner.identity, event, payload, timeout);
528
469
  }
529
470
  }
530
471
  class ClientEndpointHandle extends ClientEndpointBase {
@@ -536,7 +477,7 @@ class ClientEndpointHandle extends ClientEndpointBase {
536
477
  constructor(system, owner) {
537
478
  super();
538
479
  this.base = new EndpointOperations(system, owner, "client");
539
- this.traffic = new EndpointTrafficHandle((value, signal) => transport(system).api(value, signal), owner.identity, "client", value => endpointFromReference(system, value));
480
+ this.traffic = new EndpointTrafficHandle(representation(system), owner.identity, "client", value => endpointFromReference(system, value));
540
481
  this.lifecycle = this.base.lifecycle;
541
482
  bindEvents(this, this.base);
542
483
  this.window = new SystemWindow(system, owner);
@@ -553,9 +494,11 @@ class SystemWindow extends Events {
553
494
  system;
554
495
  process;
555
496
  constructor(system, process) {
556
- super(["move", "resize", "geometry", "minimize", "changeTitle", "front"], (event, signal, timeout) => transport(system).control({
557
- capability: "window", operation: "wait", input: { process: process.identity, event, timeout }
558
- }, signal).then(value => value.payload));
497
+ super(["move", "resize", "geometry", "minimize", "changeTitle", "front"], (event, subscriber) => {
498
+ if (event === null)
499
+ throw new Error("Window events are named");
500
+ return representation(system).on(`window:${process.identity}:${event}`, subscriber);
501
+ });
559
502
  this.system = system;
560
503
  this.process = process;
561
504
  }
@@ -563,20 +506,23 @@ class SystemWindow extends Events {
563
506
  async position() { return (await this.snapshot()).position; }
564
507
  async size() { return (await this.snapshot()).size; }
565
508
  async minimized() { return (await this.snapshot()).minimized; }
566
- async front() { return (await this.snapshot()).front; }
509
+ async front() { return frontWindow(this.system, this.process); }
567
510
  async layer() { return (await this.snapshot()).layer; }
568
511
  async location() { return (await this.snapshot()).location; }
569
- async move(position) { await this.change("move", { position }); }
570
- async resize(size) { await this.change("resize", { size }); }
571
- async setGeometry(geometry) { await this.change("setGeometry", geometry); }
572
- async minimize(minimized = true) { await this.change("minimize", { minimized }); }
573
- async changeTitle(title) { await this.change("changeTitle", { title }); }
574
- async raise() { await this.change("raise", {}); }
512
+ async move(position) { await this.change("move", position); }
513
+ async resize(size) { await this.change("resize", size); }
514
+ async setGeometry(geometry) { await this.change("geometry", geometry); }
515
+ async minimize(minimized = true) { await this.change("minimize", minimized); }
516
+ async changeTitle(title) { await this.change("change-title", title); }
517
+ async raise() { await this.change("raise"); }
575
518
  snapshot() {
576
- return transport(this.system).control({ capability: "window", operation: "inspect", input: { process: this.process.identity } });
519
+ const window = processState(this.system, this.process).client?.window;
520
+ if (!window)
521
+ throw new Error(`Process "${this.process.identity}" has no live Client Endpoint`);
522
+ return Promise.resolve(window);
577
523
  }
578
524
  async change(operation, input) {
579
- await transport(this.system).control({ capability: "window", operation, input: { process: this.process.identity, ...input } });
525
+ await representation(this.system).call(`/process/${operation}`, this.process.identity, input);
580
526
  }
581
527
  }
582
528
  class ServiceBase {
@@ -586,16 +532,16 @@ class ServiceBase {
586
532
  constructor(system, key) {
587
533
  this.system = system;
588
534
  this.key = key;
589
- this.lifecycle = new Events(["start", "stop"], (event, signal, timeout) => transport(system).api({
590
- capability: "service", operation: "wait", scope: "lifecycle", key, event, timeout
591
- }, signal));
535
+ this.lifecycle = new Events(["start", "stop"], (event, subscriber, impossible) => representation(system).follow({
536
+ scope: "service", key, kind: "lifecycle", event
537
+ }, (_received, payload) => subscriber(payload), impossible));
592
538
  }
593
- async exists() { return await transport(this.system).api({ capability: "service", operation: "exists", key: this.key }); }
539
+ async exists() { return serviceState(this.system, this.key) !== null; }
594
540
  async waitReady(timeout) {
595
- await transport(this.system).api({ capability: "service", operation: "waitReady", key: this.key, timeout });
541
+ await representation(this.system).call("/process/service/wait-ready", this.key, timeout);
596
542
  }
597
543
  publish(event, payload) {
598
- void transport(this.system).api({ capability: "service", operation: "publish", key: this.key, event, payload });
544
+ void representation(this.system).call("/process/service/publish", this.key, event, payload);
599
545
  }
600
546
  }
601
547
  /** Node SDK handle for a Service provided by a Server Endpoint. */
@@ -613,20 +559,18 @@ class ServerServiceHandle extends ServerService {
613
559
  this.key = key;
614
560
  this.base = new ServiceBase(system, key);
615
561
  this.lifecycle = this.base.lifecycle;
616
- bindEvents(this, new Events([], (event, signal, timeout) => transport(system).api({
617
- capability: "service", operation: "wait", scope: "events", key, event, timeout
618
- }, signal)));
562
+ bindEvents(this, new Events([], (event, subscriber, impossible) => representation(system).follow({
563
+ scope: "service", key, kind: "events", event
564
+ }, (_received, payload) => subscriber(payload), impossible)));
619
565
  }
620
566
  exists() { return this.base.exists(); }
621
567
  waitReady(timeout) { return this.base.waitReady(timeout); }
622
568
  publish = (event, payload) => this.base.publish(event, payload);
623
569
  async ask(event, payload) {
624
- return await transport(this.system).api({ capability: "service", operation: "ask", key: this.key, event, payload });
570
+ return await representation(this.system).call("/process/service/ask", this.key, event, payload, 10_000);
625
571
  }
626
572
  timeout(milliseconds) {
627
- return { ask: (event, payload) => transport(this.system).api({
628
- capability: "service", operation: "ask", key: this.key, event, payload, timeout: milliseconds
629
- }) };
573
+ return { ask: (event, payload) => representation(this.system).call("/process/service/ask", this.key, event, payload, milliseconds) };
630
574
  }
631
575
  }
632
576
  /** Node SDK handle for a Service provided by a Client Endpoint. */
@@ -640,27 +584,21 @@ class ClientServiceHandle extends ClientService {
640
584
  super();
641
585
  this.base = new ServiceBase(system, key);
642
586
  this.lifecycle = this.base.lifecycle;
643
- bindEvents(this, new Events([], (event, signal, timeout) => transport(system).api({
644
- capability: "service", operation: "wait", scope: "events", key, event, timeout
645
- }, signal)));
587
+ bindEvents(this, new Events([], (event, subscriber, impossible) => representation(system).follow({
588
+ scope: "service", key, kind: "events", event
589
+ }, (_received, payload) => subscriber(payload), impossible)));
646
590
  }
647
591
  exists() { return this.base.exists(); }
648
592
  waitReady(timeout) { return this.base.waitReady(timeout); }
649
593
  publish = (event, payload) => this.base.publish(event, payload);
650
594
  }
651
595
  async function listProcesses(system) {
652
- const processes = [];
653
- let offset = 0;
654
- while (true) {
655
- const page = await transport(system).control({ capability: "process", operation: "list", input: { limit: 100, offset } });
656
- processes.push(...page.data.map(snapshot => processHandle(system, snapshot)));
657
- offset += page.data.length;
658
- if (!page.truncated || !page.data.length)
659
- return processes;
660
- }
661
- }
662
- async function* command(system, request) {
663
- for await (const event of transport(system).stream("program", request)) {
596
+ return [...representation(system).processes.values()]
597
+ .sort((left, right) => right.startedAt.getTime() - left.startedAt.getTime())
598
+ .map(process => processHandle(system, process));
599
+ }
600
+ async function* command(system, operation, subject, value) {
601
+ for await (const event of representation(system).command(operation, subject, value)) {
664
602
  if (event.event === "output")
665
603
  yield {
666
604
  stream: event.stream === "stderr" ? "stderr" : "stdout",
@@ -668,49 +606,21 @@ async function* command(system, request) {
668
606
  };
669
607
  }
670
608
  }
671
- async function waitEndpointLifecycle(system, owner, endpoint, event, signal, timeout = 10_000) {
609
+ function endpointLifecycle(system, owner, endpoint, event, subscriber, impossible) {
672
610
  if (event !== "start" && event !== "stop")
673
611
  throw new Error(`An Endpoint lifecycle has no "${event}" event`);
674
- await transport(system).control({
675
- capability: "endpoint",
676
- operation: "waitLifecycle",
677
- input: { process: owner.identity, endpoint, event, timeout }
678
- }, signal);
679
- }
680
- function processEvent(system, value) {
681
- const waited = value;
682
- const payload = waited.payload;
683
- if (waited.event === "exit" && payload)
684
- return {
685
- process: processHandle(system, required(payload.processSnapshot, String(payload.process ?? ""))),
686
- status: payload.status,
687
- code: payload.code,
688
- signal: payload.signal
689
- };
690
- if (payload && typeof payload.identity === "string")
691
- return processHandle(system, payload);
692
- return payload;
693
- }
694
- function programProcessEvent(system, event, value) {
695
- if (event === "create")
696
- return processHandle(system, value);
697
- const exit = value;
698
- return {
699
- process: processHandle(system, required(exit.process)),
700
- status: exit.status,
701
- code: exit.code,
702
- signal: exit.signal
703
- };
612
+ const model = representation(system);
613
+ const stopEvent = model.on(`endpoint:${processReference(owner)}:${endpoint}:${event}`, () => subscriber(undefined));
614
+ const stopExit = model.on(`process:${processReference(owner)}:exit`, () => impossible?.(new Error(`Process "${owner.identity}" exited`)));
615
+ return () => { stopEvent(); stopExit(); };
704
616
  }
705
- function exactProcessEvent(value) {
706
- const payload = value.payload;
707
- if (!payload)
708
- return payload;
709
- return {
710
- status: payload.status,
711
- code: payload.code,
712
- signal: payload.signal
713
- };
617
+ function processEvent(system, event, values) {
618
+ const process = processHandle(system, required(values[0]));
619
+ return event === "exit" ? { process, ...values[1] } : process;
620
+ }
621
+ function programProcessEvent(system, event, values) {
622
+ const process = processHandle(system, required(values[0]));
623
+ return event === "exit" ? { process, ...values[1] } : process;
714
624
  }
715
625
  function bindEvents(target, events) {
716
626
  Object.assign(target, eventsOf(events));
@@ -724,36 +634,84 @@ function eventsOf(events) {
724
634
  }
725
635
  function chronological(left, right) { return left.startedAt.getTime() - right.startedAt.getTime(); }
726
636
  async function programStoragePath(system, handle, area) {
727
- const value = await transport(system).api({ capability: "program", operation: "storagePath", handle, area });
637
+ const value = await representation(system).call("/program/area", handle, area, "path", []);
728
638
  if (typeof value !== "string")
729
639
  throw new Error("The System returned an invalid Program storage path");
730
640
  return value;
731
641
  }
732
642
  function endpointFromReference(system, value) {
733
643
  const reference = value;
734
- if (!reference || (reference.kind !== "server" && reference.kind !== "client"))
644
+ if (!reference || (reference.kind !== "server" && reference.kind !== "client") || typeof reference.process?.identity !== "string") {
735
645
  throw new Error("The System returned an invalid Endpoint reference");
736
- const owner = processHandle(system, snapshotFromReference(reference.process));
646
+ }
647
+ const owner = processHandle(system, required(representation(system).processes.get(reference.process.identity), reference.process.identity));
737
648
  return reference.kind === "server" ? owner.server : owner.client;
738
649
  }
739
- function snapshotFromReference(reference) {
740
- const owner = reference.program;
741
- if (!owner || typeof owner.reference !== "string" || typeof owner.identity !== "string")
742
- throw new Error("The System returned an invalid Process reference");
743
- return {
744
- reference: reference.reference,
745
- identity: reference.identity,
746
- name: reference.name,
747
- program: owner.identity,
748
- programSnapshot: owner,
749
- parent: null,
750
- options: reference.options,
751
- startedAt: reference.startedAt,
752
- server: { declared: owner.server !== null, running: reference.server !== null, service: reference.server?.service === true },
753
- client: { declared: owner.client !== null, running: reference.client !== null, service: reference.client?.service === true }
754
- };
650
+ function processState(system, process) {
651
+ const original = required(processSnapshots.get(process));
652
+ const current = representation(system).processes.get(process.identity);
653
+ if (!current || current.reference !== original.reference)
654
+ throw new Error(`Process "${process.identity}" no longer exists`);
655
+ return current;
656
+ }
657
+ function processReference(process) { return required(processSnapshots.get(process)).reference; }
658
+ function endpointState(system, process, endpoint) {
659
+ const current = representation(system).processes.get(process.identity);
660
+ const original = required(processSnapshots.get(process));
661
+ return current?.reference === original.reference ? current[endpoint] : null;
662
+ }
663
+ function waitEndpointReady(system, process, endpoint, timeout = 10_000) {
664
+ if (endpointReady(endpointState(system, process, endpoint), endpoint))
665
+ return Promise.resolve();
666
+ return new Promise((resolve, reject) => {
667
+ const model = representation(system);
668
+ const reference = processReference(process);
669
+ const finish = (work) => {
670
+ clearTimeout(timer);
671
+ stopStart();
672
+ stopReady();
673
+ stopExit();
674
+ work();
675
+ };
676
+ const inspect = () => {
677
+ if (endpointReady(endpointState(system, process, endpoint), endpoint))
678
+ finish(resolve);
679
+ };
680
+ const stopStart = model.on(`endpoint:${reference}:${endpoint}:start`, inspect);
681
+ const stopReady = model.on(`endpoint:${reference}:${endpoint}:ready`, inspect);
682
+ const stopExit = model.on(`process:${reference}:exit`, () => finish(() => reject(new Error(`Process "${process.identity}" exited`))));
683
+ const timer = setTimeout(() => finish(() => reject(new Error("The Endpoint did not become ready before the timeout"))), timeout);
684
+ inspect();
685
+ });
686
+ }
687
+ function endpointReady(state, endpoint) {
688
+ return state !== null && (endpoint === "client" || "ready" in state && state.ready);
689
+ }
690
+ function serviceState(system, key) {
691
+ const model = representation(system);
692
+ const process = key.program === undefined
693
+ ? model.processes.get(key.process)
694
+ : [...model.processes.values()].find(candidate => candidate.program === key.program && (candidate.identity === key.process || candidate.name === key.process));
695
+ const endpoint = process?.[key.endpoint];
696
+ return endpoint?.service === true ? endpoint : null;
697
+ }
698
+ function frontWindow(system, process) {
699
+ const window = processState(system, process).client?.window;
700
+ if (!window || window.minimized)
701
+ return false;
702
+ return ![...representation(system).processes.values()].some(candidate => {
703
+ const other = candidate.client?.window;
704
+ return other && !other.minimized && other.layer === window.layer && other.depth > window.depth;
705
+ });
706
+ }
707
+ async function uploadRequest(system, value) {
708
+ const request = value;
709
+ if (request.operation === "access")
710
+ return representation(system).call("/uploads/access");
711
+ if (request.operation === "stat")
712
+ return representation(system).call("/uploads/stat", request.file);
713
+ throw new Error(`The Uploads API does not know "${String(request.operation)}"`);
755
714
  }
756
- function unknown(error, entity) { return error instanceof Error && error.message.startsWith(`Unknown ${entity}`); }
757
715
  function required(value, identity = "") {
758
716
  if (value !== undefined)
759
717
  return value;