@winstonsayno/mcp-gateway 5.0.0 → 5.2.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 +27 -0
- package/README.md +15 -0
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +2 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/features/conformance.d.ts +51 -0
- package/dist/features/conformance.d.ts.map +1 -0
- package/dist/features/conformance.js +236 -0
- package/dist/features/conformance.js.map +1 -0
- package/dist/features/index.d.ts +10 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +10 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/regions.d.ts +132 -0
- package/dist/features/regions.d.ts.map +1 -0
- package/dist/features/regions.js +268 -0
- package/dist/features/regions.js.map +1 -0
- package/dist/gateway/features.d.ts +51 -0
- package/dist/gateway/features.d.ts.map +1 -0
- package/dist/gateway/features.js +54 -0
- package/dist/gateway/features.js.map +1 -0
- package/dist/gateway/index.d.ts +3 -0
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +27 -0
- package/dist/gateway/index.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/types.d.ts +2 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP conformance suite (5.1): black-box checks of a Streamable HTTP MCP endpoint against the protocol revisions
|
|
3
|
+
* the gateway speaks. Used by `mcp-gateway conformance <url>`, `POST /api/v1/admin/conformance/run` (self-test of
|
|
4
|
+
* this gateway's `/mcp`) and the test suite.
|
|
5
|
+
*
|
|
6
|
+
* Every check is independent and reports `pass` / `fail` / `skip` with a reason; the run never throws.
|
|
7
|
+
*
|
|
8
|
+
* @module features/conformance
|
|
9
|
+
*/
|
|
10
|
+
import { PROTOCOL_VERSIONS } from '../mcp/compat.js';
|
|
11
|
+
import { registerFeature } from '../gateway/features.js';
|
|
12
|
+
async function post(ctx, body, extra = {}) {
|
|
13
|
+
const res = await ctx.f(ctx.url, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: {
|
|
16
|
+
'content-type': 'application/json',
|
|
17
|
+
accept: 'application/json, text/event-stream',
|
|
18
|
+
...ctx.headers,
|
|
19
|
+
...(ctx.session ? { 'mcp-session-id': ctx.session } : {}),
|
|
20
|
+
...(ctx.version ? { 'mcp-protocol-version': ctx.version } : {}),
|
|
21
|
+
...extra,
|
|
22
|
+
},
|
|
23
|
+
body: typeof body === 'string' ? body : JSON.stringify(body),
|
|
24
|
+
});
|
|
25
|
+
const text = await res.text();
|
|
26
|
+
let parsed = undefined;
|
|
27
|
+
if (text) {
|
|
28
|
+
const ct = res.headers.get('content-type') ?? '';
|
|
29
|
+
if (ct.includes('text/event-stream')) {
|
|
30
|
+
const data = text.split('\n').filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim());
|
|
31
|
+
parsed = data.length ? JSON.parse(data[data.length - 1]) : undefined;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(text);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
parsed = text;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { status: res.status, headers: res.headers, body: parsed };
|
|
43
|
+
}
|
|
44
|
+
const init = (version, id = 1) => ({
|
|
45
|
+
jsonrpc: '2.0',
|
|
46
|
+
id,
|
|
47
|
+
method: 'initialize',
|
|
48
|
+
params: { protocolVersion: version, capabilities: {}, clientInfo: { name: 'mcp-gateway-conformance', version: '1' } },
|
|
49
|
+
});
|
|
50
|
+
class Fail extends Error {
|
|
51
|
+
}
|
|
52
|
+
const expect = (cond, msg) => {
|
|
53
|
+
if (!cond)
|
|
54
|
+
throw new Fail(msg);
|
|
55
|
+
};
|
|
56
|
+
export const CHECKS = [
|
|
57
|
+
{
|
|
58
|
+
id: 'initialize',
|
|
59
|
+
title: 'initialize negotiates the latest revision and returns a session id',
|
|
60
|
+
run: async (ctx) => {
|
|
61
|
+
const r = await post(ctx, init(PROTOCOL_VERSIONS[0]));
|
|
62
|
+
expect(r.status === 200, `HTTP ${r.status}`);
|
|
63
|
+
expect(r.body?.result?.protocolVersion, 'no result.protocolVersion');
|
|
64
|
+
expect(r.body.result.serverInfo?.name, 'no serverInfo.name');
|
|
65
|
+
expect(r.body.result.capabilities && typeof r.body.result.capabilities === 'object', 'no capabilities');
|
|
66
|
+
return `protocol ${r.body.result.protocolVersion}`;
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'version-negotiation',
|
|
71
|
+
title: 'every advertised revision is accepted; an unknown revision gets a supported one',
|
|
72
|
+
run: async (ctx) => {
|
|
73
|
+
const accepted = [];
|
|
74
|
+
for (const v of PROTOCOL_VERSIONS) {
|
|
75
|
+
const r = await post({ ...ctx, session: undefined, version: undefined }, init(v));
|
|
76
|
+
if (r.body?.result?.protocolVersion === v)
|
|
77
|
+
accepted.push(v);
|
|
78
|
+
}
|
|
79
|
+
expect(accepted.length > 0, 'no revision accepted');
|
|
80
|
+
const r = await post({ ...ctx, session: undefined, version: undefined }, init('1999-01-01'));
|
|
81
|
+
const got = r.body?.result?.protocolVersion;
|
|
82
|
+
expect(typeof got === 'string' && PROTOCOL_VERSIONS.includes(got), `unknown revision answered with ${JSON.stringify(got)}`);
|
|
83
|
+
return `accepted ${accepted.join(', ')}`;
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'parse-error',
|
|
88
|
+
title: 'malformed JSON gets JSON-RPC -32700',
|
|
89
|
+
run: async (ctx) => {
|
|
90
|
+
const r = await post(ctx, '{not json');
|
|
91
|
+
expect(r.body?.error?.code === -32700, `got ${JSON.stringify(r.body?.error ?? r.status)}`);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'invalid-request',
|
|
96
|
+
title: 'a non-JSON-RPC body gets -32600',
|
|
97
|
+
run: async (ctx) => {
|
|
98
|
+
const r = await post(ctx, { hello: 'world' });
|
|
99
|
+
expect(r.body?.error?.code === -32600, `got ${JSON.stringify(r.body?.error ?? r.status)}`);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'ping',
|
|
104
|
+
title: 'ping answers with an empty result',
|
|
105
|
+
needsSession: true,
|
|
106
|
+
run: async (ctx) => {
|
|
107
|
+
const r = await post(ctx, { jsonrpc: '2.0', id: 'p', method: 'ping' });
|
|
108
|
+
expect(r.body?.id === 'p' && r.body?.result && typeof r.body.result === 'object', `got ${JSON.stringify(r.body)}`);
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'method-not-found',
|
|
113
|
+
title: 'an unknown method gets -32601',
|
|
114
|
+
needsSession: true,
|
|
115
|
+
run: async (ctx) => {
|
|
116
|
+
const r = await post(ctx, { jsonrpc: '2.0', id: 9, method: 'no/such/method' });
|
|
117
|
+
expect(r.body?.error?.code === -32601, `got ${JSON.stringify(r.body?.error ?? r.body)}`);
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'notification-202',
|
|
122
|
+
title: 'notifications are acknowledged with 202 and no body',
|
|
123
|
+
needsSession: true,
|
|
124
|
+
run: async (ctx) => {
|
|
125
|
+
const r = await post(ctx, { jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
126
|
+
expect(r.status === 202, `HTTP ${r.status}`);
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'tools-list',
|
|
131
|
+
title: 'tools/list returns tools with name and inputSchema',
|
|
132
|
+
needsSession: true,
|
|
133
|
+
run: async (ctx) => {
|
|
134
|
+
const r = await post(ctx, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
|
|
135
|
+
const tools = r.body?.result?.tools;
|
|
136
|
+
expect(Array.isArray(tools), `no result.tools: ${JSON.stringify(r.body).slice(0, 200)}`);
|
|
137
|
+
for (const t of tools)
|
|
138
|
+
expect(typeof t.name === 'string' && t.inputSchema && typeof t.inputSchema === 'object', `bad tool ${JSON.stringify(t).slice(0, 120)}`);
|
|
139
|
+
return `${tools.length} tools`;
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'unknown-tool',
|
|
144
|
+
title: 'calling an unknown tool is an error (JSON-RPC error or isError result)',
|
|
145
|
+
needsSession: true,
|
|
146
|
+
run: async (ctx) => {
|
|
147
|
+
const r = await post(ctx, { jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: '__conformance_missing__', arguments: {} } });
|
|
148
|
+
expect(r.body?.error || r.body?.result?.isError === true, `got ${JSON.stringify(r.body).slice(0, 200)}`);
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'bad-protocol-header',
|
|
153
|
+
title: 'an unsupported MCP-Protocol-Version header is rejected with 400',
|
|
154
|
+
needsSession: true,
|
|
155
|
+
run: async (ctx) => {
|
|
156
|
+
const r = await post(ctx, { jsonrpc: '2.0', id: 4, method: 'ping' }, { 'mcp-protocol-version': '1999-01-01' });
|
|
157
|
+
expect(r.status === 400, `HTTP ${r.status}`);
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'unknown-session',
|
|
162
|
+
title: 'a request with an unknown session id gets 404',
|
|
163
|
+
run: async (ctx) => {
|
|
164
|
+
const r = await post({ ...ctx, session: 'conformance-unknown-session' }, { jsonrpc: '2.0', id: 5, method: 'ping' });
|
|
165
|
+
expect(r.status === 404, `HTTP ${r.status}`);
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
/** Run the suite against a Streamable HTTP MCP endpoint. */
|
|
170
|
+
export async function runConformance(url, opts = {}) {
|
|
171
|
+
const started = Date.now();
|
|
172
|
+
const ctx = { url, f: opts.fetch ?? fetch, headers: opts.headers ?? {} };
|
|
173
|
+
const checks = [];
|
|
174
|
+
const selected = CHECKS.filter((c) => !opts.only?.length || opts.only.includes(c.id));
|
|
175
|
+
let sessionError;
|
|
176
|
+
if (selected.some((c) => c.needsSession)) {
|
|
177
|
+
try {
|
|
178
|
+
const r = await post(ctx, init(PROTOCOL_VERSIONS[0]));
|
|
179
|
+
ctx.session = r.headers.get('mcp-session-id') ?? undefined;
|
|
180
|
+
ctx.version = r.body?.result?.protocolVersion;
|
|
181
|
+
if (!ctx.version)
|
|
182
|
+
sessionError = `initialize failed (HTTP ${r.status})`;
|
|
183
|
+
else
|
|
184
|
+
await post(ctx, { jsonrpc: '2.0', method: 'notifications/initialized' });
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
sessionError = `initialize failed: ${e.message}`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const c of selected) {
|
|
191
|
+
if (c.needsSession && sessionError) {
|
|
192
|
+
checks.push({ id: c.id, title: c.title, status: 'skip', detail: sessionError });
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const detail = await c.run(ctx);
|
|
197
|
+
checks.push({ id: c.id, title: c.title, status: 'pass', ...(detail ? { detail } : {}) });
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
checks.push({ id: c.id, title: c.title, status: 'fail', detail: e.message });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const n = (s) => checks.filter((c) => c.status === s).length;
|
|
204
|
+
return { url, startedAt: new Date(started).toISOString(), durationMs: Date.now() - started, passed: n('pass'), failed: n('fail'), skipped: n('skip'), checks };
|
|
205
|
+
}
|
|
206
|
+
/** Plain-text rendering for the CLI. */
|
|
207
|
+
export function formatReport(r) {
|
|
208
|
+
const mark = { pass: '✓', fail: '✗', skip: '-' };
|
|
209
|
+
return [
|
|
210
|
+
`MCP conformance — ${r.url}`,
|
|
211
|
+
...r.checks.map((c) => ` ${mark[c.status]} ${c.id}: ${c.title}${c.detail ? ` (${c.detail})` : ''}`),
|
|
212
|
+
`${r.passed} passed, ${r.failed} failed, ${r.skipped} skipped in ${r.durationMs} ms`,
|
|
213
|
+
].join('\n');
|
|
214
|
+
}
|
|
215
|
+
registerFeature({
|
|
216
|
+
id: 'conformance',
|
|
217
|
+
since: '5.1.0',
|
|
218
|
+
summary: 'MCP conformance self-test of this gateway\'s /mcp endpoint',
|
|
219
|
+
mount: (router, ctx) => {
|
|
220
|
+
router.get('/checks', (_req, res) => void res.json({ checks: CHECKS.map(({ id, title }) => ({ id, title })) }));
|
|
221
|
+
router.post('/run', async (req, res) => {
|
|
222
|
+
const base = ctx.baseUrl();
|
|
223
|
+
if (!base)
|
|
224
|
+
return void res.status(503).json({ error: 'Service Unavailable', message: 'gateway is not listening' });
|
|
225
|
+
if (ctx.config().mcp?.enabled === false)
|
|
226
|
+
return void res.status(409).json({ error: 'Conflict', message: 'the MCP endpoint is disabled (mcp.enabled: false)' });
|
|
227
|
+
const only = Array.isArray(req.body?.only) ? (req.body.only.map(String)) : undefined;
|
|
228
|
+
const headers = {};
|
|
229
|
+
for (const h of ['authorization', 'x-api-key'])
|
|
230
|
+
if (req.headers[h])
|
|
231
|
+
headers[h] = String(req.headers[h]);
|
|
232
|
+
res.json(await runConformance(base + (ctx.config().mcp?.path ?? '/mcp'), { only, headers }));
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
//# sourceMappingURL=conformance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance.js","sourceRoot":"","sources":["../../src/features/conformance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in feature modules (5.1+). Importing this module registers each of them with the feature registry
|
|
3
|
+
* ({@link ../gateway/features.ts}); they are mounted under `/api/v1/admin/<id>`.
|
|
4
|
+
*
|
|
5
|
+
* @module features
|
|
6
|
+
*/
|
|
7
|
+
import './conformance.js';
|
|
8
|
+
import './regions.js';
|
|
9
|
+
export { listFeatures, registerFeature, createFeatureRouter } from '../gateway/features.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/features/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,cAAc,CAAC;AAEtB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in feature modules (5.1+). Importing this module registers each of them with the feature registry
|
|
3
|
+
* ({@link ../gateway/features.ts}); they are mounted under `/api/v1/admin/<id>`.
|
|
4
|
+
*
|
|
5
|
+
* @module features
|
|
6
|
+
*/
|
|
7
|
+
import './conformance.js';
|
|
8
|
+
import './regions.js';
|
|
9
|
+
export { listFeatures, registerFeature, createFeatureRouter } from '../gateway/features.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/features/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,cAAc,CAAC;AAEtB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-region active-active (5.2).
|
|
3
|
+
*
|
|
4
|
+
* Every region runs a full gateway. Regions replicate a small shared key-value state (last-writer-wins per key,
|
|
5
|
+
* ties broken by region id) and gossip which upstream servers are online, so a call whose upstream is down locally
|
|
6
|
+
* can be failed over to a region that has it.
|
|
7
|
+
*
|
|
8
|
+
* ```yaml
|
|
9
|
+
* regions:
|
|
10
|
+
* self: eu-west
|
|
11
|
+
* syncIntervalMs: 5000
|
|
12
|
+
* peers:
|
|
13
|
+
* - { id: us-east, url: https://us.gw.example.com, apiKey: ${US_ADMIN_KEY}, priority: 1 }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* - `GET /admin/regions` — this region, peers (health, last sync, servers online), replicated key count.
|
|
17
|
+
* - `POST /admin/regions/sync` — peer exchange `{ region, since, entries, servers }` → `{ region, entries, servers }`.
|
|
18
|
+
* - `GET|PUT|DELETE /admin/regions/kv/:key` — replicated state (`PUT` body `{ value }`).
|
|
19
|
+
* - `GET /admin/regions/route/:serverId` — where a call to that server should run: `local`, a peer, or none.
|
|
20
|
+
*
|
|
21
|
+
* @module features/regions
|
|
22
|
+
*/
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
export declare const RegionsSchema: z.ZodObject<{
|
|
25
|
+
self: z.ZodString;
|
|
26
|
+
syncIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
/** Consecutive failed syncs before a peer is considered down. */
|
|
28
|
+
downAfter: z.ZodDefault<z.ZodNumber>;
|
|
29
|
+
peers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
30
|
+
id: z.ZodString;
|
|
31
|
+
url: z.ZodString;
|
|
32
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
33
|
+
priority: z.ZodDefault<z.ZodNumber>;
|
|
34
|
+
}, "strict", z.ZodTypeAny, {
|
|
35
|
+
id: string;
|
|
36
|
+
url: string;
|
|
37
|
+
priority: number;
|
|
38
|
+
apiKey?: string | undefined;
|
|
39
|
+
}, {
|
|
40
|
+
id: string;
|
|
41
|
+
url: string;
|
|
42
|
+
apiKey?: string | undefined;
|
|
43
|
+
priority?: number | undefined;
|
|
44
|
+
}>, "many">>;
|
|
45
|
+
}, "strict", z.ZodTypeAny, {
|
|
46
|
+
self: string;
|
|
47
|
+
syncIntervalMs: number;
|
|
48
|
+
downAfter: number;
|
|
49
|
+
peers: {
|
|
50
|
+
id: string;
|
|
51
|
+
url: string;
|
|
52
|
+
priority: number;
|
|
53
|
+
apiKey?: string | undefined;
|
|
54
|
+
}[];
|
|
55
|
+
}, {
|
|
56
|
+
self: string;
|
|
57
|
+
syncIntervalMs?: number | undefined;
|
|
58
|
+
downAfter?: number | undefined;
|
|
59
|
+
peers?: {
|
|
60
|
+
id: string;
|
|
61
|
+
url: string;
|
|
62
|
+
apiKey?: string | undefined;
|
|
63
|
+
priority?: number | undefined;
|
|
64
|
+
}[] | undefined;
|
|
65
|
+
}>;
|
|
66
|
+
export type RegionsConfig = z.input<typeof RegionsSchema>;
|
|
67
|
+
type Resolved = z.output<typeof RegionsSchema>;
|
|
68
|
+
export interface ReplicatedEntry {
|
|
69
|
+
key: string;
|
|
70
|
+
/** `null` = tombstone (deleted). */
|
|
71
|
+
value: unknown;
|
|
72
|
+
/** Hybrid timestamp: wall-clock ms, bumped to stay monotonic per region. */
|
|
73
|
+
ts: number;
|
|
74
|
+
region: string;
|
|
75
|
+
}
|
|
76
|
+
export interface PeerState {
|
|
77
|
+
id: string;
|
|
78
|
+
url: string;
|
|
79
|
+
priority: number;
|
|
80
|
+
status: 'unknown' | 'up' | 'down';
|
|
81
|
+
failures: number;
|
|
82
|
+
lastSync?: string;
|
|
83
|
+
lastError?: string;
|
|
84
|
+
/** Highest `ts` received from this peer (sync cursor). */
|
|
85
|
+
cursor: number;
|
|
86
|
+
servers: string[];
|
|
87
|
+
}
|
|
88
|
+
/** Last-writer-wins: newer ts wins; equal ts → lexicographically larger region wins. */
|
|
89
|
+
export declare const newer: (a: ReplicatedEntry, b: ReplicatedEntry | undefined) => boolean;
|
|
90
|
+
export declare class RegionMesh {
|
|
91
|
+
private cfg;
|
|
92
|
+
private readonly now;
|
|
93
|
+
private readonly entries;
|
|
94
|
+
private readonly peers;
|
|
95
|
+
private clock;
|
|
96
|
+
private timer?;
|
|
97
|
+
constructor(cfg: Resolved, now?: () => number);
|
|
98
|
+
get self(): string;
|
|
99
|
+
configure(cfg: Resolved): void;
|
|
100
|
+
private tick;
|
|
101
|
+
get(key: string): unknown;
|
|
102
|
+
put(key: string, value: unknown): ReplicatedEntry;
|
|
103
|
+
delete(key: string): boolean;
|
|
104
|
+
size(): number;
|
|
105
|
+
/** Merge remote entries; returns how many changed local state. */
|
|
106
|
+
merge(entries: ReplicatedEntry[]): number;
|
|
107
|
+
/** Entries changed after `since` (any origin, so state relays through intermediate regions). */
|
|
108
|
+
delta(since?: number): ReplicatedEntry[];
|
|
109
|
+
peerList(): PeerState[];
|
|
110
|
+
/** Record a peer's gossip (from an inbound or outbound sync). */
|
|
111
|
+
sawPeer(id: string, servers: unknown, cursor?: number): void;
|
|
112
|
+
peerFailed(id: string, err: string): void;
|
|
113
|
+
/** Where a call to `serverId` should run. */
|
|
114
|
+
route(serverId: string, localOnline: string[]): {
|
|
115
|
+
target: 'local';
|
|
116
|
+
} | {
|
|
117
|
+
target: 'peer';
|
|
118
|
+
peer: string;
|
|
119
|
+
url: string;
|
|
120
|
+
} | {
|
|
121
|
+
target: 'none';
|
|
122
|
+
};
|
|
123
|
+
/** One outbound sync round with every peer. */
|
|
124
|
+
syncOnce(localOnline: string[], f?: typeof fetch): Promise<void>;
|
|
125
|
+
start(localOnline: () => string[], f?: typeof fetch): void;
|
|
126
|
+
stop(): void;
|
|
127
|
+
status(): Record<string, unknown>;
|
|
128
|
+
}
|
|
129
|
+
/** Resolve (validate + defaults) a `regions` section. */
|
|
130
|
+
export declare const resolveRegions: (raw: unknown) => Resolved;
|
|
131
|
+
export {};
|
|
132
|
+
//# sourceMappingURL=regions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regions.d.ts","sourceRoot":"","sources":["../../src/features/regions.ts"],"names":[],"mappings":"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"}
|