@phreshos/node 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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2026 Zohayr SLILEH
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @phreshos/node
2
+
3
+ The Node.js interface for a running PhreshOS System and local Program projects.
4
+
5
+ ```ts
6
+ import { Gateway, Project } from "@phreshos/node"
7
+
8
+ const project = await Project.open()
9
+ const gateway = await Gateway.open()
10
+
11
+ for await (const event of gateway.install(project)) {
12
+ // installation progress
13
+ }
14
+
15
+ // The complete transport-neutral System contract used by Server Programs.
16
+ const programs = await gateway.system.program.list()
17
+
18
+ await gateway.close()
19
+ ```
20
+
21
+ `Project.open()` discovers `phresh.config.ts` from the current working
22
+ directory by default. `Gateway.open()` resolves its home from an explicit
23
+ argument, then `PHRESHOS_HOME`, then the current user's `.phreshos` directory.
24
+
25
+ Project operations remain available without duplicating CLI logic:
26
+
27
+ ```ts
28
+ const project = await Project.open() // process.cwd()
29
+ const gateway = await Gateway.open()
30
+
31
+ await project.pack()
32
+ for await (const event of gateway.start(project)) { /* production run */ }
33
+ for await (const event of gateway.dev(project)) { /* development run */ }
34
+ for await (const event of gateway.install(project)) { /* installation */ }
35
+ ```
36
+
37
+ `start`, `dev`, and `install` expose ordered asynchronous event streams. The
38
+ CLI only interprets arguments and presents those events; it does not implement
39
+ a second Project or Gateway lifecycle.
@@ -0,0 +1,2 @@
1
+ /** Resolve the one owner-local gateway address for a PhreshOS home. */
2
+ export declare function gatewayAddress(home: string, platform?: NodeJS.Platform): string;
@@ -0,0 +1,10 @@
1
+ import { createHash } from "node:crypto";
2
+ import { join } from "node:path";
3
+ /** Resolve the one owner-local gateway address for a PhreshOS home. */
4
+ export function gatewayAddress(home, platform = process.platform) {
5
+ if (platform !== "win32")
6
+ return join(home, "gateway.sock");
7
+ const owner = home.replaceAll("\\", "/").replace(/\/+$/, "").toLowerCase();
8
+ const identity = createHash("sha256").update(owner).digest("hex").slice(0, 32);
9
+ return `\\\\.\\pipe\\phreshos-${identity}-gateway`;
10
+ }
@@ -0,0 +1,29 @@
1
+ import type { ClientDevelopment } from "@phreshos/core";
2
+ import type { GatewayEvent } from "./transport.js";
3
+ /** One client development server owned by a Gateway development run. */
4
+ export declare class DevelopmentClient {
5
+ private readonly child;
6
+ private readonly output;
7
+ private stopped;
8
+ private result;
9
+ private outputWaiter;
10
+ private readonly completion;
11
+ constructor(command: string, directory: string);
12
+ drain(): GatewayEvent[];
13
+ exited(): Promise<CommandExit>;
14
+ exitResult(): CommandExit | null;
15
+ outputAvailable(): Promise<void>;
16
+ stop(): Promise<void>;
17
+ endingWasRequested(): boolean;
18
+ private push;
19
+ }
20
+ /** Refuse to claim a URL already served by an unrelated process. */
21
+ export declare function assertAvailable(url: string): Promise<void>;
22
+ /** Wait until a development Client can be loaded by a sandboxed Program iframe. */
23
+ export declare function waitForDevelopmentClient(config: ClientDevelopment, client?: DevelopmentClient, signal?: AbortSignal): AsyncGenerator<GatewayEvent, void, unknown>;
24
+ export declare function commandFailure(exit: CommandExit): Error;
25
+ export interface CommandExit {
26
+ code: number | null;
27
+ signal: NodeJS.Signals | null;
28
+ error: Error | null;
29
+ }
@@ -0,0 +1,199 @@
1
+ import { spawn } from "node:child_process";
2
+ import { connect } from "node:net";
3
+ import { delimiter, join } from "node:path";
4
+ const readinessTimeout = 15_000;
5
+ const pollingInterval = 200;
6
+ const reportingInterval = 2_000;
7
+ const sandboxedClientOrigin = "null";
8
+ /** One client development server owned by a Gateway development run. */
9
+ export class DevelopmentClient {
10
+ child;
11
+ output = [];
12
+ stopped = false;
13
+ result = null;
14
+ outputWaiter = null;
15
+ completion;
16
+ constructor(command, directory) {
17
+ this.child = spawn(command, {
18
+ cwd: directory,
19
+ env: commandEnvironment(directory),
20
+ shell: true,
21
+ stdio: ["ignore", "pipe", "pipe"],
22
+ detached: true
23
+ });
24
+ this.child.stdout?.on("data", chunk => this.push(outputEvent("out", chunk)));
25
+ this.child.stderr?.on("data", chunk => this.push(outputEvent("err", chunk)));
26
+ this.completion = new Promise(resolve => {
27
+ let settled = false;
28
+ const finish = (exit) => {
29
+ if (settled)
30
+ return;
31
+ settled = true;
32
+ this.result = exit;
33
+ this.outputWaiter?.();
34
+ this.outputWaiter = null;
35
+ resolve(exit);
36
+ };
37
+ this.child.once("error", error => finish({ code: null, signal: null, error }));
38
+ this.child.once("exit", (code, signal) => finish({ code, signal, error: null }));
39
+ });
40
+ }
41
+ drain() { return this.output.splice(0); }
42
+ exited() { return this.completion; }
43
+ exitResult() { return this.result; }
44
+ outputAvailable() {
45
+ if (this.output.length || this.result)
46
+ return Promise.resolve();
47
+ return new Promise(resolve => { this.outputWaiter = resolve; });
48
+ }
49
+ async stop() {
50
+ if (this.stopped)
51
+ return;
52
+ this.stopped = true;
53
+ if (!running(this.child))
54
+ return;
55
+ terminate(this.child, "SIGTERM");
56
+ await waitUntilStopped(this.child, 1_000);
57
+ if (running(this.child))
58
+ terminate(this.child, "SIGKILL");
59
+ await waitUntilStopped(this.child, 1_000);
60
+ }
61
+ endingWasRequested() { return this.stopped; }
62
+ push(event) {
63
+ this.output.push(event);
64
+ this.outputWaiter?.();
65
+ this.outputWaiter = null;
66
+ }
67
+ }
68
+ /** Refuse to claim a URL already served by an unrelated process. */
69
+ export async function assertAvailable(url) {
70
+ if (!await occupied(url))
71
+ return;
72
+ throw new Error(`Client development URL is already in use: ${url}`);
73
+ }
74
+ /** Wait until a development Client can be loaded by a sandboxed Program iframe. */
75
+ export async function* waitForDevelopmentClient(config, client, signal) {
76
+ const began = Date.now();
77
+ let nextReport = began + reportingInterval;
78
+ while (Date.now() - began < readinessTimeout) {
79
+ throwIfAborted(signal);
80
+ for (const event of client?.drain() ?? [])
81
+ yield event;
82
+ const exit = client?.exitResult();
83
+ if (exit && !client?.endingWasRequested())
84
+ throw commandFailure(exit);
85
+ const availability = await inspect(config.url, readinessTimeout - (Date.now() - began));
86
+ if (availability === "ready")
87
+ return;
88
+ if (availability === "cors-blocked") {
89
+ throw new Error([
90
+ `Client development URL responded, but does not allow the sandboxed Client origin: ${config.url}`,
91
+ "Enable CORS so the response includes Access-Control-Allow-Origin: *."
92
+ ].join("\n"));
93
+ }
94
+ const now = Date.now();
95
+ if (now >= nextReport) {
96
+ yield { event: "waiting", subject: "client", url: config.url };
97
+ while (nextReport <= now)
98
+ nextReport += reportingInterval;
99
+ }
100
+ await pause(Math.min(pollingInterval, readinessTimeout - (now - began)), signal);
101
+ }
102
+ throw new Error(`Client development URL did not respond within 15 seconds: ${config.url}`);
103
+ }
104
+ export function commandFailure(exit) {
105
+ if (exit.error)
106
+ return new Error(`Client development command failed: ${exit.error.message}`);
107
+ if (exit.signal)
108
+ return new Error(`Client development command ended on ${exit.signal}`);
109
+ return new Error(`Client development command exited with ${exit.code ?? 0}`);
110
+ }
111
+ function outputEvent(stream, chunk) {
112
+ return { event: "output", source: "client-development", stream, text: String(chunk) };
113
+ }
114
+ function commandEnvironment(directory) {
115
+ const key = Object.keys(process.env).find(name => name.toLowerCase() === "path") ?? "PATH";
116
+ const inherited = process.env[key];
117
+ return { ...process.env, [key]: [join(directory, "node_modules", ".bin"), inherited].filter(Boolean).join(delimiter) };
118
+ }
119
+ async function occupied(url) {
120
+ const location = new URL(url);
121
+ const port = Number(location.port || (location.protocol === "https:" ? 443 : 80));
122
+ return await new Promise(resolve => {
123
+ const socket = connect({ host: location.hostname, port });
124
+ let done = false;
125
+ const finish = (value) => {
126
+ if (done)
127
+ return;
128
+ done = true;
129
+ socket.destroy();
130
+ resolve(value);
131
+ };
132
+ socket.setTimeout(500);
133
+ socket.once("connect", () => finish(true));
134
+ socket.once("error", () => finish(false));
135
+ socket.once("timeout", () => finish(false));
136
+ });
137
+ }
138
+ async function inspect(url, remaining) {
139
+ try {
140
+ const response = await fetch(url, {
141
+ headers: { origin: sandboxedClientOrigin },
142
+ signal: AbortSignal.timeout(Math.max(1, Math.min(500, remaining)))
143
+ });
144
+ const allowedOrigin = response.headers.get("access-control-allow-origin")?.trim();
145
+ await response.body?.cancel();
146
+ return allowedOrigin === "*" || allowedOrigin === sandboxedClientOrigin ? "ready" : "cors-blocked";
147
+ }
148
+ catch {
149
+ return "unavailable";
150
+ }
151
+ }
152
+ function terminate(child, signal) {
153
+ if (!child.pid)
154
+ return;
155
+ try {
156
+ process.kill(-child.pid, signal);
157
+ }
158
+ catch {
159
+ child.kill(signal);
160
+ }
161
+ }
162
+ function running(child) {
163
+ if (!child.pid)
164
+ return false;
165
+ try {
166
+ process.kill(-child.pid, 0);
167
+ return true;
168
+ }
169
+ catch {
170
+ return child.exitCode === null && child.signalCode === null;
171
+ }
172
+ }
173
+ async function waitUntilStopped(child, milliseconds) {
174
+ const deadline = Date.now() + milliseconds;
175
+ while (running(child) && Date.now() < deadline)
176
+ await new Promise(resolve => setTimeout(resolve, 20));
177
+ }
178
+ function pause(milliseconds, signal) {
179
+ return new Promise((resolve, reject) => {
180
+ const timer = setTimeout(finish, Math.max(0, milliseconds));
181
+ const cancel = () => {
182
+ cleanup();
183
+ reject(signal?.reason instanceof Error ? signal.reason : new Error("The operation was cancelled"));
184
+ };
185
+ const cleanup = () => {
186
+ clearTimeout(timer);
187
+ signal?.removeEventListener("abort", cancel);
188
+ };
189
+ function finish() { cleanup(); resolve(); }
190
+ if (signal?.aborted)
191
+ cancel();
192
+ else
193
+ signal?.addEventListener("abort", cancel, { once: true });
194
+ });
195
+ }
196
+ function throwIfAborted(signal) {
197
+ if (signal?.aborted)
198
+ throw signal.reason instanceof Error ? signal.reason : new Error("The operation was cancelled");
199
+ }
@@ -0,0 +1,13 @@
1
+ import type { Subscribable } from "@phreshos/core";
2
+ type Wait = (event: string | null, signal: AbortSignal, timeout?: number) => Promise<unknown>;
3
+ /** Adapt authoritative one-event waits into the shared Subscribable contract. */
4
+ export default class Events<Definitions extends object, Fallback = never> {
5
+ private readonly names;
6
+ private readonly wait;
7
+ constructor(names: readonly string[], wait: Wait);
8
+ readonly subscribe: Subscribable<Definitions, Fallback>["subscribe"];
9
+ readonly waitFor: Subscribable<Definitions, Fallback>["waitFor"];
10
+ readonly events: Subscribable<Definitions, Fallback>["events"];
11
+ readonly observe: Subscribable<Definitions, Fallback>["observe"];
12
+ }
13
+ export {};
package/dist/events.js ADDED
@@ -0,0 +1,78 @@
1
+ /** Adapt authoritative one-event waits into the shared Subscribable contract. */
2
+ export default class Events {
3
+ names;
4
+ wait;
5
+ constructor(names, wait) {
6
+ this.names = names;
7
+ this.wait = wait;
8
+ }
9
+ subscribe = ((event, subscriber) => {
10
+ const controller = new AbortController();
11
+ void (async () => {
12
+ while (!controller.signal.aborted) {
13
+ try {
14
+ subscriber(await this.wait(event, controller.signal, 86_400_000));
15
+ }
16
+ catch (error) {
17
+ if (!controller.signal.aborted && timeout(error))
18
+ continue;
19
+ if (!controller.signal.aborted)
20
+ controller.abort();
21
+ }
22
+ }
23
+ })();
24
+ return () => controller.abort();
25
+ });
26
+ waitFor = ((event, timeout) => {
27
+ return this.wait(event, new AbortController().signal, timeout);
28
+ });
29
+ events = ((event, options = {}) => {
30
+ const wait = this.wait;
31
+ return (async function* () {
32
+ const controller = new AbortController();
33
+ const abort = () => controller.abort(options.signal?.reason);
34
+ options.signal?.addEventListener("abort", abort, { once: true });
35
+ try {
36
+ while (!controller.signal.aborted) {
37
+ try {
38
+ yield await wait(event, controller.signal, 86_400_000);
39
+ }
40
+ catch (error) {
41
+ if (!controller.signal.aborted && timeout(error))
42
+ continue;
43
+ throw error;
44
+ }
45
+ }
46
+ }
47
+ finally {
48
+ options.signal?.removeEventListener("abort", abort);
49
+ controller.abort();
50
+ }
51
+ })();
52
+ });
53
+ observe = ((observer) => {
54
+ if (this.names.length) {
55
+ const stops = this.names.map(event => this.subscribe(event, message => observer({ event, message })));
56
+ return () => stops.forEach(stop => stop());
57
+ }
58
+ const controller = new AbortController();
59
+ void (async () => {
60
+ while (!controller.signal.aborted) {
61
+ try {
62
+ const capture = await this.wait(null, controller.signal, 86_400_000);
63
+ observer({ event: capture.event, message: capture.payload });
64
+ }
65
+ catch (error) {
66
+ if (!controller.signal.aborted && timeout(error))
67
+ continue;
68
+ if (!controller.signal.aborted)
69
+ controller.abort();
70
+ }
71
+ }
72
+ })();
73
+ return () => controller.abort();
74
+ });
75
+ }
76
+ function timeout(error) {
77
+ return error instanceof Error && /timeout|timed out/i.test(error.message);
78
+ }
@@ -0,0 +1,49 @@
1
+ import type { ProgramDescription, System, SystemControlClient, SystemControlRequest } from "@phreshos/core";
2
+ import { Project, type PackedProject } from "./project.js";
3
+ import { type GatewayEvent } from "./transport.js";
4
+ /** One explicit owner-local connection to a running PhreshOS System. */
5
+ export declare class Gateway implements SystemControlClient {
6
+ private readonly connection;
7
+ readonly home: string;
8
+ readonly address: string;
9
+ readonly system: System;
10
+ private closed;
11
+ private readonly lifetime;
12
+ private constructor();
13
+ /** Connect to an already running System selected by argument, environment, or owner default. */
14
+ static open(home?: string): Promise<Gateway>;
15
+ /** Execute one operation from the transport-neutral System-control vocabulary. */
16
+ execute(request: SystemControlRequest, signal?: AbortSignal): Promise<unknown>;
17
+ /** Build and package one local Program project. */
18
+ pack(project: Project): Promise<PackedProject>;
19
+ /** Install one local Program project in this Gateway's System. */
20
+ install(source: Project | ProgramDescription, options?: InstallOptions): AsyncGenerator<GatewayEvent, void, void>;
21
+ /** Build and start one local production Program, attached to this Gateway. */
22
+ start(project: Project, options?: RunOptions): AsyncGenerator<GatewayEvent, void, unknown>;
23
+ /** Start one local Program in development, including its declared Client development server. */
24
+ dev(project: Project, options?: RunOptions): AsyncGenerator<GatewayEvent, void, unknown>;
25
+ /** Uninstall one Program by identity or local Project. */
26
+ uninstall(program: string | Project, options?: UninstallOptions): AsyncGenerator<GatewayEvent, void, void>;
27
+ /** Close this Gateway without stopping the System. */
28
+ close(): Promise<void>;
29
+ private runProject;
30
+ private program;
31
+ private control;
32
+ private api;
33
+ private lifecycle;
34
+ private signal;
35
+ private requireOpen;
36
+ }
37
+ export interface InstallOptions {
38
+ run?: boolean;
39
+ startup?: boolean;
40
+ signal?: AbortSignal;
41
+ }
42
+ export interface RunOptions {
43
+ options?: Record<string, string>;
44
+ signal?: AbortSignal;
45
+ }
46
+ export interface UninstallOptions {
47
+ everything?: boolean;
48
+ signal?: AbortSignal;
49
+ }
@@ -0,0 +1,151 @@
1
+ import { gatewayAddress } from "./address.js";
2
+ import { resolveHome } from "./home.js";
3
+ import { Project } from "./project.js";
4
+ import { openConnection, request as gatewayRequest, streamProgram } from "./transport.js";
5
+ import { gatewaySystem } from "./system.js";
6
+ import { assertAvailable, commandFailure, DevelopmentClient, waitForDevelopmentClient } from "./client-development.js";
7
+ /** One explicit owner-local connection to a running PhreshOS System. */
8
+ export class Gateway {
9
+ connection;
10
+ home;
11
+ address;
12
+ system;
13
+ closed = false;
14
+ lifetime = new AbortController();
15
+ constructor(home, address, connection) {
16
+ this.connection = connection;
17
+ this.home = home;
18
+ this.address = address;
19
+ this.system = gatewaySystem({
20
+ control: (request, signal) => this.control(request, signal),
21
+ api: (request, signal) => this.api(request, signal),
22
+ lifecycle: (request, signal) => this.lifecycle(request, signal)
23
+ });
24
+ }
25
+ /** Connect to an already running System selected by argument, environment, or owner default. */
26
+ static async open(home) {
27
+ const resolvedHome = resolveHome(home);
28
+ const address = gatewayAddress(resolvedHome);
29
+ const connection = await openConnection(address);
30
+ return new Gateway(resolvedHome, address, connection);
31
+ }
32
+ /** Execute one operation from the transport-neutral System-control vocabulary. */
33
+ execute(request, signal) {
34
+ this.requireOpen();
35
+ return this.control(request, signal);
36
+ }
37
+ /** Build and package one local Program project. */
38
+ pack(project) {
39
+ this.requireOpen();
40
+ return project.pack();
41
+ }
42
+ /** Install one local Program project in this Gateway's System. */
43
+ async *install(source, options = {}) {
44
+ this.requireOpen();
45
+ if (source instanceof Project)
46
+ await source.build();
47
+ const program = source instanceof Project ? source.description("production") : source;
48
+ yield* this.program({
49
+ word: "install",
50
+ program,
51
+ run: options.run === true,
52
+ startup: options.startup === true
53
+ }, options.signal);
54
+ }
55
+ /** Build and start one local production Program, attached to this Gateway. */
56
+ start(project, options = {}) {
57
+ this.requireOpen();
58
+ return this.runProject(project, "production", options);
59
+ }
60
+ /** Start one local Program in development, including its declared Client development server. */
61
+ dev(project, options = {}) {
62
+ this.requireOpen();
63
+ return this.runProject(project, "development", options);
64
+ }
65
+ /** Uninstall one Program by identity or local Project. */
66
+ uninstall(program, options = {}) {
67
+ this.requireOpen();
68
+ const identity = typeof program === "string" ? program : program.config.identity;
69
+ return this.program({ word: "uninstall", identity, everything: options.everything === true }, options.signal);
70
+ }
71
+ /** Close this Gateway without stopping the System. */
72
+ async close() {
73
+ if (this.closed)
74
+ return;
75
+ this.closed = true;
76
+ this.lifetime.abort(new Error("This Gateway is closed"));
77
+ this.connection.destroy();
78
+ }
79
+ async *runProject(project, mode, options) {
80
+ if (mode === "production")
81
+ await project.build();
82
+ const program = project.description(mode);
83
+ const development = mode === "development" && program.client && (program.client.start ?? true)
84
+ ? project.config.client?.development
85
+ : undefined;
86
+ const command = development?.startCommand;
87
+ if (command)
88
+ await assertAvailable(development.url);
89
+ const client = command ? new DevelopmentClient(command, project.directory) : undefined;
90
+ const controller = new AbortController();
91
+ const signal = this.signal(options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal);
92
+ try {
93
+ if (development)
94
+ yield* waitForDevelopmentClient(development, client, signal);
95
+ const lifecycle = this.program({ word: "run", program, options: options.options ?? {} }, signal);
96
+ const iterator = lifecycle[Symbol.asyncIterator]();
97
+ let lifecycleNext = iterator.next();
98
+ let exit = client?.exited();
99
+ let output = client?.outputAvailable();
100
+ while (true) {
101
+ for (const event of client?.drain() ?? [])
102
+ yield event;
103
+ const outcome = await Promise.race([
104
+ lifecycleNext.then(result => ({ source: "system", result })),
105
+ ...(exit ? [exit.then(result => ({ source: "client", result }))] : []),
106
+ ...(output ? [output.then(() => ({ source: "output" }))] : [])
107
+ ]);
108
+ if (outcome.source === "output") {
109
+ output = client?.outputAvailable();
110
+ continue;
111
+ }
112
+ if (outcome.source === "client") {
113
+ exit = undefined;
114
+ if (!client?.endingWasRequested())
115
+ throw commandFailure(outcome.result);
116
+ continue;
117
+ }
118
+ if (outcome.result.done)
119
+ return;
120
+ yield outcome.result.value;
121
+ lifecycleNext = iterator.next();
122
+ }
123
+ }
124
+ finally {
125
+ controller.abort(new Error("The local Program run ended"));
126
+ await client?.stop();
127
+ }
128
+ }
129
+ program(request, signal) {
130
+ return this.lifecycle(request, signal);
131
+ }
132
+ control(request, signal) {
133
+ this.requireOpen();
134
+ return gatewayRequest(this.address, "system", request, this.signal(signal));
135
+ }
136
+ api(request, signal) {
137
+ this.requireOpen();
138
+ return gatewayRequest(this.address, "api", request, this.signal(signal));
139
+ }
140
+ lifecycle(request, signal) {
141
+ this.requireOpen();
142
+ return streamProgram(this.address, request, this.signal(signal));
143
+ }
144
+ signal(signal) {
145
+ return signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal;
146
+ }
147
+ requireOpen() {
148
+ if (this.closed)
149
+ throw new Error("This Gateway is closed");
150
+ }
151
+ }
package/dist/home.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** Resolve the absolute PhreshOS home selected for one Gateway. */
2
+ export declare function resolveHome(home?: string, environment?: NodeJS.ProcessEnv, userHome?: string): string;
package/dist/home.js ADDED
@@ -0,0 +1,16 @@
1
+ import { existsSync, realpathSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, join, normalize } from "node:path";
4
+ /** Resolve the absolute PhreshOS home selected for one Gateway. */
5
+ export function resolveHome(home, environment = process.env, userHome = homedir()) {
6
+ const selected = home ?? environment.PHRESHOS_HOME;
7
+ if (selected === undefined)
8
+ return canonical(join(userHome, ".phreshos"));
9
+ if (!isAbsolute(selected))
10
+ throw new Error("The PhreshOS home must be an absolute filesystem path");
11
+ return canonical(selected);
12
+ }
13
+ function canonical(path) {
14
+ const normalized = normalize(path);
15
+ return existsSync(normalized) ? realpathSync(normalized) : normalized;
16
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { gatewayAddress } from "./address.js";
2
+ export { Gateway, type InstallOptions, type RunOptions, type UninstallOptions } from "./gateway.js";
3
+ export { resolveHome } from "./home.js";
4
+ export { Project, type Manifest, type PackedProject, type ProjectMode, type ProjectOptions } from "./project.js";
5
+ export { type GatewayEvent } from "./transport.js";
package/dist/main.js ADDED
@@ -0,0 +1,5 @@
1
+ export { gatewayAddress } from "./address.js";
2
+ export { Gateway } from "./gateway.js";
3
+ export { resolveHome } from "./home.js";
4
+ export { Project } from "./project.js";
5
+ export {} from "./transport.js";
@@ -0,0 +1,37 @@
1
+ import { type Config, type ProgramDescription } from "@phreshos/core";
2
+ /** One loaded Program authoring project rooted at an absolute directory. */
3
+ export declare class Project {
4
+ readonly directory: string;
5
+ readonly config: Config;
6
+ private constructor();
7
+ /** Discover a project from cwd, a directory, or a phresh.config.ts path. */
8
+ static open(source?: string): Promise<Project>;
9
+ /** Create a project from an already loaded definition. */
10
+ static define(config: Config, options?: ProjectOptions): Project;
11
+ /** Read this project's package manifest. */
12
+ manifest(): Promise<Manifest>;
13
+ /** Resolve this authoring definition into one runnable Program description. */
14
+ description(mode: ProjectMode): ProgramDescription;
15
+ /** Run the optional author-owned production build command. */
16
+ build(): Promise<void>;
17
+ /** Build and package this Program into its canonical release shape. */
18
+ pack(): Promise<PackedProject>;
19
+ }
20
+ export type ProjectMode = "production" | "development";
21
+ export interface ProjectOptions {
22
+ directory?: string;
23
+ }
24
+ export type PackedProject = Readonly<{
25
+ archive: string;
26
+ archivePath: string;
27
+ checksumPath: string;
28
+ declarationPath: string;
29
+ digest: string;
30
+ }>;
31
+ export interface Manifest {
32
+ name: string;
33
+ version?: string;
34
+ description?: string;
35
+ packageManager?: string;
36
+ scripts?: Record<string, string>;
37
+ }