@phreshos/node 0.1.0 → 0.1.1
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 +18 -16
- package/dist/client-development.d.ts +4 -4
- package/dist/client-development.js +1 -1
- package/dist/home.d.ts +1 -1
- package/dist/home.js +1 -1
- package/dist/main.d.ts +2 -3
- package/dist/main.js +1 -2
- package/dist/project.d.ts +22 -1
- package/dist/project.js +108 -0
- package/dist/system.d.ts +67 -6
- package/dist/system.js +155 -34
- package/dist/transport.d.ts +3 -3
- package/dist/transport.js +19 -13
- package/package.json +2 -2
- package/dist/gateway.d.ts +0 -49
- package/dist/gateway.js +0 -151
package/README.md
CHANGED
|
@@ -3,37 +3,39 @@
|
|
|
3
3
|
The Node.js interface for a running PhreshOS System and local Program projects.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import {
|
|
6
|
+
import { Project, System } from "@phreshos/node"
|
|
7
7
|
|
|
8
8
|
const project = await Project.open()
|
|
9
|
-
const
|
|
9
|
+
const system = await System.connect()
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
// installation progress
|
|
13
|
-
}
|
|
11
|
+
await project.install(system)
|
|
14
12
|
|
|
15
|
-
//
|
|
16
|
-
const programs = await
|
|
13
|
+
// This is the same transport-neutral System contract used by Server Programs.
|
|
14
|
+
const programs = await system.program.list()
|
|
17
15
|
|
|
18
|
-
await
|
|
16
|
+
await system.disconnect()
|
|
19
17
|
```
|
|
20
18
|
|
|
21
19
|
`Project.open()` discovers `phresh.config.ts` from the current working
|
|
22
|
-
directory by default. `
|
|
20
|
+
directory by default. `System.connect()` resolves its home from an explicit
|
|
23
21
|
argument, then `PHRESHOS_HOME`, then the current user's `.phreshos` directory.
|
|
24
22
|
|
|
25
23
|
Project operations remain available without duplicating CLI logic:
|
|
26
24
|
|
|
27
25
|
```ts
|
|
28
26
|
const project = await Project.open() // process.cwd()
|
|
29
|
-
const
|
|
27
|
+
const system = await System.connect()
|
|
30
28
|
|
|
31
29
|
await project.pack()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
await project.start(system, { signal })
|
|
31
|
+
await project.dev(system, { signal })
|
|
32
|
+
await project.install(system)
|
|
33
|
+
|
|
34
|
+
await system.disconnect()
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
`
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
`Project` owns authoring concepts such as production builds and development
|
|
38
|
+
Client servers. The lower-level System API stays composable: use
|
|
39
|
+
`system.forceCreateProgram(description)` to replace one runtime Program, and
|
|
40
|
+
`program.process.run(launch, { signal })` when one Process should live exactly
|
|
41
|
+
as long as its asynchronous iterator.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ClientDevelopment } from "@phreshos/core";
|
|
2
|
-
import type {
|
|
3
|
-
/** One
|
|
2
|
+
import type { TransportEvent } from "./transport.js";
|
|
3
|
+
/** One Client development server owned by a Project development run. */
|
|
4
4
|
export declare class DevelopmentClient {
|
|
5
5
|
private readonly child;
|
|
6
6
|
private readonly output;
|
|
@@ -9,7 +9,7 @@ export declare class DevelopmentClient {
|
|
|
9
9
|
private outputWaiter;
|
|
10
10
|
private readonly completion;
|
|
11
11
|
constructor(command: string, directory: string);
|
|
12
|
-
drain():
|
|
12
|
+
drain(): TransportEvent[];
|
|
13
13
|
exited(): Promise<CommandExit>;
|
|
14
14
|
exitResult(): CommandExit | null;
|
|
15
15
|
outputAvailable(): Promise<void>;
|
|
@@ -20,7 +20,7 @@ export declare class DevelopmentClient {
|
|
|
20
20
|
/** Refuse to claim a URL already served by an unrelated process. */
|
|
21
21
|
export declare function assertAvailable(url: string): Promise<void>;
|
|
22
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<
|
|
23
|
+
export declare function waitForDevelopmentClient(config: ClientDevelopment, client?: DevelopmentClient, signal?: AbortSignal): AsyncGenerator<TransportEvent, void, unknown>;
|
|
24
24
|
export declare function commandFailure(exit: CommandExit): Error;
|
|
25
25
|
export interface CommandExit {
|
|
26
26
|
code: number | null;
|
|
@@ -5,7 +5,7 @@ const readinessTimeout = 15_000;
|
|
|
5
5
|
const pollingInterval = 200;
|
|
6
6
|
const reportingInterval = 2_000;
|
|
7
7
|
const sandboxedClientOrigin = "null";
|
|
8
|
-
/** One
|
|
8
|
+
/** One Client development server owned by a Project development run. */
|
|
9
9
|
export class DevelopmentClient {
|
|
10
10
|
child;
|
|
11
11
|
output = [];
|
package/dist/home.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/** Resolve the absolute PhreshOS home selected for one
|
|
1
|
+
/** Resolve the absolute PhreshOS home selected for one System connection. */
|
|
2
2
|
export declare function resolveHome(home?: string, environment?: NodeJS.ProcessEnv, userHome?: string): string;
|
package/dist/home.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { isAbsolute, join, normalize } from "node:path";
|
|
4
|
-
/** Resolve the absolute PhreshOS home selected for one
|
|
4
|
+
/** Resolve the absolute PhreshOS home selected for one System connection. */
|
|
5
5
|
export function resolveHome(home, environment = process.env, userHome = homedir()) {
|
|
6
6
|
const selected = home ?? environment.PHRESHOS_HOME;
|
|
7
7
|
if (selected === undefined)
|
package/dist/main.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { gatewayAddress } from "./address.js";
|
|
2
|
-
export {
|
|
2
|
+
export { System, type ProgramProcessRunEvent, type ProgramProcessRunOptions } from "./system.js";
|
|
3
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";
|
|
4
|
+
export { Project, type Manifest, type PackedProject, type ProjectMode, type ProjectOptions, type ProjectRunOptions, type ProjectRunResult } from "./project.js";
|
package/dist/main.js
CHANGED
package/dist/project.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Config, type ProgramDescription } from "@phreshos/core";
|
|
1
|
+
import { type Config, type Exit, type ProgramDescription, type System as SystemContract, type SystemProcessEntity, type SystemProgramEntity } from "@phreshos/core";
|
|
2
2
|
/** One loaded Program authoring project rooted at an absolute directory. */
|
|
3
3
|
export declare class Project {
|
|
4
4
|
readonly directory: string;
|
|
@@ -14,13 +14,34 @@ export declare class Project {
|
|
|
14
14
|
description(mode: ProjectMode): ProgramDescription;
|
|
15
15
|
/** Run the optional author-owned production build command. */
|
|
16
16
|
build(): Promise<void>;
|
|
17
|
+
/** Build and run this project's production Program until its Process exits. */
|
|
18
|
+
start(system: SystemContract, options?: ProjectRunOptions): Promise<Readonly<{
|
|
19
|
+
process: SystemProcessEntity;
|
|
20
|
+
exit: Exit;
|
|
21
|
+
}>>;
|
|
22
|
+
/** Run this project's development Program and its optional Client server. */
|
|
23
|
+
dev(system: SystemContract, options?: ProjectRunOptions): Promise<Readonly<{
|
|
24
|
+
process: SystemProcessEntity;
|
|
25
|
+
exit: Exit;
|
|
26
|
+
}>>;
|
|
27
|
+
/** Build and install this project's production Program. */
|
|
28
|
+
install(system: SystemContract): Promise<SystemProgramEntity>;
|
|
17
29
|
/** Build and package this Program into its canonical release shape. */
|
|
18
30
|
pack(): Promise<PackedProject>;
|
|
31
|
+
private run;
|
|
19
32
|
}
|
|
20
33
|
export type ProjectMode = "production" | "development";
|
|
21
34
|
export interface ProjectOptions {
|
|
22
35
|
directory?: string;
|
|
23
36
|
}
|
|
37
|
+
export interface ProjectRunOptions {
|
|
38
|
+
options?: Record<string, string>;
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}
|
|
41
|
+
export type ProjectRunResult = Readonly<{
|
|
42
|
+
process: SystemProcessEntity;
|
|
43
|
+
exit: Exit;
|
|
44
|
+
}>;
|
|
24
45
|
export type PackedProject = Readonly<{
|
|
25
46
|
archive: string;
|
|
26
47
|
archivePath: string;
|
package/dist/project.js
CHANGED
|
@@ -6,6 +6,7 @@ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
7
7
|
import { delimiter, isAbsolute, join, normalize, resolve, sep } from "node:path";
|
|
8
8
|
import { createJiti } from "jiti";
|
|
9
|
+
import { assertAvailable, commandFailure, DevelopmentClient, waitForDevelopmentClient } from "./client-development.js";
|
|
9
10
|
const configFile = "phresh.config.ts";
|
|
10
11
|
/** One loaded Program authoring project rooted at an absolute directory. */
|
|
11
12
|
export class Project {
|
|
@@ -101,6 +102,31 @@ export class Project {
|
|
|
101
102
|
});
|
|
102
103
|
});
|
|
103
104
|
}
|
|
105
|
+
/** Build and run this project's production Program until its Process exits. */
|
|
106
|
+
async start(system, options = {}) {
|
|
107
|
+
await this.build();
|
|
108
|
+
return await this.run(system, "production", options);
|
|
109
|
+
}
|
|
110
|
+
/** Run this project's development Program and its optional Client server. */
|
|
111
|
+
async dev(system, options = {}) {
|
|
112
|
+
return await this.run(system, "development", options);
|
|
113
|
+
}
|
|
114
|
+
/** Build and install this project's production Program. */
|
|
115
|
+
async install(system) {
|
|
116
|
+
await this.build();
|
|
117
|
+
const program = await system.forceCreateProgram(this.description("production"));
|
|
118
|
+
let installed = false;
|
|
119
|
+
try {
|
|
120
|
+
for await (const chunk of program.install())
|
|
121
|
+
write(chunk.stream, chunk.text);
|
|
122
|
+
installed = true;
|
|
123
|
+
return program;
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
if (!installed)
|
|
127
|
+
await forgetCurrent(program);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
104
130
|
/** Build and package this Program into its canonical release shape. */
|
|
105
131
|
async pack() {
|
|
106
132
|
await this.build();
|
|
@@ -131,6 +157,88 @@ export class Project {
|
|
|
131
157
|
writeFileSync(checksumPath, `${digest} ${archive}\n`);
|
|
132
158
|
return Object.freeze({ archive, archivePath, checksumPath, declarationPath, digest });
|
|
133
159
|
}
|
|
160
|
+
async run(system, mode, options) {
|
|
161
|
+
const description = this.description(mode);
|
|
162
|
+
const development = mode === "development" && description.client && (description.client.start ?? true)
|
|
163
|
+
? this.config.client?.development
|
|
164
|
+
: undefined;
|
|
165
|
+
const command = development?.startCommand;
|
|
166
|
+
if (command)
|
|
167
|
+
await assertAvailable(development.url);
|
|
168
|
+
const client = command ? new DevelopmentClient(command, this.directory) : undefined;
|
|
169
|
+
const controller = new AbortController();
|
|
170
|
+
const signal = options.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
|
|
171
|
+
let program = null;
|
|
172
|
+
try {
|
|
173
|
+
if (development) {
|
|
174
|
+
for await (const event of waitForDevelopmentClient(development, client, signal))
|
|
175
|
+
presentDevelopment(event);
|
|
176
|
+
}
|
|
177
|
+
program = await system.forceCreateProgram(description);
|
|
178
|
+
const run = program.process.run({ options: options.options ?? {} }, { signal });
|
|
179
|
+
const iterator = run[Symbol.asyncIterator]();
|
|
180
|
+
let lifecycle = iterator.next();
|
|
181
|
+
let developmentExit = client?.exited();
|
|
182
|
+
let developmentOutput = client?.outputAvailable();
|
|
183
|
+
let process = null;
|
|
184
|
+
let ending = null;
|
|
185
|
+
while (true) {
|
|
186
|
+
for (const event of client?.drain() ?? [])
|
|
187
|
+
presentDevelopment(event);
|
|
188
|
+
const outcome = await Promise.race([
|
|
189
|
+
lifecycle.then(result => ({ source: "system", result })),
|
|
190
|
+
...(developmentExit ? [developmentExit.then(result => ({ source: "client", result }))] : []),
|
|
191
|
+
...(developmentOutput ? [developmentOutput.then(() => ({ source: "output" }))] : [])
|
|
192
|
+
]);
|
|
193
|
+
if (outcome.source === "output") {
|
|
194
|
+
developmentOutput = client?.outputAvailable();
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (outcome.source === "client") {
|
|
198
|
+
developmentExit = undefined;
|
|
199
|
+
if (!client?.endingWasRequested())
|
|
200
|
+
throw commandFailure(outcome.result);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (outcome.result.done)
|
|
204
|
+
break;
|
|
205
|
+
const event = outcome.result.value;
|
|
206
|
+
if (event.event === "started")
|
|
207
|
+
process = event.process;
|
|
208
|
+
else if (event.event === "output")
|
|
209
|
+
write(event.stream, event.text);
|
|
210
|
+
else
|
|
211
|
+
ending = event.exit;
|
|
212
|
+
lifecycle = iterator.next();
|
|
213
|
+
}
|
|
214
|
+
if (!process || !ending)
|
|
215
|
+
throw new Error("The System ended the Program run without a complete Process lifecycle");
|
|
216
|
+
return Object.freeze({ process, exit: ending });
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
controller.abort(new Error("The Project run ended"));
|
|
220
|
+
await client?.stop();
|
|
221
|
+
if (program)
|
|
222
|
+
await forgetCurrent(program);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function presentDevelopment(event) {
|
|
227
|
+
if (event.event === "output")
|
|
228
|
+
write(event.stream === "err" ? "stderr" : "stdout", String(event.text ?? ""));
|
|
229
|
+
}
|
|
230
|
+
function write(stream, text) {
|
|
231
|
+
(stream === "stderr" ? process.stderr : process.stdout).write(text);
|
|
232
|
+
}
|
|
233
|
+
async function forgetCurrent(program) {
|
|
234
|
+
try {
|
|
235
|
+
await program.forget();
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
if (error instanceof Error && error.message === "The Program represented by this handle does not exist")
|
|
239
|
+
return;
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
134
242
|
}
|
|
135
243
|
function serverHalf(half, mode) {
|
|
136
244
|
if (!half)
|
package/dist/system.d.ts
CHANGED
|
@@ -1,9 +1,70 @@
|
|
|
1
|
-
import { type System } from "@phreshos/core";
|
|
2
|
-
import type
|
|
3
|
-
export
|
|
1
|
+
import { ClientServiceHandler as CoreClientServiceHandler, ServerServiceHandler as CoreServerServiceHandler, type ClientServiceChannel, type ProgramDescription, type ProgramCommandChunk, type ServerServiceChannel, type ServiceKey, type System as CoreSystem, type SystemProcessEntity, type SystemProcess, type SystemProgram, type SystemProgramEntity, type SystemUploads, type WritableAppearance } from "@phreshos/core";
|
|
2
|
+
import { type TransportEvent } from "./transport.js";
|
|
3
|
+
export type ProgramProcessRunOptions = Readonly<{
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
}>;
|
|
6
|
+
export type ProgramProcessRunEvent = Readonly<{
|
|
7
|
+
event: "started";
|
|
8
|
+
process: SystemProcessEntity;
|
|
9
|
+
}> | (Readonly<{
|
|
10
|
+
event: "output";
|
|
11
|
+
}> & ProgramCommandChunk) | Readonly<{
|
|
12
|
+
event: "exited";
|
|
13
|
+
process: SystemProcessEntity;
|
|
14
|
+
exit: import("@phreshos/core").Exit;
|
|
15
|
+
}>;
|
|
16
|
+
interface SystemTransport {
|
|
4
17
|
control(request: object, signal?: AbortSignal): Promise<unknown>;
|
|
5
18
|
api(request: object, signal?: AbortSignal): Promise<unknown>;
|
|
6
|
-
lifecycle(request: object, signal?: AbortSignal): AsyncGenerator<
|
|
19
|
+
lifecycle(request: object, signal?: AbortSignal): AsyncGenerator<TransportEvent, void, void>;
|
|
7
20
|
}
|
|
8
|
-
/**
|
|
9
|
-
export declare
|
|
21
|
+
/** One connected owner-local implementation of the shared System contract. */
|
|
22
|
+
export declare class System implements CoreSystem {
|
|
23
|
+
private readonly connection;
|
|
24
|
+
readonly home: string;
|
|
25
|
+
readonly address: string;
|
|
26
|
+
readonly storage: import("@phreshos/core").Storage;
|
|
27
|
+
readonly appearance: WritableAppearance;
|
|
28
|
+
readonly program: SystemProgram;
|
|
29
|
+
readonly process: SystemProcess;
|
|
30
|
+
readonly uploads: SystemUploads;
|
|
31
|
+
readonly transport: SystemTransport;
|
|
32
|
+
private closed;
|
|
33
|
+
private readonly lifetime;
|
|
34
|
+
private constructor();
|
|
35
|
+
/** Connect to the System selected by argument, environment, or owner default. */
|
|
36
|
+
static connect(home?: string): Promise<System>;
|
|
37
|
+
/** Atomically replace one runtime Program without touching its installed form. */
|
|
38
|
+
forceCreateProgram(source: ProgramDescription | string): Promise<SystemProgramEntity>;
|
|
39
|
+
/** Close this owner connection and abort every attached operation it owns. */
|
|
40
|
+
disconnect(): Promise<void>;
|
|
41
|
+
service<EventsMap extends object = {}>(key: ServiceKey & {
|
|
42
|
+
endpoint: "server";
|
|
43
|
+
}): ServerService<EventsMap>;
|
|
44
|
+
service<EventsMap extends object = {}>(key: ServiceKey & {
|
|
45
|
+
endpoint: "client";
|
|
46
|
+
}): ClientService<EventsMap>;
|
|
47
|
+
private signal;
|
|
48
|
+
private requireConnected;
|
|
49
|
+
}
|
|
50
|
+
declare class ServerService<EventsMap extends object = {}> extends CoreServerServiceHandler<EventsMap> {
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly channel: ServerServiceChannel<EventsMap>;
|
|
53
|
+
private readonly base;
|
|
54
|
+
constructor(system: System, key: ServiceKey & {
|
|
55
|
+
endpoint: "server";
|
|
56
|
+
});
|
|
57
|
+
enabled(): Promise<boolean>;
|
|
58
|
+
waitReady(timeout?: number): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
declare class ClientService<EventsMap extends object = {}> extends CoreClientServiceHandler<EventsMap> {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly channel: ClientServiceChannel<EventsMap>;
|
|
63
|
+
private readonly base;
|
|
64
|
+
constructor(system: System, key: ServiceKey & {
|
|
65
|
+
endpoint: "client";
|
|
66
|
+
});
|
|
67
|
+
enabled(): Promise<boolean>;
|
|
68
|
+
waitReady(timeout?: number): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
package/dist/system.js
CHANGED
|
@@ -1,33 +1,77 @@
|
|
|
1
1
|
import { ClientServiceHandler as CoreClientServiceHandler, ServerServiceHandler as CoreServerServiceHandler } from "@phreshos/core";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
+
import { gatewayAddress } from "./address.js";
|
|
3
4
|
import Events from "./events.js";
|
|
5
|
+
import { resolveHome } from "./home.js";
|
|
4
6
|
import { filesystemStorage } from "./storage.js";
|
|
7
|
+
import { openConnection, request, streamProgram } from "./transport.js";
|
|
5
8
|
import Uploads from "./uploads.js";
|
|
6
|
-
/**
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
transport;
|
|
9
|
+
/** One connected owner-local implementation of the shared System contract. */
|
|
10
|
+
export class System {
|
|
11
|
+
connection;
|
|
12
|
+
home;
|
|
13
|
+
address;
|
|
12
14
|
storage = filesystemStorage(homedir(), "the native home directory");
|
|
13
15
|
appearance;
|
|
14
16
|
program;
|
|
15
17
|
process;
|
|
16
18
|
uploads;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
transport;
|
|
20
|
+
closed = false;
|
|
21
|
+
lifetime = new AbortController();
|
|
22
|
+
constructor(home, address, connection) {
|
|
23
|
+
this.connection = connection;
|
|
24
|
+
this.home = home;
|
|
25
|
+
this.address = address;
|
|
26
|
+
this.transport = {
|
|
27
|
+
control: (value, signal) => request(address, "system", value, this.signal(signal)),
|
|
28
|
+
api: (value, signal) => request(address, "api", value, this.signal(signal)),
|
|
29
|
+
lifecycle: (value, signal) => streamProgram(address, value, this.signal(signal))
|
|
30
|
+
};
|
|
31
|
+
this.appearance = new SystemAppearance(this.transport);
|
|
20
32
|
this.program = new ProgramRegistry(this);
|
|
21
33
|
this.process = new ProcessRegistry(this);
|
|
22
|
-
this.uploads = new Uploads(
|
|
34
|
+
this.uploads = new Uploads(value => this.transport.api(value));
|
|
35
|
+
}
|
|
36
|
+
/** Connect to the System selected by argument, environment, or owner default. */
|
|
37
|
+
static async connect(home) {
|
|
38
|
+
const resolved = resolveHome(home);
|
|
39
|
+
const address = gatewayAddress(resolved);
|
|
40
|
+
return new System(resolved, address, await openConnection(address));
|
|
41
|
+
}
|
|
42
|
+
/** Atomically replace one runtime Program without touching its installed form. */
|
|
43
|
+
async forceCreateProgram(source) {
|
|
44
|
+
this.requireConnected();
|
|
45
|
+
for await (const event of this.transport.lifecycle({ word: "force-create", program: source })) {
|
|
46
|
+
if (event.event === "created")
|
|
47
|
+
return new ProgramHandle(this, required(event.program));
|
|
48
|
+
}
|
|
49
|
+
throw new Error("The System did not confirm the created Program");
|
|
50
|
+
}
|
|
51
|
+
/** Close this owner connection and abort every attached operation it owns. */
|
|
52
|
+
async disconnect() {
|
|
53
|
+
if (this.closed)
|
|
54
|
+
return;
|
|
55
|
+
this.closed = true;
|
|
56
|
+
this.lifetime.abort(new Error("This System connection is closed"));
|
|
57
|
+
this.connection.destroy();
|
|
23
58
|
}
|
|
24
59
|
service(key) {
|
|
60
|
+
this.requireConnected();
|
|
25
61
|
return key.endpoint === "server"
|
|
26
62
|
? new ServerService(this, key)
|
|
27
63
|
: new ClientService(this, key);
|
|
28
64
|
}
|
|
65
|
+
signal(signal) {
|
|
66
|
+
this.requireConnected();
|
|
67
|
+
return signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal;
|
|
68
|
+
}
|
|
69
|
+
requireConnected() {
|
|
70
|
+
if (this.closed)
|
|
71
|
+
throw new Error("This System connection is closed");
|
|
72
|
+
}
|
|
29
73
|
}
|
|
30
|
-
class
|
|
74
|
+
class SystemAppearance extends Events {
|
|
31
75
|
transport;
|
|
32
76
|
constructor(transport) {
|
|
33
77
|
super(["change"], (_event, signal) => transport.api({ capability: "appearance", operation: "wait" }, signal));
|
|
@@ -74,17 +118,11 @@ class ProgramRegistry extends Events {
|
|
|
74
118
|
}
|
|
75
119
|
}
|
|
76
120
|
async create(source) {
|
|
77
|
-
let identity = null;
|
|
78
121
|
for await (const event of this.system.transport.lifecycle({ word: "create", program: source })) {
|
|
79
122
|
if (event.event === "created")
|
|
80
|
-
|
|
123
|
+
return new ProgramHandle(this.system, required(event.program));
|
|
81
124
|
}
|
|
82
|
-
|
|
83
|
-
throw new Error("The System did not confirm the created Program");
|
|
84
|
-
const program = await this.find(identity);
|
|
85
|
-
if (!program)
|
|
86
|
-
throw new Error("The created Program cannot be found");
|
|
87
|
-
return program;
|
|
125
|
+
throw new Error("The System did not confirm the created Program");
|
|
88
126
|
}
|
|
89
127
|
async event(value) {
|
|
90
128
|
const waited = value;
|
|
@@ -97,6 +135,7 @@ class ProgramRegistry extends Events {
|
|
|
97
135
|
}
|
|
98
136
|
class ProgramHandle extends Events {
|
|
99
137
|
system;
|
|
138
|
+
reference;
|
|
100
139
|
identity;
|
|
101
140
|
name;
|
|
102
141
|
version;
|
|
@@ -105,11 +144,13 @@ class ProgramHandle extends Events {
|
|
|
105
144
|
server;
|
|
106
145
|
client;
|
|
107
146
|
process;
|
|
147
|
+
startup;
|
|
108
148
|
constructor(system, snapshot) {
|
|
109
149
|
super(["forget", "uninstall"], (event, signal, timeout) => system.transport.control({
|
|
110
150
|
capability: "program", operation: "wait", input: { program: snapshot.identity, event, timeout }
|
|
111
151
|
}, signal).then(value => value.payload));
|
|
112
152
|
this.system = system;
|
|
153
|
+
this.reference = snapshot.reference;
|
|
113
154
|
this.identity = snapshot.identity;
|
|
114
155
|
this.name = snapshot.name;
|
|
115
156
|
this.version = snapshot.version;
|
|
@@ -125,6 +166,7 @@ class ProgramHandle extends Events {
|
|
|
125
166
|
minimize: snapshot.client.minimize
|
|
126
167
|
}) : null;
|
|
127
168
|
this.process = new ProgramProcesses(system, this);
|
|
169
|
+
this.startup = new ProgramStartup(system, this);
|
|
128
170
|
}
|
|
129
171
|
async agent() {
|
|
130
172
|
if (!this.hasAgent)
|
|
@@ -133,15 +175,49 @@ class ProgramHandle extends Events {
|
|
|
133
175
|
return typeof value.content === "string" ? value.content : null;
|
|
134
176
|
}
|
|
135
177
|
async installed() {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
178
|
+
for await (const event of this.system.transport.lifecycle({ word: "installed", handle: this.address() })) {
|
|
179
|
+
if (event.event === "installedState")
|
|
180
|
+
return event.installed === true;
|
|
181
|
+
}
|
|
182
|
+
throw new Error("The System returned no Program installation state");
|
|
140
183
|
}
|
|
141
|
-
install() { return command(this.system, { word: "install-existing",
|
|
142
|
-
uninstall(everything = false) { return command(this.system, { word: "uninstall-existing",
|
|
184
|
+
install() { return command(this.system, { word: "install-existing", handle: this.address() }); }
|
|
185
|
+
uninstall(everything = false) { return command(this.system, { word: "uninstall-existing", handle: this.address(), everything }); }
|
|
143
186
|
async forget() {
|
|
144
|
-
for await (const _event of this.system.transport.lifecycle({ word: "forget",
|
|
187
|
+
for await (const _event of this.system.transport.lifecycle({ word: "forget", handle: this.address() })) { /* consume completion */ }
|
|
188
|
+
}
|
|
189
|
+
address() { return Object.freeze({ identity: this.identity, reference: this.reference }); }
|
|
190
|
+
}
|
|
191
|
+
class ProgramStartup {
|
|
192
|
+
system;
|
|
193
|
+
program;
|
|
194
|
+
constructor(system, program) {
|
|
195
|
+
this.system = system;
|
|
196
|
+
this.program = program;
|
|
197
|
+
}
|
|
198
|
+
async get() {
|
|
199
|
+
for await (const event of this.system.transport.lifecycle({
|
|
200
|
+
word: "startup", handle: this.program.address(), operation: "get"
|
|
201
|
+
})) {
|
|
202
|
+
if (event.event === "startup")
|
|
203
|
+
return event.launch;
|
|
204
|
+
}
|
|
205
|
+
throw new Error("The System returned no Program startup state");
|
|
206
|
+
}
|
|
207
|
+
async enable(launch = {}) {
|
|
208
|
+
await this.change("enable", launch);
|
|
209
|
+
}
|
|
210
|
+
async disable() {
|
|
211
|
+
await this.change("disable");
|
|
212
|
+
}
|
|
213
|
+
async change(operation, launch) {
|
|
214
|
+
for await (const event of this.system.transport.lifecycle({
|
|
215
|
+
word: "startup", handle: this.program.address(), operation, launch
|
|
216
|
+
})) {
|
|
217
|
+
if (event.event === "startup")
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
throw new Error("The System did not confirm the Program startup change");
|
|
145
221
|
}
|
|
146
222
|
}
|
|
147
223
|
class ProgramProcesses extends Events {
|
|
@@ -161,13 +237,62 @@ class ProgramProcesses extends Events {
|
|
|
161
237
|
const found = (await this.list()).find(process => process.identity === identityOrName || process.name === identityOrName);
|
|
162
238
|
return found ?? null;
|
|
163
239
|
}
|
|
164
|
-
create(launch = {}) { return
|
|
165
|
-
|
|
240
|
+
create(launch = {}) { return this.createExact("create-process", launch); }
|
|
241
|
+
async *run(launch = {}, options = {}) {
|
|
242
|
+
let process = null;
|
|
243
|
+
for await (const event of this.system.transport.lifecycle({
|
|
244
|
+
word: "run-process",
|
|
245
|
+
handle: this.program.address(),
|
|
246
|
+
launch
|
|
247
|
+
}, options.signal)) {
|
|
248
|
+
if (event.event === "started") {
|
|
249
|
+
process = new ProcessHandle(this.system, required(event.process));
|
|
250
|
+
yield Object.freeze({ event: "started", process });
|
|
251
|
+
}
|
|
252
|
+
else if (event.event === "output") {
|
|
253
|
+
if ((event.stream !== "stdout" && event.stream !== "stderr") || typeof event.text !== "string") {
|
|
254
|
+
throw new Error("The System returned an invalid Process output event");
|
|
255
|
+
}
|
|
256
|
+
yield Object.freeze({
|
|
257
|
+
event: "output",
|
|
258
|
+
stream: event.stream,
|
|
259
|
+
text: event.text
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
else if (event.event === "exited") {
|
|
263
|
+
if (!process)
|
|
264
|
+
throw new Error("The System ended a Process before confirming its start");
|
|
265
|
+
const value = event.exit;
|
|
266
|
+
if (!value
|
|
267
|
+
|| (value.status !== "exited" && value.status !== "signaled")
|
|
268
|
+
|| (value.code !== null && typeof value.code !== "number")
|
|
269
|
+
|| (value.signal !== null && typeof value.signal !== "string")) {
|
|
270
|
+
throw new Error("The System returned an invalid Process exit event");
|
|
271
|
+
}
|
|
272
|
+
const exit = Object.freeze({
|
|
273
|
+
status: value.status,
|
|
274
|
+
code: value.code,
|
|
275
|
+
signal: value.signal
|
|
276
|
+
});
|
|
277
|
+
yield Object.freeze({ event: "exited", process, exit });
|
|
278
|
+
}
|
|
279
|
+
else
|
|
280
|
+
throw new Error("The System returned an unknown Process run event");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
findOrCreate(launch) { return this.createExact("find-or-create-process", launch); }
|
|
166
284
|
async exitAll() {
|
|
167
285
|
const processes = await this.list();
|
|
168
286
|
await Promise.all(processes.map(process => process.exit()));
|
|
169
287
|
return processes.map(process => process.identity);
|
|
170
288
|
}
|
|
289
|
+
async createExact(word, launch) {
|
|
290
|
+
for await (const event of this.system.transport.lifecycle({ word, handle: this.program.address(), launch })) {
|
|
291
|
+
if (event.event === "createdProcess")
|
|
292
|
+
return new ProcessHandle(this.system, required(event.process));
|
|
293
|
+
}
|
|
294
|
+
throw new Error("The System did not confirm the created Process");
|
|
295
|
+
}
|
|
171
296
|
}
|
|
172
297
|
class ProcessRegistry extends Events {
|
|
173
298
|
system;
|
|
@@ -280,14 +405,14 @@ class ClientEndpoint extends EndpointHandle {
|
|
|
280
405
|
window;
|
|
281
406
|
constructor(system, owner) {
|
|
282
407
|
super(system, owner, "client");
|
|
283
|
-
this.window = new
|
|
408
|
+
this.window = new SystemWindow(system, owner);
|
|
284
409
|
}
|
|
285
410
|
async start(overrides) { await this.operation("start", overrides); }
|
|
286
411
|
async service() {
|
|
287
412
|
return await super.service();
|
|
288
413
|
}
|
|
289
414
|
}
|
|
290
|
-
class
|
|
415
|
+
class SystemWindow extends Events {
|
|
291
416
|
system;
|
|
292
417
|
process;
|
|
293
418
|
constructor(system, process) {
|
|
@@ -393,10 +518,6 @@ async function listProcesses(system, program) {
|
|
|
393
518
|
return processes;
|
|
394
519
|
}
|
|
395
520
|
}
|
|
396
|
-
async function createProcess(system, operation, program, launch) {
|
|
397
|
-
const snapshot = await system.transport.control({ capability: "process", operation, input: { program, launch } });
|
|
398
|
-
return new ProcessHandle(system, snapshot);
|
|
399
|
-
}
|
|
400
521
|
async function* command(system, request) {
|
|
401
522
|
for await (const event of system.transport.lifecycle(request)) {
|
|
402
523
|
if (event.event === "output")
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type Socket } from "node:net";
|
|
2
|
-
export interface
|
|
2
|
+
export interface TransportEvent {
|
|
3
3
|
event?: string;
|
|
4
4
|
[key: string]: unknown;
|
|
5
5
|
}
|
|
6
|
-
/** Open and retain one owner-local
|
|
6
|
+
/** Open and retain one owner-local System connection. */
|
|
7
7
|
export declare function openConnection(path: string): Promise<Socket>;
|
|
8
8
|
/** Execute one short authoritative System-control request. */
|
|
9
9
|
export declare function request(path: string, target: "api" | "system", request: unknown, signal?: AbortSignal): Promise<unknown>;
|
|
10
10
|
/** Stream one Program lifecycle operation until the System completes it. */
|
|
11
|
-
export declare function streamProgram(path: string, request: unknown, signal?: AbortSignal): AsyncGenerator<
|
|
11
|
+
export declare function streamProgram(path: string, request: unknown, signal?: AbortSignal): AsyncGenerator<TransportEvent, void, unknown>;
|
package/dist/transport.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { connect } from "node:net";
|
|
2
|
-
/** Open and retain one owner-local
|
|
2
|
+
/** Open and retain one owner-local System connection. */
|
|
3
3
|
export function openConnection(path) {
|
|
4
4
|
return new Promise((resolve, reject) => {
|
|
5
5
|
const socket = connect(path);
|
|
@@ -39,7 +39,7 @@ export function request(path, target, request, signal) {
|
|
|
39
39
|
outcome = JSON.parse(buffer.slice(0, boundary));
|
|
40
40
|
}
|
|
41
41
|
catch {
|
|
42
|
-
return finish(() => reject(new Error("The System returned an invalid
|
|
42
|
+
return finish(() => reject(new Error("The System returned an invalid response")));
|
|
43
43
|
}
|
|
44
44
|
if (outcome.success)
|
|
45
45
|
finish(() => resolve(outcome.result));
|
|
@@ -47,7 +47,7 @@ export function request(path, target, request, signal) {
|
|
|
47
47
|
finish(() => reject(new Error(outcome.error)));
|
|
48
48
|
});
|
|
49
49
|
socket.on("error", () => finish(() => reject(unavailable(path))));
|
|
50
|
-
socket.on("close", () => finish(() => reject(new Error("The System closed the
|
|
50
|
+
socket.on("close", () => finish(() => reject(new Error("The System closed the request without an answer"))));
|
|
51
51
|
if (signal?.aborted)
|
|
52
52
|
cancel();
|
|
53
53
|
});
|
|
@@ -95,19 +95,25 @@ export function streamProgram(path, request, signal) {
|
|
|
95
95
|
wake = null;
|
|
96
96
|
});
|
|
97
97
|
return (async function* () {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
try {
|
|
99
|
+
while (true) {
|
|
100
|
+
if (events.length) {
|
|
101
|
+
yield events.shift();
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (failure)
|
|
105
|
+
throw failure;
|
|
106
|
+
if (ended)
|
|
107
|
+
return;
|
|
108
|
+
await new Promise(resolve => { wake = resolve; });
|
|
102
109
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await new Promise(resolve => { wake = resolve; });
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
signal?.removeEventListener("abort", cancel);
|
|
113
|
+
socket.destroy();
|
|
108
114
|
}
|
|
109
115
|
})();
|
|
110
116
|
}
|
|
111
117
|
function unavailable(path) {
|
|
112
|
-
return new Error(`No System
|
|
118
|
+
return new Error(`No System is listening at ${path} — start PhreshOS first`);
|
|
113
119
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Node.js access to PhreshOS and Program projects.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"prepack": "node --run build"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@phreshos/core": "^0.1.
|
|
35
|
+
"@phreshos/core": "^0.1.22",
|
|
36
36
|
"adm-zip": "^0.6.0",
|
|
37
37
|
"jiti": "^2.7.0"
|
|
38
38
|
},
|
package/dist/gateway.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/gateway.js
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
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
|
-
}
|