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