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