@volter/twin-tunnel 0.1.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.
@@ -0,0 +1,62 @@
1
+ import { mkdtempSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { createTunnelTwinServer } from './tunnel-server.ts';
5
+
6
+ export type TunnelConformanceReport = { ok: boolean; probes: number; violations: string[] };
7
+
8
+ /** Literal control-plane probes through the same Bun HTTP/WebSocket adapter callers use. Payload
9
+ * transport is covered separately, including an unmodified @volter/tunnel SDK integration test. */
10
+ export async function checkTunnelConformance(options: { root?: string } = {}): Promise<TunnelConformanceReport> {
11
+ const root = options.root ?? mkdtempSync(join(tmpdir(), 'tunnel-conformance-'));
12
+ const violations: string[] = [];
13
+ let probes = 0;
14
+ let socket: WebSocket | undefined;
15
+ let relay: ReturnType<typeof createTunnelTwinServer> | undefined;
16
+ const require = (condition: boolean, label: string) => { probes++; if (!condition) violations.push(label); };
17
+ try {
18
+ relay = createTunnelTwinServer({ root });
19
+ const allocated = await fetch(`${relay.url}/tunnel`, {
20
+ method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}',
21
+ });
22
+ if (!allocated.ok) throw new Error(`POST /tunnel transport returned HTTP ${allocated.status}`);
23
+ const allocationBody = await allocated.json() as Record<string, any>;
24
+ require(allocated.status === 200 && allocated.headers.get('content-type')?.startsWith('application/json') === true
25
+ && allocationBody.success === true && typeof allocationBody.result?.hostname === 'string', 'POST /tunnel response');
26
+
27
+ const status = await fetch(`${relay.url}/api/status`);
28
+ const statusBody = await status.json() as Record<string, unknown>;
29
+ require(status.status === 200 && status.headers.get('content-type')?.startsWith('application/json') === true
30
+ && statusBody.relay === 'twin-local' && statusBody.tunnels === 0, 'GET /api/status response');
31
+
32
+ const missing = await fetch(`${relay.url}/accounts`);
33
+ const missingBody = await missing.json() as Record<string, unknown>;
34
+ require(missing.status === 404 && missingBody.error === 'not found', 'unknown route refusal');
35
+
36
+ socket = new WebSocket(`${relay.url.replace(/^http/, 'ws')}/ws`);
37
+ const registered = new Promise<Record<string, unknown>>((resolve, reject) => {
38
+ const timer = setTimeout(() => reject(new Error('relay register transport timed out')), 3_000);
39
+ socket!.addEventListener('message', (event) => {
40
+ const message = JSON.parse(String(event.data)) as Record<string, unknown>;
41
+ if (message.type !== 'registered' && message.type !== 'error') return;
42
+ clearTimeout(timer); resolve(message);
43
+ });
44
+ socket!.addEventListener('error', () => { clearTimeout(timer); reject(new Error('relay register transport failed')); }, { once: true });
45
+ });
46
+ await new Promise<void>((resolve, reject) => {
47
+ socket!.addEventListener('open', () => resolve(), { once: true });
48
+ socket!.addEventListener('error', () => reject(new Error('relay websocket did not open')), { once: true });
49
+ });
50
+ socket.send(JSON.stringify({ type: 'register', tunnelId: 'conformance', secret: 'vt_fake_local', authRequired: false }));
51
+ const registration = await registered;
52
+ require(registration.type === 'registered' && registration.tunnelId === 'conformance'
53
+ && registration.url === `${relay.url}/__twin/tunnels/conformance`, 'relay register frame');
54
+ } catch (error) {
55
+ violations.push(`conformance probe threw: ${error instanceof Error ? error.message : String(error)}`);
56
+ } finally {
57
+ socket?.close();
58
+ relay?.stop();
59
+ if (!options.root) rmSync(root, { recursive: true, force: true });
60
+ }
61
+ return { ok: violations.length === 0, probes, violations };
62
+ }
@@ -0,0 +1,64 @@
1
+ import { assertBudgetGuardIntact, syncPull, type SyncResource } from '@volter/world-core';
2
+ import { TunnelBudget, TunnelBudgetError, tunnelCallWeight, type TunnelBudgetOptions } from './tunnel-budget.ts';
3
+
4
+ export type TunnelExecute = (method: 'GET' | 'POST' | 'DELETE', path: string, body?: unknown) => Promise<{ status: number; body: any }>;
5
+
6
+ export function liveTunnelExecute(token: string, base = 'https://voltertest.xyz', opts: {
7
+ fetchImpl?: typeof fetch; budget?: TunnelBudget; budgetOptions?: TunnelBudgetOptions;
8
+ } = {}): TunnelExecute {
9
+ const doFetch = opts.fetchImpl ?? fetch;
10
+ const budget = opts.budget !== undefined && opts.budget !== null
11
+ ? assertBudgetGuardIntact(opts.budget, TunnelBudget, 'liveTunnelExecute')
12
+ : new TunnelBudget({ token, ...(opts.budgetOptions ?? {}) });
13
+ return async (method, path, body) => {
14
+ const headers: Record<string, string> = { authorization: `Bearer ${token}` };
15
+ const init: RequestInit = { method, headers };
16
+ if (body !== undefined) { headers['content-type'] = 'application/json'; init.body = JSON.stringify(body); }
17
+ const weight = tunnelCallWeight(method, path.split('?')[0] ?? path);
18
+ const reservation = budget.checkBudget(weight);
19
+ const response = await doFetch(`${base.replace(/\/$/, '')}${path}`, init);
20
+ const responseHeaders: Record<string, string> = {};
21
+ response.headers.forEach((v, k) => { responseHeaders[k.toLowerCase()] = v; });
22
+ const text = await response.text();
23
+ // An answer the relay ACCEPTED is kept even if recordCall then throws (a back-off beyond the
24
+ // cap, cooldown already armed): a write that landed is never recorded as failed and redone.
25
+ try {
26
+ budget.recordCall(weight, responseHeaders, { status: response.status, reservation });
27
+ } catch (error) {
28
+ if (!(error instanceof TunnelBudgetError) || !response.ok) throw error;
29
+ }
30
+ let parsed: unknown = {};
31
+ if (text) { try { parsed = JSON.parse(text); } catch { parsed = { error: text }; } }
32
+ return { status: response.status, body: parsed };
33
+ };
34
+ }
35
+
36
+ export function mapTunnelRelay(status: Record<string, unknown>): SyncResource {
37
+ return {
38
+ type: 'relay', id: 'relay:default',
39
+ // The owned public endpoint is exactly { ok: true, relay: 'cloudflare-do' }. Live socket
40
+ // counts are a useful twin-local diagnostic, not a real field and therefore never folded.
41
+ fields: { ok: status.ok === true, relay: String(status.relay ?? 'unknown') },
42
+ };
43
+ }
44
+
45
+ export async function pullTunnelRelay(execute: TunnelExecute): Promise<SyncResource[]> {
46
+ const response = await execute('GET', '/api/status');
47
+ if (response.status < 200 || response.status >= 300) throw new Error(`tunnel pull status refused: HTTP ${response.status}`);
48
+ if (!response.body || typeof response.body !== 'object' || Array.isArray(response.body)
49
+ || response.body.ok !== true || response.body.relay !== 'cloudflare-do'
50
+ || Object.keys(response.body).length !== 2) {
51
+ throw new Error('tunnel pull status returned no healthy relay — refusing to fold an empty observation');
52
+ }
53
+ return [mapTunnelRelay(response.body)];
54
+ }
55
+
56
+ export async function syncTunnelFromReal(execute: TunnelExecute, opts: { root?: string; occurredAt?: string } = {}) {
57
+ const resources = await pullTunnelRelay(execute);
58
+ const result = syncPull({ service: 'tunnel', resources, occurredAt: opts.occurredAt ?? new Date().toISOString(), ...(opts.root ? { root: opts.root } : {}) });
59
+ return { observed: result.observed, deltasAppended: result.deltasAppended };
60
+ }
61
+
62
+ export async function pushPendingTunnelActions(): Promise<never> {
63
+ throw new Error('tunnel push is not implemented: publishing a local tunnel to a real edge needs an explicit operator-selected provider and credential');
64
+ }