pmxtjs 2.25.2 → 2.26.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/dist/esm/generated/src/apis/DefaultApi.d.ts +58 -0
- package/dist/esm/generated/src/apis/DefaultApi.js +87 -29
- package/dist/esm/index.d.ts +47 -2
- package/dist/esm/index.js +29 -2
- package/dist/esm/pmxt/client.d.ts +62 -0
- package/dist/esm/pmxt/client.js +70 -0
- package/dist/esm/pmxt/server-manager.d.ts +37 -0
- package/dist/esm/pmxt/server-manager.js +99 -0
- package/dist/generated/src/apis/DefaultApi.d.ts +58 -0
- package/dist/generated/src/apis/DefaultApi.js +87 -29
- package/dist/index.d.ts +47 -2
- package/dist/index.js +32 -1
- package/dist/pmxt/client.d.ts +62 -0
- package/dist/pmxt/client.js +75 -1
- package/dist/pmxt/server-manager.d.ts +37 -0
- package/dist/pmxt/server-manager.js +99 -0
- package/generated/docs/DefaultApi.md +58 -58
- package/generated/package.json +1 -1
- package/generated/src/apis/DefaultApi.ts +87 -29
- package/index.ts +30 -2
- package/package.json +2 -2
- package/pmxt/client.ts +74 -0
- package/pmxt/server-manager.ts +109 -0
package/pmxt/server-manager.ts
CHANGED
|
@@ -31,6 +31,29 @@ export class ServerManager {
|
|
|
31
31
|
private lockPath: string;
|
|
32
32
|
private static readonly DEFAULT_PORT = 3847;
|
|
33
33
|
|
|
34
|
+
// Process-wide coalescing of concurrent ensureServerRunning() calls.
|
|
35
|
+
//
|
|
36
|
+
// Each `Exchange` instance constructs its own ServerManager and each one
|
|
37
|
+
// kicks off ensureServerRunning() from its constructor. Without
|
|
38
|
+
// coalescing, N Exchange instances created in parallel all see "no
|
|
39
|
+
// server running", all spawn their own sidecar via pmxt-ensure-server,
|
|
40
|
+
// and the lock file ends up pointing at whichever spawn wrote last. Each
|
|
41
|
+
// Exchange instance has already captured its own basePath at
|
|
42
|
+
// construction time, so most of them end up talking to a sidecar whose
|
|
43
|
+
// access token does NOT match the token they later read from the lock
|
|
44
|
+
// file — every request returns 401 Unauthorized.
|
|
45
|
+
//
|
|
46
|
+
// The fix is process-wide: when a ensureServerRunning call is in
|
|
47
|
+
// flight, all subsequent callers await the same promise. After the
|
|
48
|
+
// in-flight call settles (success OR failure) the cache is cleared so
|
|
49
|
+
// later callers can re-check the sidecar state (e.g. if it was killed
|
|
50
|
+
// by the user between ticks).
|
|
51
|
+
//
|
|
52
|
+
// This is static on purpose — all ServerManager instances in the
|
|
53
|
+
// process share the same sidecar and the same lock file, so they must
|
|
54
|
+
// share the same in-flight promise.
|
|
55
|
+
private static ensurePromise: Promise<void> | null = null;
|
|
56
|
+
|
|
34
57
|
constructor(options: ServerManagerOptions = {}) {
|
|
35
58
|
this.baseUrl = options.baseUrl || `http://localhost:${ServerManager.DEFAULT_PORT}`;
|
|
36
59
|
this.maxRetries = options.maxRetries || 30;
|
|
@@ -124,8 +147,22 @@ export class ServerManager {
|
|
|
124
147
|
|
|
125
148
|
/**
|
|
126
149
|
* Ensure the server is running, starting it if necessary.
|
|
150
|
+
*
|
|
151
|
+
* Concurrent calls across all ServerManager instances in the process
|
|
152
|
+
* are coalesced onto a single in-flight promise. See the comment on
|
|
153
|
+
* `ServerManager.ensurePromise` for why this matters.
|
|
127
154
|
*/
|
|
128
155
|
async ensureServerRunning(): Promise<void> {
|
|
156
|
+
if (ServerManager.ensurePromise) {
|
|
157
|
+
return ServerManager.ensurePromise;
|
|
158
|
+
}
|
|
159
|
+
ServerManager.ensurePromise = this.doEnsureServerRunning().finally(() => {
|
|
160
|
+
ServerManager.ensurePromise = null;
|
|
161
|
+
});
|
|
162
|
+
return ServerManager.ensurePromise;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async doEnsureServerRunning(): Promise<void> {
|
|
129
166
|
// Check for force restart
|
|
130
167
|
if (process.env.PMXT_ALWAYS_RESTART === '1') {
|
|
131
168
|
await this.killOldServer();
|
|
@@ -235,6 +272,78 @@ export class ServerManager {
|
|
|
235
272
|
await this.ensureServerRunning();
|
|
236
273
|
}
|
|
237
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Start the server if it is not already running.
|
|
277
|
+
*
|
|
278
|
+
* Idempotent: if the server is already running and healthy this returns
|
|
279
|
+
* immediately without restarting.
|
|
280
|
+
*/
|
|
281
|
+
async start(): Promise<void> {
|
|
282
|
+
await this.ensureServerRunning();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Get a structured snapshot of the sidecar server state.
|
|
287
|
+
*
|
|
288
|
+
* Returns a fresh object on every call (no shared mutable state).
|
|
289
|
+
*/
|
|
290
|
+
async status(): Promise<{
|
|
291
|
+
running: boolean;
|
|
292
|
+
pid: number | null;
|
|
293
|
+
port: number | null;
|
|
294
|
+
version: string | null;
|
|
295
|
+
uptimeSeconds: number | null;
|
|
296
|
+
lockFile: string;
|
|
297
|
+
}> {
|
|
298
|
+
const info = this.getServerInfo();
|
|
299
|
+
const running = await this.isServerRunning();
|
|
300
|
+
|
|
301
|
+
let uptimeSeconds: number | null = null;
|
|
302
|
+
if (info && typeof info.timestamp === "number") {
|
|
303
|
+
const nowSeconds = Date.now() / 1000;
|
|
304
|
+
const tsSeconds = info.timestamp > 1e12 ? info.timestamp / 1000 : info.timestamp;
|
|
305
|
+
const delta = nowSeconds - tsSeconds;
|
|
306
|
+
if (delta >= 0) uptimeSeconds = delta;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
running,
|
|
311
|
+
pid: info?.pid ?? null,
|
|
312
|
+
port: info?.port ?? null,
|
|
313
|
+
version: info?.version ?? null,
|
|
314
|
+
uptimeSeconds,
|
|
315
|
+
lockFile: this.lockPath,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Check whether the server's /health endpoint is currently responsive.
|
|
321
|
+
*/
|
|
322
|
+
async health(): Promise<boolean> {
|
|
323
|
+
return this.isServerRunning();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Return the last `n` lines from the sidecar server log file.
|
|
328
|
+
*
|
|
329
|
+
* The launcher writes server stdout/stderr to ~/.pmxt/server.log.
|
|
330
|
+
* Returns an empty array if no log file is present.
|
|
331
|
+
*/
|
|
332
|
+
logs(n: number = 50): string[] {
|
|
333
|
+
if (n <= 0) return [];
|
|
334
|
+
const logPath = join(dirname(this.lockPath), "server.log");
|
|
335
|
+
try {
|
|
336
|
+
if (!existsSync(logPath)) return [];
|
|
337
|
+
const content = readFileSync(logPath, "utf-8");
|
|
338
|
+
const lines = content.split(/\r?\n/);
|
|
339
|
+
// split on a trailing newline produces an empty final element; drop it
|
|
340
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
341
|
+
return lines.length > n ? lines.slice(lines.length - n) : lines;
|
|
342
|
+
} catch {
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
238
347
|
private async killOldServer(): Promise<void> {
|
|
239
348
|
const info = this.getServerInfo();
|
|
240
349
|
if (info && info.pid) {
|