@phreshos/client 0.1.15 → 0.1.16
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 +10 -0
- package/dist/domain.js +12 -1
- package/dist/wire.d.ts +4 -0
- package/dist/wire.js +78 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -148,6 +148,16 @@ representation on this desktop. Local reads and updates have no events and do
|
|
|
148
148
|
not change Server state. `current.window` is only convenient access to the
|
|
149
149
|
executing Client's canonical Window; it has no additional authority.
|
|
150
150
|
|
|
151
|
+
A Client may uninstall only its own Program. The operation is an async
|
|
152
|
+
generator so a declared Server cleanup command remains observable without a
|
|
153
|
+
fixed answer timeout:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
for await (const chunk of program.uninstall()) {
|
|
157
|
+
console.log(chunk.stream, chunk.text)
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
151
161
|
A Program may converge its Clients on one named Process without a manual
|
|
152
162
|
`find()`/`create()` race:
|
|
153
163
|
|
package/dist/domain.js
CHANGED
|
@@ -59,9 +59,20 @@ class ProgramHandle extends ProgramBase {
|
|
|
59
59
|
const answer = await wire.request(["installed"]);
|
|
60
60
|
return answer[0];
|
|
61
61
|
}
|
|
62
|
-
async uninstall(everything = false) {
|
|
62
|
+
async *uninstall(everything = false) {
|
|
63
|
+
for await (const value of wire.stream(["uninstall", undefined, everything])) {
|
|
64
|
+
yield programCommandChunk(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
63
67
|
async forget() { await wire.request(["forget"]); }
|
|
64
68
|
}
|
|
69
|
+
function programCommandChunk(value) {
|
|
70
|
+
const chunk = value;
|
|
71
|
+
if (!chunk || (chunk.stream !== "stdout" && chunk.stream !== "stderr") || typeof chunk.text !== "string") {
|
|
72
|
+
throw new Error("The desktop returned an invalid Program command chunk");
|
|
73
|
+
}
|
|
74
|
+
return Object.freeze({ stream: chunk.stream, text: chunk.text });
|
|
75
|
+
}
|
|
65
76
|
function declaration(record) {
|
|
66
77
|
return Object.freeze({
|
|
67
78
|
start: record.start
|
package/dist/wire.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ type TrafficKind = "publish" | "ask" | "answer";
|
|
|
8
8
|
declare class Wire {
|
|
9
9
|
private readonly parent;
|
|
10
10
|
private readonly pending;
|
|
11
|
+
private readonly streams;
|
|
11
12
|
private readonly subscribers;
|
|
12
13
|
private readonly every;
|
|
13
14
|
private readonly impossible;
|
|
@@ -15,6 +16,8 @@ declare class Wire {
|
|
|
15
16
|
constructor();
|
|
16
17
|
send(route: string, ...values: unknown[]): void;
|
|
17
18
|
request(values: unknown[], timeout?: number, transfer?: Transferable[]): Promise<unknown>;
|
|
19
|
+
/** Opens one long-running desktop operation and yields its ordered values. */
|
|
20
|
+
stream(values: unknown[], timeout?: number): AsyncIterableIterator<unknown>;
|
|
18
21
|
/** Requests a value while treating the caller's deadline as an absent answer. */
|
|
19
22
|
requestOrNull(values: unknown[], timeout: number): Promise<unknown | null>;
|
|
20
23
|
requestWithin(values: unknown[], deadline: Deadline, transfer?: Transferable[]): Promise<unknown>;
|
|
@@ -40,6 +43,7 @@ declare class Wire {
|
|
|
40
43
|
private unregister;
|
|
41
44
|
private deliver;
|
|
42
45
|
private settle;
|
|
46
|
+
private receiveStream;
|
|
43
47
|
}
|
|
44
48
|
declare const wire: Wire;
|
|
45
49
|
export default wire;
|
package/dist/wire.js
CHANGED
|
@@ -6,6 +6,7 @@ import { deserialize, serialize } from "./messagepack.js";
|
|
|
6
6
|
class Wire {
|
|
7
7
|
parent = window.parent === window ? null : window.parent;
|
|
8
8
|
pending = new Map();
|
|
9
|
+
streams = new Map();
|
|
9
10
|
subscribers = new Map();
|
|
10
11
|
every = new Map();
|
|
11
12
|
impossible = new Map();
|
|
@@ -40,6 +41,10 @@ class Wire {
|
|
|
40
41
|
this.settle(values[1], values.at(-1));
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
44
|
+
if (values[0] === "stream" && typeof values[1] === "string" && typeof values[2] === "string") {
|
|
45
|
+
this.receiveStream(values[1], values[2], values[3]);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
43
48
|
this.deliver(route, values);
|
|
44
49
|
});
|
|
45
50
|
}
|
|
@@ -49,6 +54,46 @@ class Wire {
|
|
|
49
54
|
request(values, timeout = defaultTimeout, transfer = []) {
|
|
50
55
|
return this.question("end-host", values, timeout, transfer);
|
|
51
56
|
}
|
|
57
|
+
/** Opens one long-running desktop operation and yields its ordered values. */
|
|
58
|
+
stream(values, timeout = defaultTimeout) {
|
|
59
|
+
const wire = this;
|
|
60
|
+
return (async function* () {
|
|
61
|
+
const question = `client:${crypto.randomUUID()}:${crypto.randomUUID()}`;
|
|
62
|
+
const state = {
|
|
63
|
+
queue: [],
|
|
64
|
+
opened: false,
|
|
65
|
+
ended: false,
|
|
66
|
+
failure: null,
|
|
67
|
+
wake: null,
|
|
68
|
+
timer: setTimeout(() => {
|
|
69
|
+
state.failure = new Error(`Answer timeout ${timeout}ms`);
|
|
70
|
+
state.wake?.();
|
|
71
|
+
state.wake = null;
|
|
72
|
+
}, timeout)
|
|
73
|
+
};
|
|
74
|
+
wire.send("boundary", "expect", question);
|
|
75
|
+
wire.streams.set(question, state);
|
|
76
|
+
wire.send("end-host", "stream", question, ...values);
|
|
77
|
+
try {
|
|
78
|
+
while (true) {
|
|
79
|
+
if (state.queue.length) {
|
|
80
|
+
yield state.queue.shift();
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (state.failure)
|
|
84
|
+
throw state.failure;
|
|
85
|
+
if (state.ended)
|
|
86
|
+
return;
|
|
87
|
+
await new Promise(resolve => { state.wake = resolve; });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
clearTimeout(state.timer);
|
|
92
|
+
wire.streams.delete(question);
|
|
93
|
+
wire.send("boundary", "forget", question);
|
|
94
|
+
}
|
|
95
|
+
})();
|
|
96
|
+
}
|
|
52
97
|
/** Requests a value while treating the caller's deadline as an absent answer. */
|
|
53
98
|
requestOrNull(values, timeout) {
|
|
54
99
|
return this.question("end-host", values, timeout, [], timeout, true);
|
|
@@ -261,6 +306,38 @@ class Wire {
|
|
|
261
306
|
else
|
|
262
307
|
pending.reject(new Error("The boundary returned an invalid outcome"));
|
|
263
308
|
}
|
|
309
|
+
receiveStream(question, operation, value) {
|
|
310
|
+
const stream = this.streams.get(question);
|
|
311
|
+
if (!stream || stream.ended || stream.failure)
|
|
312
|
+
return;
|
|
313
|
+
if (operation === "open") {
|
|
314
|
+
if (stream.opened)
|
|
315
|
+
return;
|
|
316
|
+
stream.opened = true;
|
|
317
|
+
clearTimeout(stream.timer);
|
|
318
|
+
}
|
|
319
|
+
else if (!stream.opened)
|
|
320
|
+
stream.failure = new Error("The boundary produced a stream value before opening the stream");
|
|
321
|
+
else if (operation === "data") {
|
|
322
|
+
if (stream.queue.length >= maximumStreamQueue)
|
|
323
|
+
stream.failure = new Error(`Host stream queue exceeded its capacity of ${maximumStreamQueue}`);
|
|
324
|
+
else
|
|
325
|
+
stream.queue.push(value);
|
|
326
|
+
}
|
|
327
|
+
else if (operation === "answer") {
|
|
328
|
+
const outcome = value;
|
|
329
|
+
if (outcome?.success === true)
|
|
330
|
+
stream.ended = true;
|
|
331
|
+
else if (outcome?.success === false && typeof outcome.error === "string")
|
|
332
|
+
stream.failure = new Error(outcome.error);
|
|
333
|
+
else
|
|
334
|
+
stream.failure = new Error("The boundary returned an invalid stream outcome");
|
|
335
|
+
}
|
|
336
|
+
else
|
|
337
|
+
stream.failure = new Error(`The boundary returned an invalid stream operation "${operation}"`);
|
|
338
|
+
stream.wake?.();
|
|
339
|
+
stream.wake = null;
|
|
340
|
+
}
|
|
264
341
|
}
|
|
265
342
|
function invoke(handler, values) {
|
|
266
343
|
Promise.resolve().then(() => handler(...values)).catch(error => queueMicrotask(() => { throw error; }));
|
|
@@ -305,6 +382,7 @@ function nativeAttachments(value, transfer) {
|
|
|
305
382
|
visit(value);
|
|
306
383
|
return attachments;
|
|
307
384
|
}
|
|
385
|
+
const maximumStreamQueue = 256;
|
|
308
386
|
const wire = new Wire();
|
|
309
387
|
captureClientOutput(wire);
|
|
310
388
|
export default wire;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "The SDK used by a Program's client endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@msgpack/msgpack": "^3.1.3"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@phreshos/core": "^0.1.
|
|
56
|
+
"@phreshos/core": "^0.1.13",
|
|
57
57
|
"typescript": "^6.0.3"
|
|
58
58
|
}
|
|
59
59
|
}
|