@phreshos/node 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -41
- package/dist/events.d.ts +4 -4
- package/dist/events.js +18 -29
- package/dist/main.d.ts +1 -1
- package/dist/main.js +1 -1
- package/dist/program-resources.d.ts +5 -5
- package/dist/program-resources.js +11 -25
- package/dist/representation.d.ts +99 -0
- package/dist/representation.js +340 -0
- package/dist/system.d.ts +1 -0
- package/dist/system.js +241 -285
- package/dist/traffic.d.ts +4 -4
- package/dist/traffic.js +14 -32
- package/dist/transport.d.ts +20 -10
- package/dist/transport.js +106 -124
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.js +15 -0
- package/package.json +4 -2
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
|
|
10
|
+
import SystemRepresentation, { processIdentityState } 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(
|
|
38
|
+
constructor(connection) {
|
|
33
39
|
const lifetime = new AbortController();
|
|
34
40
|
const handles = new HandleRegistry();
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
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(
|
|
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 =>
|
|
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(
|
|
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
|
-
|
|
58
|
-
|
|
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.
|
|
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
|
|
107
|
+
function representation(system) {
|
|
107
108
|
requireConnected(system);
|
|
108
|
-
return systemState(system).
|
|
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
|
-
|
|
120
|
-
constructor(
|
|
121
|
-
super(["change"], (_event,
|
|
122
|
-
this.
|
|
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
|
|
126
|
+
return representation(this.system).appearance;
|
|
126
127
|
}
|
|
127
128
|
async update(appearance) {
|
|
128
|
-
await this.
|
|
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,
|
|
135
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
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,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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(
|
|
207
|
-
this.logs = programSql(
|
|
208
|
-
this.database = programSql(
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
261
|
-
uninstall(everything = false) { return command(this.system,
|
|
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
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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,
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
396
|
-
|
|
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,30 @@ class ProcessHandle extends ProcessBase {
|
|
|
414
369
|
constructor(system, snapshot) {
|
|
415
370
|
super();
|
|
416
371
|
this.system = system;
|
|
417
|
-
this
|
|
418
|
-
bindEvents(this, new Events(["exit"], (
|
|
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() {
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return parent;
|
|
387
|
+
const value = await representation(this.system).call("/process/parent", {
|
|
388
|
+
identity: this.identity,
|
|
389
|
+
reference: processReference(this)
|
|
390
|
+
});
|
|
391
|
+
return value === null ? null : processHandle(this.system, processIdentityState(value));
|
|
437
392
|
}
|
|
438
|
-
async option(name) { return this.
|
|
393
|
+
async option(name) { return processState(this.system, this).options[name]; }
|
|
439
394
|
async exit() {
|
|
440
|
-
await
|
|
395
|
+
await representation(this.system).call("/process/exit", this.identity);
|
|
441
396
|
}
|
|
442
397
|
async exited() { return await this.system.process.find(this.identity) === null; }
|
|
443
398
|
async exists() { return !await this.exited(); }
|
|
@@ -448,49 +403,34 @@ class EndpointOperations extends Events {
|
|
|
448
403
|
endpoint;
|
|
449
404
|
lifecycle;
|
|
450
405
|
constructor(system, owner, endpoint) {
|
|
451
|
-
super([], (event,
|
|
452
|
-
|
|
453
|
-
:
|
|
454
|
-
|
|
455
|
-
|
|
406
|
+
super([], (event, subscriber, impossible) => representation(system).follow({
|
|
407
|
+
scope: "endpoint",
|
|
408
|
+
process: owner.identity,
|
|
409
|
+
endpoint,
|
|
410
|
+
event
|
|
411
|
+
}, (_received, payload) => subscriber(payload), impossible));
|
|
456
412
|
this.system = system;
|
|
457
413
|
this.owner = owner;
|
|
458
414
|
this.endpoint = endpoint;
|
|
459
|
-
this.lifecycle = new Events(["start", "stop"], (event,
|
|
460
|
-
return waitEndpointLifecycle(system, owner, endpoint, event, signal, timeout);
|
|
461
|
-
});
|
|
415
|
+
this.lifecycle = new Events(["start", "stop"], (event, subscriber, impossible) => (endpointLifecycle(system, owner, endpoint, event, subscriber, impossible)));
|
|
462
416
|
}
|
|
463
417
|
process() { return Promise.resolve(this.owner); }
|
|
464
418
|
async exists() {
|
|
465
|
-
|
|
466
|
-
return value.running;
|
|
419
|
+
return endpointState(this.system, this.owner, this.endpoint) !== null;
|
|
467
420
|
}
|
|
468
421
|
async start(launch = {}) { await this.operation("start", launch); }
|
|
469
422
|
async stop() { await this.operation("stop"); }
|
|
470
423
|
async waitReady(timeout) {
|
|
471
|
-
await
|
|
472
|
-
process: this.owner.identity, endpoint: this.endpoint, timeout
|
|
473
|
-
} });
|
|
424
|
+
await waitEndpointReady(this.system, this.owner, this.endpoint, timeout);
|
|
474
425
|
}
|
|
475
426
|
async isService() {
|
|
476
|
-
return
|
|
477
|
-
capability: "endpoint", operation: "isService", process: this.owner.identity, endpoint: this.endpoint
|
|
478
|
-
});
|
|
427
|
+
return endpointState(this.system, this.owner, this.endpoint)?.service === true;
|
|
479
428
|
}
|
|
480
429
|
publish(event, payload) {
|
|
481
|
-
void
|
|
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
|
-
} });
|
|
430
|
+
void representation(this.system).call("/process/endpoint/publish", this.owner.identity, this.endpoint, event, payload);
|
|
489
431
|
}
|
|
490
432
|
async operation(operation, launch) {
|
|
491
|
-
await
|
|
492
|
-
process: this.owner.identity, endpoint: this.endpoint, ...(launch ? { launch } : {})
|
|
493
|
-
} });
|
|
433
|
+
await representation(this.system).call(`/process/endpoint/${operation}`, this.owner.identity, this.endpoint, launch);
|
|
494
434
|
}
|
|
495
435
|
}
|
|
496
436
|
class ServerEndpointHandle extends ServerEndpointBase {
|
|
@@ -505,7 +445,7 @@ class ServerEndpointHandle extends ServerEndpointBase {
|
|
|
505
445
|
this.system = system;
|
|
506
446
|
this.owner = owner;
|
|
507
447
|
this.base = new EndpointOperations(system, owner, "server");
|
|
508
|
-
this.traffic = new ServerTrafficHandle((
|
|
448
|
+
this.traffic = new ServerTrafficHandle(representation(system), owner.identity, "server", value => endpointFromReference(system, value));
|
|
509
449
|
this.lifecycle = this.base.lifecycle;
|
|
510
450
|
bindEvents(this, this.base);
|
|
511
451
|
}
|
|
@@ -517,14 +457,13 @@ class ServerEndpointHandle extends ServerEndpointBase {
|
|
|
517
457
|
stop() { return this.base.stop(); }
|
|
518
458
|
publish(event, payload) { return this.base.publish(event, payload); }
|
|
519
459
|
async ask(event, payload) {
|
|
520
|
-
return await
|
|
521
|
-
process: this.owner.identity, endpoint: "server", event, payload
|
|
522
|
-
} });
|
|
460
|
+
return await this.askWithin(event, payload, 10_000);
|
|
523
461
|
}
|
|
524
462
|
timeout(milliseconds) {
|
|
525
|
-
return { ask: (event, payload) =>
|
|
526
|
-
|
|
527
|
-
|
|
463
|
+
return { ask: (event, payload) => this.askWithin(event, payload, milliseconds) };
|
|
464
|
+
}
|
|
465
|
+
askWithin(event, payload, timeout) {
|
|
466
|
+
return representation(this.system).call("/process/endpoint/ask", this.owner.identity, event, payload, timeout);
|
|
528
467
|
}
|
|
529
468
|
}
|
|
530
469
|
class ClientEndpointHandle extends ClientEndpointBase {
|
|
@@ -536,7 +475,7 @@ class ClientEndpointHandle extends ClientEndpointBase {
|
|
|
536
475
|
constructor(system, owner) {
|
|
537
476
|
super();
|
|
538
477
|
this.base = new EndpointOperations(system, owner, "client");
|
|
539
|
-
this.traffic = new EndpointTrafficHandle((
|
|
478
|
+
this.traffic = new EndpointTrafficHandle(representation(system), owner.identity, "client", value => endpointFromReference(system, value));
|
|
540
479
|
this.lifecycle = this.base.lifecycle;
|
|
541
480
|
bindEvents(this, this.base);
|
|
542
481
|
this.window = new SystemWindow(system, owner);
|
|
@@ -553,9 +492,11 @@ class SystemWindow extends Events {
|
|
|
553
492
|
system;
|
|
554
493
|
process;
|
|
555
494
|
constructor(system, process) {
|
|
556
|
-
super(["move", "resize", "geometry", "minimize", "changeTitle", "front"], (event,
|
|
557
|
-
|
|
558
|
-
|
|
495
|
+
super(["move", "resize", "geometry", "minimize", "changeTitle", "front"], (event, subscriber) => {
|
|
496
|
+
if (event === null)
|
|
497
|
+
throw new Error("Window events are named");
|
|
498
|
+
return representation(system).on(`window:${process.identity}:${event}`, subscriber);
|
|
499
|
+
});
|
|
559
500
|
this.system = system;
|
|
560
501
|
this.process = process;
|
|
561
502
|
}
|
|
@@ -563,20 +504,23 @@ class SystemWindow extends Events {
|
|
|
563
504
|
async position() { return (await this.snapshot()).position; }
|
|
564
505
|
async size() { return (await this.snapshot()).size; }
|
|
565
506
|
async minimized() { return (await this.snapshot()).minimized; }
|
|
566
|
-
async front() { return (
|
|
507
|
+
async front() { return frontWindow(this.system, this.process); }
|
|
567
508
|
async layer() { return (await this.snapshot()).layer; }
|
|
568
509
|
async location() { return (await this.snapshot()).location; }
|
|
569
|
-
async move(position) { await this.change("move",
|
|
570
|
-
async resize(size) { await this.change("resize",
|
|
571
|
-
async setGeometry(geometry) { await this.change("
|
|
572
|
-
async minimize(minimized = true) { await this.change("minimize",
|
|
573
|
-
async changeTitle(title) { await this.change("
|
|
574
|
-
async raise() { await this.change("raise"
|
|
510
|
+
async move(position) { await this.change("move", position); }
|
|
511
|
+
async resize(size) { await this.change("resize", size); }
|
|
512
|
+
async setGeometry(geometry) { await this.change("geometry", geometry); }
|
|
513
|
+
async minimize(minimized = true) { await this.change("minimize", minimized); }
|
|
514
|
+
async changeTitle(title) { await this.change("change-title", title); }
|
|
515
|
+
async raise() { await this.change("raise"); }
|
|
575
516
|
snapshot() {
|
|
576
|
-
|
|
517
|
+
const window = processState(this.system, this.process).client?.window;
|
|
518
|
+
if (!window)
|
|
519
|
+
throw new Error(`Process "${this.process.identity}" has no live Client Endpoint`);
|
|
520
|
+
return Promise.resolve(window);
|
|
577
521
|
}
|
|
578
522
|
async change(operation, input) {
|
|
579
|
-
await
|
|
523
|
+
await representation(this.system).call(`/process/${operation}`, this.process.identity, input);
|
|
580
524
|
}
|
|
581
525
|
}
|
|
582
526
|
class ServiceBase {
|
|
@@ -586,16 +530,16 @@ class ServiceBase {
|
|
|
586
530
|
constructor(system, key) {
|
|
587
531
|
this.system = system;
|
|
588
532
|
this.key = key;
|
|
589
|
-
this.lifecycle = new Events(["start", "stop"], (event,
|
|
590
|
-
|
|
591
|
-
},
|
|
533
|
+
this.lifecycle = new Events(["start", "stop"], (event, subscriber, impossible) => representation(system).follow({
|
|
534
|
+
scope: "service", key, kind: "lifecycle", event
|
|
535
|
+
}, (_received, payload) => subscriber(payload), impossible));
|
|
592
536
|
}
|
|
593
|
-
async exists() { return
|
|
537
|
+
async exists() { return serviceState(this.system, this.key) !== null; }
|
|
594
538
|
async waitReady(timeout) {
|
|
595
|
-
await
|
|
539
|
+
await representation(this.system).call("/process/service/wait-ready", this.key, timeout);
|
|
596
540
|
}
|
|
597
541
|
publish(event, payload) {
|
|
598
|
-
void
|
|
542
|
+
void representation(this.system).call("/process/service/publish", this.key, event, payload);
|
|
599
543
|
}
|
|
600
544
|
}
|
|
601
545
|
/** Node SDK handle for a Service provided by a Server Endpoint. */
|
|
@@ -613,20 +557,18 @@ class ServerServiceHandle extends ServerService {
|
|
|
613
557
|
this.key = key;
|
|
614
558
|
this.base = new ServiceBase(system, key);
|
|
615
559
|
this.lifecycle = this.base.lifecycle;
|
|
616
|
-
bindEvents(this, new Events([], (event,
|
|
617
|
-
|
|
618
|
-
},
|
|
560
|
+
bindEvents(this, new Events([], (event, subscriber, impossible) => representation(system).follow({
|
|
561
|
+
scope: "service", key, kind: "events", event
|
|
562
|
+
}, (_received, payload) => subscriber(payload), impossible)));
|
|
619
563
|
}
|
|
620
564
|
exists() { return this.base.exists(); }
|
|
621
565
|
waitReady(timeout) { return this.base.waitReady(timeout); }
|
|
622
566
|
publish = (event, payload) => this.base.publish(event, payload);
|
|
623
567
|
async ask(event, payload) {
|
|
624
|
-
return await
|
|
568
|
+
return await representation(this.system).call("/process/service/ask", this.key, event, payload, 10_000);
|
|
625
569
|
}
|
|
626
570
|
timeout(milliseconds) {
|
|
627
|
-
return { ask: (event, payload) =>
|
|
628
|
-
capability: "service", operation: "ask", key: this.key, event, payload, timeout: milliseconds
|
|
629
|
-
}) };
|
|
571
|
+
return { ask: (event, payload) => representation(this.system).call("/process/service/ask", this.key, event, payload, milliseconds) };
|
|
630
572
|
}
|
|
631
573
|
}
|
|
632
574
|
/** Node SDK handle for a Service provided by a Client Endpoint. */
|
|
@@ -640,27 +582,21 @@ class ClientServiceHandle extends ClientService {
|
|
|
640
582
|
super();
|
|
641
583
|
this.base = new ServiceBase(system, key);
|
|
642
584
|
this.lifecycle = this.base.lifecycle;
|
|
643
|
-
bindEvents(this, new Events([], (event,
|
|
644
|
-
|
|
645
|
-
},
|
|
585
|
+
bindEvents(this, new Events([], (event, subscriber, impossible) => representation(system).follow({
|
|
586
|
+
scope: "service", key, kind: "events", event
|
|
587
|
+
}, (_received, payload) => subscriber(payload), impossible)));
|
|
646
588
|
}
|
|
647
589
|
exists() { return this.base.exists(); }
|
|
648
590
|
waitReady(timeout) { return this.base.waitReady(timeout); }
|
|
649
591
|
publish = (event, payload) => this.base.publish(event, payload);
|
|
650
592
|
}
|
|
651
593
|
async function listProcesses(system) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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)) {
|
|
594
|
+
return [...representation(system).processes.values()]
|
|
595
|
+
.sort((left, right) => right.startedAt.getTime() - left.startedAt.getTime())
|
|
596
|
+
.map(process => processHandle(system, process));
|
|
597
|
+
}
|
|
598
|
+
async function* command(system, operation, subject, value) {
|
|
599
|
+
for await (const event of representation(system).command(operation, subject, value)) {
|
|
664
600
|
if (event.event === "output")
|
|
665
601
|
yield {
|
|
666
602
|
stream: event.stream === "stderr" ? "stderr" : "stdout",
|
|
@@ -668,49 +604,21 @@ async function* command(system, request) {
|
|
|
668
604
|
};
|
|
669
605
|
}
|
|
670
606
|
}
|
|
671
|
-
|
|
607
|
+
function endpointLifecycle(system, owner, endpoint, event, subscriber, impossible) {
|
|
672
608
|
if (event !== "start" && event !== "stop")
|
|
673
609
|
throw new Error(`An Endpoint lifecycle has no "${event}" event`);
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
};
|
|
610
|
+
const model = representation(system);
|
|
611
|
+
const stopEvent = model.on(`endpoint:${processReference(owner)}:${endpoint}:${event}`, () => subscriber(undefined));
|
|
612
|
+
const stopExit = model.on(`process:${processReference(owner)}:exit`, () => impossible?.(new Error(`Process "${owner.identity}" exited`)));
|
|
613
|
+
return () => { stopEvent(); stopExit(); };
|
|
704
614
|
}
|
|
705
|
-
function
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
signal: payload.signal
|
|
713
|
-
};
|
|
615
|
+
function processEvent(system, event, values) {
|
|
616
|
+
const process = processHandle(system, required(values[0]));
|
|
617
|
+
return event === "exit" ? { process, ...values[1] } : process;
|
|
618
|
+
}
|
|
619
|
+
function programProcessEvent(system, event, values) {
|
|
620
|
+
const process = processHandle(system, required(values[0]));
|
|
621
|
+
return event === "exit" ? { process, ...values[1] } : process;
|
|
714
622
|
}
|
|
715
623
|
function bindEvents(target, events) {
|
|
716
624
|
Object.assign(target, eventsOf(events));
|
|
@@ -724,36 +632,84 @@ function eventsOf(events) {
|
|
|
724
632
|
}
|
|
725
633
|
function chronological(left, right) { return left.startedAt.getTime() - right.startedAt.getTime(); }
|
|
726
634
|
async function programStoragePath(system, handle, area) {
|
|
727
|
-
const value = await
|
|
635
|
+
const value = await representation(system).call("/program/area", handle, area, "path", []);
|
|
728
636
|
if (typeof value !== "string")
|
|
729
637
|
throw new Error("The System returned an invalid Program storage path");
|
|
730
638
|
return value;
|
|
731
639
|
}
|
|
732
640
|
function endpointFromReference(system, value) {
|
|
733
641
|
const reference = value;
|
|
734
|
-
if (!reference || (reference.kind !== "server" && reference.kind !== "client"))
|
|
642
|
+
if (!reference || (reference.kind !== "server" && reference.kind !== "client") || typeof reference.process?.identity !== "string") {
|
|
735
643
|
throw new Error("The System returned an invalid Endpoint reference");
|
|
736
|
-
|
|
644
|
+
}
|
|
645
|
+
const owner = processHandle(system, required(representation(system).processes.get(reference.process.identity), reference.process.identity));
|
|
737
646
|
return reference.kind === "server" ? owner.server : owner.client;
|
|
738
647
|
}
|
|
739
|
-
function
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
648
|
+
function processState(system, process) {
|
|
649
|
+
const original = required(processSnapshots.get(process));
|
|
650
|
+
const current = representation(system).processes.get(process.identity);
|
|
651
|
+
if (!current || current.reference !== original.reference)
|
|
652
|
+
throw new Error(`Process "${process.identity}" no longer exists`);
|
|
653
|
+
return current;
|
|
654
|
+
}
|
|
655
|
+
function processReference(process) { return required(processSnapshots.get(process)).reference; }
|
|
656
|
+
function endpointState(system, process, endpoint) {
|
|
657
|
+
const current = representation(system).processes.get(process.identity);
|
|
658
|
+
const original = required(processSnapshots.get(process));
|
|
659
|
+
return current?.reference === original.reference ? current[endpoint] : null;
|
|
660
|
+
}
|
|
661
|
+
function waitEndpointReady(system, process, endpoint, timeout = 10_000) {
|
|
662
|
+
if (endpointReady(endpointState(system, process, endpoint), endpoint))
|
|
663
|
+
return Promise.resolve();
|
|
664
|
+
return new Promise((resolve, reject) => {
|
|
665
|
+
const model = representation(system);
|
|
666
|
+
const reference = processReference(process);
|
|
667
|
+
const finish = (work) => {
|
|
668
|
+
clearTimeout(timer);
|
|
669
|
+
stopStart();
|
|
670
|
+
stopReady();
|
|
671
|
+
stopExit();
|
|
672
|
+
work();
|
|
673
|
+
};
|
|
674
|
+
const inspect = () => {
|
|
675
|
+
if (endpointReady(endpointState(system, process, endpoint), endpoint))
|
|
676
|
+
finish(resolve);
|
|
677
|
+
};
|
|
678
|
+
const stopStart = model.on(`endpoint:${reference}:${endpoint}:start`, inspect);
|
|
679
|
+
const stopReady = model.on(`endpoint:${reference}:${endpoint}:ready`, inspect);
|
|
680
|
+
const stopExit = model.on(`process:${reference}:exit`, () => finish(() => reject(new Error(`Process "${process.identity}" exited`))));
|
|
681
|
+
const timer = setTimeout(() => finish(() => reject(new Error("The Endpoint did not become ready before the timeout"))), timeout);
|
|
682
|
+
inspect();
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
function endpointReady(state, endpoint) {
|
|
686
|
+
return state !== null && (endpoint === "client" || "ready" in state && state.ready);
|
|
687
|
+
}
|
|
688
|
+
function serviceState(system, key) {
|
|
689
|
+
const model = representation(system);
|
|
690
|
+
const process = key.program === undefined
|
|
691
|
+
? model.processes.get(key.process)
|
|
692
|
+
: [...model.processes.values()].find(candidate => candidate.program === key.program && (candidate.identity === key.process || candidate.name === key.process));
|
|
693
|
+
const endpoint = process?.[key.endpoint];
|
|
694
|
+
return endpoint?.service === true ? endpoint : null;
|
|
695
|
+
}
|
|
696
|
+
function frontWindow(system, process) {
|
|
697
|
+
const window = processState(system, process).client?.window;
|
|
698
|
+
if (!window || window.minimized)
|
|
699
|
+
return false;
|
|
700
|
+
return ![...representation(system).processes.values()].some(candidate => {
|
|
701
|
+
const other = candidate.client?.window;
|
|
702
|
+
return other && !other.minimized && other.layer === window.layer && other.depth > window.depth;
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
async function uploadRequest(system, value) {
|
|
706
|
+
const request = value;
|
|
707
|
+
if (request.operation === "access")
|
|
708
|
+
return representation(system).call("/uploads/access");
|
|
709
|
+
if (request.operation === "stat")
|
|
710
|
+
return representation(system).call("/uploads/stat", request.file);
|
|
711
|
+
throw new Error(`The Uploads API does not know "${String(request.operation)}"`);
|
|
755
712
|
}
|
|
756
|
-
function unknown(error, entity) { return error instanceof Error && error.message.startsWith(`Unknown ${entity}`); }
|
|
757
713
|
function required(value, identity = "") {
|
|
758
714
|
if (value !== undefined)
|
|
759
715
|
return value;
|