@phreshos/node 0.1.13 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -41
- package/dist/events.d.ts +4 -4
- package/dist/events.js +18 -29
- package/dist/main.d.ts +1 -1
- package/dist/main.js +1 -1
- package/dist/program-resources.d.ts +4 -4
- package/dist/program-resources.js +9 -24
- package/dist/representation.d.ts +100 -0
- package/dist/representation.js +331 -0
- package/dist/system.d.ts +1 -0
- package/dist/system.js +239 -281
- package/dist/traffic.d.ts +4 -4
- package/dist/traffic.js +14 -32
- package/dist/transport.d.ts +20 -10
- package/dist/transport.js +106 -124
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.js +15 -0
- package/package.json +4 -2
package/dist/traffic.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { AnswerSubscriber, AskSubscriber, Cleanup, Endpoint, EventOptions, ServerTraffic, TrafficEvents } from "@phreshos/core";
|
|
2
2
|
import Events from "./events.js";
|
|
3
|
+
import type SystemRepresentation from "./representation.js";
|
|
3
4
|
type Kind = "publish" | "ask" | "answer";
|
|
4
|
-
type Request = (value: object, signal?: AbortSignal) => Promise<unknown>;
|
|
5
5
|
type ResolveEndpoint = (value: unknown) => Endpoint;
|
|
6
6
|
/** Directed traffic originating from one canonical Endpoint. */
|
|
7
7
|
export declare class EndpointTrafficHandle<Definitions extends object = {}> extends Events<TrafficEvents<Definitions>, never> {
|
|
8
|
-
private readonly
|
|
8
|
+
private readonly representation;
|
|
9
9
|
private readonly process;
|
|
10
10
|
private readonly endpoint;
|
|
11
11
|
protected readonly resolveEndpoint: ResolveEndpoint;
|
|
12
|
-
constructor(
|
|
12
|
+
constructor(representation: SystemRepresentation, process: string, endpoint: "server" | "client", resolveEndpoint: ResolveEndpoint);
|
|
13
13
|
subscribeAsks<Payload = unknown>(subscriber: AskSubscriber<Payload>): Cleanup;
|
|
14
14
|
asks<Payload = unknown>(options?: EventOptions): AsyncIterableIterator<Readonly<{
|
|
15
15
|
event: string;
|
|
@@ -29,7 +29,7 @@ export declare class ServerTrafficHandle<Definitions extends object = {}> extend
|
|
|
29
29
|
questionId: string;
|
|
30
30
|
message: Readonly<{
|
|
31
31
|
to: Endpoint<{}, unknown> | null;
|
|
32
|
-
outcome: import("@phreshos/core").
|
|
32
|
+
outcome: import("@phreshos/core").AnswerOutcome<Result>;
|
|
33
33
|
}>;
|
|
34
34
|
}>>;
|
|
35
35
|
}
|
package/dist/traffic.js
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import Events, { stream } from "./events.js";
|
|
2
2
|
/** Directed traffic originating from one canonical Endpoint. */
|
|
3
3
|
export class EndpointTrafficHandle extends Events {
|
|
4
|
-
|
|
4
|
+
representation;
|
|
5
5
|
process;
|
|
6
6
|
endpoint;
|
|
7
7
|
resolveEndpoint;
|
|
8
|
-
constructor(
|
|
9
|
-
super([], (event,
|
|
10
|
-
|
|
11
|
-
operation: "wait",
|
|
8
|
+
constructor(representation, process, endpoint, resolveEndpoint) {
|
|
9
|
+
super([], (event, subscriber, impossible) => representation.follow({
|
|
10
|
+
scope: "traffic",
|
|
12
11
|
process,
|
|
13
12
|
endpoint,
|
|
14
13
|
kind: "publish",
|
|
15
|
-
event
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
this.request = request;
|
|
14
|
+
event
|
|
15
|
+
}, (received, values) => subscriber(publication({ event: received, values }, resolveEndpoint, event === null)), impossible));
|
|
16
|
+
this.representation = representation;
|
|
19
17
|
this.process = process;
|
|
20
18
|
this.endpoint = endpoint;
|
|
21
19
|
this.resolveEndpoint = resolveEndpoint;
|
|
@@ -27,29 +25,13 @@ export class EndpointTrafficHandle extends Events {
|
|
|
27
25
|
return stream((subscriber, impossible) => this.follow("ask", value => question(value, this.resolveEndpoint), subscriber, impossible), options);
|
|
28
26
|
}
|
|
29
27
|
follow(kind, convert, subscriber, impossible) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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();
|
|
28
|
+
return this.representation.follow({
|
|
29
|
+
scope: "traffic",
|
|
30
|
+
process: this.process,
|
|
31
|
+
endpoint: this.endpoint,
|
|
32
|
+
kind,
|
|
33
|
+
event: null
|
|
34
|
+
}, (event, values) => subscriber(convert({ event, values })), impossible);
|
|
53
35
|
}
|
|
54
36
|
}
|
|
55
37
|
/** Directed traffic originating from one canonical Server Endpoint. */
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/** One persistent owner-local IPC connection to the System Link Manager. */
|
|
2
|
+
export declare class GatewayConnection {
|
|
3
|
+
private readonly link;
|
|
4
|
+
session: unknown;
|
|
5
|
+
private active;
|
|
6
|
+
private readonly queued;
|
|
7
|
+
private readonly subscribers;
|
|
8
|
+
private constructor();
|
|
9
|
+
static open(path: string): Promise<GatewayConnection>;
|
|
10
|
+
onDisconnect(subscriber: (error: Error) => void): () => void;
|
|
11
|
+
disconnect(): Promise<void>;
|
|
12
|
+
/** Invoke one operation exposed by the connected System Link Manager. */
|
|
13
|
+
call<Result = unknown>(event: string, ...values: unknown[]): Promise<Result>;
|
|
14
|
+
/** Observe one live event emitted by the connected System Link Manager. */
|
|
15
|
+
subscribe(event: string, subscriber: (...values: unknown[]) => unknown): () => void;
|
|
16
|
+
/** Begin delivery after the connected representation has installed its listeners. */
|
|
17
|
+
activate(): void;
|
|
18
|
+
private deliver;
|
|
5
19
|
}
|
|
6
|
-
/** Open and
|
|
7
|
-
export declare function openConnection(path: string): Promise<
|
|
8
|
-
/** Execute one short authoritative System-control request. */
|
|
9
|
-
export declare function request(path: string, target: "api" | "system", request: unknown, signal?: AbortSignal): Promise<unknown>;
|
|
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>;
|
|
20
|
+
/** Open and authenticate one owner-local System connection. */
|
|
21
|
+
export declare function openConnection(path: string): Promise<GatewayConnection>;
|
package/dist/transport.js
CHANGED
|
@@ -1,133 +1,115 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export function request(path, target, request, signal) {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
const socket = connect(path);
|
|
20
|
-
let buffer = "";
|
|
21
|
-
let settled = false;
|
|
22
|
-
const finish = (work) => {
|
|
23
|
-
if (settled)
|
|
1
|
+
import { SocketClient } from "@the-link/ipc/socket-client";
|
|
2
|
+
import messagepack from "@the-link/messagepack";
|
|
3
|
+
const events = {
|
|
4
|
+
ready: "/gateway/ready"
|
|
5
|
+
};
|
|
6
|
+
/** One persistent owner-local IPC connection to the System Link Manager. */
|
|
7
|
+
export class GatewayConnection {
|
|
8
|
+
link;
|
|
9
|
+
session = null;
|
|
10
|
+
active = false;
|
|
11
|
+
queued = [];
|
|
12
|
+
subscribers = new Map();
|
|
13
|
+
constructor(link) {
|
|
14
|
+
this.link = link;
|
|
15
|
+
link.$inbound.forwardTo((event, ...values) => {
|
|
16
|
+
if (event === events.ready)
|
|
24
17
|
return;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
socket.destroy();
|
|
28
|
-
work();
|
|
29
|
-
};
|
|
30
|
-
const cancel = () => finish(() => reject(signal?.reason instanceof Error ? signal.reason : new Error("The request was cancelled")));
|
|
31
|
-
signal?.addEventListener("abort", cancel, { once: true });
|
|
32
|
-
socket.on("connect", () => socket.write(`${JSON.stringify({ target, request })}\n`));
|
|
33
|
-
socket.on("data", chunk => {
|
|
34
|
-
buffer += String(chunk);
|
|
35
|
-
const boundary = buffer.indexOf("\n");
|
|
36
|
-
if (boundary < 0)
|
|
37
|
-
return;
|
|
38
|
-
let outcome;
|
|
39
|
-
try {
|
|
40
|
-
outcome = JSON.parse(buffer.slice(0, boundary));
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return finish(() => reject(new Error("The System returned an invalid response")));
|
|
44
|
-
}
|
|
45
|
-
if (outcome.success)
|
|
46
|
-
finish(() => resolve(outcome.result));
|
|
18
|
+
if (!this.active)
|
|
19
|
+
this.queued.push({ event, values });
|
|
47
20
|
else
|
|
48
|
-
|
|
21
|
+
this.deliver(event, values);
|
|
49
22
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
export function stream(path, target, request, signal) {
|
|
58
|
-
const events = [];
|
|
59
|
-
let wake = null;
|
|
60
|
-
let ended = false;
|
|
61
|
-
let failure = null;
|
|
62
|
-
const socket = connect(path);
|
|
63
|
-
const cancel = () => socket.destroy(signal?.reason instanceof Error ? signal.reason : undefined);
|
|
64
|
-
if (signal?.aborted)
|
|
65
|
-
cancel();
|
|
66
|
-
else
|
|
67
|
-
signal?.addEventListener("abort", cancel, { once: true });
|
|
68
|
-
let buffer = "";
|
|
69
|
-
socket.on("connect", () => socket.write(`${JSON.stringify({ target, request })}\n`));
|
|
70
|
-
socket.on("data", chunk => {
|
|
71
|
-
buffer += String(chunk);
|
|
72
|
-
const lines = buffer.split("\n");
|
|
73
|
-
buffer = lines.pop() ?? "";
|
|
74
|
-
for (const line of lines)
|
|
75
|
-
if (line.trim()) {
|
|
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
|
-
}
|
|
90
|
-
if (event.event === "error")
|
|
91
|
-
failure = new Error(String(event.message));
|
|
92
|
-
else
|
|
93
|
-
events.push(event);
|
|
94
|
-
}
|
|
95
|
-
wake?.();
|
|
96
|
-
wake = null;
|
|
97
|
-
});
|
|
98
|
-
socket.on("error", error => {
|
|
99
|
-
failure = signal?.aborted
|
|
100
|
-
? signal.reason instanceof Error ? signal.reason : new Error("The operation was cancelled")
|
|
101
|
-
: error;
|
|
102
|
-
wake?.();
|
|
103
|
-
wake = null;
|
|
104
|
-
});
|
|
105
|
-
socket.on("close", () => {
|
|
106
|
-
ended = true;
|
|
107
|
-
signal?.removeEventListener("abort", cancel);
|
|
108
|
-
wake?.();
|
|
109
|
-
wake = null;
|
|
110
|
-
});
|
|
111
|
-
return (async function* () {
|
|
23
|
+
}
|
|
24
|
+
static async open(path) {
|
|
25
|
+
const link = new SocketClient(path);
|
|
26
|
+
const connection = new GatewayConnection(link);
|
|
27
|
+
link.setSerialize(messagepack.serialize);
|
|
28
|
+
link.setDeserialize(messagepack.deserialize);
|
|
29
|
+
const readiness = ready(link);
|
|
112
30
|
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
31
|
+
await link.connect();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
readiness.cancel();
|
|
35
|
+
await link.disconnect();
|
|
36
|
+
throw unavailable(path, error);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
connection.session = await readiness.promise;
|
|
40
|
+
return connection;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
await link.disconnect();
|
|
44
|
+
throw new Error("The System Gateway did not establish an owner session", { cause: error });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
onDisconnect(subscriber) {
|
|
48
|
+
return this.link.$internal.subscribe("disconnect", subscriber);
|
|
49
|
+
}
|
|
50
|
+
disconnect() {
|
|
51
|
+
return this.link.disconnect();
|
|
52
|
+
}
|
|
53
|
+
/** Invoke one operation exposed by the connected System Link Manager. */
|
|
54
|
+
call(event, ...values) {
|
|
55
|
+
return this.link.$outbound.publishFirst(event, ...values);
|
|
56
|
+
}
|
|
57
|
+
/** Observe one live event emitted by the connected System Link Manager. */
|
|
58
|
+
subscribe(event, subscriber) {
|
|
59
|
+
const subscribers = this.subscribers.get(event) ?? new Set();
|
|
60
|
+
subscribers.add(subscriber);
|
|
61
|
+
this.subscribers.set(event, subscribers);
|
|
62
|
+
return () => {
|
|
63
|
+
subscribers.delete(subscriber);
|
|
64
|
+
if (!subscribers.size)
|
|
65
|
+
this.subscribers.delete(event);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Begin delivery after the connected representation has installed its listeners. */
|
|
69
|
+
activate() {
|
|
70
|
+
if (this.active)
|
|
71
|
+
return;
|
|
72
|
+
this.active = true;
|
|
73
|
+
for (const { event, values } of this.queued.splice(0))
|
|
74
|
+
this.deliver(event, values);
|
|
75
|
+
}
|
|
76
|
+
deliver(event, values) {
|
|
77
|
+
for (const subscriber of this.subscribers.get(event) ?? []) {
|
|
78
|
+
try {
|
|
79
|
+
subscriber(...values);
|
|
123
80
|
}
|
|
81
|
+
catch { /* One local observer cannot break boundary delivery. */ }
|
|
124
82
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function ready(link) {
|
|
86
|
+
let received = () => undefined;
|
|
87
|
+
let disconnected = () => undefined;
|
|
88
|
+
const promise = new Promise((resolve, reject) => {
|
|
89
|
+
received = link.$inbound.subscribeOnce(events.ready, value => {
|
|
90
|
+
disconnected();
|
|
91
|
+
resolve(value);
|
|
92
|
+
});
|
|
93
|
+
disconnected = link.$internal.subscribeOnce("disconnect", error => {
|
|
94
|
+
received();
|
|
95
|
+
reject(exception(error));
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
promise,
|
|
100
|
+
cancel() {
|
|
101
|
+
received();
|
|
102
|
+
disconnected();
|
|
128
103
|
}
|
|
129
|
-
}
|
|
104
|
+
};
|
|
130
105
|
}
|
|
131
|
-
function
|
|
132
|
-
return
|
|
106
|
+
function exception(error) {
|
|
107
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
108
|
+
}
|
|
109
|
+
function unavailable(path, cause) {
|
|
110
|
+
return new Error(`No System is listening at ${path} — start PhreshOS first`, { cause });
|
|
111
|
+
}
|
|
112
|
+
/** Open and authenticate one owner-local System connection. */
|
|
113
|
+
export function openConnection(path) {
|
|
114
|
+
return GatewayConnection.open(path);
|
|
133
115
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Open a native WebSocket owned by one connected System lifetime. */
|
|
2
|
+
export default async function websocket(url, protocols, signal) {
|
|
3
|
+
if (signal.aborted)
|
|
4
|
+
throw signal.reason;
|
|
5
|
+
const socket = protocols === undefined
|
|
6
|
+
? new WebSocket(url)
|
|
7
|
+
: new WebSocket(url, protocols);
|
|
8
|
+
const close = () => socket.close();
|
|
9
|
+
const release = () => signal.removeEventListener("abort", close);
|
|
10
|
+
signal.addEventListener("abort", close, { once: true });
|
|
11
|
+
socket.addEventListener("close", release, { once: true });
|
|
12
|
+
if (signal.aborted)
|
|
13
|
+
close();
|
|
14
|
+
return socket;
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Node.js access to PhreshOS and Program projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"prepack": "node --run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@phreshos/core": "^0.1.
|
|
39
|
+
"@phreshos/core": "^0.1.39",
|
|
40
|
+
"@the-link/ipc": "^0.2.1",
|
|
41
|
+
"@the-link/messagepack": "^0.1.0",
|
|
40
42
|
"adm-zip": "^0.6.0",
|
|
41
43
|
"jiti": "^2.7.0"
|
|
42
44
|
},
|