@phreshos/server 0.1.14 → 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 +25 -3
- package/dist/domain.d.ts +6 -12
- package/dist/domain.js +15 -5
- package/dist/host.d.ts +3 -0
- package/dist/host.js +2 -0
- package/dist/main.d.ts +2 -1
- package/dist/main.js +1 -0
- package/dist/storage.d.ts +5 -3
- package/dist/storage.js +59 -15
- package/dist/wire.d.ts +4 -0
- package/dist/wire.js +82 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -110,12 +110,20 @@ incarnation is ready. The Server SDK owns one deadline across readiness and the
|
|
|
110
110
|
answer; absence or incarnation loss rejects without turning a boundary into a
|
|
111
111
|
waiter.
|
|
112
112
|
|
|
113
|
-
Server Program handles add `install()` and `fork()`.
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
Server Program handles add `install()` and `fork()`. `install()` is an async
|
|
114
|
+
generator that opens immediately, yields ordered `stdout` and `stderr` command
|
|
115
|
+
chunks, completes only after installation succeeds, and throws the owning
|
|
116
|
+
System error when installation fails. Their `Storage` values also expose
|
|
117
|
+
`path()` and traversal-safe `resolve()`, because filesystem work is performed
|
|
118
|
+
locally in the Server SDK after the Host supplies only the area root.
|
|
116
119
|
Object descriptions passed to `host.program.create()` therefore require an
|
|
117
120
|
explicit absolute storage root as well as at least one declared Endpoint.
|
|
118
121
|
|
|
122
|
+
`host.storage` implements that same refined `Storage` contract against the
|
|
123
|
+
native operating-system home directory. The System supplies the authoritative
|
|
124
|
+
root; traversal and symbolic-link escape remain rejected by the SDK before any
|
|
125
|
+
filesystem operation.
|
|
126
|
+
|
|
119
127
|
Host registries are separated by owner. Reads, commands, and the complete
|
|
120
128
|
subscription contract live on their relevant capability rather than directly
|
|
121
129
|
on `host`:
|
|
@@ -190,3 +198,17 @@ The system validates this through the same launch contract as
|
|
|
190
198
|
`program.process.create()`. Setting startup does not create a Process immediately.
|
|
191
199
|
`uninstall(false)` preserves the configuration but makes it inactive until the
|
|
192
200
|
Program is installed again; removing everything deletes it.
|
|
201
|
+
|
|
202
|
+
Installation and uninstallation expose any declared Server command output as
|
|
203
|
+
ordered streams. Consuming the generator waits for the authoritative operation
|
|
204
|
+
to finish:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
for await (const chunk of program.install()) {
|
|
208
|
+
(chunk.stream === "stderr" ? process.stderr : process.stdout).write(chunk.text)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for await (const chunk of program.uninstall(true)) {
|
|
212
|
+
(chunk.stream === "stderr" ? process.stderr : process.stdout).write(chunk.text)
|
|
213
|
+
}
|
|
214
|
+
```
|
package/dist/domain.d.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, type AnswerCapture, type AskCapture, type Cleanup, type ClientDeclaration, type Exit, type Launch, type Position, type
|
|
1
|
+
import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, type AnswerCapture, type AskCapture, type Cleanup, type ClientDeclaration, type Exit, type Launch, type Position, type ProgramCommandChunk, type ProgramPermission, type ProgramProcess as CoreProgramProcess, type Size, type TrafficMessage, type Window as CoreWindow, type WindowState } from "@phreshos/core";
|
|
2
2
|
import Events from "./events.js";
|
|
3
|
+
import { type Storage } from "./storage.js";
|
|
3
4
|
import { type ProgramStartup } from "./startup.js";
|
|
4
5
|
import { type ClientServiceHandler, type ServerServiceHandler } from "./service.js";
|
|
5
6
|
export interface HandleAddress {
|
|
6
7
|
identity: string;
|
|
7
8
|
reference: string;
|
|
8
9
|
}
|
|
9
|
-
/** Server-side filesystem storage with access to its resolved host path. */
|
|
10
|
-
export interface ProgramArea extends CoreProgramArea {
|
|
11
|
-
/** Returns the absolute host path of this storage area. */
|
|
12
|
-
path(): Promise<string>;
|
|
13
|
-
/** Resolves path segments within this storage area without permitting escape. */
|
|
14
|
-
resolve(...path: string[]): Promise<string>;
|
|
15
|
-
}
|
|
16
10
|
/** Client-safe Program data transported by the authoritative host. */
|
|
17
11
|
export interface EndpointDeclarationRecord {
|
|
18
12
|
start: boolean;
|
|
@@ -54,17 +48,17 @@ export type WindowRecord = WindowState;
|
|
|
54
48
|
/** Server-visible Program handle and privileged Program operations. */
|
|
55
49
|
export interface Program<Events extends object = {}> extends Omit<CoreProgram<Events>, "process"> {
|
|
56
50
|
/** Persistent filesystem data shared by every Process of this Program. */
|
|
57
|
-
readonly data:
|
|
51
|
+
readonly data: Storage;
|
|
58
52
|
/** Disposable filesystem data shared by every Process of this Program. */
|
|
59
|
-
readonly cache:
|
|
53
|
+
readonly cache: Storage;
|
|
60
54
|
/** Persistent Process launch used when the system starts. */
|
|
61
55
|
readonly startup: ProgramStartup;
|
|
62
56
|
/** Persistent permission decisions owned by this Program. */
|
|
63
57
|
readonly permission: ProgramPermission;
|
|
64
58
|
/** Operations and lifecycle observation for this Program's Processes. */
|
|
65
59
|
readonly process: ProgramProcess;
|
|
66
|
-
/** Installs this Program
|
|
67
|
-
install():
|
|
60
|
+
/** Installs this Program while yielding its command output in order. */
|
|
61
|
+
install(): AsyncGenerator<ProgramCommandChunk, void, void>;
|
|
68
62
|
/** Creates a new runtime Program with the supplied stable identity. */
|
|
69
63
|
fork(identity: string): Promise<Program>;
|
|
70
64
|
}
|
package/dist/domain.js
CHANGED
|
@@ -70,21 +70,31 @@ class ProgramHandle extends ProgramBase {
|
|
|
70
70
|
const answer = await wire.request(["installed", this.address]);
|
|
71
71
|
return answer[0];
|
|
72
72
|
}
|
|
73
|
-
async install() {
|
|
74
|
-
await wire.
|
|
75
|
-
|
|
73
|
+
async *install() {
|
|
74
|
+
for await (const value of wire.stream(["install", this.address])) {
|
|
75
|
+
yield programCommandChunk(value);
|
|
76
|
+
}
|
|
76
77
|
}
|
|
77
78
|
async fork(identity) {
|
|
78
79
|
const answer = await wire.request(["fork", this.address, identity]);
|
|
79
80
|
return program(answer[0]);
|
|
80
81
|
}
|
|
81
|
-
async uninstall(everything = false) {
|
|
82
|
-
await wire.
|
|
82
|
+
async *uninstall(everything = false) {
|
|
83
|
+
for await (const value of wire.stream(["uninstall", this.address, everything])) {
|
|
84
|
+
yield programCommandChunk(value);
|
|
85
|
+
}
|
|
83
86
|
}
|
|
84
87
|
async forget() {
|
|
85
88
|
await wire.request(["forget", this.address]);
|
|
86
89
|
}
|
|
87
90
|
}
|
|
91
|
+
function programCommandChunk(value) {
|
|
92
|
+
const chunk = value;
|
|
93
|
+
if (!chunk || (chunk.stream !== "stdout" && chunk.stream !== "stderr") || typeof chunk.text !== "string") {
|
|
94
|
+
throw new Error("The host returned an invalid Program command chunk");
|
|
95
|
+
}
|
|
96
|
+
return Object.freeze({ stream: chunk.stream, text: chunk.text });
|
|
97
|
+
}
|
|
88
98
|
function declaration(record) {
|
|
89
99
|
return Object.freeze({
|
|
90
100
|
start: record.start
|
package/dist/host.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DesktopWallpaper, Exit, FileWallpaper, Layer, Position, ServedFile, ServiceKey, Size, Subscribable, ThemeProperties, WritableTheme } from "@phreshos/core";
|
|
2
2
|
import type { ClientServiceHandler, ServerServiceHandler } from "./service.js";
|
|
3
3
|
import { type Client, type Process, type Program, type Server } from "./domain.js";
|
|
4
|
+
import { type Storage } from "./storage.js";
|
|
4
5
|
/** Resolved production description for a Program's Server. */
|
|
5
6
|
export type ServerDescription = Readonly<{
|
|
6
7
|
/** Absolute directory containing the production Server files. */
|
|
@@ -104,6 +105,8 @@ export interface HostProcess extends Subscribable<ProcessHostEvents, never> {
|
|
|
104
105
|
}
|
|
105
106
|
/** Authoritative system capabilities available to a Server endpoint. */
|
|
106
107
|
export interface Host {
|
|
108
|
+
/** Native operating-system home storage available to Server endpoints. */
|
|
109
|
+
readonly storage: Storage;
|
|
107
110
|
/** Observable system Theme authority. */
|
|
108
111
|
readonly theme: WritableTheme<ThemeProperties>;
|
|
109
112
|
/** Authoritative wallpaper visible before authentication. */
|
package/dist/host.js
CHANGED
|
@@ -5,7 +5,9 @@ import ServerTheme from "./theme.js";
|
|
|
5
5
|
import { ServerDesktopWallpaper, ServerSignInWallpaper } from "./wallpaper.js";
|
|
6
6
|
import wire from "./wire.js";
|
|
7
7
|
import { prepareService } from "./service.js";
|
|
8
|
+
import { hostStorage } from "./storage.js";
|
|
8
9
|
class ServerHost {
|
|
10
|
+
storage = hostStorage();
|
|
9
11
|
theme = new ServerTheme();
|
|
10
12
|
signInWallpaper = new ServerSignInWallpaper();
|
|
11
13
|
desktopWallpaper = new ServerDesktopWallpaper();
|
package/dist/main.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ export { host, type Host, type HostProcess, type HostProgram, type ClientDescrip
|
|
|
2
2
|
export { current, type Current, type CurrentClient } from "./current.js";
|
|
3
3
|
export { type Answerer, type Channel } from "./channel.js";
|
|
4
4
|
export { type ProgramStartup } from "./startup.js";
|
|
5
|
+
export { type Storage } from "./storage.js";
|
|
5
6
|
export { ClientServiceHandler, ServerServiceHandler } from "./service.js";
|
|
6
7
|
export { ServiceHandler, type ClientServiceChannel, type ServerServiceChannel, type ServiceChannel, type ServiceKey, type ServiceLifecycleEvents } from "@phreshos/core";
|
|
7
|
-
export { Client, Endpoint, Process, Program, Server, type Window, type
|
|
8
|
+
export { Client, Endpoint, Process, Program, Server, type Window, type ProgramProcess } from "./domain.js";
|
|
8
9
|
export type { AnswerCapture, AnswerMessage, AnswerObserver, Askable, AskCapture, AskMessage, AskObserver, Capture, Captures, ChannelCapture, ChannelEvents, ChannelMessage, ClientTraffic, ClientDeclaration, Cleanup, DirectoryStat, EndpointDeclaration, EndpointTraffic, EntryStat, EventMessage, EventName, EventObserver, EventOptions, EventSubscriber, Exit, FileStat, Launch, LaunchClient, Layer, LogKind, LogRecord, LogSource, Message, OtherStat, Outcome, Position, ProgramEvents, ProgramPermission, ProgramProcessEvents, ProgramProcessExit, ProgramSql, ProgramStore, ProcessEvents, Publishable, ServedFile, ServerTraffic, Size, Subscribable, SubscribableEvents, SubscribableFallback, TimedAskable, TrafficCapture, TrafficEvents, TrafficMessage, Value, Theme, ThemeEvents, ThemeProperties, WallpaperLaunch, FileWallpaper, DesktopWallpaper, WritableTheme, WindowEvents, WindowGeometry, WindowLayer, WindowState } from "@phreshos/core";
|
package/dist/main.js
CHANGED
|
@@ -2,6 +2,7 @@ export { host } from "./host.js";
|
|
|
2
2
|
export { current } from "./current.js";
|
|
3
3
|
export {} from "./channel.js";
|
|
4
4
|
export {} from "./startup.js";
|
|
5
|
+
export {} from "./storage.js";
|
|
5
6
|
export { ClientServiceHandler, ServerServiceHandler } from "./service.js";
|
|
6
7
|
export { ServiceHandler } from "@phreshos/core";
|
|
7
8
|
export { Client, Endpoint, Process, Program, Server } from "./domain.js";
|
package/dist/storage.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ProgramSql, ProgramStore, Storage as CoreStorage } from "@phreshos/core";
|
|
2
2
|
import type { HandleAddress } from "./domain.js";
|
|
3
|
-
export interface
|
|
3
|
+
export interface Storage extends CoreStorage {
|
|
4
4
|
path(): Promise<string>;
|
|
5
5
|
resolve(...path: string[]): Promise<string>;
|
|
6
6
|
}
|
|
7
7
|
/** Server-local implementation of one Program-owned filesystem area. */
|
|
8
|
-
export declare function area(program: HandleAddress, which: "data" | "cache"):
|
|
8
|
+
export declare function area(program: HandleAddress, which: "data" | "cache"): Storage;
|
|
9
|
+
/** Server-local access to the native home directory supplied by the System. */
|
|
10
|
+
export declare function hostStorage(): Storage;
|
|
9
11
|
export declare function store(program: HandleAddress): ProgramStore;
|
|
10
12
|
export declare function sql(kind: "database" | "logs", program: HandleAddress): ProgramSql;
|
|
11
13
|
export declare function content(value: unknown): {
|
package/dist/storage.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { createReadStream, createWriteStream, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { createReadStream, createWriteStream, mkdirSync, lstatSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
5
5
|
import { Readable } from "node:stream";
|
|
@@ -7,31 +7,50 @@ import { pipeline } from "node:stream/promises";
|
|
|
7
7
|
import wire from "./wire.js";
|
|
8
8
|
/** Server-local implementation of one Program-owned filesystem area. */
|
|
9
9
|
export function area(program, which) {
|
|
10
|
-
async function
|
|
10
|
+
return createStorage(async function () {
|
|
11
11
|
const answer = await wire.request([which, program, "path"]);
|
|
12
12
|
return answer[0];
|
|
13
|
+
}, `this Program's ${which}`);
|
|
14
|
+
}
|
|
15
|
+
/** Server-local access to the native home directory supplied by the System. */
|
|
16
|
+
export function hostStorage() {
|
|
17
|
+
return createStorage(async function () {
|
|
18
|
+
const answer = await wire.request(["host-storage", "path"]);
|
|
19
|
+
return answer[0];
|
|
20
|
+
}, "the native home directory");
|
|
21
|
+
}
|
|
22
|
+
function createStorage(root, label) {
|
|
23
|
+
let resolvedRoot = null;
|
|
24
|
+
async function path() {
|
|
25
|
+
if (!resolvedRoot) {
|
|
26
|
+
const resolving = root().then(value => {
|
|
27
|
+
if (!isAbsolute(value))
|
|
28
|
+
throw new Error("The host returned an invalid Storage directory");
|
|
29
|
+
return value;
|
|
30
|
+
});
|
|
31
|
+
const retained = resolving.catch(error => {
|
|
32
|
+
if (resolvedRoot === retained)
|
|
33
|
+
resolvedRoot = null;
|
|
34
|
+
throw error;
|
|
35
|
+
});
|
|
36
|
+
resolvedRoot = retained;
|
|
37
|
+
}
|
|
38
|
+
return resolvedRoot;
|
|
13
39
|
}
|
|
14
40
|
async function resolve(...parts) {
|
|
15
41
|
const root = await path();
|
|
16
|
-
|
|
17
|
-
const step = relative(root, destination);
|
|
18
|
-
if (step === ".." || step.startsWith(`..${sep}`) || isAbsolute(step)) {
|
|
19
|
-
throw new Error("A storage path may not leave its area");
|
|
20
|
-
}
|
|
21
|
-
return destination;
|
|
42
|
+
return contained(root, parts);
|
|
22
43
|
}
|
|
23
44
|
async function stream(...parts) {
|
|
24
45
|
const destination = await resolve(...parts);
|
|
25
46
|
const found = describe(destination);
|
|
26
47
|
if (!found)
|
|
27
|
-
throw new Error(`There is no ${parts.join("/")} in
|
|
48
|
+
throw new Error(`There is no ${parts.join("/")} in ${label}`);
|
|
28
49
|
if (found.kind !== "file")
|
|
29
50
|
throw new Error(`${parts.join("/")} is not a file`);
|
|
30
51
|
return Readable.toWeb(createReadStream(destination));
|
|
31
52
|
}
|
|
32
53
|
async function write(...args) {
|
|
33
|
-
if (args.length < 2)
|
|
34
|
-
throw new Error("Writing takes a file name and what to write");
|
|
35
54
|
const parts = args.slice(0, -1);
|
|
36
55
|
const destination = await resolve(...parts);
|
|
37
56
|
const temporary = join(dirname(destination), `.${randomUUID()}.writing`);
|
|
@@ -70,13 +89,38 @@ export function area(program, which) {
|
|
|
70
89
|
throw new Error("Emptying a place is clear, not delete");
|
|
71
90
|
rmSync(await resolve(...parts), { recursive: true, force: true });
|
|
72
91
|
},
|
|
73
|
-
async clear() {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
async clear(...parts) {
|
|
93
|
+
const destination = await resolve(...parts);
|
|
94
|
+
const found = describe(destination);
|
|
95
|
+
if (found && found.kind !== "directory")
|
|
96
|
+
throw new Error("Only a storage directory can be cleared");
|
|
97
|
+
rmSync(destination, { recursive: true, force: true });
|
|
98
|
+
mkdirSync(destination, { recursive: true });
|
|
77
99
|
}
|
|
78
100
|
};
|
|
79
101
|
}
|
|
102
|
+
function contained(root, parts) {
|
|
103
|
+
const destination = join(root, ...parts);
|
|
104
|
+
const step = relative(root, destination);
|
|
105
|
+
if (step === ".." || step.startsWith(`..${sep}`) || isAbsolute(step)) {
|
|
106
|
+
throw new Error("A storage path may not leave its configured directory");
|
|
107
|
+
}
|
|
108
|
+
let current = root;
|
|
109
|
+
for (const part of step.split(sep).filter(Boolean)) {
|
|
110
|
+
current = join(current, part);
|
|
111
|
+
try {
|
|
112
|
+
if (lstatSync(current).isSymbolicLink()) {
|
|
113
|
+
throw new Error("A storage path may not pass through a symbolic link");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error.code === "ENOENT")
|
|
118
|
+
break;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return destination;
|
|
123
|
+
}
|
|
80
124
|
export function store(program) {
|
|
81
125
|
async function ask(operation, ...values) {
|
|
82
126
|
const answer = await wire.request(["store", program, operation, ...values]);
|
package/dist/wire.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ type TrafficKind = "publish" | "ask" | "answer";
|
|
|
7
7
|
/** The server endpoint's sole IPC adapter. */
|
|
8
8
|
declare class Wire {
|
|
9
9
|
private readonly pending;
|
|
10
|
+
private readonly streams;
|
|
10
11
|
private readonly subscribers;
|
|
11
12
|
private readonly every;
|
|
12
13
|
private readonly answerers;
|
|
@@ -17,6 +18,8 @@ declare class Wire {
|
|
|
17
18
|
send(route: string, ...values: unknown[]): void;
|
|
18
19
|
request(values: unknown[], timeout?: number): Promise<unknown>;
|
|
19
20
|
requestWithin(values: unknown[], deadline: Deadline): Promise<unknown>;
|
|
21
|
+
/** Opens one long-running host operation and yields its ordered values. */
|
|
22
|
+
stream(values: unknown[], timeout?: number): AsyncIterableIterator<unknown>;
|
|
20
23
|
/** Resolves this endpoint's Process address only for operations that need it. */
|
|
21
24
|
identity(): Promise<{
|
|
22
25
|
process: string;
|
|
@@ -40,6 +43,7 @@ declare class Wire {
|
|
|
40
43
|
private releaseWaiting;
|
|
41
44
|
private forgetIncoming;
|
|
42
45
|
private settle;
|
|
46
|
+
private receiveStream;
|
|
43
47
|
}
|
|
44
48
|
declare const _default: Wire;
|
|
45
49
|
export default _default;
|
package/dist/wire.js
CHANGED
|
@@ -5,6 +5,7 @@ import { deserialize, serialize } from "./messagepack.js";
|
|
|
5
5
|
/** The server endpoint's sole IPC adapter. */
|
|
6
6
|
class Wire {
|
|
7
7
|
pending = new Map();
|
|
8
|
+
streams = new Map();
|
|
8
9
|
subscribers = new Map();
|
|
9
10
|
every = new Map();
|
|
10
11
|
answerers = new Map();
|
|
@@ -35,6 +36,10 @@ class Wire {
|
|
|
35
36
|
}
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
39
|
+
if (values[0] === "stream" && typeof values[1] === "string" && typeof values[2] === "string") {
|
|
40
|
+
this.receiveStream(values[1], values[2], values[3]);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
38
43
|
if (values[0] === "answer" && typeof values[1] === "string") {
|
|
39
44
|
this.settle(values[1], values.at(-1));
|
|
40
45
|
return;
|
|
@@ -66,6 +71,46 @@ class Wire {
|
|
|
66
71
|
this.send("end-host", "wait", question, ...values);
|
|
67
72
|
});
|
|
68
73
|
}
|
|
74
|
+
/** Opens one long-running host operation and yields its ordered values. */
|
|
75
|
+
stream(values, timeout = defaultTimeout) {
|
|
76
|
+
const wire = this;
|
|
77
|
+
return (async function* () {
|
|
78
|
+
const question = randomUUID();
|
|
79
|
+
const state = {
|
|
80
|
+
queue: [],
|
|
81
|
+
opened: false,
|
|
82
|
+
ended: false,
|
|
83
|
+
failure: null,
|
|
84
|
+
wake: null,
|
|
85
|
+
timer: setTimeout(() => {
|
|
86
|
+
state.failure = new Error(`Answer timeout ${timeout}ms`);
|
|
87
|
+
state.wake?.();
|
|
88
|
+
state.wake = null;
|
|
89
|
+
}, timeout)
|
|
90
|
+
};
|
|
91
|
+
wire.send("boundary", "expect", question);
|
|
92
|
+
wire.streams.set(question, state);
|
|
93
|
+
wire.send("end-host", "stream", question, ...values);
|
|
94
|
+
try {
|
|
95
|
+
while (true) {
|
|
96
|
+
if (state.queue.length) {
|
|
97
|
+
yield state.queue.shift();
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (state.failure)
|
|
101
|
+
throw state.failure;
|
|
102
|
+
if (state.ended)
|
|
103
|
+
return;
|
|
104
|
+
await new Promise(resolve => { state.wake = resolve; });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
clearTimeout(state.timer);
|
|
109
|
+
wire.streams.delete(question);
|
|
110
|
+
wire.send("boundary", "forget", question);
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
}
|
|
69
114
|
/** Resolves this endpoint's Process address only for operations that need it. */
|
|
70
115
|
identity() {
|
|
71
116
|
if (!this.identityPromise) {
|
|
@@ -255,7 +300,44 @@ class Wire {
|
|
|
255
300
|
else
|
|
256
301
|
pending.reject(new Error("The boundary returned an invalid outcome"));
|
|
257
302
|
}
|
|
303
|
+
receiveStream(question, operation, value) {
|
|
304
|
+
const stream = this.streams.get(question);
|
|
305
|
+
if (!stream || stream.ended || stream.failure)
|
|
306
|
+
return;
|
|
307
|
+
if (operation === "open") {
|
|
308
|
+
if (stream.opened)
|
|
309
|
+
return;
|
|
310
|
+
stream.opened = true;
|
|
311
|
+
clearTimeout(stream.timer);
|
|
312
|
+
}
|
|
313
|
+
else if (!stream.opened) {
|
|
314
|
+
stream.failure = new Error("The boundary produced a stream value before opening the stream");
|
|
315
|
+
}
|
|
316
|
+
else if (operation === "data") {
|
|
317
|
+
if (stream.queue.length >= maximumStreamQueue) {
|
|
318
|
+
stream.failure = new Error(`Host stream queue exceeded its capacity of ${maximumStreamQueue}`);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
stream.queue.push(value);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else if (operation === "answer") {
|
|
325
|
+
const outcome = value;
|
|
326
|
+
if (outcome?.success === true)
|
|
327
|
+
stream.ended = true;
|
|
328
|
+
else if (outcome?.success === false && typeof outcome.error === "string")
|
|
329
|
+
stream.failure = new Error(outcome.error);
|
|
330
|
+
else
|
|
331
|
+
stream.failure = new Error("The boundary returned an invalid stream outcome");
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
stream.failure = new Error(`The boundary returned an invalid stream operation "${operation}"`);
|
|
335
|
+
}
|
|
336
|
+
stream.wake?.();
|
|
337
|
+
stream.wake = null;
|
|
338
|
+
}
|
|
258
339
|
}
|
|
340
|
+
const maximumStreamQueue = 256;
|
|
259
341
|
function failed(error) {
|
|
260
342
|
return {
|
|
261
343
|
success: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "The SDK used by a Program's server endpoint.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@msgpack/msgpack": "^3.1.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@phreshos/core": "^0.1.
|
|
55
|
+
"@phreshos/core": "^0.1.14",
|
|
56
56
|
"@types/node": "^26.2.0",
|
|
57
57
|
"typescript": "^6.0.3"
|
|
58
58
|
}
|