@procedures/server 0.11.0 → 0.12.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/CHANGELOG.md +35 -0
- package/README.md +15 -6
- package/dist/.tsbuildinfo +1 -1
- package/dist/host.d.ts +52 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +51 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/run.d.ts +11 -3
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +53 -44
- package/dist/run.js.map +1 -1
- package/package.json +5 -4
- package/src/host.ts +76 -0
- package/src/index.ts +4 -2
- package/src/run.ts +70 -52
package/src/run.ts
CHANGED
|
@@ -26,12 +26,13 @@ import {
|
|
|
26
26
|
type ProcedureModule,
|
|
27
27
|
type ProcedureRegistry,
|
|
28
28
|
} from "@procedures/client";
|
|
29
|
-
import
|
|
29
|
+
import type { RunOptions } from "@mark1russell7/service";
|
|
30
|
+
import { createPeer, peerHostOptions, type Peer, type PeerEndpoint } from "@procedures/peer";
|
|
30
31
|
import peerModule from "@procedures/peer/module";
|
|
31
32
|
import { parseConfig } from "./config.js";
|
|
33
|
+
import { runProcedureHost } from "./host.js";
|
|
32
34
|
import { serverModule } from "./module.js";
|
|
33
35
|
import type { ServerConfig } from "./types.js";
|
|
34
|
-
import { seconds } from "@procedures/client";
|
|
35
36
|
|
|
36
37
|
/** A package name and the subpath after it: "@a/b/register" gives ["@a/b", "./register"]. */
|
|
37
38
|
function splitSpec(spec: string): { name: string; subpath: string } {
|
|
@@ -152,27 +153,42 @@ export interface RunningServer {
|
|
|
152
153
|
refusedUnderRoot?: string[] | undefined;
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
/**
|
|
156
|
+
/**
|
|
157
|
+
* The token of a configuration: `--token`, else `SERVER_TOKEN`, else a random one of 32 bytes, the
|
|
158
|
+
* size of the token of `mark --server` (CLI-8). `--no-token`: none.
|
|
159
|
+
*/
|
|
156
160
|
function tokenOf(config: ServerConfig): { token: string | undefined; generated: boolean } {
|
|
157
161
|
const envToken = process.env["SERVER_TOKEN"] || undefined;
|
|
158
162
|
if (config.token === null) return { token: undefined, generated: false };
|
|
159
163
|
if (config.token !== undefined) return { token: config.token, generated: false };
|
|
160
164
|
if (envToken !== undefined) return { token: envToken, generated: false };
|
|
161
|
-
return { token: randomBytes(
|
|
165
|
+
return { token: randomBytes(32).toString("hex"), generated: true };
|
|
162
166
|
}
|
|
163
167
|
|
|
164
168
|
/** The options of `startServer()` and `runServer()`. */
|
|
165
169
|
export interface ServerRunOptions {
|
|
166
170
|
/** The folders that a `--procedures` package resolves from, first to last (default: the current folder). */
|
|
167
171
|
resolveFrom?: readonly string[] | undefined;
|
|
172
|
+
/** The options of `run()` of `runServer()`: a process port and a clock, for a test (CLI-8). */
|
|
173
|
+
run?: RunOptions | undefined;
|
|
168
174
|
}
|
|
169
175
|
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
/** What the server of a configuration serves: its registry, its policy, its token and its root. */
|
|
177
|
+
interface PreparedServer {
|
|
178
|
+
registry: ProcedureRegistry;
|
|
179
|
+
policy: HostPolicy;
|
|
180
|
+
token: string | undefined;
|
|
181
|
+
generated: boolean;
|
|
182
|
+
confined: Confined | undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** A new peer id. */
|
|
186
|
+
function newPeerId(): string {
|
|
187
|
+
return `peer-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** This function builds the registry and the host policy of a configuration. */
|
|
191
|
+
async function prepareServer(config: ServerConfig, log: (line: string) => void, options: ServerRunOptions): Promise<PreparedServer> {
|
|
176
192
|
// The server.* procedures: those of the peer package (server.create, server.call and the
|
|
177
193
|
// others), and server.start, server.stop and server.status of this package
|
|
178
194
|
const modules: ProcedureModule[] = [peerModule, serverModule];
|
|
@@ -205,12 +221,20 @@ export async function startServer(
|
|
|
205
221
|
// bus, which is also the ctx.bus of the procedures.
|
|
206
222
|
events: { topics: config.events ?? [] },
|
|
207
223
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
224
|
+
return { registry, policy, token, generated, confined };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* This function starts the server of a configuration outside a service, as a peer. It returns when
|
|
229
|
+
* the transports listen. `peer.stop()` stops it. The `server` CLI uses `runServer()` instead.
|
|
230
|
+
*/
|
|
231
|
+
export async function startServer(
|
|
232
|
+
config: ServerConfig,
|
|
233
|
+
log: (line: string) => void = console.log,
|
|
234
|
+
options: ServerRunOptions = {}
|
|
235
|
+
): Promise<RunningServer> {
|
|
236
|
+
const { registry, policy, token, generated, confined } = await prepareServer(config, log, options);
|
|
237
|
+
const peer = await createPeer({ id: newPeerId(), transports: config.transports, registry, policy });
|
|
214
238
|
await peer.start();
|
|
215
239
|
return {
|
|
216
240
|
peer,
|
|
@@ -224,8 +248,10 @@ export async function startServer(
|
|
|
224
248
|
}
|
|
225
249
|
|
|
226
250
|
/**
|
|
227
|
-
* The `server` CLI: parse the options, start the server
|
|
228
|
-
*
|
|
251
|
+
* The `server` CLI: parse the options, start the server under `run()` of `@mark1russell7/service`,
|
|
252
|
+
* and print where it listens. `mark serve` calls it with its own arguments. The runner owns the
|
|
253
|
+
* signals, the stop in stages and the exit: this function installs no signal handler and no
|
|
254
|
+
* hard-exit timer (CLI-8). Its promise ends when the lifecycle ends.
|
|
229
255
|
*/
|
|
230
256
|
export async function runServer(argv: string[], options: ServerRunOptions = {}): Promise<void> {
|
|
231
257
|
const config = parseConfig(argv);
|
|
@@ -235,19 +261,33 @@ export async function runServer(argv: string[], options: ServerRunOptions = {}):
|
|
|
235
261
|
console.log(" Transports:", config.transports.map((t) => t.type).join(", "));
|
|
236
262
|
}
|
|
237
263
|
|
|
238
|
-
const
|
|
264
|
+
const { registry, policy, token, generated, confined } = await prepareServer(config, console.log, options);
|
|
265
|
+
const { host, life } = await runProcedureHost({
|
|
266
|
+
name: "procedure-server",
|
|
267
|
+
mode: "foreground",
|
|
268
|
+
host: peerHostOptions({ id: newPeerId(), transports: config.transports, registry, policy }, "procedures"),
|
|
269
|
+
log: console.log,
|
|
270
|
+
...(options.run ? { run: options.run } : {}),
|
|
271
|
+
});
|
|
272
|
+
if (life.state !== "running") {
|
|
273
|
+
// The startup failed: the lifecycle stops the parts that started, and the process exits with 1
|
|
274
|
+
console.error("Server failed to start:");
|
|
275
|
+
for (const error of life.errors) console.error(` ${error}`);
|
|
276
|
+
await life.done;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
239
279
|
|
|
240
280
|
console.log("\nServer started!");
|
|
241
|
-
console.log(` Server ID: ${
|
|
242
|
-
console.log(` Procedures: ${
|
|
281
|
+
console.log(` Server ID: ${host.id}`);
|
|
282
|
+
console.log(` Procedures: ${host.server.procedureCount}`);
|
|
243
283
|
console.log("\nEndpoints:");
|
|
244
|
-
for (const endpoint of
|
|
284
|
+
for (const endpoint of host.info.endpoints()) {
|
|
245
285
|
console.log(` [${endpoint.type}] ${endpoint.address}`);
|
|
246
286
|
}
|
|
247
|
-
if (
|
|
287
|
+
if (token === undefined) {
|
|
248
288
|
console.log("\nNo token: any program on this computer can call the server.");
|
|
249
289
|
} else {
|
|
250
|
-
console.log(`\nToken${
|
|
290
|
+
console.log(`\nToken${generated ? " (random, for this run)" : ""}: ${token}`);
|
|
251
291
|
console.log(" Send it as 'Authorization: Bearer <token>', or as ?token=<token> for WebSocket.");
|
|
252
292
|
}
|
|
253
293
|
if (config.expose.length > 0) {
|
|
@@ -255,34 +295,12 @@ export async function runServer(argv: string[], options: ServerRunOptions = {}):
|
|
|
255
295
|
}
|
|
256
296
|
const events = config.events ?? [];
|
|
257
297
|
console.log(events.length > 0 ? `Event topics (events.subscribe): ${events.join(", ")}` : "Event topics: none (--events adds them)");
|
|
258
|
-
if (
|
|
259
|
-
console.log(`File root: ${
|
|
260
|
-
console.log(` Refused under the root: ${
|
|
298
|
+
if (confined !== undefined) {
|
|
299
|
+
console.log(`File root: ${confined.root} (each procedure refuses a path outside it)`);
|
|
300
|
+
console.log(` Refused under the root: ${confined.refused.length} procedures that the root cannot confine`);
|
|
261
301
|
}
|
|
262
302
|
console.log("\nPress Ctrl+C to stop the server.");
|
|
263
|
-
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
let stopping = false;
|
|
267
|
-
const stop = (): void => {
|
|
268
|
-
if (stopping) process.exit(1);
|
|
269
|
-
stopping = true;
|
|
270
|
-
console.log("\nShutting down...");
|
|
271
|
-
setTimeout(() => process.exit(1), 10_000).unref();
|
|
272
|
-
// The calls in flight have 5 seconds to end (roadmap R14)
|
|
273
|
-
running.peer.stop({ grace: seconds(5) }).then(
|
|
274
|
-
() => process.exit(0),
|
|
275
|
-
(error: unknown) => {
|
|
276
|
-
console.error(`The server did not stop cleanly: ${error instanceof Error ? error.message : String(error)}`);
|
|
277
|
-
process.exit(1);
|
|
278
|
-
},
|
|
279
|
-
);
|
|
280
|
-
};
|
|
281
|
-
process.on("SIGINT", stop);
|
|
282
|
-
process.on("SIGTERM", stop);
|
|
283
|
-
|
|
284
|
-
// Keep the process alive
|
|
285
|
-
await new Promise(() => {
|
|
286
|
-
// Never resolves - keeps process running
|
|
287
|
-
});
|
|
303
|
+
// A signal starts the stop in stages: the stop of the intake, the drain, the close of the
|
|
304
|
+
// listeners and of the storages. A second Ctrl-C exits at once (the profile localDev, D6).
|
|
305
|
+
await life.done;
|
|
288
306
|
}
|