@phreshos/node 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/events.d.ts +4 -1
- package/dist/events.js +1 -1
- package/dist/main.d.ts +1 -1
- package/dist/main.js +1 -1
- package/dist/program-resources.d.ts +7 -0
- package/dist/program-resources.js +33 -0
- package/dist/project.js +6 -0
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +19 -11
- package/dist/system.d.ts +2 -2
- package/dist/system.js +99 -33
- package/dist/traffic.d.ts +36 -0
- package/dist/traffic.js +101 -0
- package/package.json +2 -2
package/dist/events.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { Subscribable } from "@phreshos/core";
|
|
1
|
+
import type { Cleanup, EventOptions, Subscribable } from "@phreshos/core";
|
|
2
2
|
type Wait = (event: string | null, signal: AbortSignal, timeout?: number) => Promise<unknown>;
|
|
3
|
+
type Failure = (error: Error) => void;
|
|
4
|
+
type Register<Message> = (subscriber: (message: Message) => unknown, impossible?: Failure) => Cleanup;
|
|
3
5
|
/** Adapts authoritative one-event waits into the shared Subscribable contract. */
|
|
4
6
|
export default class Events<Definitions extends object, Fallback = never> {
|
|
5
7
|
private readonly names;
|
|
@@ -10,4 +12,5 @@ export default class Events<Definitions extends object, Fallback = never> {
|
|
|
10
12
|
readonly events: Subscribable<Definitions, Fallback>["events"];
|
|
11
13
|
private listen;
|
|
12
14
|
}
|
|
15
|
+
export declare function stream<Message>(register: Register<Message>, options?: EventOptions): AsyncIterableIterator<Message>;
|
|
13
16
|
export {};
|
package/dist/events.js
CHANGED
|
@@ -57,7 +57,7 @@ export default class Events {
|
|
|
57
57
|
return () => controller.abort();
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
function stream(register, options = {}) {
|
|
60
|
+
export function stream(register, options = {}) {
|
|
61
61
|
const capacity = options.capacity ?? 64;
|
|
62
62
|
if (capacity !== Infinity && (!Number.isInteger(capacity) || capacity < 0)) {
|
|
63
63
|
throw new Error("An event queue capacity must be a non-negative integer or Infinity");
|
package/dist/main.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { gatewayAddress } from "./address.js";
|
|
2
2
|
export { Client, ClientService, Endpoint, Process, Program, Server, ServerService, System, type ProgramProcessRunEvent, type ProgramProcessRunOptions } from "./system.js";
|
|
3
|
-
export { Service, type
|
|
3
|
+
export { Service, type ClientLaunch, type Launch, type ProgramDefinition, type ServerLaunch, type ServiceKey, } from "@phreshos/core";
|
|
4
4
|
export { resolveHome } from "./home.js";
|
|
5
5
|
export { Project, type Manifest, type PackedProject, type ProjectMode, type ProjectOptions, type ProjectRunOptions } from "./project.js";
|
package/dist/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { gatewayAddress } from "./address.js";
|
|
2
2
|
export { Client, ClientService, Endpoint, Process, Program, Server, ServerService, System } from "./system.js";
|
|
3
|
-
export { Service } from "@phreshos/core";
|
|
3
|
+
export { Service, } from "@phreshos/core";
|
|
4
4
|
export { resolveHome } from "./home.js";
|
|
5
5
|
export { Project } from "./project.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProgramSql, ProgramStore } from "@phreshos/core";
|
|
2
|
+
type Request = (value: object) => Promise<unknown>;
|
|
3
|
+
/** Program-owned key-value storage carried through the owner-local Gateway. */
|
|
4
|
+
export declare function programStore(request: Request, program: string): ProgramStore;
|
|
5
|
+
/** Program-owned SQL capability carried through the owner-local Gateway. */
|
|
6
|
+
export declare function programSql(request: Request, program: string, database: "database" | "logs"): ProgramSql;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Program-owned key-value storage carried through the owner-local Gateway. */
|
|
2
|
+
export function programStore(request, program) {
|
|
3
|
+
const operate = (storeOperation, key, value, ttl) => request({
|
|
4
|
+
capability: "program",
|
|
5
|
+
operation: "store",
|
|
6
|
+
program,
|
|
7
|
+
storeOperation,
|
|
8
|
+
key,
|
|
9
|
+
value,
|
|
10
|
+
ttl
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
get: (key) => operate("get", key),
|
|
14
|
+
set: (key, value, ttl) => operate("set", key, value, ttl),
|
|
15
|
+
delete: (key) => operate("delete", key),
|
|
16
|
+
has: (key) => operate("has", key),
|
|
17
|
+
clear: () => operate("clear")
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Program-owned SQL capability carried through the owner-local Gateway. */
|
|
21
|
+
export function programSql(request, program, database) {
|
|
22
|
+
return {
|
|
23
|
+
query(statement, ...rest) {
|
|
24
|
+
const [text, values] = written(statement, rest);
|
|
25
|
+
return request({ capability: "program", operation: "query", program, database, statement: text, values });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function written(statement, rest) {
|
|
30
|
+
if (typeof statement === "string")
|
|
31
|
+
return [statement, Array.isArray(rest[0]) ? rest[0] : []];
|
|
32
|
+
return [statement.raw.join("?"), rest];
|
|
33
|
+
}
|
package/dist/project.js
CHANGED
|
@@ -70,6 +70,7 @@ export class Project {
|
|
|
70
70
|
...server && { server: {
|
|
71
71
|
location: resolve(this.directory, server.location),
|
|
72
72
|
start: server.start,
|
|
73
|
+
service: server.service,
|
|
73
74
|
installCommand: config.server?.installCommand,
|
|
74
75
|
uninstallCommand: config.server?.uninstallCommand,
|
|
75
76
|
...serverExecution(server)
|
|
@@ -77,6 +78,7 @@ export class Project {
|
|
|
77
78
|
...client && { client: {
|
|
78
79
|
location: /^https?:\/\//i.test(client.location) ? client.location : resolve(this.directory, client.location),
|
|
79
80
|
start: client.start,
|
|
81
|
+
service: client.service,
|
|
80
82
|
title: config.client?.title,
|
|
81
83
|
size: config.client?.size,
|
|
82
84
|
position: config.client?.position,
|
|
@@ -203,6 +205,8 @@ function validateConfig(config) {
|
|
|
203
205
|
throw new Error(`A declared ${half} half must have a location`);
|
|
204
206
|
if (declared.start !== undefined && typeof declared.start !== "boolean")
|
|
205
207
|
throw new Error(`A declared ${half} Endpoint's start default must be true or false`);
|
|
208
|
+
if (declared.service !== undefined && typeof declared.service !== "boolean")
|
|
209
|
+
throw new Error(`A declared ${half} Endpoint's service default must be true or false`);
|
|
206
210
|
}
|
|
207
211
|
if (!(config.server && (config.server.start ?? true)) && !(config.client && (config.client.start ?? true))) {
|
|
208
212
|
throw new Error("A Program's default Process must start a Server Endpoint, a Client Endpoint, or both");
|
|
@@ -269,6 +273,7 @@ function packageDefinition(config, version) {
|
|
|
269
273
|
...config.server && { server: {
|
|
270
274
|
location: "server",
|
|
271
275
|
start: config.server.start,
|
|
276
|
+
service: config.server.service,
|
|
272
277
|
installCommand: config.server.installCommand,
|
|
273
278
|
uninstallCommand: config.server.uninstallCommand,
|
|
274
279
|
...serverExecution(config.server)
|
|
@@ -276,6 +281,7 @@ function packageDefinition(config, version) {
|
|
|
276
281
|
...config.client && { client: {
|
|
277
282
|
location: "client",
|
|
278
283
|
start: config.client.start,
|
|
284
|
+
service: config.client.service,
|
|
279
285
|
title: config.client.title,
|
|
280
286
|
size: config.client.size,
|
|
281
287
|
position: config.client.position,
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Storage } from "@phreshos/core";
|
|
2
|
-
/** Create one filesystem implementation bounded beneath
|
|
3
|
-
export declare function filesystemStorage(
|
|
2
|
+
/** Create one filesystem implementation bounded beneath a resolved absolute root. */
|
|
3
|
+
export declare function filesystemStorage(source: string | (() => Promise<string>), label: string): Storage;
|
package/dist/storage.js
CHANGED
|
@@ -4,13 +4,21 @@ import { rm } from "node:fs/promises";
|
|
|
4
4
|
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
5
5
|
import { Readable } from "node:stream";
|
|
6
6
|
import { pipeline } from "node:stream/promises";
|
|
7
|
-
/** Create one filesystem implementation bounded beneath
|
|
8
|
-
export function filesystemStorage(
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
/** Create one filesystem implementation bounded beneath a resolved absolute root. */
|
|
8
|
+
export function filesystemStorage(source, label) {
|
|
9
|
+
let root = null;
|
|
10
|
+
const resolveRoot = () => {
|
|
11
|
+
if (!root)
|
|
12
|
+
root = Promise.resolve(typeof source === "string" ? source : source()).then(value => {
|
|
13
|
+
if (!isAbsolute(value))
|
|
14
|
+
throw new Error("A Storage root must be absolute");
|
|
15
|
+
return value;
|
|
16
|
+
});
|
|
17
|
+
return root;
|
|
18
|
+
};
|
|
19
|
+
const resolve = async (...parts) => contained(await resolveRoot(), parts);
|
|
12
20
|
async function stream(...parts) {
|
|
13
|
-
const destination = resolve(...parts);
|
|
21
|
+
const destination = await resolve(...parts);
|
|
14
22
|
const found = describe(destination);
|
|
15
23
|
if (!found)
|
|
16
24
|
throw new Error(`There is no ${parts.join("/")} in ${label}`);
|
|
@@ -20,7 +28,7 @@ export function filesystemStorage(root, label) {
|
|
|
20
28
|
}
|
|
21
29
|
async function write(...args) {
|
|
22
30
|
const parts = args.slice(0, -1);
|
|
23
|
-
const destination = resolve(...parts);
|
|
31
|
+
const destination = await resolve(...parts);
|
|
24
32
|
const temporary = join(dirname(destination), `.${randomUUID()}.writing`);
|
|
25
33
|
mkdirSync(dirname(destination), { recursive: true });
|
|
26
34
|
try {
|
|
@@ -38,15 +46,15 @@ export function filesystemStorage(root, label) {
|
|
|
38
46
|
async text(...parts) { return new Response(await stream(...parts)).text(); },
|
|
39
47
|
async json(...parts) { return JSON.parse(await new Response(await stream(...parts)).text()); },
|
|
40
48
|
write,
|
|
41
|
-
async stat(...parts) { return describe(resolve(...parts)); },
|
|
42
|
-
async list(...parts) { return readdirSync(resolve(...parts)).sort(); },
|
|
49
|
+
async stat(...parts) { return describe(await resolve(...parts)); },
|
|
50
|
+
async list(...parts) { return readdirSync(await resolve(...parts)).sort(); },
|
|
43
51
|
async delete(...parts) {
|
|
44
52
|
if (!parts.length)
|
|
45
53
|
throw new Error("Emptying a place is clear, not delete");
|
|
46
|
-
rmSync(resolve(...parts), { recursive: true, force: true });
|
|
54
|
+
rmSync(await resolve(...parts), { recursive: true, force: true });
|
|
47
55
|
},
|
|
48
56
|
async clear(...parts) {
|
|
49
|
-
const destination = resolve(...parts);
|
|
57
|
+
const destination = await resolve(...parts);
|
|
50
58
|
const found = describe(destination);
|
|
51
59
|
if (found && found.kind !== "directory")
|
|
52
60
|
throw new Error("Only a Storage directory can be cleared");
|
package/dist/system.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Client as CoreClient, ClientService as CoreClientService, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, ServerService as CoreServerService, type ProgramDefinition, type ProgramCommandChunk, type ServiceKey, type System as CoreSystem, type SystemClientEntity, type SystemEndpointEntity, type SystemProcessEntity, type SystemProcess, type SystemProgram, type SystemProgramEntity, type SystemServerEntity, type SystemUploads, type WritableAppearance } from "@phreshos/core";
|
|
1
|
+
import { Client as CoreClient, ClientService as CoreClientService, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, ServerService as CoreServerService, type ProgramDefinition, type ProgramCommandChunk, type ServiceKey, type System as CoreSystem, type SystemClientEntity, type SystemEndpointEntity, type SystemProcessEntity, type SystemProcess, type SystemProgram, type SystemProgramEntity, type SystemServerEntity, type SystemUploads, type Storage, type WritableAppearance } from "@phreshos/core";
|
|
2
2
|
export type ProgramProcessRunOptions = Readonly<{
|
|
3
3
|
signal?: AbortSignal;
|
|
4
4
|
}>;
|
|
@@ -14,7 +14,7 @@ export type ProgramProcessRunEvent = Readonly<{
|
|
|
14
14
|
}>;
|
|
15
15
|
/** One connected owner-local implementation of the shared System contract. */
|
|
16
16
|
export declare class System implements CoreSystem {
|
|
17
|
-
readonly storage:
|
|
17
|
+
readonly storage: Storage;
|
|
18
18
|
readonly appearance: WritableAppearance;
|
|
19
19
|
readonly program: SystemProgram;
|
|
20
20
|
readonly process: SystemProcess;
|
package/dist/system.js
CHANGED
|
@@ -5,6 +5,8 @@ import Events from "./events.js";
|
|
|
5
5
|
import HandleRegistry from "./handle-registry.js";
|
|
6
6
|
import { resolveHome } from "./home.js";
|
|
7
7
|
import { filesystemStorage } from "./storage.js";
|
|
8
|
+
import { programSql, programStore } from "./program-resources.js";
|
|
9
|
+
import { EndpointTrafficHandle, ServerTrafficHandle } from "./traffic.js";
|
|
8
10
|
import { openConnection, request, streamProgram } from "./transport.js";
|
|
9
11
|
import Uploads from "./uploads.js";
|
|
10
12
|
const systems = new WeakMap();
|
|
@@ -62,8 +64,12 @@ export class System {
|
|
|
62
64
|
requireConnected(this);
|
|
63
65
|
if (!isServiceKey(key))
|
|
64
66
|
throw new Error("A complete service key is required");
|
|
65
|
-
const normalized = Object.freeze({
|
|
66
|
-
|
|
67
|
+
const normalized = Object.freeze({
|
|
68
|
+
...(key.program === undefined ? {} : { program: key.program }),
|
|
69
|
+
process: key.process,
|
|
70
|
+
endpoint: key.endpoint
|
|
71
|
+
});
|
|
72
|
+
const identity = JSON.stringify([key.program ?? null, key.process, key.endpoint]);
|
|
67
73
|
return systemState(this).handles.obtain(`service:${identity}`, () => normalized.endpoint === "server"
|
|
68
74
|
? new ServerServiceHandle(this, normalized)
|
|
69
75
|
: new ClientServiceHandle(this, normalized));
|
|
@@ -162,6 +168,11 @@ class ProgramHandle extends ProgramBase {
|
|
|
162
168
|
system;
|
|
163
169
|
reference;
|
|
164
170
|
identity;
|
|
171
|
+
data;
|
|
172
|
+
cache;
|
|
173
|
+
store;
|
|
174
|
+
logs;
|
|
175
|
+
database;
|
|
165
176
|
process;
|
|
166
177
|
startup;
|
|
167
178
|
snapshot;
|
|
@@ -174,6 +185,12 @@ class ProgramHandle extends ProgramBase {
|
|
|
174
185
|
}, signal).then(value => programEntityEvent(event, value))));
|
|
175
186
|
this.reference = snapshot.reference;
|
|
176
187
|
this.identity = snapshot.identity;
|
|
188
|
+
const request = (value) => transport(system).api(value);
|
|
189
|
+
this.data = filesystemStorage(() => programStoragePath(system, this.identity, "data"), `Program "${this.identity}" data`);
|
|
190
|
+
this.cache = filesystemStorage(() => programStoragePath(system, this.identity, "cache"), `Program "${this.identity}" cache`);
|
|
191
|
+
this.store = programStore(request, this.identity);
|
|
192
|
+
this.logs = programSql(request, this.identity, "logs");
|
|
193
|
+
this.database = programSql(request, this.identity, "database");
|
|
177
194
|
this.process = new ProgramProcesses(system, this);
|
|
178
195
|
this.startup = new ProgramStartup(system, this);
|
|
179
196
|
}
|
|
@@ -182,11 +199,15 @@ class ProgramHandle extends ProgramBase {
|
|
|
182
199
|
get description() { return this.snapshot.description; }
|
|
183
200
|
get hasAgent() { return this.snapshot.hasAgent; }
|
|
184
201
|
get server() {
|
|
185
|
-
return this.snapshot.server ? Object.freeze({
|
|
202
|
+
return this.snapshot.server ? Object.freeze({
|
|
203
|
+
start: this.snapshot.server.start,
|
|
204
|
+
service: this.snapshot.server.service
|
|
205
|
+
}) : null;
|
|
186
206
|
}
|
|
187
207
|
get client() {
|
|
188
208
|
return this.snapshot.client ? Object.freeze({
|
|
189
209
|
start: this.snapshot.client.start,
|
|
210
|
+
service: this.snapshot.client.service,
|
|
190
211
|
title: this.snapshot.client.title,
|
|
191
212
|
size: this.snapshot.client.size,
|
|
192
213
|
position: this.snapshot.client.position,
|
|
@@ -199,6 +220,12 @@ class ProgramHandle extends ProgramBase {
|
|
|
199
220
|
throw new Error("A Program handle cannot become another Program");
|
|
200
221
|
this.snapshot = snapshot;
|
|
201
222
|
}
|
|
223
|
+
async icon(size = "medium") {
|
|
224
|
+
const value = await transport(this.system).api({ capability: "program", operation: "icon", program: this.identity, size });
|
|
225
|
+
if (!Array.isArray(value) || value.some(byte => typeof byte !== "number"))
|
|
226
|
+
throw new Error("The System returned an invalid Program icon");
|
|
227
|
+
return new Blob([Uint8Array.from(value)], { type: "image/png" });
|
|
228
|
+
}
|
|
202
229
|
async agent() {
|
|
203
230
|
if (!this.hasAgent)
|
|
204
231
|
return null;
|
|
@@ -368,10 +395,22 @@ class ProcessHandle extends ProcessBase {
|
|
|
368
395
|
this.client = new ClientEndpoint(system, this);
|
|
369
396
|
}
|
|
370
397
|
program() { return programHandle(this.system, required(this.snapshot.programSnapshot, this.snapshot.program)); }
|
|
398
|
+
async parent() {
|
|
399
|
+
if (!await this.exists())
|
|
400
|
+
throw new Error(`Process "${this.identity}" no longer exists`);
|
|
401
|
+
if (this.snapshot.parent === null)
|
|
402
|
+
return null;
|
|
403
|
+
const parent = await this.system.process.find(this.snapshot.parent);
|
|
404
|
+
if (!parent)
|
|
405
|
+
throw new Error("The parent Process no longer exists");
|
|
406
|
+
return parent;
|
|
407
|
+
}
|
|
408
|
+
async option(name) { return this.snapshot.options[name]; }
|
|
371
409
|
async exit() {
|
|
372
410
|
await transport(this.system).control({ capability: "process", operation: "exit", input: { process: this.identity } });
|
|
373
411
|
}
|
|
374
412
|
async exited() { return await this.system.process.find(this.identity) === null; }
|
|
413
|
+
async exists() { return !await this.exited(); }
|
|
375
414
|
}
|
|
376
415
|
class EndpointOperations extends Events {
|
|
377
416
|
system;
|
|
@@ -396,11 +435,12 @@ class EndpointOperations extends Events {
|
|
|
396
435
|
const value = await this.inspect();
|
|
397
436
|
return value.running;
|
|
398
437
|
}
|
|
399
|
-
async start(
|
|
438
|
+
async start(launch = {}) { await this.operation("start", launch); }
|
|
400
439
|
async stop() { await this.operation("stop"); }
|
|
401
|
-
async
|
|
402
|
-
|
|
403
|
-
|
|
440
|
+
async isService() {
|
|
441
|
+
return await transport(this.system).api({
|
|
442
|
+
capability: "endpoint", operation: "isService", process: this.owner.identity, endpoint: this.endpoint
|
|
443
|
+
});
|
|
404
444
|
}
|
|
405
445
|
publish(event, payload) {
|
|
406
446
|
void transport(this.system).control({ capability: "endpoint", operation: "publish", input: {
|
|
@@ -412,9 +452,9 @@ class EndpointOperations extends Events {
|
|
|
412
452
|
process: this.owner.identity, endpoint: this.endpoint
|
|
413
453
|
} });
|
|
414
454
|
}
|
|
415
|
-
async operation(operation,
|
|
455
|
+
async operation(operation, launch) {
|
|
416
456
|
await transport(this.system).control({ capability: "endpoint", operation, input: {
|
|
417
|
-
process: this.owner.identity, endpoint: this.endpoint, ...(
|
|
457
|
+
process: this.owner.identity, endpoint: this.endpoint, ...(launch ? { launch } : {})
|
|
418
458
|
} });
|
|
419
459
|
}
|
|
420
460
|
}
|
|
@@ -422,6 +462,7 @@ class ServerEndpoint extends ServerBase {
|
|
|
422
462
|
system;
|
|
423
463
|
owner;
|
|
424
464
|
endpoint = "server";
|
|
465
|
+
traffic;
|
|
425
466
|
lifecycle;
|
|
426
467
|
base;
|
|
427
468
|
constructor(system, owner) {
|
|
@@ -429,12 +470,14 @@ class ServerEndpoint extends ServerBase {
|
|
|
429
470
|
this.system = system;
|
|
430
471
|
this.owner = owner;
|
|
431
472
|
this.base = new EndpointOperations(system, owner, "server");
|
|
473
|
+
this.traffic = new ServerTrafficHandle((value, signal) => transport(system).api(value, signal), owner.identity, "server", value => endpointFromReference(system, value));
|
|
432
474
|
this.lifecycle = this.base.lifecycle;
|
|
433
475
|
bindEvents(this, this.base);
|
|
434
476
|
}
|
|
435
477
|
process() { return this.base.process(); }
|
|
436
478
|
exists() { return this.base.exists(); }
|
|
437
|
-
|
|
479
|
+
isService() { return this.base.isService(); }
|
|
480
|
+
start(launch) { return this.base.start(launch); }
|
|
438
481
|
stop() { return this.base.stop(); }
|
|
439
482
|
publish(event, payload) { return this.base.publish(event, payload); }
|
|
440
483
|
async ask(event, payload) {
|
|
@@ -450,30 +493,27 @@ class ServerEndpoint extends ServerBase {
|
|
|
450
493
|
async waitReady(timeout) {
|
|
451
494
|
await transport(this.system).control({ capability: "endpoint", operation: "waitReady", input: { process: this.owner.identity, endpoint: "server", timeout } });
|
|
452
495
|
}
|
|
453
|
-
async service() {
|
|
454
|
-
return await this.base.service();
|
|
455
|
-
}
|
|
456
496
|
}
|
|
457
497
|
class ClientEndpoint extends ClientBase {
|
|
458
498
|
endpoint = "client";
|
|
499
|
+
traffic;
|
|
459
500
|
lifecycle;
|
|
460
501
|
window;
|
|
461
502
|
base;
|
|
462
503
|
constructor(system, owner) {
|
|
463
504
|
super();
|
|
464
505
|
this.base = new EndpointOperations(system, owner, "client");
|
|
506
|
+
this.traffic = new EndpointTrafficHandle((value, signal) => transport(system).api(value, signal), owner.identity, "client", value => endpointFromReference(system, value));
|
|
465
507
|
this.lifecycle = this.base.lifecycle;
|
|
466
508
|
bindEvents(this, this.base);
|
|
467
509
|
this.window = new SystemWindow(system, owner);
|
|
468
510
|
}
|
|
469
511
|
process() { return this.base.process(); }
|
|
470
512
|
exists() { return this.base.exists(); }
|
|
471
|
-
|
|
513
|
+
isService() { return this.base.isService(); }
|
|
514
|
+
start(launch) { return this.base.start(launch); }
|
|
472
515
|
stop() { return this.base.stop(); }
|
|
473
516
|
publish(event, payload) { return this.base.publish(event, payload); }
|
|
474
|
-
async service() {
|
|
475
|
-
return await this.base.service();
|
|
476
|
-
}
|
|
477
517
|
}
|
|
478
518
|
class SystemWindow extends Events {
|
|
479
519
|
system;
|
|
@@ -508,18 +548,18 @@ class SystemWindow extends Events {
|
|
|
508
548
|
class ServiceBase {
|
|
509
549
|
system;
|
|
510
550
|
key;
|
|
511
|
-
name;
|
|
512
551
|
lifecycle;
|
|
513
552
|
constructor(system, key) {
|
|
514
553
|
this.system = system;
|
|
515
554
|
this.key = key;
|
|
516
|
-
this.lifecycle = new Events(["
|
|
555
|
+
this.lifecycle = new Events(["start", "stop"], (event, signal, timeout) => transport(system).api({
|
|
517
556
|
capability: "service", operation: "wait", scope: "lifecycle", key, event, timeout
|
|
518
557
|
}, signal));
|
|
519
|
-
this.name = key.name;
|
|
520
558
|
}
|
|
521
|
-
async
|
|
522
|
-
|
|
559
|
+
async exists() { return await transport(this.system).api({ capability: "service", operation: "exists", key: this.key }); }
|
|
560
|
+
publish(event, payload) {
|
|
561
|
+
void transport(this.system).api({ capability: "service", operation: "publish", key: this.key, event, payload });
|
|
562
|
+
}
|
|
523
563
|
}
|
|
524
564
|
/** Node-SDK handle for a Service provided by a Server Endpoint. */
|
|
525
565
|
export class ServerService extends CoreServerService {
|
|
@@ -528,7 +568,6 @@ export class ServerService extends CoreServerService {
|
|
|
528
568
|
class ServerServiceHandle extends ServerService {
|
|
529
569
|
system;
|
|
530
570
|
key;
|
|
531
|
-
name;
|
|
532
571
|
lifecycle;
|
|
533
572
|
base;
|
|
534
573
|
constructor(system, key) {
|
|
@@ -536,17 +575,16 @@ class ServerServiceHandle extends ServerService {
|
|
|
536
575
|
this.system = system;
|
|
537
576
|
this.key = key;
|
|
538
577
|
this.base = new ServiceBase(system, key);
|
|
539
|
-
this.name = key.name;
|
|
540
578
|
this.lifecycle = this.base.lifecycle;
|
|
541
579
|
bindEvents(this, new Events([], (event, signal, timeout) => transport(system).api({
|
|
542
580
|
capability: "service", operation: "wait", scope: "events", key, event, timeout
|
|
543
581
|
}, signal)));
|
|
544
582
|
}
|
|
545
|
-
|
|
546
|
-
waitReady(timeout) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
583
|
+
exists() { return this.base.exists(); }
|
|
584
|
+
async waitReady(timeout) {
|
|
585
|
+
await transport(this.system).api({ capability: "service", operation: "waitReady", key: this.key, timeout });
|
|
586
|
+
}
|
|
587
|
+
publish = (event, payload) => this.base.publish(event, payload);
|
|
550
588
|
async ask(event, payload) {
|
|
551
589
|
return await transport(this.system).api({ capability: "service", operation: "ask", key: this.key, event, payload });
|
|
552
590
|
}
|
|
@@ -561,20 +599,18 @@ export class ClientService extends CoreClientService {
|
|
|
561
599
|
constructor() { super(); }
|
|
562
600
|
}
|
|
563
601
|
class ClientServiceHandle extends ClientService {
|
|
564
|
-
name;
|
|
565
602
|
lifecycle;
|
|
566
603
|
base;
|
|
567
604
|
constructor(system, key) {
|
|
568
605
|
super();
|
|
569
606
|
this.base = new ServiceBase(system, key);
|
|
570
|
-
this.name = key.name;
|
|
571
607
|
this.lifecycle = this.base.lifecycle;
|
|
572
608
|
bindEvents(this, new Events([], (event, signal, timeout) => transport(system).api({
|
|
573
609
|
capability: "service", operation: "wait", scope: "events", key, event, timeout
|
|
574
610
|
}, signal)));
|
|
575
611
|
}
|
|
576
|
-
|
|
577
|
-
|
|
612
|
+
exists() { return this.base.exists(); }
|
|
613
|
+
publish = (event, payload) => this.base.publish(event, payload);
|
|
578
614
|
}
|
|
579
615
|
async function listProcesses(system, program) {
|
|
580
616
|
const processes = [];
|
|
@@ -644,6 +680,36 @@ function eventsOf(events) {
|
|
|
644
680
|
};
|
|
645
681
|
}
|
|
646
682
|
function chronological(left, right) { return left.startedAt.getTime() - right.startedAt.getTime(); }
|
|
683
|
+
async function programStoragePath(system, program, area) {
|
|
684
|
+
const value = await transport(system).api({ capability: "program", operation: "storagePath", program, area });
|
|
685
|
+
if (typeof value !== "string")
|
|
686
|
+
throw new Error("The System returned an invalid Program storage path");
|
|
687
|
+
return value;
|
|
688
|
+
}
|
|
689
|
+
function endpointFromReference(system, value) {
|
|
690
|
+
const reference = value;
|
|
691
|
+
if (!reference || (reference.kind !== "server" && reference.kind !== "client"))
|
|
692
|
+
throw new Error("The System returned an invalid Endpoint reference");
|
|
693
|
+
const owner = processHandle(system, snapshotFromReference(reference.process));
|
|
694
|
+
return reference.kind === "server" ? owner.server : owner.client;
|
|
695
|
+
}
|
|
696
|
+
function snapshotFromReference(reference) {
|
|
697
|
+
const owner = reference.program;
|
|
698
|
+
if (!owner || typeof owner.reference !== "string" || typeof owner.identity !== "string")
|
|
699
|
+
throw new Error("The System returned an invalid Process reference");
|
|
700
|
+
return {
|
|
701
|
+
reference: reference.reference,
|
|
702
|
+
identity: reference.identity,
|
|
703
|
+
name: reference.name,
|
|
704
|
+
program: owner.identity,
|
|
705
|
+
programSnapshot: owner,
|
|
706
|
+
parent: null,
|
|
707
|
+
options: reference.options,
|
|
708
|
+
startedAt: reference.startedAt,
|
|
709
|
+
server: { declared: owner.server !== null, running: reference.server !== null, service: reference.server?.service === true },
|
|
710
|
+
client: { declared: owner.client !== null, running: reference.client !== null, service: reference.client?.service === true }
|
|
711
|
+
};
|
|
712
|
+
}
|
|
647
713
|
function unknown(error, entity) { return error instanceof Error && error.message.startsWith(`Unknown ${entity}`); }
|
|
648
714
|
function required(value, identity = "") {
|
|
649
715
|
if (value !== undefined)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AnswerSubscriber, AskSubscriber, Cleanup, Endpoint, EventOptions, ServerTraffic, TrafficEvents, TrafficMessage } from "@phreshos/core";
|
|
2
|
+
import Events from "./events.js";
|
|
3
|
+
type Kind = "publish" | "ask" | "answer";
|
|
4
|
+
type Request = (value: object, signal?: AbortSignal) => Promise<unknown>;
|
|
5
|
+
type ResolveEndpoint = (value: unknown) => Endpoint;
|
|
6
|
+
/** Directed traffic originating from one canonical Endpoint. */
|
|
7
|
+
export declare class EndpointTrafficHandle<Definitions extends object = {}> extends Events<TrafficEvents<Definitions>, keyof Definitions extends never ? TrafficMessage : never> {
|
|
8
|
+
private readonly request;
|
|
9
|
+
private readonly process;
|
|
10
|
+
private readonly endpoint;
|
|
11
|
+
protected readonly resolveEndpoint: ResolveEndpoint;
|
|
12
|
+
constructor(request: Request, process: string, endpoint: "server" | "client", resolveEndpoint: ResolveEndpoint);
|
|
13
|
+
subscribeAsks<Payload = unknown>(subscriber: AskSubscriber<Payload>): Cleanup;
|
|
14
|
+
asks<Payload = unknown>(options?: EventOptions): AsyncIterableIterator<Readonly<{
|
|
15
|
+
event: string;
|
|
16
|
+
questionId: string;
|
|
17
|
+
message: Readonly<{
|
|
18
|
+
to: import("@phreshos/core").Server<{}>;
|
|
19
|
+
payload: Payload;
|
|
20
|
+
}>;
|
|
21
|
+
}>>;
|
|
22
|
+
protected follow<Capture>(kind: Kind, convert: (value: unknown) => Capture, subscriber: (capture: Capture) => unknown, impossible?: (error: Error) => void): Cleanup;
|
|
23
|
+
}
|
|
24
|
+
/** Directed traffic originating from one canonical Server. */
|
|
25
|
+
export declare class ServerTrafficHandle<Definitions extends object = {}> extends EndpointTrafficHandle<Definitions> implements ServerTraffic<Definitions> {
|
|
26
|
+
subscribeAnswers<Result = unknown>(subscriber: AnswerSubscriber<Result>): Cleanup;
|
|
27
|
+
answers<Result = unknown>(options?: EventOptions): AsyncIterableIterator<Readonly<{
|
|
28
|
+
event: string;
|
|
29
|
+
questionId: string;
|
|
30
|
+
message: Readonly<{
|
|
31
|
+
to: Endpoint<{}>;
|
|
32
|
+
outcome: import("@phreshos/core").Outcome<Result>;
|
|
33
|
+
}>;
|
|
34
|
+
}>>;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
package/dist/traffic.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import Events, { stream } from "./events.js";
|
|
2
|
+
/** Directed traffic originating from one canonical Endpoint. */
|
|
3
|
+
export class EndpointTrafficHandle extends Events {
|
|
4
|
+
request;
|
|
5
|
+
process;
|
|
6
|
+
endpoint;
|
|
7
|
+
resolveEndpoint;
|
|
8
|
+
constructor(request, process, endpoint, resolveEndpoint) {
|
|
9
|
+
super([], (event, signal, timeout) => request({
|
|
10
|
+
capability: "traffic",
|
|
11
|
+
operation: "wait",
|
|
12
|
+
process,
|
|
13
|
+
endpoint,
|
|
14
|
+
kind: "publish",
|
|
15
|
+
event,
|
|
16
|
+
timeout
|
|
17
|
+
}, signal).then(value => publication(value, resolveEndpoint, event === null)));
|
|
18
|
+
this.request = request;
|
|
19
|
+
this.process = process;
|
|
20
|
+
this.endpoint = endpoint;
|
|
21
|
+
this.resolveEndpoint = resolveEndpoint;
|
|
22
|
+
}
|
|
23
|
+
subscribeAsks(subscriber) {
|
|
24
|
+
return this.follow("ask", value => question(value, this.resolveEndpoint), subscriber);
|
|
25
|
+
}
|
|
26
|
+
asks(options) {
|
|
27
|
+
return stream((subscriber, impossible) => this.follow("ask", value => question(value, this.resolveEndpoint), subscriber, impossible), options);
|
|
28
|
+
}
|
|
29
|
+
follow(kind, convert, subscriber, impossible) {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
void (async () => {
|
|
32
|
+
while (!controller.signal.aborted) {
|
|
33
|
+
try {
|
|
34
|
+
const value = await this.request({
|
|
35
|
+
capability: "traffic",
|
|
36
|
+
operation: "wait",
|
|
37
|
+
process: this.process,
|
|
38
|
+
endpoint: this.endpoint,
|
|
39
|
+
kind,
|
|
40
|
+
event: null,
|
|
41
|
+
timeout: 86_400_000
|
|
42
|
+
}, controller.signal);
|
|
43
|
+
subscriber(convert(value));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (!controller.signal.aborted)
|
|
47
|
+
impossible?.(error instanceof Error ? error : new Error(String(error)));
|
|
48
|
+
controller.abort();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
52
|
+
return () => controller.abort();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Directed traffic originating from one canonical Server. */
|
|
56
|
+
export class ServerTrafficHandle extends EndpointTrafficHandle {
|
|
57
|
+
subscribeAnswers(subscriber) {
|
|
58
|
+
return this.follow("answer", value => answer(value, this.resolveEndpoint), subscriber);
|
|
59
|
+
}
|
|
60
|
+
answers(options) {
|
|
61
|
+
return stream((subscriber, impossible) => this.follow("answer", value => answer(value, this.resolveEndpoint), subscriber, impossible), options);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function publication(value, resolve, captured) {
|
|
65
|
+
const received = traffic(value);
|
|
66
|
+
const message = directed(received.values[0], resolve);
|
|
67
|
+
return captured ? { event: received.event, payload: message } : message;
|
|
68
|
+
}
|
|
69
|
+
function question(value, resolve) {
|
|
70
|
+
const received = traffic(value);
|
|
71
|
+
if (typeof received.values[0] !== "string")
|
|
72
|
+
throw new Error("The System returned invalid question traffic");
|
|
73
|
+
return {
|
|
74
|
+
event: received.event,
|
|
75
|
+
questionId: received.values[0],
|
|
76
|
+
message: directed(received.values[1], resolve)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function answer(value, resolve) {
|
|
80
|
+
const received = traffic(value);
|
|
81
|
+
const raw = received.values[1];
|
|
82
|
+
if (typeof received.values[0] !== "string" || !raw || typeof raw !== "object")
|
|
83
|
+
throw new Error("The System returned invalid answer traffic");
|
|
84
|
+
return {
|
|
85
|
+
event: received.event,
|
|
86
|
+
questionId: received.values[0],
|
|
87
|
+
message: { to: resolve(raw.to), outcome: raw.outcome }
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function directed(value, resolve) {
|
|
91
|
+
const raw = value;
|
|
92
|
+
if (!raw || typeof raw !== "object")
|
|
93
|
+
throw new Error("The System returned invalid Endpoint traffic");
|
|
94
|
+
return { to: resolve(raw.to), payload: raw.payload };
|
|
95
|
+
}
|
|
96
|
+
function traffic(value) {
|
|
97
|
+
const received = value;
|
|
98
|
+
if (!received || typeof received.event !== "string" || !Array.isArray(received.values))
|
|
99
|
+
throw new Error("The System returned invalid traffic");
|
|
100
|
+
return { event: received.event, values: received.values };
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Node.js access to PhreshOS and Program projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"prepack": "node --run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@phreshos/core": "^0.1.
|
|
39
|
+
"@phreshos/core": "^0.1.30",
|
|
40
40
|
"adm-zip": "^0.6.0",
|
|
41
41
|
"jiti": "^2.7.0"
|
|
42
42
|
},
|