pmxtjs 2.25.3 → 2.26.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/dist/esm/index.d.ts +41 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/pmxt/server-manager.d.ts +37 -0
- package/dist/esm/pmxt/server-manager.js +99 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +24 -1
- package/dist/pmxt/server-manager.d.ts +37 -0
- package/dist/pmxt/server-manager.js +99 -0
- package/generated/package.json +1 -1
- package/index.ts +24 -0
- package/package.json +2 -2
- package/pmxt/server-manager.ts +109 -0
package/dist/esm/index.d.ts
CHANGED
|
@@ -28,6 +28,32 @@ export type * from "./pmxt/models.js";
|
|
|
28
28
|
export * from "./pmxt/errors.js";
|
|
29
29
|
declare function stopServer(): Promise<void>;
|
|
30
30
|
declare function restartServer(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Namespaced server management API.
|
|
33
|
+
*
|
|
34
|
+
* Available commands:
|
|
35
|
+
* - status() Structured snapshot of the sidecar (running, pid, port, version, uptime)
|
|
36
|
+
* - health() True if the server responds to /health, false otherwise
|
|
37
|
+
* - start() Idempotently start the sidecar (no-op if already running)
|
|
38
|
+
* - stop() Stop the sidecar and clean up the lock file
|
|
39
|
+
* - restart() Stop and start the sidecar
|
|
40
|
+
* - logs(n) Return the last n log lines from the sidecar log file
|
|
41
|
+
*/
|
|
42
|
+
export declare const server: {
|
|
43
|
+
readonly status: () => Promise<{
|
|
44
|
+
running: boolean;
|
|
45
|
+
pid: number | null;
|
|
46
|
+
port: number | null;
|
|
47
|
+
version: string | null;
|
|
48
|
+
uptimeSeconds: number | null;
|
|
49
|
+
lockFile: string;
|
|
50
|
+
}>;
|
|
51
|
+
readonly health: () => Promise<boolean>;
|
|
52
|
+
readonly start: () => Promise<void>;
|
|
53
|
+
readonly stop: () => Promise<void>;
|
|
54
|
+
readonly restart: () => Promise<void>;
|
|
55
|
+
readonly logs: (n?: number) => string[];
|
|
56
|
+
};
|
|
31
57
|
declare const pmxt: {
|
|
32
58
|
fromServerError(errorData: any): errors.PmxtError;
|
|
33
59
|
PmxtError: typeof errors.PmxtError;
|
|
@@ -58,6 +84,21 @@ declare const pmxt: {
|
|
|
58
84
|
Smarkets: typeof Smarkets;
|
|
59
85
|
PolymarketUS: typeof PolymarketUS;
|
|
60
86
|
ServerManager: typeof ServerManager;
|
|
87
|
+
server: {
|
|
88
|
+
readonly status: () => Promise<{
|
|
89
|
+
running: boolean;
|
|
90
|
+
pid: number | null;
|
|
91
|
+
port: number | null;
|
|
92
|
+
version: string | null;
|
|
93
|
+
uptimeSeconds: number | null;
|
|
94
|
+
lockFile: string;
|
|
95
|
+
}>;
|
|
96
|
+
readonly health: () => Promise<boolean>;
|
|
97
|
+
readonly start: () => Promise<void>;
|
|
98
|
+
readonly stop: () => Promise<void>;
|
|
99
|
+
readonly restart: () => Promise<void>;
|
|
100
|
+
readonly logs: (n?: number) => string[];
|
|
101
|
+
};
|
|
61
102
|
stopServer: typeof stopServer;
|
|
62
103
|
restartServer: typeof restartServer;
|
|
63
104
|
};
|
package/dist/esm/index.js
CHANGED
|
@@ -26,12 +26,34 @@ export { ServerManager } from "./pmxt/server-manager.js";
|
|
|
26
26
|
export { MarketList } from "./pmxt/models.js";
|
|
27
27
|
export * from "./pmxt/errors.js";
|
|
28
28
|
const defaultManager = new ServerManager();
|
|
29
|
+
// Flat aliases for the namespaced server commands. Kept as permanent,
|
|
30
|
+
// fully-supported shorthand — `pmxt.server.stop()` and `pmxt.stopServer()`
|
|
31
|
+
// are equivalent and both are first-class API.
|
|
29
32
|
async function stopServer() {
|
|
30
33
|
await defaultManager.stop();
|
|
31
34
|
}
|
|
32
35
|
async function restartServer() {
|
|
33
36
|
await defaultManager.restart();
|
|
34
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Namespaced server management API.
|
|
40
|
+
*
|
|
41
|
+
* Available commands:
|
|
42
|
+
* - status() Structured snapshot of the sidecar (running, pid, port, version, uptime)
|
|
43
|
+
* - health() True if the server responds to /health, false otherwise
|
|
44
|
+
* - start() Idempotently start the sidecar (no-op if already running)
|
|
45
|
+
* - stop() Stop the sidecar and clean up the lock file
|
|
46
|
+
* - restart() Stop and start the sidecar
|
|
47
|
+
* - logs(n) Return the last n log lines from the sidecar log file
|
|
48
|
+
*/
|
|
49
|
+
export const server = {
|
|
50
|
+
status: () => defaultManager.status(),
|
|
51
|
+
health: () => defaultManager.health(),
|
|
52
|
+
start: () => defaultManager.start(),
|
|
53
|
+
stop: () => defaultManager.stop(),
|
|
54
|
+
restart: () => defaultManager.restart(),
|
|
55
|
+
logs: (n = 50) => defaultManager.logs(n),
|
|
56
|
+
};
|
|
35
57
|
const pmxt = {
|
|
36
58
|
Exchange,
|
|
37
59
|
Polymarket,
|
|
@@ -46,6 +68,7 @@ const pmxt = {
|
|
|
46
68
|
Smarkets,
|
|
47
69
|
PolymarketUS,
|
|
48
70
|
ServerManager,
|
|
71
|
+
server,
|
|
49
72
|
stopServer,
|
|
50
73
|
restartServer,
|
|
51
74
|
...models,
|
|
@@ -15,6 +15,7 @@ export declare class ServerManager {
|
|
|
15
15
|
private api;
|
|
16
16
|
private lockPath;
|
|
17
17
|
private static readonly DEFAULT_PORT;
|
|
18
|
+
private static ensurePromise;
|
|
18
19
|
constructor(options?: ServerManagerOptions);
|
|
19
20
|
/**
|
|
20
21
|
* Read server information from lock file.
|
|
@@ -43,8 +44,13 @@ export declare class ServerManager {
|
|
|
43
44
|
private waitForServer;
|
|
44
45
|
/**
|
|
45
46
|
* Ensure the server is running, starting it if necessary.
|
|
47
|
+
*
|
|
48
|
+
* Concurrent calls across all ServerManager instances in the process
|
|
49
|
+
* are coalesced onto a single in-flight promise. See the comment on
|
|
50
|
+
* `ServerManager.ensurePromise` for why this matters.
|
|
46
51
|
*/
|
|
47
52
|
ensureServerRunning(): Promise<void>;
|
|
53
|
+
private doEnsureServerRunning;
|
|
48
54
|
private isVersionMismatch;
|
|
49
55
|
/**
|
|
50
56
|
* Stop the currently running server.
|
|
@@ -54,5 +60,36 @@ export declare class ServerManager {
|
|
|
54
60
|
* Restart the server.
|
|
55
61
|
*/
|
|
56
62
|
restart(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Start the server if it is not already running.
|
|
65
|
+
*
|
|
66
|
+
* Idempotent: if the server is already running and healthy this returns
|
|
67
|
+
* immediately without restarting.
|
|
68
|
+
*/
|
|
69
|
+
start(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Get a structured snapshot of the sidecar server state.
|
|
72
|
+
*
|
|
73
|
+
* Returns a fresh object on every call (no shared mutable state).
|
|
74
|
+
*/
|
|
75
|
+
status(): Promise<{
|
|
76
|
+
running: boolean;
|
|
77
|
+
pid: number | null;
|
|
78
|
+
port: number | null;
|
|
79
|
+
version: string | null;
|
|
80
|
+
uptimeSeconds: number | null;
|
|
81
|
+
lockFile: string;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Check whether the server's /health endpoint is currently responsive.
|
|
85
|
+
*/
|
|
86
|
+
health(): Promise<boolean>;
|
|
87
|
+
/**
|
|
88
|
+
* Return the last `n` lines from the sidecar server log file.
|
|
89
|
+
*
|
|
90
|
+
* The launcher writes server stdout/stderr to ~/.pmxt/server.log.
|
|
91
|
+
* Returns an empty array if no log file is present.
|
|
92
|
+
*/
|
|
93
|
+
logs(n?: number): string[];
|
|
57
94
|
private killOldServer;
|
|
58
95
|
}
|
|
@@ -14,6 +14,28 @@ export class ServerManager {
|
|
|
14
14
|
api;
|
|
15
15
|
lockPath;
|
|
16
16
|
static DEFAULT_PORT = 3847;
|
|
17
|
+
// Process-wide coalescing of concurrent ensureServerRunning() calls.
|
|
18
|
+
//
|
|
19
|
+
// Each `Exchange` instance constructs its own ServerManager and each one
|
|
20
|
+
// kicks off ensureServerRunning() from its constructor. Without
|
|
21
|
+
// coalescing, N Exchange instances created in parallel all see "no
|
|
22
|
+
// server running", all spawn their own sidecar via pmxt-ensure-server,
|
|
23
|
+
// and the lock file ends up pointing at whichever spawn wrote last. Each
|
|
24
|
+
// Exchange instance has already captured its own basePath at
|
|
25
|
+
// construction time, so most of them end up talking to a sidecar whose
|
|
26
|
+
// access token does NOT match the token they later read from the lock
|
|
27
|
+
// file — every request returns 401 Unauthorized.
|
|
28
|
+
//
|
|
29
|
+
// The fix is process-wide: when a ensureServerRunning call is in
|
|
30
|
+
// flight, all subsequent callers await the same promise. After the
|
|
31
|
+
// in-flight call settles (success OR failure) the cache is cleared so
|
|
32
|
+
// later callers can re-check the sidecar state (e.g. if it was killed
|
|
33
|
+
// by the user between ticks).
|
|
34
|
+
//
|
|
35
|
+
// This is static on purpose — all ServerManager instances in the
|
|
36
|
+
// process share the same sidecar and the same lock file, so they must
|
|
37
|
+
// share the same in-flight promise.
|
|
38
|
+
static ensurePromise = null;
|
|
17
39
|
constructor(options = {}) {
|
|
18
40
|
this.baseUrl = options.baseUrl || `http://localhost:${ServerManager.DEFAULT_PORT}`;
|
|
19
41
|
this.maxRetries = options.maxRetries || 30;
|
|
@@ -101,8 +123,21 @@ export class ServerManager {
|
|
|
101
123
|
}
|
|
102
124
|
/**
|
|
103
125
|
* Ensure the server is running, starting it if necessary.
|
|
126
|
+
*
|
|
127
|
+
* Concurrent calls across all ServerManager instances in the process
|
|
128
|
+
* are coalesced onto a single in-flight promise. See the comment on
|
|
129
|
+
* `ServerManager.ensurePromise` for why this matters.
|
|
104
130
|
*/
|
|
105
131
|
async ensureServerRunning() {
|
|
132
|
+
if (ServerManager.ensurePromise) {
|
|
133
|
+
return ServerManager.ensurePromise;
|
|
134
|
+
}
|
|
135
|
+
ServerManager.ensurePromise = this.doEnsureServerRunning().finally(() => {
|
|
136
|
+
ServerManager.ensurePromise = null;
|
|
137
|
+
});
|
|
138
|
+
return ServerManager.ensurePromise;
|
|
139
|
+
}
|
|
140
|
+
async doEnsureServerRunning() {
|
|
106
141
|
// Check for force restart
|
|
107
142
|
if (process.env.PMXT_ALWAYS_RESTART === '1') {
|
|
108
143
|
await this.killOldServer();
|
|
@@ -201,6 +236,70 @@ export class ServerManager {
|
|
|
201
236
|
await this.stop();
|
|
202
237
|
await this.ensureServerRunning();
|
|
203
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Start the server if it is not already running.
|
|
241
|
+
*
|
|
242
|
+
* Idempotent: if the server is already running and healthy this returns
|
|
243
|
+
* immediately without restarting.
|
|
244
|
+
*/
|
|
245
|
+
async start() {
|
|
246
|
+
await this.ensureServerRunning();
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Get a structured snapshot of the sidecar server state.
|
|
250
|
+
*
|
|
251
|
+
* Returns a fresh object on every call (no shared mutable state).
|
|
252
|
+
*/
|
|
253
|
+
async status() {
|
|
254
|
+
const info = this.getServerInfo();
|
|
255
|
+
const running = await this.isServerRunning();
|
|
256
|
+
let uptimeSeconds = null;
|
|
257
|
+
if (info && typeof info.timestamp === "number") {
|
|
258
|
+
const nowSeconds = Date.now() / 1000;
|
|
259
|
+
const tsSeconds = info.timestamp > 1e12 ? info.timestamp / 1000 : info.timestamp;
|
|
260
|
+
const delta = nowSeconds - tsSeconds;
|
|
261
|
+
if (delta >= 0)
|
|
262
|
+
uptimeSeconds = delta;
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
running,
|
|
266
|
+
pid: info?.pid ?? null,
|
|
267
|
+
port: info?.port ?? null,
|
|
268
|
+
version: info?.version ?? null,
|
|
269
|
+
uptimeSeconds,
|
|
270
|
+
lockFile: this.lockPath,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Check whether the server's /health endpoint is currently responsive.
|
|
275
|
+
*/
|
|
276
|
+
async health() {
|
|
277
|
+
return this.isServerRunning();
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Return the last `n` lines from the sidecar server log file.
|
|
281
|
+
*
|
|
282
|
+
* The launcher writes server stdout/stderr to ~/.pmxt/server.log.
|
|
283
|
+
* Returns an empty array if no log file is present.
|
|
284
|
+
*/
|
|
285
|
+
logs(n = 50) {
|
|
286
|
+
if (n <= 0)
|
|
287
|
+
return [];
|
|
288
|
+
const logPath = join(dirname(this.lockPath), "server.log");
|
|
289
|
+
try {
|
|
290
|
+
if (!existsSync(logPath))
|
|
291
|
+
return [];
|
|
292
|
+
const content = readFileSync(logPath, "utf-8");
|
|
293
|
+
const lines = content.split(/\r?\n/);
|
|
294
|
+
// split on a trailing newline produces an empty final element; drop it
|
|
295
|
+
if (lines.length > 0 && lines[lines.length - 1] === "")
|
|
296
|
+
lines.pop();
|
|
297
|
+
return lines.length > n ? lines.slice(lines.length - n) : lines;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
204
303
|
async killOldServer() {
|
|
205
304
|
const info = this.getServerInfo();
|
|
206
305
|
if (info && info.pid) {
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,32 @@ export type * from "./pmxt/models.js";
|
|
|
28
28
|
export * from "./pmxt/errors.js";
|
|
29
29
|
declare function stopServer(): Promise<void>;
|
|
30
30
|
declare function restartServer(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Namespaced server management API.
|
|
33
|
+
*
|
|
34
|
+
* Available commands:
|
|
35
|
+
* - status() Structured snapshot of the sidecar (running, pid, port, version, uptime)
|
|
36
|
+
* - health() True if the server responds to /health, false otherwise
|
|
37
|
+
* - start() Idempotently start the sidecar (no-op if already running)
|
|
38
|
+
* - stop() Stop the sidecar and clean up the lock file
|
|
39
|
+
* - restart() Stop and start the sidecar
|
|
40
|
+
* - logs(n) Return the last n log lines from the sidecar log file
|
|
41
|
+
*/
|
|
42
|
+
export declare const server: {
|
|
43
|
+
readonly status: () => Promise<{
|
|
44
|
+
running: boolean;
|
|
45
|
+
pid: number | null;
|
|
46
|
+
port: number | null;
|
|
47
|
+
version: string | null;
|
|
48
|
+
uptimeSeconds: number | null;
|
|
49
|
+
lockFile: string;
|
|
50
|
+
}>;
|
|
51
|
+
readonly health: () => Promise<boolean>;
|
|
52
|
+
readonly start: () => Promise<void>;
|
|
53
|
+
readonly stop: () => Promise<void>;
|
|
54
|
+
readonly restart: () => Promise<void>;
|
|
55
|
+
readonly logs: (n?: number) => string[];
|
|
56
|
+
};
|
|
31
57
|
declare const pmxt: {
|
|
32
58
|
fromServerError(errorData: any): errors.PmxtError;
|
|
33
59
|
PmxtError: typeof errors.PmxtError;
|
|
@@ -58,6 +84,21 @@ declare const pmxt: {
|
|
|
58
84
|
Smarkets: typeof Smarkets;
|
|
59
85
|
PolymarketUS: typeof PolymarketUS;
|
|
60
86
|
ServerManager: typeof ServerManager;
|
|
87
|
+
server: {
|
|
88
|
+
readonly status: () => Promise<{
|
|
89
|
+
running: boolean;
|
|
90
|
+
pid: number | null;
|
|
91
|
+
port: number | null;
|
|
92
|
+
version: string | null;
|
|
93
|
+
uptimeSeconds: number | null;
|
|
94
|
+
lockFile: string;
|
|
95
|
+
}>;
|
|
96
|
+
readonly health: () => Promise<boolean>;
|
|
97
|
+
readonly start: () => Promise<void>;
|
|
98
|
+
readonly stop: () => Promise<void>;
|
|
99
|
+
readonly restart: () => Promise<void>;
|
|
100
|
+
readonly logs: (n?: number) => string[];
|
|
101
|
+
};
|
|
61
102
|
stopServer: typeof stopServer;
|
|
62
103
|
restartServer: typeof restartServer;
|
|
63
104
|
};
|
package/dist/index.js
CHANGED
|
@@ -55,7 +55,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
55
55
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
56
56
|
};
|
|
57
57
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
-
exports.MarketList = exports.ServerManager = exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Baozi = exports.Probable = exports.Myriad = exports.Limitless = exports.KalshiDemo = exports.Kalshi = exports.Polymarket = exports.Exchange = void 0;
|
|
58
|
+
exports.server = exports.MarketList = exports.ServerManager = exports.PolymarketUS = exports.Smarkets = exports.Metaculus = exports.Opinion = exports.Baozi = exports.Probable = exports.Myriad = exports.Limitless = exports.KalshiDemo = exports.Kalshi = exports.Polymarket = exports.Exchange = void 0;
|
|
59
59
|
const client_js_1 = require("./pmxt/client.js");
|
|
60
60
|
const server_manager_js_1 = require("./pmxt/server-manager.js");
|
|
61
61
|
const models = __importStar(require("./pmxt/models.js"));
|
|
@@ -79,12 +79,34 @@ var models_js_1 = require("./pmxt/models.js");
|
|
|
79
79
|
Object.defineProperty(exports, "MarketList", { enumerable: true, get: function () { return models_js_1.MarketList; } });
|
|
80
80
|
__exportStar(require("./pmxt/errors.js"), exports);
|
|
81
81
|
const defaultManager = new server_manager_js_1.ServerManager();
|
|
82
|
+
// Flat aliases for the namespaced server commands. Kept as permanent,
|
|
83
|
+
// fully-supported shorthand — `pmxt.server.stop()` and `pmxt.stopServer()`
|
|
84
|
+
// are equivalent and both are first-class API.
|
|
82
85
|
async function stopServer() {
|
|
83
86
|
await defaultManager.stop();
|
|
84
87
|
}
|
|
85
88
|
async function restartServer() {
|
|
86
89
|
await defaultManager.restart();
|
|
87
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Namespaced server management API.
|
|
93
|
+
*
|
|
94
|
+
* Available commands:
|
|
95
|
+
* - status() Structured snapshot of the sidecar (running, pid, port, version, uptime)
|
|
96
|
+
* - health() True if the server responds to /health, false otherwise
|
|
97
|
+
* - start() Idempotently start the sidecar (no-op if already running)
|
|
98
|
+
* - stop() Stop the sidecar and clean up the lock file
|
|
99
|
+
* - restart() Stop and start the sidecar
|
|
100
|
+
* - logs(n) Return the last n log lines from the sidecar log file
|
|
101
|
+
*/
|
|
102
|
+
exports.server = {
|
|
103
|
+
status: () => defaultManager.status(),
|
|
104
|
+
health: () => defaultManager.health(),
|
|
105
|
+
start: () => defaultManager.start(),
|
|
106
|
+
stop: () => defaultManager.stop(),
|
|
107
|
+
restart: () => defaultManager.restart(),
|
|
108
|
+
logs: (n = 50) => defaultManager.logs(n),
|
|
109
|
+
};
|
|
88
110
|
const pmxt = {
|
|
89
111
|
Exchange: client_js_1.Exchange,
|
|
90
112
|
Polymarket: client_js_1.Polymarket,
|
|
@@ -99,6 +121,7 @@ const pmxt = {
|
|
|
99
121
|
Smarkets: client_js_1.Smarkets,
|
|
100
122
|
PolymarketUS: client_js_1.PolymarketUS,
|
|
101
123
|
ServerManager: server_manager_js_1.ServerManager,
|
|
124
|
+
server: exports.server,
|
|
102
125
|
stopServer,
|
|
103
126
|
restartServer,
|
|
104
127
|
...models,
|
|
@@ -15,6 +15,7 @@ export declare class ServerManager {
|
|
|
15
15
|
private api;
|
|
16
16
|
private lockPath;
|
|
17
17
|
private static readonly DEFAULT_PORT;
|
|
18
|
+
private static ensurePromise;
|
|
18
19
|
constructor(options?: ServerManagerOptions);
|
|
19
20
|
/**
|
|
20
21
|
* Read server information from lock file.
|
|
@@ -43,8 +44,13 @@ export declare class ServerManager {
|
|
|
43
44
|
private waitForServer;
|
|
44
45
|
/**
|
|
45
46
|
* Ensure the server is running, starting it if necessary.
|
|
47
|
+
*
|
|
48
|
+
* Concurrent calls across all ServerManager instances in the process
|
|
49
|
+
* are coalesced onto a single in-flight promise. See the comment on
|
|
50
|
+
* `ServerManager.ensurePromise` for why this matters.
|
|
46
51
|
*/
|
|
47
52
|
ensureServerRunning(): Promise<void>;
|
|
53
|
+
private doEnsureServerRunning;
|
|
48
54
|
private isVersionMismatch;
|
|
49
55
|
/**
|
|
50
56
|
* Stop the currently running server.
|
|
@@ -54,5 +60,36 @@ export declare class ServerManager {
|
|
|
54
60
|
* Restart the server.
|
|
55
61
|
*/
|
|
56
62
|
restart(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Start the server if it is not already running.
|
|
65
|
+
*
|
|
66
|
+
* Idempotent: if the server is already running and healthy this returns
|
|
67
|
+
* immediately without restarting.
|
|
68
|
+
*/
|
|
69
|
+
start(): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Get a structured snapshot of the sidecar server state.
|
|
72
|
+
*
|
|
73
|
+
* Returns a fresh object on every call (no shared mutable state).
|
|
74
|
+
*/
|
|
75
|
+
status(): Promise<{
|
|
76
|
+
running: boolean;
|
|
77
|
+
pid: number | null;
|
|
78
|
+
port: number | null;
|
|
79
|
+
version: string | null;
|
|
80
|
+
uptimeSeconds: number | null;
|
|
81
|
+
lockFile: string;
|
|
82
|
+
}>;
|
|
83
|
+
/**
|
|
84
|
+
* Check whether the server's /health endpoint is currently responsive.
|
|
85
|
+
*/
|
|
86
|
+
health(): Promise<boolean>;
|
|
87
|
+
/**
|
|
88
|
+
* Return the last `n` lines from the sidecar server log file.
|
|
89
|
+
*
|
|
90
|
+
* The launcher writes server stdout/stderr to ~/.pmxt/server.log.
|
|
91
|
+
* Returns an empty array if no log file is present.
|
|
92
|
+
*/
|
|
93
|
+
logs(n?: number): string[];
|
|
57
94
|
private killOldServer;
|
|
58
95
|
}
|
|
@@ -50,6 +50,28 @@ class ServerManager {
|
|
|
50
50
|
api;
|
|
51
51
|
lockPath;
|
|
52
52
|
static DEFAULT_PORT = 3847;
|
|
53
|
+
// Process-wide coalescing of concurrent ensureServerRunning() calls.
|
|
54
|
+
//
|
|
55
|
+
// Each `Exchange` instance constructs its own ServerManager and each one
|
|
56
|
+
// kicks off ensureServerRunning() from its constructor. Without
|
|
57
|
+
// coalescing, N Exchange instances created in parallel all see "no
|
|
58
|
+
// server running", all spawn their own sidecar via pmxt-ensure-server,
|
|
59
|
+
// and the lock file ends up pointing at whichever spawn wrote last. Each
|
|
60
|
+
// Exchange instance has already captured its own basePath at
|
|
61
|
+
// construction time, so most of them end up talking to a sidecar whose
|
|
62
|
+
// access token does NOT match the token they later read from the lock
|
|
63
|
+
// file — every request returns 401 Unauthorized.
|
|
64
|
+
//
|
|
65
|
+
// The fix is process-wide: when a ensureServerRunning call is in
|
|
66
|
+
// flight, all subsequent callers await the same promise. After the
|
|
67
|
+
// in-flight call settles (success OR failure) the cache is cleared so
|
|
68
|
+
// later callers can re-check the sidecar state (e.g. if it was killed
|
|
69
|
+
// by the user between ticks).
|
|
70
|
+
//
|
|
71
|
+
// This is static on purpose — all ServerManager instances in the
|
|
72
|
+
// process share the same sidecar and the same lock file, so they must
|
|
73
|
+
// share the same in-flight promise.
|
|
74
|
+
static ensurePromise = null;
|
|
53
75
|
constructor(options = {}) {
|
|
54
76
|
this.baseUrl = options.baseUrl || `http://localhost:${ServerManager.DEFAULT_PORT}`;
|
|
55
77
|
this.maxRetries = options.maxRetries || 30;
|
|
@@ -137,8 +159,21 @@ class ServerManager {
|
|
|
137
159
|
}
|
|
138
160
|
/**
|
|
139
161
|
* Ensure the server is running, starting it if necessary.
|
|
162
|
+
*
|
|
163
|
+
* Concurrent calls across all ServerManager instances in the process
|
|
164
|
+
* are coalesced onto a single in-flight promise. See the comment on
|
|
165
|
+
* `ServerManager.ensurePromise` for why this matters.
|
|
140
166
|
*/
|
|
141
167
|
async ensureServerRunning() {
|
|
168
|
+
if (ServerManager.ensurePromise) {
|
|
169
|
+
return ServerManager.ensurePromise;
|
|
170
|
+
}
|
|
171
|
+
ServerManager.ensurePromise = this.doEnsureServerRunning().finally(() => {
|
|
172
|
+
ServerManager.ensurePromise = null;
|
|
173
|
+
});
|
|
174
|
+
return ServerManager.ensurePromise;
|
|
175
|
+
}
|
|
176
|
+
async doEnsureServerRunning() {
|
|
142
177
|
// Check for force restart
|
|
143
178
|
if (process.env.PMXT_ALWAYS_RESTART === '1') {
|
|
144
179
|
await this.killOldServer();
|
|
@@ -237,6 +272,70 @@ class ServerManager {
|
|
|
237
272
|
await this.stop();
|
|
238
273
|
await this.ensureServerRunning();
|
|
239
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() {
|
|
282
|
+
await this.ensureServerRunning();
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get a structured snapshot of the sidecar server state.
|
|
286
|
+
*
|
|
287
|
+
* Returns a fresh object on every call (no shared mutable state).
|
|
288
|
+
*/
|
|
289
|
+
async status() {
|
|
290
|
+
const info = this.getServerInfo();
|
|
291
|
+
const running = await this.isServerRunning();
|
|
292
|
+
let uptimeSeconds = null;
|
|
293
|
+
if (info && typeof info.timestamp === "number") {
|
|
294
|
+
const nowSeconds = Date.now() / 1000;
|
|
295
|
+
const tsSeconds = info.timestamp > 1e12 ? info.timestamp / 1000 : info.timestamp;
|
|
296
|
+
const delta = nowSeconds - tsSeconds;
|
|
297
|
+
if (delta >= 0)
|
|
298
|
+
uptimeSeconds = delta;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
running,
|
|
302
|
+
pid: info?.pid ?? null,
|
|
303
|
+
port: info?.port ?? null,
|
|
304
|
+
version: info?.version ?? null,
|
|
305
|
+
uptimeSeconds,
|
|
306
|
+
lockFile: this.lockPath,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Check whether the server's /health endpoint is currently responsive.
|
|
311
|
+
*/
|
|
312
|
+
async health() {
|
|
313
|
+
return this.isServerRunning();
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Return the last `n` lines from the sidecar server log file.
|
|
317
|
+
*
|
|
318
|
+
* The launcher writes server stdout/stderr to ~/.pmxt/server.log.
|
|
319
|
+
* Returns an empty array if no log file is present.
|
|
320
|
+
*/
|
|
321
|
+
logs(n = 50) {
|
|
322
|
+
if (n <= 0)
|
|
323
|
+
return [];
|
|
324
|
+
const logPath = (0, path_1.join)((0, path_1.dirname)(this.lockPath), "server.log");
|
|
325
|
+
try {
|
|
326
|
+
if (!(0, fs_1.existsSync)(logPath))
|
|
327
|
+
return [];
|
|
328
|
+
const content = (0, fs_1.readFileSync)(logPath, "utf-8");
|
|
329
|
+
const lines = content.split(/\r?\n/);
|
|
330
|
+
// split on a trailing newline produces an empty final element; drop it
|
|
331
|
+
if (lines.length > 0 && lines[lines.length - 1] === "")
|
|
332
|
+
lines.pop();
|
|
333
|
+
return lines.length > n ? lines.slice(lines.length - n) : lines;
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
}
|
|
240
339
|
async killOldServer() {
|
|
241
340
|
const info = this.getServerInfo();
|
|
242
341
|
if (info && info.pid) {
|
package/generated/package.json
CHANGED
package/index.ts
CHANGED
|
@@ -33,6 +33,9 @@ export * from "./pmxt/errors.js";
|
|
|
33
33
|
|
|
34
34
|
const defaultManager = new ServerManager();
|
|
35
35
|
|
|
36
|
+
// Flat aliases for the namespaced server commands. Kept as permanent,
|
|
37
|
+
// fully-supported shorthand — `pmxt.server.stop()` and `pmxt.stopServer()`
|
|
38
|
+
// are equivalent and both are first-class API.
|
|
36
39
|
async function stopServer(): Promise<void> {
|
|
37
40
|
await defaultManager.stop();
|
|
38
41
|
}
|
|
@@ -41,6 +44,26 @@ async function restartServer(): Promise<void> {
|
|
|
41
44
|
await defaultManager.restart();
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Namespaced server management API.
|
|
49
|
+
*
|
|
50
|
+
* Available commands:
|
|
51
|
+
* - status() Structured snapshot of the sidecar (running, pid, port, version, uptime)
|
|
52
|
+
* - health() True if the server responds to /health, false otherwise
|
|
53
|
+
* - start() Idempotently start the sidecar (no-op if already running)
|
|
54
|
+
* - stop() Stop the sidecar and clean up the lock file
|
|
55
|
+
* - restart() Stop and start the sidecar
|
|
56
|
+
* - logs(n) Return the last n log lines from the sidecar log file
|
|
57
|
+
*/
|
|
58
|
+
export const server = {
|
|
59
|
+
status: () => defaultManager.status(),
|
|
60
|
+
health: () => defaultManager.health(),
|
|
61
|
+
start: () => defaultManager.start(),
|
|
62
|
+
stop: () => defaultManager.stop(),
|
|
63
|
+
restart: () => defaultManager.restart(),
|
|
64
|
+
logs: (n: number = 50) => defaultManager.logs(n),
|
|
65
|
+
} as const;
|
|
66
|
+
|
|
44
67
|
const pmxt = {
|
|
45
68
|
Exchange,
|
|
46
69
|
Polymarket,
|
|
@@ -55,6 +78,7 @@ const pmxt = {
|
|
|
55
78
|
Smarkets,
|
|
56
79
|
PolymarketUS,
|
|
57
80
|
ServerManager,
|
|
81
|
+
server,
|
|
58
82
|
stopServer,
|
|
59
83
|
restartServer,
|
|
60
84
|
...models,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmxtjs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.1",
|
|
4
4
|
"description": "Unified prediction market data API - The ccxt for prediction markets",
|
|
5
5
|
"author": "PMXT Contributors",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"unified"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"pmxt-core": "2.
|
|
46
|
+
"pmxt-core": "2.26.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/jest": "^30.0.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) {
|