@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/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 +4 -4
- package/dist/program-resources.js +9 -24
- package/dist/representation.d.ts +100 -0
- package/dist/representation.js +331 -0
- package/dist/system.d.ts +1 -0
- package/dist/system.js +239 -281
- 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, {} 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,32 @@ 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
|
-
|
|
387
|
+
const snapshot = processState(this.system, this);
|
|
388
|
+
if (snapshot.parent === null)
|
|
432
389
|
return null;
|
|
433
|
-
const parent = await this.system.process.find(
|
|
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.
|
|
395
|
+
async option(name) { return processState(this.system, this).options[name]; }
|
|
439
396
|
async exit() {
|
|
440
|
-
await
|
|
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,
|
|
452
|
-
|
|
453
|
-
:
|
|
454
|
-
|
|
455
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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((
|
|
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
|
|
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) =>
|
|
526
|
-
|
|
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((
|
|
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,
|
|
557
|
-
|
|
558
|
-
|
|
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 (
|
|
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",
|
|
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"
|
|
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
|
-
|
|
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
|
|
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,
|
|
590
|
-
|
|
591
|
-
},
|
|
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
|
|
539
|
+
async exists() { return serviceState(this.system, this.key) !== null; }
|
|
594
540
|
async waitReady(timeout) {
|
|
595
|
-
await
|
|
541
|
+
await representation(this.system).call("/process/service/wait-ready", this.key, timeout);
|
|
596
542
|
}
|
|
597
543
|
publish(event, payload) {
|
|
598
|
-
void
|
|
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,
|
|
617
|
-
|
|
618
|
-
},
|
|
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
|
|
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) =>
|
|
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,
|
|
644
|
-
|
|
645
|
-
},
|
|
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
|
-
|
|
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)) {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
};
|
|
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
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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;
|