@phreshos/node 0.1.11 → 0.1.13
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 +5 -0
- package/dist/development-client.d.ts +20 -0
- package/dist/development-client.js +259 -0
- package/dist/main.d.ts +2 -2
- package/dist/main.js +1 -1
- package/dist/process-tree.d.ts +10 -0
- package/dist/process-tree.js +67 -0
- package/dist/program-resources.d.ts +9 -3
- package/dist/program-resources.js +22 -4
- package/dist/project.d.ts +5 -3
- package/dist/project.js +53 -13
- package/dist/shell.d.ts +3 -0
- package/dist/shell.js +125 -0
- package/dist/storage.d.ts +5 -1
- package/dist/storage.js +29 -7
- package/dist/system.d.ts +25 -31
- package/dist/system.js +106 -63
- package/dist/traffic.d.ts +3 -3
- package/dist/traffic.js +1 -1
- package/dist/transport.d.ts +2 -2
- package/dist/transport.js +18 -4
- package/dist/uploads.d.ts +4 -1
- package/dist/uploads.js +20 -5
- package/package.json +3 -3
package/dist/system.js
CHANGED
|
@@ -1,39 +1,49 @@
|
|
|
1
|
-
import {
|
|
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";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { gatewayAddress } from "./address.js";
|
|
4
4
|
import Events from "./events.js";
|
|
5
5
|
import HandleRegistry from "./handle-registry.js";
|
|
6
6
|
import { resolveHome } from "./home.js";
|
|
7
|
-
import { filesystemStorage } from "./storage.js";
|
|
8
|
-
import { programSql, programStore } from "./program-resources.js";
|
|
7
|
+
import { filesystemStorage, nativeStorage } from "./storage.js";
|
|
8
|
+
import { programPermissions, programSql, programStore } from "./program-resources.js";
|
|
9
9
|
import { EndpointTrafficHandle, ServerTrafficHandle } from "./traffic.js";
|
|
10
|
-
import { openConnection, request,
|
|
10
|
+
import { openConnection, request, stream } from "./transport.js";
|
|
11
11
|
import Uploads from "./uploads.js";
|
|
12
|
+
import shell from "./shell.js";
|
|
12
13
|
const systems = new WeakMap();
|
|
13
14
|
const ProgramBase = CoreProgram;
|
|
14
15
|
const ProcessBase = CoreProcess;
|
|
15
|
-
const
|
|
16
|
-
const
|
|
16
|
+
const ServerEndpointBase = CoreServerEndpoint;
|
|
17
|
+
const ClientEndpointBase = CoreClientEndpoint;
|
|
17
18
|
/** One connected owner-local implementation of the shared System contract. */
|
|
18
19
|
export class System {
|
|
19
|
-
storage
|
|
20
|
+
storage;
|
|
20
21
|
appearance;
|
|
21
22
|
program;
|
|
22
23
|
process;
|
|
23
24
|
uploads;
|
|
25
|
+
async fetch(input, init) {
|
|
26
|
+
const request = new Request(input, init);
|
|
27
|
+
return await fetch(request, { signal: connectedSignal(this, request.signal) });
|
|
28
|
+
}
|
|
29
|
+
async *shell(command, options = {}) {
|
|
30
|
+
yield* shell(command, { ...options, signal: connectedSignal(this, options.signal) });
|
|
31
|
+
}
|
|
24
32
|
constructor(address, connection) {
|
|
25
33
|
const lifetime = new AbortController();
|
|
26
34
|
const handles = new HandleRegistry();
|
|
27
35
|
const transport = {
|
|
28
36
|
control: (value, signal) => request(address, "system", value, connectedSignal(this, signal)),
|
|
29
37
|
api: (value, signal) => request(address, "api", value, connectedSignal(this, signal)),
|
|
30
|
-
|
|
38
|
+
stream: (target, value, signal) => stream(address, target, value, connectedSignal(this, signal))
|
|
31
39
|
};
|
|
32
40
|
systems.set(this, { address, connection, handles, lifetime, transport, closed: false });
|
|
41
|
+
connection.once("close", () => closeSystem(this, new Error("This System connection is closed")));
|
|
42
|
+
this.storage = nativeStorage(homedir(), "the native filesystem", () => connectedSignal(this));
|
|
33
43
|
this.appearance = new SystemAppearance(transport);
|
|
34
44
|
this.program = new ProgramRegistry(this);
|
|
35
45
|
this.process = new ProcessRegistry(this);
|
|
36
|
-
this.uploads = new Uploads(value => transport.api(value));
|
|
46
|
+
this.uploads = new Uploads(value => transport.api(value), () => connectedSignal(this));
|
|
37
47
|
}
|
|
38
48
|
/** Connect to the System selected by argument, environment, or owner default. */
|
|
39
49
|
static async connect(home) {
|
|
@@ -44,7 +54,7 @@ export class System {
|
|
|
44
54
|
/** Atomically replace one runtime Program without touching its installed form. */
|
|
45
55
|
async forceCreateProgram(source) {
|
|
46
56
|
requireConnected(this);
|
|
47
|
-
for await (const event of transport(this).
|
|
57
|
+
for await (const event of transport(this).stream("program", { word: "force-create", program: source })) {
|
|
48
58
|
if (event.event === "created")
|
|
49
59
|
return programHandle(this, required(event.program));
|
|
50
60
|
}
|
|
@@ -52,13 +62,7 @@ export class System {
|
|
|
52
62
|
}
|
|
53
63
|
/** Close this owner connection and abort every attached operation it owns. */
|
|
54
64
|
async disconnect() {
|
|
55
|
-
|
|
56
|
-
if (state.closed)
|
|
57
|
-
return;
|
|
58
|
-
state.closed = true;
|
|
59
|
-
state.lifetime.abort(new Error("This System connection is closed"));
|
|
60
|
-
state.connection.destroy();
|
|
61
|
-
state.handles.clear();
|
|
65
|
+
closeSystem(this, new Error("This System connection is closed"));
|
|
62
66
|
}
|
|
63
67
|
service(key) {
|
|
64
68
|
requireConnected(this);
|
|
@@ -81,6 +85,15 @@ function systemState(system) {
|
|
|
81
85
|
throw new Error("Unknown System connection");
|
|
82
86
|
return state;
|
|
83
87
|
}
|
|
88
|
+
function closeSystem(system, reason) {
|
|
89
|
+
const state = systemState(system);
|
|
90
|
+
if (state.closed)
|
|
91
|
+
return;
|
|
92
|
+
state.closed = true;
|
|
93
|
+
state.lifetime.abort(reason);
|
|
94
|
+
state.connection.destroy();
|
|
95
|
+
state.handles.clear();
|
|
96
|
+
}
|
|
84
97
|
function requireConnected(system) {
|
|
85
98
|
if (systemState(system).closed)
|
|
86
99
|
throw new Error("This System connection is closed");
|
|
@@ -149,7 +162,7 @@ class ProgramRegistry extends Events {
|
|
|
149
162
|
}
|
|
150
163
|
}
|
|
151
164
|
async create(source) {
|
|
152
|
-
for await (const event of transport(this.system).
|
|
165
|
+
for await (const event of transport(this.system).stream("program", { word: "create", program: source })) {
|
|
153
166
|
if (event.event === "created")
|
|
154
167
|
return programHandle(this.system, required(event.program));
|
|
155
168
|
}
|
|
@@ -175,26 +188,30 @@ class ProgramHandle extends ProgramBase {
|
|
|
175
188
|
database;
|
|
176
189
|
process;
|
|
177
190
|
startup;
|
|
191
|
+
permissions;
|
|
178
192
|
snapshot;
|
|
179
193
|
constructor(system, snapshot) {
|
|
180
194
|
super();
|
|
181
195
|
this.system = system;
|
|
182
196
|
this.snapshot = snapshot;
|
|
183
|
-
bindEvents(this, new Events(["forget", "uninstall"], (event, signal, timeout) => transport(system).control({
|
|
184
|
-
capability: "program", operation: "wait", input: { program: snapshot.identity, event, timeout }
|
|
185
|
-
}, signal).then(value => programEntityEvent(event, value))));
|
|
186
197
|
this.reference = snapshot.reference;
|
|
187
198
|
this.identity = snapshot.identity;
|
|
199
|
+
const address = this.address();
|
|
200
|
+
bindEvents(this, new Events(["forget", "uninstall"], (event, signal, timeout) => transport(system).api({
|
|
201
|
+
capability: "program", operation: "wait", handle: address, event, timeout
|
|
202
|
+
}, signal)));
|
|
188
203
|
const request = (value) => transport(system).api(value);
|
|
189
|
-
this.data = filesystemStorage(() => programStoragePath(system,
|
|
190
|
-
this.cache = filesystemStorage(() => programStoragePath(system,
|
|
191
|
-
this.store = programStore(request,
|
|
192
|
-
this.logs = programSql(request,
|
|
193
|
-
this.database = programSql(request,
|
|
204
|
+
this.data = filesystemStorage(() => programStoragePath(system, address, "data"), `Program "${this.identity}" data`, () => connectedSignal(system));
|
|
205
|
+
this.cache = filesystemStorage(() => programStoragePath(system, address, "cache"), `Program "${this.identity}" cache`, () => connectedSignal(system));
|
|
206
|
+
this.store = programStore(request, address);
|
|
207
|
+
this.logs = programSql(request, address, "logs");
|
|
208
|
+
this.database = programSql(request, address, "database");
|
|
194
209
|
this.process = new ProgramProcesses(system, this);
|
|
195
210
|
this.startup = new ProgramStartup(system, this);
|
|
211
|
+
this.permissions = programPermissions(request, address);
|
|
196
212
|
}
|
|
197
213
|
get name() { return this.snapshot.name; }
|
|
214
|
+
get assetId() { return this.snapshot.assetId; }
|
|
198
215
|
get version() { return this.snapshot.version; }
|
|
199
216
|
get description() { return this.snapshot.description; }
|
|
200
217
|
get hasAgent() { return this.snapshot.hasAgent; }
|
|
@@ -212,7 +229,8 @@ class ProgramHandle extends ProgramBase {
|
|
|
212
229
|
size: this.snapshot.client.size,
|
|
213
230
|
position: this.snapshot.client.position,
|
|
214
231
|
layer: this.snapshot.client.layer,
|
|
215
|
-
minimize: this.snapshot.client.minimize
|
|
232
|
+
minimize: this.snapshot.client.minimize,
|
|
233
|
+
permissions: Object.freeze(Object.fromEntries(Object.entries(this.snapshot.client.permissions).map(([name, values]) => [name, Object.freeze([...values])])))
|
|
216
234
|
}) : null;
|
|
217
235
|
}
|
|
218
236
|
update(snapshot) {
|
|
@@ -221,7 +239,7 @@ class ProgramHandle extends ProgramBase {
|
|
|
221
239
|
this.snapshot = snapshot;
|
|
222
240
|
}
|
|
223
241
|
async icon(size = "medium") {
|
|
224
|
-
const value = await transport(this.system).api({ capability: "program", operation: "icon",
|
|
242
|
+
const value = await transport(this.system).api({ capability: "program", operation: "icon", handle: this.address(), size });
|
|
225
243
|
if (!Array.isArray(value) || value.some(byte => typeof byte !== "number"))
|
|
226
244
|
throw new Error("The System returned an invalid Program icon");
|
|
227
245
|
return new Blob([Uint8Array.from(value)], { type: "image/png" });
|
|
@@ -229,11 +247,11 @@ class ProgramHandle extends ProgramBase {
|
|
|
229
247
|
async agent() {
|
|
230
248
|
if (!this.hasAgent)
|
|
231
249
|
return null;
|
|
232
|
-
const value = await transport(this.system).
|
|
233
|
-
return typeof value
|
|
250
|
+
const value = await transport(this.system).api({ capability: "program", operation: "agent", handle: this.address() });
|
|
251
|
+
return typeof value === "string" ? value : null;
|
|
234
252
|
}
|
|
235
253
|
async installed() {
|
|
236
|
-
for await (const event of transport(this.system).
|
|
254
|
+
for await (const event of transport(this.system).stream("program", { word: "installed", handle: this.address() })) {
|
|
237
255
|
if (event.event === "installedState")
|
|
238
256
|
return event.installed === true;
|
|
239
257
|
}
|
|
@@ -241,8 +259,15 @@ class ProgramHandle extends ProgramBase {
|
|
|
241
259
|
}
|
|
242
260
|
install() { return command(this.system, { word: "install-existing", handle: this.address() }); }
|
|
243
261
|
uninstall(everything = false) { return command(this.system, { word: "uninstall-existing", handle: this.address(), everything }); }
|
|
262
|
+
async fork(identity) {
|
|
263
|
+
for await (const event of transport(this.system).stream("program", { word: "fork", handle: this.address(), identity })) {
|
|
264
|
+
if (event.event === "created")
|
|
265
|
+
return programHandle(this.system, required(event.program));
|
|
266
|
+
}
|
|
267
|
+
throw new Error("The System did not confirm the forked Program");
|
|
268
|
+
}
|
|
244
269
|
async forget() {
|
|
245
|
-
for await (const _event of transport(this.system).
|
|
270
|
+
for await (const _event of transport(this.system).stream("program", { word: "forget", handle: this.address() })) { /* consume completion */ }
|
|
246
271
|
}
|
|
247
272
|
address() { return Object.freeze({ identity: this.identity, reference: this.reference }); }
|
|
248
273
|
}
|
|
@@ -254,7 +279,7 @@ class ProgramStartup {
|
|
|
254
279
|
this.program = program;
|
|
255
280
|
}
|
|
256
281
|
async get() {
|
|
257
|
-
for await (const event of transport(this.system).
|
|
282
|
+
for await (const event of transport(this.system).stream("program", {
|
|
258
283
|
word: "startup", handle: this.program.address(), operation: "get"
|
|
259
284
|
})) {
|
|
260
285
|
if (event.event === "startup")
|
|
@@ -269,7 +294,7 @@ class ProgramStartup {
|
|
|
269
294
|
await this.change("disable");
|
|
270
295
|
}
|
|
271
296
|
async change(operation, launch) {
|
|
272
|
-
for await (const event of transport(this.system).
|
|
297
|
+
for await (const event of transport(this.system).stream("program", {
|
|
273
298
|
word: "startup", handle: this.program.address(), operation, launch
|
|
274
299
|
})) {
|
|
275
300
|
if (event.event === "startup")
|
|
@@ -282,13 +307,18 @@ class ProgramProcesses extends Events {
|
|
|
282
307
|
system;
|
|
283
308
|
program;
|
|
284
309
|
constructor(system, program) {
|
|
285
|
-
super(["create", "exit"], (event, signal, timeout) => transport(system).
|
|
286
|
-
capability: "
|
|
287
|
-
}, signal).then(value =>
|
|
310
|
+
super(["create", "exit"], (event, signal, timeout) => transport(system).api({
|
|
311
|
+
capability: "programProcess", operation: "wait", handle: program.address(), event, timeout
|
|
312
|
+
}, signal).then(value => programProcessEvent(system, event, value)));
|
|
288
313
|
this.system = system;
|
|
289
314
|
this.program = program;
|
|
290
315
|
}
|
|
291
|
-
async list() {
|
|
316
|
+
async list() {
|
|
317
|
+
const value = await transport(this.system).api({ capability: "programProcess", operation: "list", handle: this.program.address() });
|
|
318
|
+
if (!Array.isArray(value))
|
|
319
|
+
throw new Error("The System returned an invalid Program Process list");
|
|
320
|
+
return value.map(snapshot => processHandle(this.system, snapshot));
|
|
321
|
+
}
|
|
292
322
|
async first() { return (await this.list()).sort(chronological)[0] ?? null; }
|
|
293
323
|
async last() { return (await this.list()).sort(chronological).at(-1) ?? null; }
|
|
294
324
|
async find(identityOrName) {
|
|
@@ -298,7 +328,7 @@ class ProgramProcesses extends Events {
|
|
|
298
328
|
create(launch = {}) { return this.createExact("create-process", launch); }
|
|
299
329
|
async *run(launch = {}, options = {}) {
|
|
300
330
|
let process = null;
|
|
301
|
-
for await (const event of transport(this.system).
|
|
331
|
+
for await (const event of transport(this.system).stream("program", {
|
|
302
332
|
word: "run-process",
|
|
303
333
|
handle: this.program.address(),
|
|
304
334
|
launch
|
|
@@ -345,7 +375,7 @@ class ProgramProcesses extends Events {
|
|
|
345
375
|
return processes.map(process => process.identity);
|
|
346
376
|
}
|
|
347
377
|
async createExact(word, launch) {
|
|
348
|
-
for await (const event of transport(this.system).
|
|
378
|
+
for await (const event of transport(this.system).stream("program", { word, handle: this.program.address(), launch })) {
|
|
349
379
|
if (event.event === "createdProcess")
|
|
350
380
|
return processHandle(this.system, required(event.process));
|
|
351
381
|
}
|
|
@@ -391,8 +421,8 @@ class ProcessHandle extends ProcessBase {
|
|
|
391
421
|
this.identity = snapshot.identity;
|
|
392
422
|
this.name = snapshot.name;
|
|
393
423
|
this.startedAt = new Date(snapshot.startedAt);
|
|
394
|
-
this.server = new
|
|
395
|
-
this.client = new
|
|
424
|
+
this.server = new ServerEndpointHandle(system, this);
|
|
425
|
+
this.client = new ClientEndpointHandle(system, this);
|
|
396
426
|
}
|
|
397
427
|
program() { return programHandle(this.system, required(this.snapshot.programSnapshot, this.snapshot.program)); }
|
|
398
428
|
async parent() {
|
|
@@ -437,6 +467,11 @@ class EndpointOperations extends Events {
|
|
|
437
467
|
}
|
|
438
468
|
async start(launch = {}) { await this.operation("start", launch); }
|
|
439
469
|
async stop() { await this.operation("stop"); }
|
|
470
|
+
async waitReady(timeout) {
|
|
471
|
+
await transport(this.system).control({ capability: "endpoint", operation: "waitReady", input: {
|
|
472
|
+
process: this.owner.identity, endpoint: this.endpoint, timeout
|
|
473
|
+
} });
|
|
474
|
+
}
|
|
440
475
|
async isService() {
|
|
441
476
|
return await transport(this.system).api({
|
|
442
477
|
capability: "endpoint", operation: "isService", process: this.owner.identity, endpoint: this.endpoint
|
|
@@ -458,7 +493,7 @@ class EndpointOperations extends Events {
|
|
|
458
493
|
} });
|
|
459
494
|
}
|
|
460
495
|
}
|
|
461
|
-
class
|
|
496
|
+
class ServerEndpointHandle extends ServerEndpointBase {
|
|
462
497
|
system;
|
|
463
498
|
owner;
|
|
464
499
|
endpoint = "server";
|
|
@@ -476,6 +511,7 @@ class ServerEndpoint extends ServerBase {
|
|
|
476
511
|
}
|
|
477
512
|
process() { return this.base.process(); }
|
|
478
513
|
exists() { return this.base.exists(); }
|
|
514
|
+
waitReady(timeout) { return this.base.waitReady(timeout); }
|
|
479
515
|
isService() { return this.base.isService(); }
|
|
480
516
|
start(launch) { return this.base.start(launch); }
|
|
481
517
|
stop() { return this.base.stop(); }
|
|
@@ -490,11 +526,8 @@ class ServerEndpoint extends ServerBase {
|
|
|
490
526
|
capability: "endpoint", operation: "ask", input: { process: this.owner.identity, endpoint: "server", event, payload, timeout: milliseconds }
|
|
491
527
|
}) };
|
|
492
528
|
}
|
|
493
|
-
async waitReady(timeout) {
|
|
494
|
-
await transport(this.system).control({ capability: "endpoint", operation: "waitReady", input: { process: this.owner.identity, endpoint: "server", timeout } });
|
|
495
|
-
}
|
|
496
529
|
}
|
|
497
|
-
class
|
|
530
|
+
class ClientEndpointHandle extends ClientEndpointBase {
|
|
498
531
|
endpoint = "client";
|
|
499
532
|
traffic;
|
|
500
533
|
lifecycle;
|
|
@@ -510,6 +543,7 @@ class ClientEndpoint extends ClientBase {
|
|
|
510
543
|
}
|
|
511
544
|
process() { return this.base.process(); }
|
|
512
545
|
exists() { return this.base.exists(); }
|
|
546
|
+
waitReady(timeout) { return this.base.waitReady(timeout); }
|
|
513
547
|
isService() { return this.base.isService(); }
|
|
514
548
|
start(launch) { return this.base.start(launch); }
|
|
515
549
|
stop() { return this.base.stop(); }
|
|
@@ -557,11 +591,14 @@ class ServiceBase {
|
|
|
557
591
|
}, signal));
|
|
558
592
|
}
|
|
559
593
|
async exists() { return await transport(this.system).api({ capability: "service", operation: "exists", key: this.key }); }
|
|
594
|
+
async waitReady(timeout) {
|
|
595
|
+
await transport(this.system).api({ capability: "service", operation: "waitReady", key: this.key, timeout });
|
|
596
|
+
}
|
|
560
597
|
publish(event, payload) {
|
|
561
598
|
void transport(this.system).api({ capability: "service", operation: "publish", key: this.key, event, payload });
|
|
562
599
|
}
|
|
563
600
|
}
|
|
564
|
-
/** Node
|
|
601
|
+
/** Node SDK handle for a Service provided by a Server Endpoint. */
|
|
565
602
|
export class ServerService extends CoreServerService {
|
|
566
603
|
constructor() { super(); }
|
|
567
604
|
}
|
|
@@ -581,9 +618,7 @@ class ServerServiceHandle extends ServerService {
|
|
|
581
618
|
}, signal)));
|
|
582
619
|
}
|
|
583
620
|
exists() { return this.base.exists(); }
|
|
584
|
-
|
|
585
|
-
await transport(this.system).api({ capability: "service", operation: "waitReady", key: this.key, timeout });
|
|
586
|
-
}
|
|
621
|
+
waitReady(timeout) { return this.base.waitReady(timeout); }
|
|
587
622
|
publish = (event, payload) => this.base.publish(event, payload);
|
|
588
623
|
async ask(event, payload) {
|
|
589
624
|
return await transport(this.system).api({ capability: "service", operation: "ask", key: this.key, event, payload });
|
|
@@ -594,7 +629,7 @@ class ServerServiceHandle extends ServerService {
|
|
|
594
629
|
}) };
|
|
595
630
|
}
|
|
596
631
|
}
|
|
597
|
-
/** Node
|
|
632
|
+
/** Node SDK handle for a Service provided by a Client Endpoint. */
|
|
598
633
|
export class ClientService extends CoreClientService {
|
|
599
634
|
constructor() { super(); }
|
|
600
635
|
}
|
|
@@ -610,13 +645,14 @@ class ClientServiceHandle extends ClientService {
|
|
|
610
645
|
}, signal)));
|
|
611
646
|
}
|
|
612
647
|
exists() { return this.base.exists(); }
|
|
648
|
+
waitReady(timeout) { return this.base.waitReady(timeout); }
|
|
613
649
|
publish = (event, payload) => this.base.publish(event, payload);
|
|
614
650
|
}
|
|
615
|
-
async function listProcesses(system
|
|
651
|
+
async function listProcesses(system) {
|
|
616
652
|
const processes = [];
|
|
617
653
|
let offset = 0;
|
|
618
654
|
while (true) {
|
|
619
|
-
const page = await transport(system).control({ capability: "process", operation: "list", input: {
|
|
655
|
+
const page = await transport(system).control({ capability: "process", operation: "list", input: { limit: 100, offset } });
|
|
620
656
|
processes.push(...page.data.map(snapshot => processHandle(system, snapshot)));
|
|
621
657
|
offset += page.data.length;
|
|
622
658
|
if (!page.truncated || !page.data.length)
|
|
@@ -624,7 +660,7 @@ async function listProcesses(system, program) {
|
|
|
624
660
|
}
|
|
625
661
|
}
|
|
626
662
|
async function* command(system, request) {
|
|
627
|
-
for await (const event of transport(system).
|
|
663
|
+
for await (const event of transport(system).stream("program", request)) {
|
|
628
664
|
if (event.event === "output")
|
|
629
665
|
yield {
|
|
630
666
|
stream: event.stream === "stderr" ? "stderr" : "stdout",
|
|
@@ -641,10 +677,6 @@ async function waitEndpointLifecycle(system, owner, endpoint, event, signal, tim
|
|
|
641
677
|
input: { process: owner.identity, endpoint, event, timeout }
|
|
642
678
|
}, signal);
|
|
643
679
|
}
|
|
644
|
-
function programEntityEvent(event, value) {
|
|
645
|
-
const payload = value.payload;
|
|
646
|
-
return event === "uninstall" ? payload?.everything === true : undefined;
|
|
647
|
-
}
|
|
648
680
|
function processEvent(system, value) {
|
|
649
681
|
const waited = value;
|
|
650
682
|
const payload = waited.payload;
|
|
@@ -659,6 +691,17 @@ function processEvent(system, value) {
|
|
|
659
691
|
return processHandle(system, payload);
|
|
660
692
|
return payload;
|
|
661
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
|
+
};
|
|
704
|
+
}
|
|
662
705
|
function exactProcessEvent(value) {
|
|
663
706
|
const payload = value.payload;
|
|
664
707
|
if (!payload)
|
|
@@ -680,8 +723,8 @@ function eventsOf(events) {
|
|
|
680
723
|
};
|
|
681
724
|
}
|
|
682
725
|
function chronological(left, right) { return left.startedAt.getTime() - right.startedAt.getTime(); }
|
|
683
|
-
async function programStoragePath(system,
|
|
684
|
-
const value = await transport(system).api({ capability: "program", operation: "storagePath",
|
|
726
|
+
async function programStoragePath(system, handle, area) {
|
|
727
|
+
const value = await transport(system).api({ capability: "program", operation: "storagePath", handle, area });
|
|
685
728
|
if (typeof value !== "string")
|
|
686
729
|
throw new Error("The System returned an invalid Program storage path");
|
|
687
730
|
return value;
|
|
@@ -719,5 +762,5 @@ function required(value, identity = "") {
|
|
|
719
762
|
export const Program = CoreProgram;
|
|
720
763
|
export const Process = CoreProcess;
|
|
721
764
|
export const Endpoint = CoreEndpoint;
|
|
722
|
-
export const
|
|
723
|
-
export const
|
|
765
|
+
export const ServerEndpoint = CoreServerEndpoint;
|
|
766
|
+
export const ClientEndpoint = CoreClientEndpoint;
|
package/dist/traffic.d.ts
CHANGED
|
@@ -15,20 +15,20 @@ export declare class EndpointTrafficHandle<Definitions extends object = {}> exte
|
|
|
15
15
|
event: string;
|
|
16
16
|
questionId: string;
|
|
17
17
|
message: Readonly<{
|
|
18
|
-
to: import("@phreshos/core").
|
|
18
|
+
to: import("@phreshos/core").ServerEndpoint<{}, unknown> | null;
|
|
19
19
|
payload: Payload;
|
|
20
20
|
}>;
|
|
21
21
|
}>>;
|
|
22
22
|
protected follow<Capture>(kind: Kind, convert: (value: unknown) => Capture, subscriber: (capture: Capture) => unknown, impossible?: (error: Error) => void): Cleanup;
|
|
23
23
|
}
|
|
24
|
-
/** Directed traffic originating from one canonical Server. */
|
|
24
|
+
/** Directed traffic originating from one canonical Server Endpoint. */
|
|
25
25
|
export declare class ServerTrafficHandle<Definitions extends object = {}> extends EndpointTrafficHandle<Definitions> implements ServerTraffic<Definitions> {
|
|
26
26
|
subscribeAnswers<Result = unknown>(subscriber: AnswerSubscriber<Result>): Cleanup;
|
|
27
27
|
answers<Result = unknown>(options?: EventOptions): AsyncIterableIterator<Readonly<{
|
|
28
28
|
event: string;
|
|
29
29
|
questionId: string;
|
|
30
30
|
message: Readonly<{
|
|
31
|
-
to: Endpoint<{}> | null;
|
|
31
|
+
to: Endpoint<{}, unknown> | null;
|
|
32
32
|
outcome: import("@phreshos/core").Outcome<Result>;
|
|
33
33
|
}>;
|
|
34
34
|
}>>;
|
package/dist/traffic.js
CHANGED
|
@@ -52,7 +52,7 @@ export class EndpointTrafficHandle extends Events {
|
|
|
52
52
|
return () => controller.abort();
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
/** Directed traffic originating from one canonical Server. */
|
|
55
|
+
/** Directed traffic originating from one canonical Server Endpoint. */
|
|
56
56
|
export class ServerTrafficHandle extends EndpointTrafficHandle {
|
|
57
57
|
subscribeAnswers(subscriber) {
|
|
58
58
|
return this.follow("answer", value => answer(value, this.resolveEndpoint), subscriber);
|
package/dist/transport.d.ts
CHANGED
|
@@ -7,5 +7,5 @@ export interface TransportEvent {
|
|
|
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
|
-
/** Stream one
|
|
11
|
-
export declare function
|
|
10
|
+
/** Stream one long-running authoritative System operation. */
|
|
11
|
+
export declare function stream(path: string, target: "program", request: unknown, signal?: AbortSignal): AsyncGenerator<TransportEvent, void, unknown>;
|
package/dist/transport.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { connect } from "node:net";
|
|
2
|
+
const maximumStreamQueue = 256;
|
|
2
3
|
/** Open and retain one owner-local System connection. */
|
|
3
4
|
export function openConnection(path) {
|
|
4
5
|
return new Promise((resolve, reject) => {
|
|
@@ -52,8 +53,8 @@ export function request(path, target, request, signal) {
|
|
|
52
53
|
cancel();
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
|
-
/** Stream one
|
|
56
|
-
export function
|
|
56
|
+
/** Stream one long-running authoritative System operation. */
|
|
57
|
+
export function stream(path, target, request, signal) {
|
|
57
58
|
const events = [];
|
|
58
59
|
let wake = null;
|
|
59
60
|
let ended = false;
|
|
@@ -65,14 +66,27 @@ export function streamProgram(path, request, signal) {
|
|
|
65
66
|
else
|
|
66
67
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
67
68
|
let buffer = "";
|
|
68
|
-
socket.on("connect", () => socket.write(`${JSON.stringify({ target
|
|
69
|
+
socket.on("connect", () => socket.write(`${JSON.stringify({ target, request })}\n`));
|
|
69
70
|
socket.on("data", chunk => {
|
|
70
71
|
buffer += String(chunk);
|
|
71
72
|
const lines = buffer.split("\n");
|
|
72
73
|
buffer = lines.pop() ?? "";
|
|
73
74
|
for (const line of lines)
|
|
74
75
|
if (line.trim()) {
|
|
75
|
-
|
|
76
|
+
if (events.length >= maximumStreamQueue) {
|
|
77
|
+
failure = new Error(`System stream queue exceeded its capacity of ${maximumStreamQueue}`);
|
|
78
|
+
socket.destroy();
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
let event;
|
|
82
|
+
try {
|
|
83
|
+
event = JSON.parse(line);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
failure = new Error("The System returned an invalid stream event");
|
|
87
|
+
socket.destroy();
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
76
90
|
if (event.event === "error")
|
|
77
91
|
failure = new Error(String(event.message));
|
|
78
92
|
else
|
package/dist/uploads.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ type Ask = (request: object) => Promise<unknown>;
|
|
|
3
3
|
/** Owner-local implementation of the System's opaque upload collection. */
|
|
4
4
|
export default class Uploads implements SystemUploads {
|
|
5
5
|
private readonly ask;
|
|
6
|
+
private readonly lifetime;
|
|
6
7
|
private accessPromise;
|
|
7
|
-
constructor(ask: Ask);
|
|
8
|
+
constructor(ask: Ask, lifetime: () => AbortSignal);
|
|
9
|
+
path(): Promise<string>;
|
|
8
10
|
write(value: unknown): Promise<Upload>;
|
|
9
11
|
stream(file: string): Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>;
|
|
10
12
|
bytes(file: string): Promise<Uint8Array<ArrayBuffer>>;
|
|
@@ -17,5 +19,6 @@ export default class Uploads implements SystemUploads {
|
|
|
17
19
|
time: number;
|
|
18
20
|
}> | null>;
|
|
19
21
|
private access;
|
|
22
|
+
private active;
|
|
20
23
|
}
|
|
21
24
|
export {};
|
package/dist/uploads.js
CHANGED
|
@@ -2,17 +2,23 @@ import { isUploadFile } from "@phreshos/core";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { createReadStream, createWriteStream, mkdirSync } from "node:fs";
|
|
4
4
|
import { rename, rm } from "node:fs/promises";
|
|
5
|
-
import { join } from "node:path";
|
|
5
|
+
import { isAbsolute, join } from "node:path";
|
|
6
6
|
import { Readable } from "node:stream";
|
|
7
7
|
import { pipeline } from "node:stream/promises";
|
|
8
8
|
/** Owner-local implementation of the System's opaque upload collection. */
|
|
9
9
|
export default class Uploads {
|
|
10
10
|
ask;
|
|
11
|
+
lifetime;
|
|
11
12
|
accessPromise = null;
|
|
12
|
-
constructor(ask) {
|
|
13
|
+
constructor(ask, lifetime) {
|
|
13
14
|
this.ask = ask;
|
|
15
|
+
this.lifetime = lifetime;
|
|
16
|
+
}
|
|
17
|
+
async path() {
|
|
18
|
+
return (await this.access()).path;
|
|
14
19
|
}
|
|
15
20
|
async write(value) {
|
|
21
|
+
const signal = this.active();
|
|
16
22
|
const access = await this.access();
|
|
17
23
|
const source = content(value);
|
|
18
24
|
const identity = randomUUID();
|
|
@@ -29,7 +35,8 @@ export default class Uploads {
|
|
|
29
35
|
throw new Error(`The upload exceeds ${access.limit / 1024 / 1024 / 1024} GB`);
|
|
30
36
|
yield chunk;
|
|
31
37
|
}
|
|
32
|
-
}, createWriteStream(temporary, { flags: "wx" }));
|
|
38
|
+
}, createWriteStream(temporary, { flags: "wx" }), { signal });
|
|
39
|
+
signal.throwIfAborted();
|
|
33
40
|
await rename(temporary, destination);
|
|
34
41
|
}
|
|
35
42
|
catch (error) {
|
|
@@ -42,22 +49,25 @@ export default class Uploads {
|
|
|
42
49
|
return upload;
|
|
43
50
|
}
|
|
44
51
|
async stream(file) {
|
|
52
|
+
const signal = this.active();
|
|
45
53
|
requireFile(file);
|
|
46
54
|
const access = await this.access();
|
|
47
|
-
return Readable.toWeb(createReadStream(join(access.path, file)));
|
|
55
|
+
return Readable.toWeb(createReadStream(join(access.path, file), { signal }));
|
|
48
56
|
}
|
|
49
57
|
async bytes(file) { return new Uint8Array(await new Response(await this.stream(file)).arrayBuffer()); }
|
|
50
58
|
async text(file) { return new Response(await this.stream(file)).text(); }
|
|
51
59
|
async json(file) { return JSON.parse(await this.text(file)); }
|
|
52
60
|
async stat(file) {
|
|
61
|
+
this.active();
|
|
53
62
|
requireFile(file);
|
|
54
63
|
return await this.ask({ capability: "uploads", operation: "stat", file });
|
|
55
64
|
}
|
|
56
65
|
access() {
|
|
66
|
+
this.active();
|
|
57
67
|
if (!this.accessPromise) {
|
|
58
68
|
const resolving = this.ask({ capability: "uploads", operation: "access" }).then(value => {
|
|
59
69
|
const access = value;
|
|
60
|
-
if (!access || typeof access.path !== "string" || typeof access.limit !== "number")
|
|
70
|
+
if (!access || typeof access.path !== "string" || !isAbsolute(access.path) || typeof access.limit !== "number")
|
|
61
71
|
throw new Error("The System returned invalid upload access");
|
|
62
72
|
return { path: access.path, limit: access.limit };
|
|
63
73
|
});
|
|
@@ -70,6 +80,11 @@ export default class Uploads {
|
|
|
70
80
|
}
|
|
71
81
|
return this.accessPromise;
|
|
72
82
|
}
|
|
83
|
+
active() {
|
|
84
|
+
const signal = this.lifetime();
|
|
85
|
+
signal.throwIfAborted();
|
|
86
|
+
return signal;
|
|
87
|
+
}
|
|
73
88
|
}
|
|
74
89
|
function requireFile(file) {
|
|
75
90
|
if (!isUploadFile(file))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Node.js access to PhreshOS and Program projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
"packageManager": "bun@1.3.14",
|
|
30
30
|
"scripts": {
|
|
31
31
|
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
32
|
-
"check": "tsc --noEmit",
|
|
32
|
+
"check": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
33
33
|
"build": "node --run clean && tsc --noEmit false --outDir dist --rootDir source",
|
|
34
34
|
"test": "node --run build && node --test tests/*.test.mjs",
|
|
35
35
|
"verify": "node --run check && node --run test",
|
|
36
36
|
"prepack": "node --run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@phreshos/core": "^0.1.
|
|
39
|
+
"@phreshos/core": "^0.1.37",
|
|
40
40
|
"adm-zip": "^0.6.0",
|
|
41
41
|
"jiti": "^2.7.0"
|
|
42
42
|
},
|