@phreshos/client 0.1.15 → 0.1.17
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 +11 -1
- package/dist/domain.js +12 -1
- package/dist/main.d.ts +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/storage.js +1 -3
- package/dist/wire.d.ts +4 -0
- package/dist/wire.js +78 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -141,13 +141,23 @@ identity is removed by the server host before the message reaches the desktop.
|
|
|
141
141
|
The same rule applies to Client-visible traffic destinations.
|
|
142
142
|
|
|
143
143
|
Client-visible Program handles can operate only within their own Program.
|
|
144
|
-
They deliberately omit `install()` and `fork()`, and their
|
|
144
|
+
They deliberately omit `install()` and `fork()`, and their `Storage` values never
|
|
145
145
|
expose host filesystem paths. Every Client-side Window handle exposes the same
|
|
146
146
|
authoritative, subscribable Window capability and its `window.local` physical
|
|
147
147
|
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/main.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export { current, type Current, type CurrentServer } from "./current.js";
|
|
|
5
5
|
export { type Channel, type ChannelCapture, type ChannelEvents, type ChannelMessage } from "./channel.js";
|
|
6
6
|
export { ClientServiceHandler, ServerServiceHandler, ServiceHandler, type ClientServiceChannel, type ServerServiceChannel, type ServiceChannel, type ServiceKey, type ServiceLifecycleEvents } from "@phreshos/core";
|
|
7
7
|
export { Client, Endpoint, Process, Program, Server, type Window, type ProgramProcess, type AnswerCapture, type AnswerMessage, type AnswerObserver, type AskCapture, type AskMessage, type AskObserver, type ClientTraffic, type EndpointTraffic, type ServerTraffic, type TrafficCapture, type TrafficEvents, type TrafficMessage } from "./domain.js";
|
|
8
|
-
export type { Askable, Capture, Captures, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permission, Position,
|
|
8
|
+
export type { Askable, Capture, Captures, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, PermissionDecision, Permission, Position, Storage, ProgramEvents, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TimedPermission, Timeoutable, Theme, ThemeEvents, ThemeProperties, Value, WritableTheme, WindowEvents, WindowGeometry, WindowLayer, WindowState, Easing, LocalWindow, LocalWindowSurface, SurfaceSettings, Transaction } from "@phreshos/core";
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ProgramSql, ProgramStore, Storage } from "@phreshos/core";
|
|
2
2
|
/** Client-side Program storage, structurally scoped by the iframe boundary. */
|
|
3
|
-
export declare function area(which: "data" | "cache"):
|
|
3
|
+
export declare function area(which: "data" | "cache"): Storage;
|
|
4
4
|
export declare function store(): ProgramStore;
|
|
5
5
|
export declare function sql(kind: "database" | "logs"): ProgramSql;
|
package/dist/storage.js
CHANGED
|
@@ -38,14 +38,12 @@ export function area(which) {
|
|
|
38
38
|
async text(...path) { return new Response(await stream(...path)).text(); },
|
|
39
39
|
async json(...path) { return JSON.parse(await new Response(await stream(...path)).text()); },
|
|
40
40
|
async write(...args) {
|
|
41
|
-
if (args.length < 2)
|
|
42
|
-
throw new Error("Writing takes a file name and what to write");
|
|
43
41
|
await transfer("write", args.slice(0, -1), content(args.at(-1)).body);
|
|
44
42
|
},
|
|
45
43
|
stat: (...path) => ask("stat", ...path),
|
|
46
44
|
list: (...path) => ask("list", ...path),
|
|
47
45
|
delete: (...path) => ask("delete", ...path),
|
|
48
|
-
clear: () => ask("clear")
|
|
46
|
+
clear: (...path) => ask("clear", ...path)
|
|
49
47
|
};
|
|
50
48
|
}
|
|
51
49
|
export function store() {
|
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.17",
|
|
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.14",
|
|
57
57
|
"typescript": "^6.0.3"
|
|
58
58
|
}
|
|
59
59
|
}
|