@phreshos/server 0.1.0
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 +54 -0
- package/dist/channel.d.ts +6 -0
- package/dist/channel.js +19 -0
- package/dist/current.d.ts +21 -0
- package/dist/current.js +50 -0
- package/dist/deadline.d.ts +7 -0
- package/dist/deadline.js +13 -0
- package/dist/domain.d.ts +91 -0
- package/dist/domain.js +313 -0
- package/dist/events.d.ts +21 -0
- package/dist/events.js +92 -0
- package/dist/host.d.ts +65 -0
- package/dist/host.js +54 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +4 -0
- package/dist/served.d.ts +3 -0
- package/dist/served.js +41 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.js +172 -0
- package/dist/wire.d.ts +38 -0
- package/dist/wire.js +234 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# `@phreshos/server`
|
|
2
|
+
|
|
3
|
+
The Server SDK defines the contextual capabilities available inside a
|
|
4
|
+
Program's server endpoint.
|
|
5
|
+
|
|
6
|
+
## Package status
|
|
7
|
+
|
|
8
|
+
This package is one component of a larger architecture that is still under
|
|
9
|
+
active testing. The architecture's components will be released in stages as
|
|
10
|
+
their contracts and integrations are verified.
|
|
11
|
+
|
|
12
|
+
`@phreshos/server` is not intended to be used on its own. It requires the
|
|
13
|
+
shared contracts from `@phreshos/core` and a compatible system host to provide
|
|
14
|
+
its runtime boundary.
|
|
15
|
+
|
|
16
|
+
It uses the domain objects and shared contracts from `@phreshos/core` through
|
|
17
|
+
a peer dependency. It does not redefine those objects, own client-side
|
|
18
|
+
capabilities, or contain host and transport implementations.
|
|
19
|
+
|
|
20
|
+
Its `Host` contract exposes authoritative Program and Process discovery,
|
|
21
|
+
runtime Program creation, lifecycle events, and publicly served values.
|
|
22
|
+
|
|
23
|
+
Its JavaScript entry point adapts the Process IPC boundary to these contracts.
|
|
24
|
+
The SDK owns callbacks, waits, queues, and their cleanup; the boundary owns
|
|
25
|
+
only the forwarding registrations requested by the SDK.
|
|
26
|
+
|
|
27
|
+
`Current` combines navigation into the executing Server's Process with its
|
|
28
|
+
inbound subscription and answer Channel. The paired Client is explicitly named
|
|
29
|
+
as `current.client`; its publishing, existence, lifecycle, and Window operations
|
|
30
|
+
never masquerade as properties of `current`. `current.stop()` stops the
|
|
31
|
+
executing Server, while complete Process exit remains available only through
|
|
32
|
+
`current.process()`.
|
|
33
|
+
|
|
34
|
+
The package provides two contextual values rather than constructors:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { host, current } from "@phreshos/server"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The current Server's Channel is composed directly into `current`. It receives
|
|
41
|
+
addressed events and registers answerers for questions arriving at this Server.
|
|
42
|
+
Answer registration returns its sole cleanup function. Publishing to the paired
|
|
43
|
+
Client belongs to `current.client`.
|
|
44
|
+
|
|
45
|
+
`server.ask()` does not route a question before the addressed Server
|
|
46
|
+
incarnation is ready. The Server SDK owns one deadline across readiness and the
|
|
47
|
+
answer; absence or incarnation loss rejects without turning a boundary into a
|
|
48
|
+
waiter.
|
|
49
|
+
|
|
50
|
+
Server Program handles add `install()` and `fork()`. Their filesystem areas
|
|
51
|
+
also expose `path()` and traversal-safe `resolve()`, because filesystem work is
|
|
52
|
+
performed locally in the Server SDK after the Host supplies only the area root.
|
|
53
|
+
Object descriptions passed to `host.createProgram()` therefore require an
|
|
54
|
+
explicit absolute storage root as well as at least one declared Endpoint.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Channel as CoreChannel, ChannelMessage, Cleanup } from "@phreshos/core";
|
|
2
|
+
export type Answerer<Payload = unknown, Result = unknown> = (message: ChannelMessage<Payload>) => Result | Promise<Result>;
|
|
3
|
+
export interface Channel<Events extends object = {}> extends CoreChannel<Events> {
|
|
4
|
+
answer<Payload = unknown, Result = unknown>(event: string, answerer: Answerer<Payload, Result>): Cleanup;
|
|
5
|
+
}
|
|
6
|
+
export declare const channel: Channel;
|
package/dist/channel.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { endpoint } from "./domain.js";
|
|
2
|
+
import Events from "./events.js";
|
|
3
|
+
import wire from "./wire.js";
|
|
4
|
+
class ServerChannel extends Events {
|
|
5
|
+
constructor() {
|
|
6
|
+
super((event, listener, impossible) => wire.on("end-end", event, value => listener(message(value)), null, impossible), observer => wire.onAll("end-end", (event, value) => {
|
|
7
|
+
if (typeof event === "string")
|
|
8
|
+
observer(event, message(value));
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
answer(event, answerer) {
|
|
12
|
+
return wire.answer("end-end", event, value => answerer(message(value)));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function message(value) {
|
|
16
|
+
const raw = value;
|
|
17
|
+
return { from: endpoint(raw.from), payload: raw.payload };
|
|
18
|
+
}
|
|
19
|
+
export const channel = new ServerChannel();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Channel as CoreChannel, LaunchClient, Publishable } from "@phreshos/core";
|
|
2
|
+
import { type Answerer } from "./channel.js";
|
|
3
|
+
import { type Process, type Program, type Window } from "./domain.js";
|
|
4
|
+
/** The paired Client as addressed from the currently executing Server. */
|
|
5
|
+
export interface CurrentClient extends Publishable {
|
|
6
|
+
exists(): Promise<boolean>;
|
|
7
|
+
start(overrides?: LaunchClient): Promise<void>;
|
|
8
|
+
stop(): Promise<void>;
|
|
9
|
+
window(): Promise<Window>;
|
|
10
|
+
}
|
|
11
|
+
/** Current Server context: its inbound Channel, owner hierarchy, and paired Client. */
|
|
12
|
+
export interface Current<Events extends object = {}> extends CoreChannel<Events> {
|
|
13
|
+
readonly client: CurrentClient;
|
|
14
|
+
answer<Payload = unknown, Result = unknown>(event: string, answerer: Answerer<Payload, Result>): () => void;
|
|
15
|
+
process(): Promise<Process>;
|
|
16
|
+
parent(): Promise<Process | null>;
|
|
17
|
+
program(): Promise<Program>;
|
|
18
|
+
option(name: string): Promise<string | undefined>;
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare const current: Current;
|
package/dist/current.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { channel } from "./channel.js";
|
|
2
|
+
import { process, program } from "./domain.js";
|
|
3
|
+
import wire from "./wire.js";
|
|
4
|
+
class PairedClient {
|
|
5
|
+
publish(event, payload) { wire.send("end-end", event, payload); }
|
|
6
|
+
async exists() {
|
|
7
|
+
const answer = await wire.request(["exists", "client"]);
|
|
8
|
+
return answer[0];
|
|
9
|
+
}
|
|
10
|
+
async start(overrides = {}) {
|
|
11
|
+
await wire.request(["start-endpoint", undefined, "client", overrides]);
|
|
12
|
+
}
|
|
13
|
+
async stop() { await wire.request(["stop-endpoint", undefined, "client"]); }
|
|
14
|
+
async window() { return (await current.process()).client.window(); }
|
|
15
|
+
}
|
|
16
|
+
class ServerCurrent {
|
|
17
|
+
client = new PairedClient();
|
|
18
|
+
constructor() {
|
|
19
|
+
bindChannel(this, channel);
|
|
20
|
+
}
|
|
21
|
+
answer(event, answerer) {
|
|
22
|
+
return channel.answer(event, answerer);
|
|
23
|
+
}
|
|
24
|
+
async process() {
|
|
25
|
+
const answer = await wire.request(["process"]);
|
|
26
|
+
return process(answer[0]);
|
|
27
|
+
}
|
|
28
|
+
async parent() {
|
|
29
|
+
const answer = await wire.request(["parent"]);
|
|
30
|
+
return answer[0] ? process(answer[0]) : null;
|
|
31
|
+
}
|
|
32
|
+
async program() {
|
|
33
|
+
const answer = await wire.request(["program"]);
|
|
34
|
+
return program(answer[0]);
|
|
35
|
+
}
|
|
36
|
+
async option(name) {
|
|
37
|
+
const answer = await wire.request(["option", undefined, name]);
|
|
38
|
+
return answer[0];
|
|
39
|
+
}
|
|
40
|
+
async stop() { await wire.request(["stop-current"]); }
|
|
41
|
+
}
|
|
42
|
+
function bindChannel(target, source) {
|
|
43
|
+
Object.assign(target, {
|
|
44
|
+
subscribe: source.subscribe.bind(source),
|
|
45
|
+
waitFor: source.waitFor.bind(source),
|
|
46
|
+
events: source.events.bind(source),
|
|
47
|
+
observe: source.observe.bind(source)
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export const current = new ServerCurrent();
|
package/dist/deadline.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defaultTimeout } from "./events.js";
|
|
2
|
+
/** One SDK-owned deadline shared by every stage of an asynchronous operation. */
|
|
3
|
+
export default class Deadline {
|
|
4
|
+
milliseconds;
|
|
5
|
+
expiresAt;
|
|
6
|
+
constructor(milliseconds = defaultTimeout) {
|
|
7
|
+
this.milliseconds = milliseconds;
|
|
8
|
+
this.expiresAt = Date.now() + milliseconds;
|
|
9
|
+
}
|
|
10
|
+
remaining() {
|
|
11
|
+
return Math.max(0, this.expiresAt - Date.now());
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/domain.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, Window as CoreWindow, type ClientDeclaration, type EndpointDeclaration, type Exit, type Launch, type ProgramArea as CoreProgramArea, type TrafficMessage } from "@phreshos/core";
|
|
2
|
+
import Events from "./events.js";
|
|
3
|
+
/** Server-side filesystem storage with access to its resolved host path. */
|
|
4
|
+
export interface ProgramArea extends CoreProgramArea {
|
|
5
|
+
path(): Promise<string>;
|
|
6
|
+
resolve(...path: string[]): Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
/** Client-safe Program data transported by the authoritative host. */
|
|
9
|
+
export interface ProgramRecord {
|
|
10
|
+
identity: string;
|
|
11
|
+
installed?: boolean;
|
|
12
|
+
name: string;
|
|
13
|
+
version: string | null;
|
|
14
|
+
description: string | null;
|
|
15
|
+
icons?: boolean;
|
|
16
|
+
server: EndpointDeclaration | null;
|
|
17
|
+
client: ClientDeclaration | null;
|
|
18
|
+
}
|
|
19
|
+
/** Process data transported with every Endpoint reference. */
|
|
20
|
+
export interface ProcessRecord {
|
|
21
|
+
identity: string;
|
|
22
|
+
name: string | null;
|
|
23
|
+
program: ProgramRecord;
|
|
24
|
+
options: Record<string, string>;
|
|
25
|
+
startedAt: string | Date;
|
|
26
|
+
server: Record<string, never> | null;
|
|
27
|
+
client: Record<string, never> | null;
|
|
28
|
+
}
|
|
29
|
+
export interface EndpointReference {
|
|
30
|
+
kind: "server" | "client";
|
|
31
|
+
process: ProcessRecord;
|
|
32
|
+
}
|
|
33
|
+
/** Server-visible Program handle and privileged Program operations. */
|
|
34
|
+
export interface Program<Events extends object = {}> extends CoreProgram<Events> {
|
|
35
|
+
readonly data: ProgramArea;
|
|
36
|
+
readonly cache: ProgramArea;
|
|
37
|
+
processes(): Promise<Process[]>;
|
|
38
|
+
getProcess(identityOrName: string): Promise<Process | null>;
|
|
39
|
+
createProcess(launch?: Launch): Promise<Process>;
|
|
40
|
+
install(): Promise<this>;
|
|
41
|
+
fork(identity: string): Promise<Program>;
|
|
42
|
+
}
|
|
43
|
+
/** Server-visible Process handle. */
|
|
44
|
+
export interface Process<Events extends object = {}> extends CoreProcess<Events> {
|
|
45
|
+
readonly server: Server;
|
|
46
|
+
readonly client: Client;
|
|
47
|
+
program(): Program;
|
|
48
|
+
parent(): Promise<Process | null>;
|
|
49
|
+
}
|
|
50
|
+
/** Server-visible common Endpoint handle. */
|
|
51
|
+
export interface Endpoint<Events extends object = {}> extends CoreEndpoint<Events> {
|
|
52
|
+
process(): Process;
|
|
53
|
+
}
|
|
54
|
+
/** Server-visible Server handle. */
|
|
55
|
+
export interface Server<Events extends object = {}> extends CoreServer<Events> {
|
|
56
|
+
process(): Process;
|
|
57
|
+
}
|
|
58
|
+
/** Server-visible Client handle. */
|
|
59
|
+
export interface Client<Events extends object = {}> extends CoreClient<Events> {
|
|
60
|
+
process(): Process;
|
|
61
|
+
window(): Promise<Window>;
|
|
62
|
+
}
|
|
63
|
+
/** Server-visible authoritative Window handle. */
|
|
64
|
+
export interface Window<Events extends object = {}> extends CoreWindow<Events> {
|
|
65
|
+
client(): Client;
|
|
66
|
+
}
|
|
67
|
+
export declare function scoped(route: string, subject: string | null, convert: (event: string, values: unknown[]) => unknown): Events;
|
|
68
|
+
export declare function exit(code: unknown, signal: unknown): Exit;
|
|
69
|
+
export declare function trafficMessage(value: unknown): TrafficMessage;
|
|
70
|
+
export declare function bindEvents(target: object, events: Events): void;
|
|
71
|
+
export declare function program(record: ProgramRecord): Program;
|
|
72
|
+
export declare function process(record: ProcessRecord): Process;
|
|
73
|
+
export declare function endpoint(reference: EndpointReference | undefined): Endpoint;
|
|
74
|
+
export declare const Program: {
|
|
75
|
+
new <Events extends object = {}>(): Program<Events>;
|
|
76
|
+
};
|
|
77
|
+
export declare const Process: {
|
|
78
|
+
new <Events extends object = {}>(): Process<Events>;
|
|
79
|
+
};
|
|
80
|
+
export declare const Endpoint: {
|
|
81
|
+
new <Events extends object = {}>(): Endpoint<Events>;
|
|
82
|
+
};
|
|
83
|
+
export declare const Server: {
|
|
84
|
+
new <Events extends object = {}>(): Server<Events>;
|
|
85
|
+
};
|
|
86
|
+
export declare const Client: {
|
|
87
|
+
new <Events extends object = {}>(): Client<Events>;
|
|
88
|
+
};
|
|
89
|
+
export declare const Window: {
|
|
90
|
+
new <Events extends object = {}>(): Window<Events>;
|
|
91
|
+
};
|
package/dist/domain.js
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { Client as CoreClient, Endpoint as CoreEndpoint, Process as CoreProcess, Program as CoreProgram, Server as CoreServer, Window as CoreWindow } from "@phreshos/core";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import Events from "./events.js";
|
|
4
|
+
import Deadline from "./deadline.js";
|
|
5
|
+
import { area, sql, store } from "./storage.js";
|
|
6
|
+
import wire from "./wire.js";
|
|
7
|
+
const ProgramBase = CoreProgram;
|
|
8
|
+
const ProcessBase = CoreProcess;
|
|
9
|
+
const ServerBase = CoreServer;
|
|
10
|
+
const ClientBase = CoreClient;
|
|
11
|
+
const WindowBase = CoreWindow;
|
|
12
|
+
class ProgramHandle extends ProgramBase {
|
|
13
|
+
identity;
|
|
14
|
+
name;
|
|
15
|
+
version;
|
|
16
|
+
description;
|
|
17
|
+
server;
|
|
18
|
+
client;
|
|
19
|
+
data;
|
|
20
|
+
cache;
|
|
21
|
+
store;
|
|
22
|
+
logs;
|
|
23
|
+
database;
|
|
24
|
+
constructor(record) {
|
|
25
|
+
super();
|
|
26
|
+
this.identity = record.identity;
|
|
27
|
+
this.name = record.name;
|
|
28
|
+
this.version = record.version;
|
|
29
|
+
this.description = record.description;
|
|
30
|
+
this.server = record.server;
|
|
31
|
+
this.client = record.client;
|
|
32
|
+
this.data = area(record.identity, "data");
|
|
33
|
+
this.cache = area(record.identity, "cache");
|
|
34
|
+
this.store = store(record.identity);
|
|
35
|
+
this.logs = sql("logs", record.identity);
|
|
36
|
+
this.database = sql("database", record.identity);
|
|
37
|
+
bindEvents(this, scoped("host-end", record.identity, programEvent));
|
|
38
|
+
}
|
|
39
|
+
async processes() {
|
|
40
|
+
const answer = await wire.request(["processes", this.identity]);
|
|
41
|
+
return answer[0].map(process);
|
|
42
|
+
}
|
|
43
|
+
async getProcess(identityOrName) {
|
|
44
|
+
const answer = await wire.request(["program-process", this.identity, identityOrName]);
|
|
45
|
+
return answer[0] ? process(answer[0]) : null;
|
|
46
|
+
}
|
|
47
|
+
async createProcess(launch = {}) {
|
|
48
|
+
const answer = await wire.request(["create-process", this.identity, launch]);
|
|
49
|
+
return process(answer[0]);
|
|
50
|
+
}
|
|
51
|
+
async apiDocs() {
|
|
52
|
+
const answer = await wire.request(["api-docs", this.identity]);
|
|
53
|
+
return answer[0];
|
|
54
|
+
}
|
|
55
|
+
async installed() {
|
|
56
|
+
const answer = await wire.request(["installed", this.identity]);
|
|
57
|
+
return answer[0];
|
|
58
|
+
}
|
|
59
|
+
async install() {
|
|
60
|
+
await wire.request(["install", this.identity]);
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
async fork(identity) {
|
|
64
|
+
const answer = await wire.request(["fork", this.identity, identity]);
|
|
65
|
+
return program(answer[0]);
|
|
66
|
+
}
|
|
67
|
+
async uninstall(everything = false) {
|
|
68
|
+
await wire.request(["uninstall", this.identity, everything]);
|
|
69
|
+
}
|
|
70
|
+
async forget() {
|
|
71
|
+
await wire.request(["forget", this.identity]);
|
|
72
|
+
}
|
|
73
|
+
async exitAll() {
|
|
74
|
+
const answer = await wire.request(["exit-all", this.identity]);
|
|
75
|
+
return answer[0];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
class ProcessHandle extends ProcessBase {
|
|
79
|
+
identity;
|
|
80
|
+
name;
|
|
81
|
+
startedAt;
|
|
82
|
+
server;
|
|
83
|
+
client;
|
|
84
|
+
ownerProgram;
|
|
85
|
+
options;
|
|
86
|
+
constructor(record) {
|
|
87
|
+
super();
|
|
88
|
+
this.identity = record.identity;
|
|
89
|
+
this.name = record.name;
|
|
90
|
+
this.startedAt = new Date(record.startedAt);
|
|
91
|
+
this.ownerProgram = program(record.program);
|
|
92
|
+
this.options = record.options;
|
|
93
|
+
this.server = new ServerHandle(this);
|
|
94
|
+
this.client = new ClientHandle(this);
|
|
95
|
+
bindEvents(this, scoped("process-host", record.identity, processEvent));
|
|
96
|
+
}
|
|
97
|
+
program() { return this.ownerProgram; }
|
|
98
|
+
async parent() {
|
|
99
|
+
const answer = await wire.request(["parent", this.identity]);
|
|
100
|
+
return answer[0] ? process(answer[0]) : null;
|
|
101
|
+
}
|
|
102
|
+
async option(name) {
|
|
103
|
+
if (name in this.options)
|
|
104
|
+
return this.options[name];
|
|
105
|
+
const answer = await wire.request(["option", this.identity, name]);
|
|
106
|
+
return answer[0];
|
|
107
|
+
}
|
|
108
|
+
async exit() {
|
|
109
|
+
await wire.request(["exit", this.identity]);
|
|
110
|
+
}
|
|
111
|
+
async exited() {
|
|
112
|
+
const answer = await wire.request(["exited", this.identity]);
|
|
113
|
+
return answer[0];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
class TrafficHandle extends Events {
|
|
117
|
+
owner;
|
|
118
|
+
kind;
|
|
119
|
+
constructor(owner, kind) {
|
|
120
|
+
super((event, listener, impossible) => wire.observe(owner.identity, kind, "publish", event, value => {
|
|
121
|
+
listener(trafficMessage(value));
|
|
122
|
+
}, impossible), observer => wire.observe(owner.identity, kind, "publish", null, (event, value) => {
|
|
123
|
+
if (typeof event === "string")
|
|
124
|
+
observer(event, trafficMessage(value));
|
|
125
|
+
}));
|
|
126
|
+
this.owner = owner;
|
|
127
|
+
this.kind = kind;
|
|
128
|
+
}
|
|
129
|
+
observeAsks(observer) {
|
|
130
|
+
return wire.observe(this.owner.identity, this.kind, "ask", null, (event, questionId, message) => {
|
|
131
|
+
if (typeof event !== "string" || typeof questionId !== "string")
|
|
132
|
+
return;
|
|
133
|
+
observer({ event, questionId, message: trafficMessage(message) });
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
class ServerTrafficHandle extends TrafficHandle {
|
|
138
|
+
observeAnswers(observer) {
|
|
139
|
+
return wire.observe(this.owner.identity, "server", "answer", null, (event, questionId, message) => {
|
|
140
|
+
if (typeof event !== "string" || typeof questionId !== "string")
|
|
141
|
+
return;
|
|
142
|
+
const raw = message;
|
|
143
|
+
observer({
|
|
144
|
+
event,
|
|
145
|
+
questionId,
|
|
146
|
+
message: { to: endpoint(raw.to), outcome: raw.outcome }
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
class ServerHandle extends ServerBase {
|
|
152
|
+
owner;
|
|
153
|
+
traffic;
|
|
154
|
+
constructor(owner) {
|
|
155
|
+
super();
|
|
156
|
+
this.owner = owner;
|
|
157
|
+
this.traffic = new ServerTrafficHandle(owner, "server");
|
|
158
|
+
}
|
|
159
|
+
process() { return this.owner; }
|
|
160
|
+
publish(event, payload) {
|
|
161
|
+
wire.send("end-host", "send", this.owner.identity, "server", event, payload);
|
|
162
|
+
}
|
|
163
|
+
async exists() {
|
|
164
|
+
const answer = await wire.request(["exists", "server", this.owner.identity]);
|
|
165
|
+
return answer[0];
|
|
166
|
+
}
|
|
167
|
+
async start() { await wire.request(["start-endpoint", this.owner.identity, "server"]); }
|
|
168
|
+
async stop() { await wire.request(["stop-endpoint", this.owner.identity, "server"]); }
|
|
169
|
+
async waitReady(timeout) {
|
|
170
|
+
await wire.request(["wait-ready", this.owner.identity], timeout);
|
|
171
|
+
}
|
|
172
|
+
async ask(event, payload) {
|
|
173
|
+
return this.askWithin(undefined, event, payload);
|
|
174
|
+
}
|
|
175
|
+
timeout(milliseconds) {
|
|
176
|
+
return { ask: (event, payload) => this.askWithin(milliseconds, event, payload) };
|
|
177
|
+
}
|
|
178
|
+
async askWithin(timeout, event, payload) {
|
|
179
|
+
const deadline = new Deadline(timeout);
|
|
180
|
+
await wire.requestWithin(["wait-ready", this.owner.identity, true], deadline);
|
|
181
|
+
const identity = await wire.identity;
|
|
182
|
+
const address = `server:${identity.process}:${randomUUID()}`;
|
|
183
|
+
const questionId = randomUUID();
|
|
184
|
+
const waiting = wire.expectWithin(address, deadline);
|
|
185
|
+
wire.send("end-host", "ask", this.owner.identity, "server", address, questionId, event, payload);
|
|
186
|
+
try {
|
|
187
|
+
return await waiting;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
wire.forget(address);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
class ClientHandle extends ClientBase {
|
|
195
|
+
owner;
|
|
196
|
+
traffic;
|
|
197
|
+
constructor(owner) {
|
|
198
|
+
super();
|
|
199
|
+
this.owner = owner;
|
|
200
|
+
this.traffic = new TrafficHandle(owner, "client");
|
|
201
|
+
}
|
|
202
|
+
process() { return this.owner; }
|
|
203
|
+
publish(event, payload) { wire.send("end-host", "send", this.owner.identity, "client", event, payload); }
|
|
204
|
+
async exists() {
|
|
205
|
+
const answer = await wire.request(["exists", "client", this.owner.identity]);
|
|
206
|
+
return answer[0];
|
|
207
|
+
}
|
|
208
|
+
async start(overrides = {}) {
|
|
209
|
+
await wire.request(["start-endpoint", this.owner.identity, "client", overrides]);
|
|
210
|
+
}
|
|
211
|
+
async stop() { await wire.request(["stop-endpoint", this.owner.identity, "client"]); }
|
|
212
|
+
async window() {
|
|
213
|
+
await wire.request(["window", this.owner.identity]);
|
|
214
|
+
return new WindowHandle(this);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
class WindowHandle extends WindowBase {
|
|
218
|
+
owner;
|
|
219
|
+
constructor(owner) {
|
|
220
|
+
super();
|
|
221
|
+
this.owner = owner;
|
|
222
|
+
bindEvents(this, scoped("host-end", owner.process().identity, (_event, values) => values[0]));
|
|
223
|
+
}
|
|
224
|
+
client() { return this.owner; }
|
|
225
|
+
async state() {
|
|
226
|
+
const answer = await wire.request(["window", this.owner.process().identity]);
|
|
227
|
+
return answer[0];
|
|
228
|
+
}
|
|
229
|
+
async title() { return (await this.state()).title; }
|
|
230
|
+
async position() { return (await this.state()).position; }
|
|
231
|
+
async size() { return (await this.state()).size; }
|
|
232
|
+
async minimized() { return (await this.state()).minimized; }
|
|
233
|
+
async front() { return (await this.state()).front; }
|
|
234
|
+
async layer() { return (await this.state()).layer; }
|
|
235
|
+
async location() { return (await this.state()).location; }
|
|
236
|
+
async move(position) { await wire.request(["move", this.owner.process().identity, position]); }
|
|
237
|
+
async resize(size) { await wire.request(["resize", this.owner.process().identity, size]); }
|
|
238
|
+
async minimize(minimized = true) { await wire.request(["minimize", this.owner.process().identity, minimized]); }
|
|
239
|
+
async changeTitle(title) { await wire.request(["changeTitle", this.owner.process().identity, title]); }
|
|
240
|
+
async raise() { await wire.request(["raise", this.owner.process().identity]); }
|
|
241
|
+
}
|
|
242
|
+
export function scoped(route, subject, convert) {
|
|
243
|
+
return new Events((event, listener, impossible) => wire.on(route, event, (...values) => {
|
|
244
|
+
const message = unscoped(subject, values);
|
|
245
|
+
if (message)
|
|
246
|
+
listener(convert(event, message));
|
|
247
|
+
}, subject, impossible), observer => wire.onAll(route, (event, ...values) => {
|
|
248
|
+
if (typeof event !== "string")
|
|
249
|
+
return;
|
|
250
|
+
const message = unscoped(subject, values);
|
|
251
|
+
if (message)
|
|
252
|
+
observer(event, convert(event, message));
|
|
253
|
+
}, subject));
|
|
254
|
+
}
|
|
255
|
+
function unscoped(subject, values) {
|
|
256
|
+
if (subject === null)
|
|
257
|
+
return values;
|
|
258
|
+
return values[0] === subject ? values.slice(1) : null;
|
|
259
|
+
}
|
|
260
|
+
function programEvent(event, values) {
|
|
261
|
+
if (event === "serverStart" || event === "clientStart" || event === "clientStop" || event === "processCreate")
|
|
262
|
+
return process(values[0]);
|
|
263
|
+
if (event === "serverStop")
|
|
264
|
+
return { process: process(values[0]), code: numberOrNull(values[1]), signal: stringOrNull(values[2]) };
|
|
265
|
+
if (event === "processExit")
|
|
266
|
+
return { process: process(values[0]), ...exit(values[1], values[2]) };
|
|
267
|
+
if (event === "uninstall")
|
|
268
|
+
return { everythingRemoved: values[0] === true };
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
function processEvent(event, values) {
|
|
272
|
+
if (event === "serverStop")
|
|
273
|
+
return { code: numberOrNull(values[0]), signal: stringOrNull(values[1]) };
|
|
274
|
+
if (event === "exit")
|
|
275
|
+
return exit(values[0], values[1]);
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
export function exit(code, signal) {
|
|
279
|
+
const namedSignal = stringOrNull(signal);
|
|
280
|
+
return { status: namedSignal === null ? "exited" : "signaled", code: numberOrNull(code), signal: namedSignal };
|
|
281
|
+
}
|
|
282
|
+
function numberOrNull(value) { return typeof value === "number" ? value : null; }
|
|
283
|
+
function stringOrNull(value) { return typeof value === "string" ? value : null; }
|
|
284
|
+
export function trafficMessage(value) {
|
|
285
|
+
const raw = value;
|
|
286
|
+
return { to: endpoint(raw.to), payload: raw.payload };
|
|
287
|
+
}
|
|
288
|
+
export function bindEvents(target, events) {
|
|
289
|
+
Object.assign(target, {
|
|
290
|
+
subscribe: events.subscribe.bind(events),
|
|
291
|
+
waitFor: events.waitFor.bind(events),
|
|
292
|
+
events: events.events.bind(events),
|
|
293
|
+
observe: events.observe.bind(events)
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
export function program(record) {
|
|
297
|
+
return new ProgramHandle(record);
|
|
298
|
+
}
|
|
299
|
+
export function process(record) {
|
|
300
|
+
return new ProcessHandle(record);
|
|
301
|
+
}
|
|
302
|
+
export function endpoint(reference) {
|
|
303
|
+
if (!reference)
|
|
304
|
+
throw new Error("The boundary returned an invalid Endpoint reference");
|
|
305
|
+
const owner = new ProcessHandle(reference.process);
|
|
306
|
+
return reference.kind === "server" ? owner.server : owner.client;
|
|
307
|
+
}
|
|
308
|
+
export const Program = CoreProgram;
|
|
309
|
+
export const Process = CoreProcess;
|
|
310
|
+
export const Endpoint = CoreEndpoint;
|
|
311
|
+
export const Server = CoreServer;
|
|
312
|
+
export const Client = CoreClient;
|
|
313
|
+
export const Window = CoreWindow;
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Capture, Cleanup, EventOptions } from "@phreshos/core";
|
|
2
|
+
export declare const defaultTimeout = 10000;
|
|
3
|
+
type Failure = (error: Error) => void;
|
|
4
|
+
type Listener = (message: unknown) => unknown;
|
|
5
|
+
type Observer = (event: string, message: unknown) => unknown;
|
|
6
|
+
/**
|
|
7
|
+
* SDK-owned subscription behavior over one boundary registration source.
|
|
8
|
+
*
|
|
9
|
+
* The boundary owns forwarding state only. Promises, deadlines, queues and
|
|
10
|
+
* callbacks remain here, inside the endpoint that requested the data.
|
|
11
|
+
*/
|
|
12
|
+
export default class Events {
|
|
13
|
+
private readonly listen;
|
|
14
|
+
private readonly listenAll;
|
|
15
|
+
constructor(listen: (event: string, listener: Listener, impossible?: Failure) => Cleanup, listenAll: (observer: Observer) => Cleanup);
|
|
16
|
+
subscribe(event: string, subscriber: Listener): Cleanup;
|
|
17
|
+
waitFor(event: string, timeout?: number): Promise<unknown>;
|
|
18
|
+
events(event: string, options?: EventOptions): AsyncIterableIterator<unknown>;
|
|
19
|
+
observe(observer: (capture: Capture<string, unknown>) => unknown): Cleanup;
|
|
20
|
+
}
|
|
21
|
+
export {};
|