pmxt-core 2.26.2 → 2.27.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/exchanges/kalshi/api.d.ts +1 -1
- package/dist/exchanges/kalshi/api.js +1 -1
- package/dist/exchanges/limitless/api.d.ts +1 -1
- package/dist/exchanges/limitless/api.js +1 -1
- package/dist/exchanges/myriad/api.d.ts +1 -1
- package/dist/exchanges/myriad/api.js +1 -1
- package/dist/exchanges/opinion/api.d.ts +1 -1
- package/dist/exchanges/opinion/api.js +1 -1
- package/dist/exchanges/polymarket/api-clob.d.ts +1 -1
- package/dist/exchanges/polymarket/api-clob.js +1 -1
- package/dist/exchanges/polymarket/api-data.d.ts +1 -1
- package/dist/exchanges/polymarket/api-data.js +1 -1
- package/dist/exchanges/polymarket/api-gamma.d.ts +1 -1
- package/dist/exchanges/polymarket/api-gamma.js +1 -1
- package/dist/exchanges/probable/api.d.ts +1 -1
- package/dist/exchanges/probable/api.js +1 -1
- package/dist/server/app.d.ts +57 -0
- package/dist/server/app.js +207 -28
- package/dist/server/method-verbs.json +351 -0
- package/dist/server/openapi.yaml +446 -248
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
3
|
-
* Generated at: 2026-04-
|
|
3
|
+
* Generated at: 2026-04-09T08:46:23.692Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const kalshiApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.kalshiApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/kalshi/Kalshi.yaml
|
|
6
|
-
* Generated at: 2026-04-
|
|
6
|
+
* Generated at: 2026-04-09T08:46:23.692Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.kalshiApiSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
3
|
-
* Generated at: 2026-04-
|
|
3
|
+
* Generated at: 2026-04-09T08:46:23.728Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const limitlessApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.limitlessApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/limitless/Limitless.yaml
|
|
6
|
-
* Generated at: 2026-04-
|
|
6
|
+
* Generated at: 2026-04-09T08:46:23.728Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.limitlessApiSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
|
|
3
|
-
* Generated at: 2026-04-
|
|
3
|
+
* Generated at: 2026-04-09T08:46:23.741Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const myriadApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.myriadApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/myriad/myriad.yaml
|
|
6
|
-
* Generated at: 2026-04-
|
|
6
|
+
* Generated at: 2026-04-09T08:46:23.741Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.myriadApiSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
|
|
3
|
-
* Generated at: 2026-04-
|
|
3
|
+
* Generated at: 2026-04-09T08:46:23.746Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const opinionApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.opinionApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/opinion/opinion-openapi.yaml
|
|
6
|
-
* Generated at: 2026-04-
|
|
6
|
+
* Generated at: 2026-04-09T08:46:23.746Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.opinionApiSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
|
|
3
|
-
* Generated at: 2026-04-
|
|
3
|
+
* Generated at: 2026-04-09T08:46:23.698Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketClobSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketClobSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketClobAPI.yaml
|
|
6
|
-
* Generated at: 2026-04-
|
|
6
|
+
* Generated at: 2026-04-09T08:46:23.698Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketClobSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
|
|
3
|
-
* Generated at: 2026-04-
|
|
3
|
+
* Generated at: 2026-04-09T08:46:23.711Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketDataSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketDataSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/Polymarket_Data_API.yaml
|
|
6
|
-
* Generated at: 2026-04-
|
|
6
|
+
* Generated at: 2026-04-09T08:46:23.711Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketDataSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
|
|
3
|
-
* Generated at: 2026-04-
|
|
3
|
+
* Generated at: 2026-04-09T08:46:23.708Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const polymarketGammaSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.polymarketGammaSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/polymarket/PolymarketGammaAPI.yaml
|
|
6
|
-
* Generated at: 2026-04-
|
|
6
|
+
* Generated at: 2026-04-09T08:46:23.708Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.polymarketGammaSpec = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
|
|
3
|
-
* Generated at: 2026-04-
|
|
3
|
+
* Generated at: 2026-04-09T08:46:23.734Z
|
|
4
4
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
5
5
|
*/
|
|
6
6
|
export declare const probableApiSpec: {
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.probableApiSpec = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* Auto-generated from /home/runner/work/pmxt/pmxt/core/specs/probable/probable.yaml
|
|
6
|
-
* Generated at: 2026-04-
|
|
6
|
+
* Generated at: 2026-04-09T08:46:23.734Z
|
|
7
7
|
* Do not edit manually -- run "npm run fetch:openapi" to regenerate.
|
|
8
8
|
*/
|
|
9
9
|
exports.probableApiSpec = {
|
package/dist/server/app.d.ts
CHANGED
|
@@ -1 +1,58 @@
|
|
|
1
|
+
import { Express } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* Options accepted by {@link createApp}.
|
|
4
|
+
*/
|
|
5
|
+
export interface CreateAppOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Access token for the built-in `x-pmxt-access-token` auth middleware.
|
|
8
|
+
*
|
|
9
|
+
* When set, every non-`/health` request must carry a matching token.
|
|
10
|
+
* This is how the local sidecar protects itself from other processes
|
|
11
|
+
* on the same machine.
|
|
12
|
+
*
|
|
13
|
+
* When omitted (or empty string), the built-in token check is
|
|
14
|
+
* disabled. This is the mode hosted-pmxt uses when it mounts the core
|
|
15
|
+
* app under its own Bearer-auth middleware — no point double-checking.
|
|
16
|
+
*/
|
|
17
|
+
accessToken?: string;
|
|
18
|
+
/**
|
|
19
|
+
* If true, skip registering `cors()` and `express.json()`.
|
|
20
|
+
*
|
|
21
|
+
* Useful when the host application has already installed its own body
|
|
22
|
+
* parser / CORS middleware and you just want the route handlers.
|
|
23
|
+
* Defaults to false.
|
|
24
|
+
*/
|
|
25
|
+
skipBaseMiddleware?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build an Express app that serves the PMXT sidecar API surface without
|
|
29
|
+
* binding to a port.
|
|
30
|
+
*
|
|
31
|
+
* This is the mounting point for consumers like hosted-pmxt that want to
|
|
32
|
+
* wrap the sidecar in their own auth / quota / usage middleware and serve
|
|
33
|
+
* it as part of a larger Express application.
|
|
34
|
+
*
|
|
35
|
+
* The returned app registers:
|
|
36
|
+
* - `GET /health`
|
|
37
|
+
* - (optional) the built-in `x-pmxt-access-token` auth check
|
|
38
|
+
* - `POST /api/:exchange/:method`
|
|
39
|
+
* - the error handler
|
|
40
|
+
*
|
|
41
|
+
* Usage:
|
|
42
|
+
* ```ts
|
|
43
|
+
* import express from 'express';
|
|
44
|
+
* import { createApp as createPmxtCoreApp } from 'pmxt-core';
|
|
45
|
+
*
|
|
46
|
+
* const app = express();
|
|
47
|
+
* app.use(myAuthMiddleware);
|
|
48
|
+
* app.use('/', createPmxtCoreApp()); // no token required — we auth upstream
|
|
49
|
+
* app.listen(4000);
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare function createApp(options?: CreateAppOptions): Express;
|
|
53
|
+
/**
|
|
54
|
+
* Start the PMXT sidecar server on the given port with the built-in
|
|
55
|
+
* access-token auth middleware enabled. Returns the underlying
|
|
56
|
+
* {@link http.Server} once it is listening.
|
|
57
|
+
*/
|
|
1
58
|
export declare function startServer(port: number, accessToken: string): Promise<import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>>;
|
package/dist/server/app.js
CHANGED
|
@@ -3,9 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createApp = createApp;
|
|
6
7
|
exports.startServer = startServer;
|
|
7
8
|
const express_1 = __importDefault(require("express"));
|
|
8
9
|
const cors_1 = __importDefault(require("cors"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
9
12
|
const polymarket_1 = require("../exchanges/polymarket");
|
|
10
13
|
const limitless_1 = require("../exchanges/limitless");
|
|
11
14
|
const kalshi_1 = require("../exchanges/kalshi");
|
|
@@ -18,6 +21,113 @@ const metaculus_1 = require("../exchanges/metaculus");
|
|
|
18
21
|
const smarkets_1 = require("../exchanges/smarkets");
|
|
19
22
|
const polymarket_us_1 = require("../exchanges/polymarket_us");
|
|
20
23
|
const errors_1 = require("../errors");
|
|
24
|
+
function loadMethodVerbs() {
|
|
25
|
+
const candidates = [
|
|
26
|
+
path_1.default.join(__dirname, "method-verbs.json"),
|
|
27
|
+
path_1.default.join(__dirname, "../../src/server/method-verbs.json"),
|
|
28
|
+
];
|
|
29
|
+
for (const file of candidates) {
|
|
30
|
+
try {
|
|
31
|
+
if (fs_1.default.existsSync(file)) {
|
|
32
|
+
return JSON.parse(fs_1.default.readFileSync(file, "utf8"));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Fall through to the next candidate.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
console.warn("[pmxt-core] method-verbs.json not found — GET /api/:exchange/:method disabled. " +
|
|
40
|
+
"POST continues to work. Rebuild pmxt-core to regenerate.");
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
const METHOD_VERBS = loadMethodVerbs();
|
|
44
|
+
/**
|
|
45
|
+
* Coerce a query-string value into its closest native type, optionally
|
|
46
|
+
* honoring the arg kind declared in `method-verbs.json`.
|
|
47
|
+
*
|
|
48
|
+
* Express's built-in query parser hands us strings (or arrays of strings)
|
|
49
|
+
* regardless of what the method actually wants. When we know the target
|
|
50
|
+
* kind we respect it exactly:
|
|
51
|
+
* - `string` → never coerce, even if the value looks like a number.
|
|
52
|
+
* (Critical for venue IDs like Polymarket's all-numeric condition
|
|
53
|
+
* IDs, which must stay strings to keep `.trim()` etc. working.)
|
|
54
|
+
* - `number` → parse as float/int; non-numeric input stays a string.
|
|
55
|
+
* - `boolean` → accept the literal `"true"` / `"false"`, else leave as-is.
|
|
56
|
+
*
|
|
57
|
+
* When the kind is unknown (`undefined`, e.g. object-arg properties that
|
|
58
|
+
* aren't statically typed), fall back to the permissive heuristic:
|
|
59
|
+
* lift obvious numeric/boolean literals and leave everything else alone.
|
|
60
|
+
*/
|
|
61
|
+
function coerceQueryValue(raw, kind) {
|
|
62
|
+
if (Array.isArray(raw))
|
|
63
|
+
return raw.map((v) => coerceQueryValue(v, kind));
|
|
64
|
+
if (typeof raw !== "string")
|
|
65
|
+
return raw;
|
|
66
|
+
if (kind === "string")
|
|
67
|
+
return raw;
|
|
68
|
+
if (kind === "number") {
|
|
69
|
+
if (/^-?\d+$/.test(raw))
|
|
70
|
+
return parseInt(raw, 10);
|
|
71
|
+
if (/^-?\d*\.\d+$/.test(raw))
|
|
72
|
+
return parseFloat(raw);
|
|
73
|
+
return raw;
|
|
74
|
+
}
|
|
75
|
+
if (kind === "boolean") {
|
|
76
|
+
if (raw === "true")
|
|
77
|
+
return true;
|
|
78
|
+
if (raw === "false")
|
|
79
|
+
return false;
|
|
80
|
+
return raw;
|
|
81
|
+
}
|
|
82
|
+
// Unknown kind — permissive fallback.
|
|
83
|
+
if (raw === "true")
|
|
84
|
+
return true;
|
|
85
|
+
if (raw === "false")
|
|
86
|
+
return false;
|
|
87
|
+
if (raw === "null")
|
|
88
|
+
return null;
|
|
89
|
+
if (/^-?\d+$/.test(raw))
|
|
90
|
+
return parseInt(raw, 10);
|
|
91
|
+
if (/^-?\d*\.\d+$/.test(raw))
|
|
92
|
+
return parseFloat(raw);
|
|
93
|
+
return raw;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Translate a parsed query-string object into the positional `args`
|
|
97
|
+
* array that `exchange[method](...args)` expects, using the per-method
|
|
98
|
+
* spec extracted from BaseExchange.ts at generation time.
|
|
99
|
+
*
|
|
100
|
+
* Rules:
|
|
101
|
+
* - Primitive args are pulled by name from the query and coerced.
|
|
102
|
+
* - A single object arg swallows every *remaining* query key (after
|
|
103
|
+
* primitive args have been consumed) as its properties.
|
|
104
|
+
* - Trailing `undefined`s are trimmed so optional tail params stay
|
|
105
|
+
* optional instead of arriving as explicit `undefined`.
|
|
106
|
+
*/
|
|
107
|
+
function queryToArgs(query, spec) {
|
|
108
|
+
// Reserve primitive arg names so they don't leak into an object arg.
|
|
109
|
+
const primitiveNames = new Set(spec.filter((s) => s.kind !== "object").map((s) => s.name));
|
|
110
|
+
const args = [];
|
|
111
|
+
for (const arg of spec) {
|
|
112
|
+
if (arg.kind === "object") {
|
|
113
|
+
const obj = {};
|
|
114
|
+
for (const [k, v] of Object.entries(query)) {
|
|
115
|
+
if (primitiveNames.has(k))
|
|
116
|
+
continue;
|
|
117
|
+
obj[k] = coerceQueryValue(v);
|
|
118
|
+
}
|
|
119
|
+
args.push(Object.keys(obj).length > 0 ? obj : undefined);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const raw = query[arg.name];
|
|
123
|
+
args.push(raw !== undefined ? coerceQueryValue(raw, arg.kind) : undefined);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
while (args.length > 0 && args[args.length - 1] === undefined) {
|
|
127
|
+
args.pop();
|
|
128
|
+
}
|
|
129
|
+
return args;
|
|
130
|
+
}
|
|
21
131
|
// Singleton instances for local usage (when no credentials provided)
|
|
22
132
|
const defaultExchanges = {
|
|
23
133
|
polymarket: null,
|
|
@@ -31,37 +141,65 @@ const defaultExchanges = {
|
|
|
31
141
|
metaculus: null,
|
|
32
142
|
smarkets: null,
|
|
33
143
|
};
|
|
34
|
-
|
|
144
|
+
/**
|
|
145
|
+
* Build an Express app that serves the PMXT sidecar API surface without
|
|
146
|
+
* binding to a port.
|
|
147
|
+
*
|
|
148
|
+
* This is the mounting point for consumers like hosted-pmxt that want to
|
|
149
|
+
* wrap the sidecar in their own auth / quota / usage middleware and serve
|
|
150
|
+
* it as part of a larger Express application.
|
|
151
|
+
*
|
|
152
|
+
* The returned app registers:
|
|
153
|
+
* - `GET /health`
|
|
154
|
+
* - (optional) the built-in `x-pmxt-access-token` auth check
|
|
155
|
+
* - `POST /api/:exchange/:method`
|
|
156
|
+
* - the error handler
|
|
157
|
+
*
|
|
158
|
+
* Usage:
|
|
159
|
+
* ```ts
|
|
160
|
+
* import express from 'express';
|
|
161
|
+
* import { createApp as createPmxtCoreApp } from 'pmxt-core';
|
|
162
|
+
*
|
|
163
|
+
* const app = express();
|
|
164
|
+
* app.use(myAuthMiddleware);
|
|
165
|
+
* app.use('/', createPmxtCoreApp()); // no token required — we auth upstream
|
|
166
|
+
* app.listen(4000);
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
function createApp(options = {}) {
|
|
170
|
+
const { accessToken, skipBaseMiddleware = false } = options;
|
|
35
171
|
const app = (0, express_1.default)();
|
|
36
|
-
|
|
37
|
-
|
|
172
|
+
if (!skipBaseMiddleware) {
|
|
173
|
+
app.use((0, cors_1.default)());
|
|
174
|
+
app.use(express_1.default.json({ limit: "2mb" }));
|
|
175
|
+
}
|
|
38
176
|
// Health check (public)
|
|
39
177
|
app.get("/health", (req, res) => {
|
|
40
178
|
res.json({ status: "ok", timestamp: Date.now() });
|
|
41
179
|
});
|
|
42
|
-
//
|
|
43
|
-
app
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
180
|
+
// Optional built-in auth. Only registered when an accessToken is
|
|
181
|
+
// supplied — hosted-pmxt mounts this app without a token and relies on
|
|
182
|
+
// its own upstream Bearer middleware.
|
|
183
|
+
if (accessToken) {
|
|
184
|
+
app.use((req, res, next) => {
|
|
185
|
+
const token = req.headers["x-pmxt-access-token"];
|
|
186
|
+
if (!token || token !== accessToken) {
|
|
187
|
+
res.status(401).json({
|
|
188
|
+
success: false,
|
|
189
|
+
error: "Unauthorized: Invalid or missing access token",
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
next();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// Shared dispatch used by both GET and POST handlers below. Given the
|
|
197
|
+
// method name, the positional args, and optional credentials, it
|
|
198
|
+
// resolves the exchange instance (singleton or per-request) and
|
|
199
|
+
// invokes `exchange[method](...args)`.
|
|
200
|
+
async function dispatchMethod(req, res, next, methodName, args, credentials) {
|
|
57
201
|
try {
|
|
58
202
|
const exchangeName = req.params.exchange.toLowerCase();
|
|
59
|
-
const methodName = req.params.method;
|
|
60
|
-
const args = Array.isArray(req.body.args) ? req.body.args : [];
|
|
61
|
-
const credentials = req.body.credentials;
|
|
62
|
-
// 1. Get or Initialize Exchange
|
|
63
|
-
// If credentials are provided, create a new instance for this request
|
|
64
|
-
// Otherwise, use the singleton instance
|
|
65
203
|
let exchange;
|
|
66
204
|
if (credentials &&
|
|
67
205
|
(credentials.privateKey ||
|
|
@@ -75,15 +213,12 @@ async function startServer(port, accessToken) {
|
|
|
75
213
|
}
|
|
76
214
|
exchange = defaultExchanges[exchangeName];
|
|
77
215
|
}
|
|
78
|
-
// Apply verbose logging if requested via header
|
|
79
216
|
if (req.headers["x-pmxt-verbose"] === "true") {
|
|
80
217
|
exchange.verbose = true;
|
|
81
218
|
}
|
|
82
219
|
else {
|
|
83
|
-
// Reset to false for singleton instances to avoid leaking state between requests
|
|
84
220
|
exchange.verbose = false;
|
|
85
221
|
}
|
|
86
|
-
// 2. Validate Method
|
|
87
222
|
if (typeof exchange[methodName] !== "function") {
|
|
88
223
|
res.status(404).json({
|
|
89
224
|
success: false,
|
|
@@ -91,13 +226,48 @@ async function startServer(port, accessToken) {
|
|
|
91
226
|
});
|
|
92
227
|
return;
|
|
93
228
|
}
|
|
94
|
-
// 3. Execute with direct argument spreading
|
|
95
229
|
const result = await exchange[methodName](...args);
|
|
96
230
|
res.json({ success: true, data: result });
|
|
97
231
|
}
|
|
98
232
|
catch (error) {
|
|
99
233
|
next(error);
|
|
100
234
|
}
|
|
235
|
+
}
|
|
236
|
+
// GET /api/:exchange/:method
|
|
237
|
+
//
|
|
238
|
+
// Enabled for methods classified as idempotent reads by the OpenAPI
|
|
239
|
+
// generator (every method starting with `fetch` whose signature fits
|
|
240
|
+
// in a query string). The method name is looked up in METHOD_VERBS;
|
|
241
|
+
// if it isn't a GET method we return 405 so callers don't silently
|
|
242
|
+
// hit a stale route. POST continues to work for every method,
|
|
243
|
+
// including the ones exposed as GET here, so existing SDK clients
|
|
244
|
+
// that unconditionally POST keep functioning unchanged.
|
|
245
|
+
app.get("/api/:exchange/:method", async (req, res, next) => {
|
|
246
|
+
const methodName = req.params.method;
|
|
247
|
+
const meta = METHOD_VERBS[methodName];
|
|
248
|
+
if (!meta || meta.verb !== "get") {
|
|
249
|
+
res.status(405).json({
|
|
250
|
+
success: false,
|
|
251
|
+
error: `Method '${methodName}' is not available via GET. ` +
|
|
252
|
+
`Use POST /api/:exchange/${methodName} instead.`,
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const args = queryToArgs(req.query, meta.args);
|
|
257
|
+
// GET requests never carry credentials in the body (and query
|
|
258
|
+
// strings would leak them); unauthenticated reads only.
|
|
259
|
+
await dispatchMethod(req, res, next, methodName, args, undefined);
|
|
260
|
+
});
|
|
261
|
+
// POST /api/:exchange/:method
|
|
262
|
+
//
|
|
263
|
+
// The original RPC-shaped surface. Body: { args: any[], credentials? }.
|
|
264
|
+
// Accepts every method, including reads — so pre-existing clients
|
|
265
|
+
// that POST reads keep working forever.
|
|
266
|
+
app.post("/api/:exchange/:method", async (req, res, next) => {
|
|
267
|
+
const methodName = req.params.method;
|
|
268
|
+
const args = Array.isArray(req.body.args) ? req.body.args : [];
|
|
269
|
+
const credentials = req.body.credentials;
|
|
270
|
+
await dispatchMethod(req, res, next, methodName, args, credentials);
|
|
101
271
|
});
|
|
102
272
|
// Error handler
|
|
103
273
|
app.use((error, req, res, next) => {
|
|
@@ -139,6 +309,15 @@ async function startServer(port, accessToken) {
|
|
|
139
309
|
},
|
|
140
310
|
});
|
|
141
311
|
});
|
|
312
|
+
return app;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Start the PMXT sidecar server on the given port with the built-in
|
|
316
|
+
* access-token auth middleware enabled. Returns the underlying
|
|
317
|
+
* {@link http.Server} once it is listening.
|
|
318
|
+
*/
|
|
319
|
+
async function startServer(port, accessToken) {
|
|
320
|
+
const app = createApp({ accessToken });
|
|
142
321
|
return app.listen(port, "127.0.0.1");
|
|
143
322
|
}
|
|
144
323
|
function createExchange(name, credentials) {
|