@procedures/server 0.17.2 → 0.17.3
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 +21 -0
- package/README.md +19 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -1
- package/dist/config.js.map +1 -1
- package/dist/host.d.ts +29 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +30 -3
- package/dist/host.js.map +1 -1
- package/dist/procedures/server.start.d.ts.map +1 -1
- package/dist/procedures/server.start.js +2 -1
- package/dist/procedures/server.start.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +10 -1
- package/dist/run.js.map +1 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +3 -1
- package/dist/schemas.js.map +1 -1
- package/dist/types.d.ts +13 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/config.ts +20 -1
- package/src/host.ts +56 -4
- package/src/procedures/server.start.ts +2 -1
- package/src/run.ts +11 -1
- package/src/schemas.ts +5 -1
- package/src/types.ts +13 -1
package/src/host.ts
CHANGED
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
* removes it after the listeners close.
|
|
15
15
|
* - The option `declare` gives the builder of the service to the app before `run()`. The app
|
|
16
16
|
* declares its own parts and dependencies with the vocabulary of the runner (CLI-62).
|
|
17
|
+
* - The probes of the service answer on each listener of the host under `/_probes`, or on a port
|
|
18
|
+
* of their own with the option `probes` (CLI-9, decision D5). The service gives both.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
import { daemon, localDev, service, type Lifecycle, type Profile, type RunOptions, type ServiceBuilder } from "@mark1russell7/service";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
+
import { probeServer } from "@mark1russell7/service/http";
|
|
23
|
+
import { seconds, type Duration } from "@procedures/client";
|
|
24
|
+
import { LOCAL_LISTENER_KNOBS, createProcedureHost, type ProcedureHost, type ProcedureHostOptions } from "@procedures/peer";
|
|
22
25
|
import { lockfileHandle, type LockfileData } from "@procedures/peer/lockfile";
|
|
23
26
|
|
|
24
27
|
/** The mode of a run: in the foreground of a terminal, or as the background host of `mark`. */
|
|
@@ -27,6 +30,22 @@ export type HostRunMode = "foreground" | "background";
|
|
|
27
30
|
/** The profile of each mode, as data: `localDev` in the foreground, and `daemon` in the background. */
|
|
28
31
|
export const HOST_PROFILES: Readonly<Record<HostRunMode, Profile>> = Object.freeze({ foreground: localDev, background: daemon });
|
|
29
32
|
|
|
33
|
+
/**
|
|
34
|
+
* A probe server on a port of its own (CLI-9, decision D5): `probeServer()` of
|
|
35
|
+
* `@mark1russell7/service/http`. It starts first and closes last, thus its probes answer through
|
|
36
|
+
* the whole drain. It also answers `/report`, with the whole verdict of the service.
|
|
37
|
+
*/
|
|
38
|
+
export interface HostProbeServer {
|
|
39
|
+
/** The port of the probes. 0 gives a free port, and `RunningHost.probes()` gives the real one. */
|
|
40
|
+
readonly port: number;
|
|
41
|
+
/** The address. The default is `127.0.0.1`. */
|
|
42
|
+
readonly host?: string | undefined;
|
|
43
|
+
/** The path before each probe path. The default is none: the port has only the probes. */
|
|
44
|
+
readonly prefix?: string | undefined;
|
|
45
|
+
/** The `Retry-After` of each 503 of a probe. The default is the knob `server_retry_after` of `LOCAL_LISTENER_KNOBS`: 1 s. */
|
|
46
|
+
readonly retryAfter?: Duration | undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
30
49
|
/** The configuration of `runProcedureHost()`. */
|
|
31
50
|
export interface ProcedureHostRunConfig {
|
|
32
51
|
/** The name of the service of the process. */
|
|
@@ -42,6 +61,12 @@ export interface ProcedureHostRunConfig {
|
|
|
42
61
|
readonly log?: ((line: string) => void) | undefined;
|
|
43
62
|
/** The options of `run()`: a process port and a clock, for a test. The host gets the same clock. */
|
|
44
63
|
readonly run?: RunOptions | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* The probe server of the process on a port of its own (CLI-9, decision D5). Without it, each
|
|
66
|
+
* listener of the host answers the probes under `/_probes` (the option `probes` of the host).
|
|
67
|
+
* With it, the listeners do not answer them, unless the option `probes` of the host is given.
|
|
68
|
+
*/
|
|
69
|
+
readonly probes?: HostProbeServer | undefined;
|
|
45
70
|
/**
|
|
46
71
|
* This function declares the parts of the app on the service of the process, before `run()`
|
|
47
72
|
* (CLI-62). It gets the builder of the service and the host.
|
|
@@ -66,6 +91,11 @@ export interface RunningHost {
|
|
|
66
91
|
readonly service: ServiceBuilder;
|
|
67
92
|
/** The lifecycle. Its state is `running` after a startup that ended, and `draining` or `exited` after a startup that failed. */
|
|
68
93
|
readonly life: Lifecycle;
|
|
94
|
+
/**
|
|
95
|
+
* The base URL of each place that answers the probes (CLI-9): the probe server, or each listener
|
|
96
|
+
* of the host, for example `http://127.0.0.1:3000/_probes`. Add `/startup`, `/readiness` or `/liveness`.
|
|
97
|
+
*/
|
|
98
|
+
probes(): string[];
|
|
69
99
|
}
|
|
70
100
|
|
|
71
101
|
/**
|
|
@@ -75,9 +105,24 @@ export interface RunningHost {
|
|
|
75
105
|
*/
|
|
76
106
|
export async function runProcedureHost(config: ProcedureHostRunConfig): Promise<RunningHost> {
|
|
77
107
|
const clock = config.run?.clock;
|
|
78
|
-
|
|
108
|
+
// A probe server of its own: the listeners of the host then answer no probes, unless the host options say so (D5)
|
|
109
|
+
const dedicated = config.probes;
|
|
110
|
+
const host = createProcedureHost({
|
|
111
|
+
...config.host,
|
|
112
|
+
...(clock ? { clock } : {}),
|
|
113
|
+
...(dedicated && config.host.probes === undefined ? { probes: false } : {}),
|
|
114
|
+
});
|
|
79
115
|
const profile = config.profile ?? HOST_PROFILES[config.mode];
|
|
80
116
|
const svc = service(config.name).profile(profile);
|
|
117
|
+
const probes = dedicated
|
|
118
|
+
? probeServer({
|
|
119
|
+
port: dedicated.port,
|
|
120
|
+
host: dedicated.host ?? "127.0.0.1",
|
|
121
|
+
...(dedicated.prefix !== undefined ? { prefix: dedicated.prefix } : {}),
|
|
122
|
+
server_retry_after: dedicated.retryAfter ?? LOCAL_LISTENER_KNOBS.server_retry_after,
|
|
123
|
+
})
|
|
124
|
+
: undefined;
|
|
125
|
+
if (probes) svc.healthServer(probes);
|
|
81
126
|
// The profiles of the computer of a developer leave the policies to the service. No check gates them.
|
|
82
127
|
if (!profile.readiness) svc.readiness({ type: "debounce", interval: seconds(5), failAfter: 3, recoverAfter: 1 });
|
|
83
128
|
if (!profile.liveness) svc.liveness({ type: "window", failAfter: seconds(45) });
|
|
@@ -93,5 +138,12 @@ export async function runProcedureHost(config: ProcedureHostRunConfig): Promise<
|
|
|
93
138
|
const log = config.log;
|
|
94
139
|
if (log && profile.phases?.some((phase) => phase.name === "stop intake")) svc.onPhase("stop intake", () => log("\nShutting down..."));
|
|
95
140
|
const life = await svc.run(config.run);
|
|
96
|
-
|
|
141
|
+
const probeBase = (): string[] => {
|
|
142
|
+
const address = probes?.server.address();
|
|
143
|
+
if (typeof address !== "object" || address === null) return [];
|
|
144
|
+
const bound = dedicated?.host ?? "127.0.0.1";
|
|
145
|
+
const shown = bound === "0.0.0.0" || bound === "::" ? "localhost" : bound;
|
|
146
|
+
return [`http://${shown}:${address.port}${dedicated?.prefix ?? ""}`];
|
|
147
|
+
};
|
|
148
|
+
return { host, service: svc, life, probes: () => (probes ? probeBase() : host.info.probes()) };
|
|
97
149
|
}
|
|
@@ -200,7 +200,8 @@ export const serverStartProcedure: Procedure<
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
// Timed out waiting for lockfile, but child may still be alive
|
|
203
|
+
// Timed out waiting for lockfile, but child may still be alive. Deprecated (CLI-9): from the
|
|
204
|
+
// next MINOR of the client group, this result is `success: false`, with the PID of the child.
|
|
204
205
|
if (child.pid && isProcessAlive(child.pid)) {
|
|
205
206
|
return {
|
|
206
207
|
success: true,
|
package/src/run.ts
CHANGED
|
@@ -247,6 +247,11 @@ export async function startServer(
|
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/** The address of the probe server of `--probe-port`: the address of the first network transport, as `--host` gives it. */
|
|
251
|
+
function probeHostOf(config: ServerConfig): string {
|
|
252
|
+
return config.transports.find((transport) => transport.type !== "local")?.host ?? "127.0.0.1";
|
|
253
|
+
}
|
|
254
|
+
|
|
250
255
|
/**
|
|
251
256
|
* The `server` CLI: parse the options, start the server under `run()` of `@mark1russell7/service`,
|
|
252
257
|
* and print where it listens. `mark serve` calls it with its own arguments. The runner owns the
|
|
@@ -262,11 +267,13 @@ export async function runServer(argv: string[], options: ServerRunOptions = {}):
|
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
const { registry, policy, token, generated, confined } = await prepareServer(config, console.log, options);
|
|
265
|
-
const { host, life } = await runProcedureHost({
|
|
270
|
+
const { host, life, probes } = await runProcedureHost({
|
|
266
271
|
name: "procedure-server",
|
|
267
272
|
mode: "foreground",
|
|
268
273
|
host: peerHostOptions({ id: newPeerId(), transports: config.transports, registry, policy }, "procedures"),
|
|
269
274
|
log: console.log,
|
|
275
|
+
// --probe-port: the probes answer on a port of their own, on the address of --host (CLI-9, D5)
|
|
276
|
+
...(config.probePort !== undefined ? { probes: { port: config.probePort, host: probeHostOf(config) } } : {}),
|
|
270
277
|
...(options.run ? { run: options.run } : {}),
|
|
271
278
|
});
|
|
272
279
|
if (life.state !== "running") {
|
|
@@ -284,6 +291,9 @@ export async function runServer(argv: string[], options: ServerRunOptions = {}):
|
|
|
284
291
|
for (const endpoint of host.info.endpoints()) {
|
|
285
292
|
console.log(` [${endpoint.type}] ${endpoint.address}`);
|
|
286
293
|
}
|
|
294
|
+
for (const base of probes()) {
|
|
295
|
+
console.log(` [probes] ${base}/startup, ${base}/readiness, ${base}/liveness (no token)`);
|
|
296
|
+
}
|
|
287
297
|
if (token === undefined) {
|
|
288
298
|
console.log("\nNo token: any program on this computer can call the server.");
|
|
289
299
|
} else {
|
package/src/schemas.ts
CHANGED
|
@@ -43,7 +43,11 @@ export const serverStatusInputSchema: ZodLikeSchema<ServerStatusInput> = zodAdap
|
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
export const ServerStartOutputSchema: z.ZodType<ServerStartOutput> = z.looseObject({
|
|
46
|
-
success: z
|
|
46
|
+
success: z
|
|
47
|
+
.boolean()
|
|
48
|
+
.describe(
|
|
49
|
+
"True when the server started. Deprecated (CLI-9): at the timeout, a server that still starts gives true. From the next MINOR, it gives false."
|
|
50
|
+
),
|
|
47
51
|
pid: z.number().optional().describe("The process id of the server"),
|
|
48
52
|
port: z.number().optional().describe("The port of the server"),
|
|
49
53
|
endpoint: z.string().optional().describe("The URL of the server"),
|
package/src/types.ts
CHANGED
|
@@ -30,6 +30,11 @@ export interface ServerConfig {
|
|
|
30
30
|
events?: string[] | undefined;
|
|
31
31
|
/** The root of the files (`--fs-root`): each procedure keeps its paths in it, or it is refused. Undefined: no root. */
|
|
32
32
|
fsRoot?: string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The port of the probe server (`--probe-port`, CLI-9). Undefined: each listener answers the probes
|
|
35
|
+
* under `/_probes`, and the server has no probe server.
|
|
36
|
+
*/
|
|
37
|
+
probePort?: number | undefined;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
export interface ServerCreateResult {
|
|
@@ -52,7 +57,14 @@ export interface ServerStartInput {
|
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
export interface ServerStartOutput {
|
|
55
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* True when the server started.
|
|
62
|
+
*
|
|
63
|
+
* @deprecated Only the value at the timeout is deprecated, not this field (CLI-9). At the
|
|
64
|
+
* timeout, a server that still starts gives `true` and the message "still loading". From the next
|
|
65
|
+
* MINOR of the client group, a start that is not ready at the timeout gives `success: false`, with
|
|
66
|
+
* the PID of the process that still starts. Read the readiness probe or `mark server status` for the state.
|
|
67
|
+
*/
|
|
56
68
|
success: boolean;
|
|
57
69
|
/** Process ID of server */
|
|
58
70
|
pid?: number | undefined;
|