| … | … | @@ -0,0 +1,18 @@ |
| 1 | # Dependencies | |
| 2 | node_modules/ | |
| 3 | ||
| 4 | # Storage directories created by example CLIs | |
| 5 | .hyper-svc-*/ | |
| 6 | ||
| 7 | # UDS sockets left behind by crashes | |
| 8 | *.sock | |
| 9 | ||
| 10 | # Editor and OS cruft | |
| 11 | .DS_Store | |
| 12 | *.log | |
| 13 | *.swp | |
| 14 | .vscode/ | |
| 15 | .idea/ | |
| 16 | ||
| 17 | # Test artifacts | |
| 18 | coverage/ |
| … | … | @@ -0,0 +1,161 @@ |
| 1 | #!/usr/bin/env node | |
| 2 | /** | |
| 3 | * Mini "echo service" demo for hyper-svc. | |
| 4 | * | |
| 5 | * Demonstrates the daemon + client lifecycle in two modes: | |
| 6 | * | |
| 7 | * node examples/cli.js daemon Start the echo daemon (UDS+HTTP) | |
| 8 | * node examples/cli.js call <method> [json] Connect to the daemon and call a method | |
| 9 | * node examples/cli.js demo Run an in-process round-trip end-to-end | |
| 10 | * | |
| 11 | * The daemon registers two methods: | |
| 12 | * - "echo" → returns whatever you send | |
| 13 | * - "uppercase" → returns the upper-cased string | |
| 14 | * - "info" → returns service metadata (name, handlers, transports) | |
| 15 | * | |
| 16 | * Storage: HYPER_SVC_STORE env var (default: ./.hyper-svc-cli) | |
| 17 | * Socket: HYPER_SVC_SOCK env var (default: ./.hyper-svc-cli/svc.sock) | |
| 18 | * | |
| 19 | * NOTE: storage path is provided for parity with other modules' examples; the | |
| 20 | * echo demo doesn't actually use it. Real consumers (pear-git-v3) would pass | |
| 21 | * a corestore for the swarm transport to replicate. | |
| 22 | */ | |
| 23 | ||
| 24 | import { Service, Client } from '../index.js' | |
| 25 | import { mkdir } from 'fs/promises' | |
| 26 | import { join, dirname } from 'path' | |
| 27 | ||
| 28 | const storagePath = process.env.HYPER_SVC_STORE || './.hyper-svc-cli' | |
| 29 | const sockPath = process.env.HYPER_SVC_SOCK || join(storagePath, 'svc.sock') | |
| 30 | const httpPort = parseInt(process.env.HYPER_SVC_HTTP || '4090') | |
| 31 | ||
| 32 | const args = process.argv.slice(2) | |
| 33 | const cmd = args[0] | |
| 34 | ||
| 35 | function buildService () { | |
| 36 | const svc = new Service({ | |
| 37 | name: 'echo', | |
| 38 | uds: sockPath, | |
| 39 | http: { port: httpPort } | |
| 40 | }) | |
| 41 | svc.handle('echo', async (ctx, params) => { | |
| 42 | return { from: ctx.transport, params } | |
| 43 | }) | |
| 44 | svc.handle('uppercase', async (ctx, params) => { | |
| 45 | return String(params.text || '').toUpperCase() | |
| 46 | }) | |
| 47 | svc.handle('info', async (ctx) => { | |
| 48 | return { | |
| 49 | name: svc.name, | |
| 50 | transport: ctx.transport, | |
| 51 | identity: ctx.identity, | |
| 52 | handlers: svc.listHandlers(), | |
| 53 | mounts: svc.listMounts() | |
| 54 | } | |
| 55 | }) | |
| 56 | return svc | |
| 57 | } | |
| 58 | ||
| 59 | async function main () { | |
| 60 | if (cmd === 'daemon') { | |
| 61 | await mkdir(dirname(sockPath), { recursive: true }) | |
| 62 | const svc = buildService() | |
| 63 | await svc.listen() | |
| 64 | console.log('hyper-svc echo daemon') | |
| 65 | console.log(' uds: %s', sockPath) | |
| 66 | console.log(' http: http://127.0.0.1:%d', svc.http.port) | |
| 67 | console.log(' handlers: %s', svc.listHandlers().join(', ')) | |
| 68 | console.log(' (Ctrl-C to stop)') | |
| 69 | ||
| 70 | process.on('SIGINT', async () => { | |
| 71 | console.log('\nshutting down...') | |
| 72 | try { await svc.close() } catch {} | |
| 73 | process.exit(0) | |
| 74 | }) | |
| 75 | return | |
| 76 | } | |
| 77 | ||
| 78 | if (cmd === 'call') { | |
| 79 | const method = args[1] | |
| 80 | if (!method) { console.error('Usage: cli.js call <method> [json-params]'); process.exit(1) } | |
| 81 | let params = {} | |
| 82 | if (args[2]) { | |
| 83 | try { params = JSON.parse(args[2]) } catch (e) { | |
| 84 | console.error('bad JSON params:', e.message) | |
| 85 | process.exit(1) | |
| 86 | } | |
| 87 | } | |
| 88 | const client = await Client.discover({ | |
| 89 | uds: sockPath, | |
| 90 | http: `http://127.0.0.1:${httpPort}` | |
| 91 | }) | |
| 92 | console.log('connected via', client.kind) | |
| 93 | try { | |
| 94 | const result = await client.call(method, params) | |
| 95 | console.log(JSON.stringify(result, null, 2)) | |
| 96 | } finally { | |
| 97 | await client.close() | |
| 98 | } | |
| 99 | return | |
| 100 | } | |
| 101 | ||
| 102 | if (cmd === 'demo') { | |
| 103 | // In-process: spin up a daemon, connect to it, make calls, shut it down | |
| 104 | await mkdir(dirname(sockPath), { recursive: true }) | |
| 105 | const svc = buildService() | |
| 106 | await svc.listen() | |
| 107 | console.log('--- hyper-svc demo ---\n') | |
| 108 | console.log('Started service "echo"') | |
| 109 | console.log(' uds: %s', sockPath) | |
| 110 | console.log(' http: http://127.0.0.1:%d\n', svc.http.port) | |
| 111 | ||
| 112 | console.log('1. Discover (prefers UDS)') | |
| 113 | const client = await Client.discover({ | |
| 114 | uds: sockPath, | |
| 115 | http: `http://127.0.0.1:${svc.http.port}` | |
| 116 | }) | |
| 117 | console.log(' → connected via', client.kind, '\n') | |
| 118 | ||
| 119 | console.log('2. Call echo({ hello: "world" })') | |
| 120 | const a = await client.call('echo', { hello: 'world' }) | |
| 121 | console.log(' →', JSON.stringify(a), '\n') | |
| 122 | ||
| 123 | console.log('3. Call uppercase({ text: "hyper-svc" })') | |
| 124 | const b = await client.call('uppercase', { text: 'hyper-svc' }) | |
| 125 | console.log(' →', JSON.stringify(b), '\n') | |
| 126 | ||
| 127 | console.log('4. Call info()') | |
| 128 | const c = await client.call('info', {}) | |
| 129 | console.log(' →', JSON.stringify(c, null, 2), '\n') | |
| 130 | ||
| 131 | console.log('5. Force HTTP transport') | |
| 132 | await client.close() | |
| 133 | const httpClient = await Client.discover({ | |
| 134 | http: `http://127.0.0.1:${svc.http.port}`, | |
| 135 | prefer: 'http' | |
| 136 | }) | |
| 137 | console.log(' → connected via', httpClient.kind) | |
| 138 | const d = await httpClient.call('echo', { from: 'http' }) | |
| 139 | console.log(' →', JSON.stringify(d), '\n') | |
| 140 | ||
| 141 | await httpClient.close() | |
| 142 | await svc.close() | |
| 143 | console.log('shutdown complete.') | |
| 144 | return | |
| 145 | } | |
| 146 | ||
| 147 | console.log('hyper-svc echo service example') | |
| 148 | console.log(' daemon Start the daemon (UDS + HTTP)') | |
| 149 | console.log(' call <method> [json-params] Call a method on a running daemon') | |
| 150 | console.log(' demo Run an in-process round-trip') | |
| 151 | console.log('') | |
| 152 | console.log('Storage: HYPER_SVC_STORE env var (default: ./.hyper-svc-cli)') | |
| 153 | console.log('Socket: HYPER_SVC_SOCK env var (default: <storage>/svc.sock)') | |
| 154 | console.log('HTTP: HYPER_SVC_HTTP env var (default: 4090)') | |
| 155 | console.log('') | |
| 156 | console.log('Note: this CLI does NOT share state with examples/web.js — they use') | |
| 157 | console.log('different storage paths because the corestore single-writer lock applies') | |
| 158 | console.log('once you start mounting real modules.') | |
| 159 | } | |
| 160 | ||
| 161 | main().catch((err) => { console.error(err.message); process.exit(1) }) |
| … | … | @@ -0,0 +1,218 @@ |
| 1 | #!/usr/bin/env node | |
| 2 | /** | |
| 3 | * Mini SSR dashboard for hyper-svc. | |
| 4 | * | |
| 5 | * Demonstrates the "app routes coexist with /rpc" pattern: hyper-svc owns | |
| 6 | * the http.Server and reserves POST /rpc/<method>; the app registers its own | |
| 7 | * 'request' listener for SSR pages. | |
| 8 | * | |
| 9 | * Pages: | |
| 10 | * GET / → status overview (handlers, mounts, transport state) | |
| 11 | * GET /handlers → list of registered handlers | |
| 12 | * POST /call → form-driven call to any handler (echoes result) | |
| 13 | * | |
| 14 | * Usage: node examples/web.js [--port 9098] [--storage <path>] | |
| 15 | * | |
| 16 | * NOTE: Uses its own storage path by default (./.hyper-svc-web). Does NOT | |
| 17 | * share state with examples/cli.js — daemons can't share storage anyway. | |
| 18 | */ | |
| 19 | ||
| 20 | import { Service } from '../index.js' | |
| 21 | import b4a from 'b4a' | |
| 22 | import { mkdir } from 'fs/promises' | |
| 23 | import { join, dirname } from 'path' | |
| 24 | ||
| 25 | const args = process.argv.slice(2) | |
| 26 | function getFlag (name) { | |
| 27 | const i = args.indexOf('--' + name) | |
| 28 | return i !== -1 && args[i + 1] ? args[i + 1] : null | |
| 29 | } | |
| 30 | ||
| 31 | const port = parseInt(getFlag('port')) || 9098 | |
| 32 | const storagePath = getFlag('storage') || './.hyper-svc-web' | |
| 33 | const sockPath = join(storagePath, 'svc.sock') | |
| 34 | ||
| 35 | await mkdir(storagePath, { recursive: true }) | |
| 36 | ||
| 37 | const svc = new Service({ | |
| 38 | name: 'demo-web', | |
| 39 | uds: sockPath, | |
| 40 | http: { port } | |
| 41 | }) | |
| 42 | ||
| 43 | svc.handle('echo', async (ctx, params) => ({ from: ctx.transport, params })) | |
| 44 | svc.handle('uppercase', async (ctx, params) => String(params.text || '').toUpperCase()) | |
| 45 | svc.handle('reverse', async (ctx, params) => String(params.text || '').split('').reverse().join('')) | |
| 46 | svc.handle('time', async () => new Date().toISOString()) | |
| 47 | ||
| 48 | // App-side routes — registered on the http.Server BEFORE listen() | |
| 49 | svc.http.server.on('request', async (req, res) => { | |
| 50 | // Skip /rpc/* — hyper-svc owns those | |
| 51 | if (req.url.startsWith('/rpc/')) return | |
| 52 | ||
| 53 | try { | |
| 54 | if (req.method === 'GET' && req.url === '/') { | |
| 55 | return sendHtml(res, renderIndex()) | |
| 56 | } | |
| 57 | if (req.method === 'GET' && req.url === '/handlers') { | |
| 58 | return sendHtml(res, renderHandlers()) | |
| 59 | } | |
| 60 | if (req.method === 'POST' && req.url === '/call') { | |
| 61 | const body = await collectBody(req) | |
| 62 | const params = new URLSearchParams(b4a.toString(body, 'utf8')) | |
| 63 | const method = params.get('method') | |
| 64 | const args = params.get('args') || '{}' | |
| 65 | let parsed = {} | |
| 66 | let result, error | |
| 67 | try { | |
| 68 | parsed = JSON.parse(args) | |
| 69 | } catch (e) { | |
| 70 | error = 'bad JSON args: ' + e.message | |
| 71 | } | |
| 72 | if (!error) { | |
| 73 | try { | |
| 74 | result = await svc.dispatch(method, parsed, { transport: 'http', identity: null }) | |
| 75 | } catch (e) { | |
| 76 | error = e.message | |
| 77 | } | |
| 78 | } | |
| 79 | return sendHtml(res, renderResult(method, args, result, error)) | |
| 80 | } | |
| 81 | } catch (err) { | |
| 82 | res.writeHead(500, { 'Content-Type': 'text/plain' }) | |
| 83 | res.end(err.message) | |
| 84 | } | |
| 85 | }) | |
| 86 | ||
| 87 | await svc.listen() | |
| 88 | ||
| 89 | console.log('hyper-svc dashboard:') | |
| 90 | console.log(' http://localhost:%d', port) | |
| 91 | console.log(' uds: %s', sockPath) | |
| 92 | console.log(' storage: %s', storagePath) | |
| 93 | ||
| 94 | // ── Helpers ────────────────────────────────────────────────── | |
| 95 | ||
| 96 | function sendHtml (res, html) { | |
| 97 | res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }) | |
| 98 | res.end(html) | |
| 99 | } | |
| 100 | ||
| 101 | function collectBody (req) { | |
| 102 | return new Promise((resolve, reject) => { | |
| 103 | const parts = [] | |
| 104 | req.on('data', (c) => parts.push(c)) | |
| 105 | req.on('end', () => resolve(b4a.concat(parts))) | |
| 106 | req.on('error', reject) | |
| 107 | }) | |
| 108 | } | |
| 109 | ||
| 110 | function escapeHtml (s) { | |
| 111 | return String(s == null ? '' : s) | |
| 112 | .replace(/&/g, '&').replace(/</g, '<') | |
| 113 | .replace(/>/g, '>').replace(/"/g, '"') | |
| 114 | } | |
| 115 | ||
| 116 | function styles () { | |
| 117 | return ` | |
| 118 | body { font-family: monospace; background: #0d1117; color: #c9d1d9; padding: 2rem; max-width: 800px; margin: 0 auto; } | |
| 119 | h1 { color: #58a6ff; } | |
| 120 | h2 { font-size: 1rem; color: #c9d1d9; margin-top: 0; } | |
| 121 | a { color: #58a6ff; text-decoration: none; } | |
| 122 | a:hover { text-decoration: underline; } | |
| 123 | .card { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 1rem; margin: 1rem 0; } | |
| 124 | .card h2 { margin: 0 0 0.5rem; font-size: 0.75rem; text-transform: uppercase; color: #8b949e; } | |
| 125 | .row { display: flex; justify-content: space-between; padding: 0.3rem 0; border-bottom: 1px solid #21262d; } | |
| 126 | .row:last-child { border-bottom: none; } | |
| 127 | .label { color: #8b949e; } | |
| 128 | pre { background: #0d1117; border: 1px solid #30363d; padding: 0.8rem; border-radius: 4px; overflow-x: auto; color: #c9d1d9; } | |
| 129 | pre.error { color: #f85149; border-color: #f8514944; } | |
| 130 | input, textarea, button, select { background: #0d1117; color: #c9d1d9; border: 1px solid #30363d; padding: 0.4rem 0.6rem; border-radius: 4px; font-family: monospace; font-size: 0.85rem; } | |
| 131 | textarea { width: 100%; min-height: 4em; } | |
| 132 | input, select { width: 100%; box-sizing: border-box; } | |
| 133 | button { background: #1f6feb; border-color: #1f6feb; color: white; cursor: pointer; } | |
| 134 | button:hover { background: #1158c7; } | |
| 135 | .field { margin-bottom: 0.5rem; } | |
| 136 | .field label { display: block; color: #8b949e; font-size: 0.75rem; margin-bottom: 0.2rem; } | |
| 137 | .badge-ok { background: #2ea04322; color: #3fb950; border: 1px solid #2ea04344; padding: 0.05rem 0.4rem; border-radius: 4px; font-size: 0.7rem; } | |
| 138 | .badge-off { background: #30363d; color: #8b949e; border: 1px solid #484f58; padding: 0.05rem 0.4rem; border-radius: 4px; font-size: 0.7rem; } | |
| 139 | ` | |
| 140 | } | |
| 141 | ||
| 142 | function renderLayout (title, body) { | |
| 143 | return `<!DOCTYPE html> | |
| 144 | <html><head><title>${escapeHtml(title)}</title><style>${styles()}</style></head><body> | |
| 145 | <h1><a href="/">hyper-svc</a> · ${escapeHtml(title)}</h1> | |
| 146 | ${body} | |
| 147 | </body></html>` | |
| 148 | } | |
| 149 | ||
| 150 | function renderIndex () { | |
| 151 | const transports = [ | |
| 152 | { name: 'uds', enabled: !!svc.uds, info: svc.uds ? svc.uds.path : '—' }, | |
| 153 | { name: 'http', enabled: !!svc.http, info: svc.http ? '127.0.0.1:' + (svc.http.port || '?') : '—' }, | |
| 154 | { name: 'swarm', enabled: !!svc.swarm, info: svc.swarm ? 'topic active' : '—' } | |
| 155 | ] | |
| 156 | const handlers = svc.listHandlers() | |
| 157 | const mounts = svc.listMounts() | |
| 158 | ||
| 159 | const body = ` | |
| 160 | <div class="card"> | |
| 161 | <h2>Service</h2> | |
| 162 | <div class="row"><div class="label">name</div><div>${escapeHtml(svc.name)}</div></div> | |
| 163 | <div class="row"><div class="label">handlers</div><div>${handlers.length}</div></div> | |
| 164 | <div class="row"><div class="label">mounts</div><div>${mounts.length}</div></div> | |
| 165 | </div> | |
| 166 | ||
| 167 | <div class="card"> | |
| 168 | <h2>Transports</h2> | |
| 169 | ${transports.map(tr => ` | |
| 170 | <div class="row"> | |
| 171 | <div>${escapeHtml(tr.name)} ${tr.enabled ? '<span class="badge-ok">on</span>' : '<span class="badge-off">off</span>'}</div> | |
| 172 | <div class="label">${escapeHtml(tr.info)}</div> | |
| 173 | </div>`).join('')} | |
| 174 | </div> | |
| 175 | ||
| 176 | <div class="card"> | |
| 177 | <h2>Handlers</h2> | |
| 178 | ${handlers.length === 0 ? '<div class="label">(none)</div>' : handlers.map(h => ` | |
| 179 | <div class="row"><div>${escapeHtml(h)}</div></div>`).join('')} | |
| 180 | <div style="margin-top:0.8rem"><a href="/handlers">→ try a handler</a></div> | |
| 181 | </div> | |
| 182 | ` | |
| 183 | return renderLayout('overview', body) | |
| 184 | } | |
| 185 | ||
| 186 | function renderHandlers () { | |
| 187 | const handlers = svc.listHandlers() | |
| 188 | const body = ` | |
| 189 | <div class="card"> | |
| 190 | <h2>Call a handler</h2> | |
| 191 | <form action="/call" method="POST"> | |
| 192 | <div class="field"><label>Method</label> | |
| 193 | <select name="method"> | |
| 194 | ${handlers.map(h => `<option value="${escapeHtml(h)}">${escapeHtml(h)}</option>`).join('')} | |
| 195 | </select> | |
| 196 | </div> | |
| 197 | <div class="field"><label>JSON params</label><textarea name="args">{}</textarea></div> | |
| 198 | <button type="submit">Call</button> | |
| 199 | </form> | |
| 200 | </div> | |
| 201 | ` | |
| 202 | return renderLayout('handlers', body) | |
| 203 | } | |
| 204 | ||
| 205 | function renderResult (method, args, result, error) { | |
| 206 | const body = ` | |
| 207 | <div class="card"> | |
| 208 | <h2>Call: ${escapeHtml(method)}</h2> | |
| 209 | <div class="label">params</div> | |
| 210 | <pre>${escapeHtml(args)}</pre> | |
| 211 | ${error | |
| 212 | ? `<div class="label">error</div><pre class="error">${escapeHtml(error)}</pre>` | |
| 213 | : `<div class="label">result</div><pre>${escapeHtml(JSON.stringify(result, null, 2))}</pre>`} | |
| 214 | <div style="margin-top:0.8rem"><a href="/handlers">← back</a></div> | |
| 215 | </div> | |
| 216 | ` | |
| 217 | return renderLayout(`call ${method}`, body) | |
| 218 | } |
| … | … | @@ -0,0 +1,10 @@ |
| 1 | /** | |
| 2 | * hyper-svc — reusable per-app daemon library | |
| 3 | * | |
| 4 | * Local-only RPC (UDS + HTTP) plus Hyperswarm replication for peer mirroring. | |
| 5 | * No remote RPC: peers exchange data, not commands. | |
| 6 | */ | |
| 7 | ||
| 8 | export { Service } from './lib/service.js' | |
| 9 | export { Client } from './lib/client.js' | |
| 10 | export { encodeFrame, FrameDecoder } from './lib/frame.js' |
| … | … | @@ -0,0 +1,228 @@ |
| 1 | /** | |
| 2 | * Client — RPC client for hyper-svc daemons. | |
| 3 | * | |
| 4 | * Local-only. Connects via UDS (preferred) or HTTP. Both transports speak the | |
| 5 | * same JSON-RPC-ish protocol; the wire format differs (length-prefixed frames | |
| 6 | * for UDS, POST /rpc/<method> for HTTP) but the call signature is identical. | |
| 7 | * | |
| 8 | * Discovery: try UDS first (if a path is provided and the socket is reachable), | |
| 9 | * fall back to HTTP. Explicit transport selection is also supported. | |
| 10 | * | |
| 11 | * Usage: | |
| 12 | * | |
| 13 | * const client = await Client.discover({ | |
| 14 | * uds: '/tmp/pear-git.sock', | |
| 15 | * http: 'http://127.0.0.1:4040' | |
| 16 | * }) | |
| 17 | * | |
| 18 | * const orgs = await client.call('list-orgs', { limit: 10 }) | |
| 19 | * | |
| 20 | * await client.close() | |
| 21 | */ | |
| 22 | ||
| 23 | import net from 'net' | |
| 24 | import http from 'http' | |
| 25 | import { URL } from 'url' | |
| 26 | import b4a from 'b4a' | |
| 27 | import { stat } from 'fs/promises' | |
| 28 | import { encodeFrame, FrameDecoder } from './frame.js' | |
| 29 | ||
| 30 | export class Client { | |
| 31 | /** | |
| 32 | * Discover and connect to a running hyper-svc daemon. | |
| 33 | * | |
| 34 | * @param {object} opts | |
| 35 | * @param {string} [opts.uds] - UDS socket path. Tried first. | |
| 36 | * @param {string} [opts.http] - HTTP base URL (e.g. 'http://127.0.0.1:4040'). Fallback. | |
| 37 | * @param {'uds'|'http'} [opts.prefer] - explicit override | |
| 38 | * @returns {Promise<Client>} | |
| 39 | */ | |
| 40 | static async discover (opts = {}) { | |
| 41 | const prefer = opts.prefer | |
| 42 | ||
| 43 | if (prefer === 'uds') { | |
| 44 | if (!opts.uds) throw new Error('discover({ prefer: "uds" }) requires opts.uds') | |
| 45 | return Client.connectUds(opts.uds) | |
| 46 | } | |
| 47 | if (prefer === 'http') { | |
| 48 | if (!opts.http) throw new Error('discover({ prefer: "http" }) requires opts.http') | |
| 49 | return Client.connectHttp(opts.http) | |
| 50 | } | |
| 51 | ||
| 52 | // Auto: UDS first if reachable, then HTTP | |
| 53 | if (opts.uds) { | |
| 54 | const reachable = await udsReachable(opts.uds) | |
| 55 | if (reachable) return Client.connectUds(opts.uds) | |
| 56 | } | |
| 57 | if (opts.http) { | |
| 58 | return Client.connectHttp(opts.http) | |
| 59 | } | |
| 60 | ||
| 61 | const tried = [] | |
| 62 | if (opts.uds) tried.push(`uds:${opts.uds}`) | |
| 63 | if (opts.http) tried.push(`http:${opts.http}`) | |
| 64 | throw new Error(`no daemon reachable (tried: ${tried.join(', ') || 'nothing — pass uds and/or http'})`) | |
| 65 | } | |
| 66 | ||
| 67 | static async connectUds (path) { | |
| 68 | const sock = net.connect(path) | |
| 69 | await new Promise((resolve, reject) => { | |
| 70 | sock.once('connect', () => { | |
| 71 | sock.removeListener('error', reject) | |
| 72 | resolve() | |
| 73 | }) | |
| 74 | sock.once('error', reject) | |
| 75 | }) | |
| 76 | return new Client({ kind: 'uds', sock }) | |
| 77 | } | |
| 78 | ||
| 79 | static connectHttp (base) { | |
| 80 | return new Client({ kind: 'http', base: base.replace(/\/+$/, '') }) | |
| 81 | } | |
| 82 | ||
| 83 | constructor (opts) { | |
| 84 | this._kind = opts.kind | |
| 85 | this._sock = opts.sock || null | |
| 86 | this._base = opts.base || null | |
| 87 | this._closed = false | |
| 88 | ||
| 89 | // UDS pending request map (id → {resolve, reject}) | |
| 90 | this._pending = new Map() | |
| 91 | this._nextId = 1 | |
| 92 | ||
| 93 | if (this._kind === 'uds') { | |
| 94 | this._decoder = new FrameDecoder((frame) => this._onFrame(frame)) | |
| 95 | this._sock.on('data', (chunk) => { | |
| 96 | try { | |
| 97 | this._decoder.push(chunk) | |
| 98 | } catch (err) { | |
| 99 | this._failAll(err) | |
| 100 | this._sock.destroy() | |
| 101 | } | |
| 102 | }) | |
| 103 | this._sock.on('close', () => { | |
| 104 | this._failAll(new Error('connection closed')) | |
| 105 | }) | |
| 106 | this._sock.on('error', () => { /* close handler runs */ }) | |
| 107 | } | |
| 108 | } | |
| 109 | ||
| 110 | get kind () { return this._kind } | |
| 111 | get closed () { return this._closed } | |
| 112 | ||
| 113 | /** | |
| 114 | * Make an RPC call. Throws on transport errors or remote handler errors. | |
| 115 | * @param {string} method | |
| 116 | * @param {any} [params] | |
| 117 | */ | |
| 118 | async call (method, params = {}) { | |
| 119 | if (this._closed) throw new Error('client closed') | |
| 120 | ||
| 121 | if (this._kind === 'uds') return this._callUds(method, params) | |
| 122 | if (this._kind === 'http') return this._callHttp(method, params) | |
| 123 | throw new Error('unknown client kind: ' + this._kind) | |
| 124 | } | |
| 125 | ||
| 126 | _callUds (method, params) { | |
| 127 | const id = this._nextId++ | |
| 128 | return new Promise((resolve, reject) => { | |
| 129 | this._pending.set(id, { resolve, reject }) | |
| 130 | try { | |
| 131 | this._sock.write(encodeFrame({ id, method, params })) | |
| 132 | } catch (err) { | |
| 133 | this._pending.delete(id) | |
| 134 | reject(err) | |
| 135 | } | |
| 136 | }) | |
| 137 | } | |
| 138 | ||
| 139 | _onFrame (frame) { | |
| 140 | if (frame == null || (typeof frame.id !== 'number' && typeof frame.id !== 'string')) { | |
| 141 | // Malformed response, can't match it to a caller | |
| 142 | return | |
| 143 | } | |
| 144 | const pending = this._pending.get(frame.id) | |
| 145 | if (!pending) return | |
| 146 | this._pending.delete(frame.id) | |
| 147 | ||
| 148 | if (frame.error) { | |
| 149 | pending.reject(new Error(frame.error)) | |
| 150 | } else { | |
| 151 | pending.resolve(frame.result) | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | _failAll (err) { | |
| 156 | for (const [, pending] of this._pending) pending.reject(err) | |
| 157 | this._pending.clear() | |
| 158 | } | |
| 159 | ||
| 160 | async _callHttp (method, params) { | |
| 161 | const url = new URL(`${this._base}/rpc/${encodeURIComponent(method)}`) | |
| 162 | return new Promise((resolve, reject) => { | |
| 163 | const body = JSON.stringify(params) | |
| 164 | const req = http.request({ | |
| 165 | method: 'POST', | |
| 166 | hostname: url.hostname, | |
| 167 | port: url.port || 80, | |
| 168 | path: url.pathname + url.search, | |
| 169 | headers: { | |
| 170 | 'Content-Type': 'application/json', | |
| 171 | 'Content-Length': b4a.byteLength(body) | |
| 172 | } | |
| 173 | }, (res) => { | |
| 174 | const parts = [] | |
| 175 | res.on('data', (c) => parts.push(c)) | |
| 176 | res.on('end', () => { | |
| 177 | const text = b4a.concat(parts).toString('utf8') | |
| 178 | let parsed | |
| 179 | try { | |
| 180 | parsed = JSON.parse(text) | |
| 181 | } catch { | |
| 182 | return reject(new Error(`bad JSON response (status ${res.statusCode}): ${text.slice(0, 200)}`)) | |
| 183 | } | |
| 184 | if (res.statusCode >= 200 && res.statusCode < 300) { | |
| 185 | return resolve(parsed.result) | |
| 186 | } | |
| 187 | return reject(new Error(parsed.error || `HTTP ${res.statusCode}`)) | |
| 188 | }) | |
| 189 | res.on('error', reject) | |
| 190 | }) | |
| 191 | req.on('error', reject) | |
| 192 | req.write(body) | |
| 193 | req.end() | |
| 194 | }) | |
| 195 | } | |
| 196 | ||
| 197 | async close () { | |
| 198 | if (this._closed) return | |
| 199 | this._closed = true | |
| 200 | if (this._sock) { | |
| 201 | this._failAll(new Error('client closed')) | |
| 202 | try { this._sock.destroy() } catch {} | |
| 203 | this._sock = null | |
| 204 | } | |
| 205 | } | |
| 206 | } | |
| 207 | ||
| 208 | async function udsReachable (path) { | |
| 209 | // Check the socket file exists and is actually a socket | |
| 210 | try { | |
| 211 | const s = await stat(path) | |
| 212 | if (!s.isSocket()) return false | |
| 213 | } catch { | |
| 214 | return false | |
| 215 | } | |
| 216 | // Try a real connect — covers stale sockets where the daemon crashed | |
| 217 | return new Promise((resolve) => { | |
| 218 | const sock = net.connect(path) | |
| 219 | sock.once('connect', () => { | |
| 220 | sock.destroy() | |
| 221 | resolve(true) | |
| 222 | }) | |
| 223 | sock.once('error', () => { | |
| 224 | sock.destroy() | |
| 225 | resolve(false) | |
| 226 | }) | |
| 227 | }) | |
| 228 | } |
| … | … | @@ -0,0 +1,62 @@ |
| 1 | /** | |
| 2 | * Length-prefixed JSON framing. | |
| 3 | * | |
| 4 | * Wire format: [4 bytes BE length][JSON payload]. | |
| 5 | * | |
| 6 | * One frame = one JSON object. Multiple frames can be pipelined on a single | |
| 7 | * stream — FrameDecoder handles incremental reads where a frame may arrive | |
| 8 | * across multiple data chunks. | |
| 9 | */ | |
| 10 | ||
| 11 | import b4a from 'b4a' | |
| 12 | ||
| 13 | const MAX_FRAME = 16 * 1024 * 1024 // 16 MiB hard cap to avoid runaway buffers | |
| 14 | ||
| 15 | export function encodeFrame (obj) { | |
| 16 | const json = JSON.stringify(obj) | |
| 17 | const payload = b4a.from(json, 'utf8') | |
| 18 | if (payload.length > MAX_FRAME) { | |
| 19 | throw new Error(`Frame too large: ${payload.length} > ${MAX_FRAME}`) | |
| 20 | } | |
| 21 | const frame = b4a.alloc(4 + payload.length) | |
| 22 | frame.writeUInt32BE(payload.length, 0) | |
| 23 | payload.copy(frame, 4) | |
| 24 | return frame | |
| 25 | } | |
| 26 | ||
| 27 | export class FrameDecoder { | |
| 28 | /** | |
| 29 | * @param {(obj: any) => void} onFrame - called once per complete frame | |
| 30 | */ | |
| 31 | constructor (onFrame) { | |
| 32 | if (typeof onFrame !== 'function') throw new Error('onFrame is required') | |
| 33 | this._onFrame = onFrame | |
| 34 | this._buf = b4a.alloc(0) | |
| 35 | } | |
| 36 | ||
| 37 | /** | |
| 38 | * Feed a chunk into the decoder. Calls onFrame for each complete frame. | |
| 39 | * Throws if a frame exceeds MAX_FRAME or contains invalid JSON. | |
| 40 | */ | |
| 41 | push (chunk) { | |
| 42 | this._buf = b4a.concat([this._buf, chunk]) | |
| 43 | ||
| 44 | while (this._buf.length >= 4) { | |
| 45 | const len = this._buf.readUInt32BE(0) | |
| 46 | if (len > MAX_FRAME) { | |
| 47 | throw new Error(`Frame too large: ${len} > ${MAX_FRAME}`) | |
| 48 | } | |
| 49 | if (this._buf.length < 4 + len) break // wait for more bytes | |
| 50 | ||
| 51 | const payload = this._buf.subarray(4, 4 + len) | |
| 52 | this._buf = this._buf.subarray(4 + len) | |
| 53 | ||
| 54 | const json = b4a.toString(payload, 'utf8') | |
| 55 | const obj = JSON.parse(json) // throws on bad JSON | |
| 56 | this._onFrame(obj) | |
| 57 | } | |
| 58 | } | |
| 59 | ||
| 60 | /** Number of bytes currently buffered (waiting for the rest of a frame). */ | |
| 61 | get pending () { return this._buf.length } | |
| 62 | } |
| … | … | @@ -0,0 +1,235 @@ |
| 1 | /** | |
| 2 | * Service — reusable per-app daemon library. | |
| 3 | * | |
| 4 | * One instance per app process. Owns: | |
| 5 | * - Generic RPC handler registry (svc.handle) | |
| 6 | * - Module mounts (svc.mount) with optional swarm replication | |
| 7 | * - UDS, HTTP, and Hyperswarm transport instances (whichever the consumer enables) | |
| 8 | * | |
| 9 | * The consumer's corestore is passed in (or one is implied via swarm.corestore). | |
| 10 | * The Service does NOT create or own storage — that's the app's job. | |
| 11 | * | |
| 12 | * Local-only RPC. Peers exchange data via swarm replication, never commands. | |
| 13 | * | |
| 14 | * Usage: | |
| 15 | * | |
| 16 | * const svc = new Service({ | |
| 17 | * name: 'pear-git', | |
| 18 | * corestore: store, | |
| 19 | * uds: '/tmp/pear-git.sock', | |
| 20 | * http: { port: 4040 }, | |
| 21 | * swarm: { topic: '...32 bytes hex...' } | |
| 22 | * }) | |
| 23 | * | |
| 24 | * svc.mount('orgs', new HyperOrgs(store, { namespace: 'orgs' })) | |
| 25 | * | |
| 26 | * svc.handle('list-orgs', async (ctx, params) => { | |
| 27 | * return svc.modules.orgs.list() | |
| 28 | * }) | |
| 29 | * | |
| 30 | * await svc.listen() | |
| 31 | */ | |
| 32 | ||
| 33 | import ReadyResource from 'ready-resource' | |
| 34 | import { UdsServer } from './transports/uds.js' | |
| 35 | import { HttpServer } from './transports/http.js' | |
| 36 | import { SwarmTransport } from './transports/swarm.js' | |
| 37 | ||
| 38 | class NoSuchMethodError extends Error { | |
| 39 | constructor (method) { | |
| 40 | super(`unknown method: ${method}`) | |
| 41 | this.code = 'ENOMETHOD' | |
| 42 | } | |
| 43 | } | |
| 44 | ||
| 45 | export class Service extends ReadyResource { | |
| 46 | /** | |
| 47 | * @param {object} opts | |
| 48 | * @param {string} opts.name - service name (used for default UDS path, logging) | |
| 49 | * @param {object} [opts.corestore] - required if swarm transport is enabled | |
| 50 | * @param {string} [opts.uds] - UDS socket path. Pass null/undefined to disable. | |
| 51 | * @param {object} [opts.http] - HTTP transport options { port, host }. Disable by omitting. | |
| 52 | * @param {object} [opts.swarm] - Swarm options { topic, swarm? }. Disable by omitting. | |
| 53 | */ | |
| 54 | constructor (opts = {}) { | |
| 55 | super() | |
| 56 | if (!opts.name) throw new Error('Service requires opts.name') | |
| 57 | ||
| 58 | this._name = opts.name | |
| 59 | this._corestore = opts.corestore || null | |
| 60 | this._handlers = new Map() | |
| 61 | this._mounts = new Map() | |
| 62 | ||
| 63 | this._udsOpt = opts.uds || null | |
| 64 | this._httpOpt = opts.http || null | |
| 65 | this._swarmOpt = opts.swarm || null | |
| 66 | ||
| 67 | this._uds = null | |
| 68 | this._http = null | |
| 69 | this._swarm = null | |
| 70 | ||
| 71 | if (this._swarmOpt && !this._corestore) { | |
| 72 | throw new Error('Service: swarm transport requires opts.corestore') | |
| 73 | } | |
| 74 | ||
| 75 | // Instantiate transports eagerly so apps can register HTTP routes, | |
| 76 | // grab the UDS path, etc., BEFORE calling listen(). | |
| 77 | const dispatch = (method, params, ctx) => this.dispatch(method, params, ctx) | |
| 78 | ||
| 79 | if (this._udsOpt) { | |
| 80 | this._uds = new UdsServer({ path: this._udsOpt, dispatch }) | |
| 81 | } | |
| 82 | if (this._httpOpt) { | |
| 83 | this._http = new HttpServer({ | |
| 84 | port: this._httpOpt.port, | |
| 85 | host: this._httpOpt.host, | |
| 86 | ctxFactory: this._httpOpt.ctxFactory, | |
| 87 | dispatch | |
| 88 | }) | |
| 89 | } | |
| 90 | if (this._swarmOpt) { | |
| 91 | this._swarm = new SwarmTransport({ | |
| 92 | topic: this._swarmOpt.topic, | |
| 93 | corestore: this._corestore, | |
| 94 | mounts: () => this._listMounts(), | |
| 95 | swarm: this._swarmOpt.swarm, | |
| 96 | server: this._swarmOpt.server, | |
| 97 | client: this._swarmOpt.client | |
| 98 | }) | |
| 99 | } | |
| 100 | } | |
| 101 | ||
| 102 | // ── Properties ────────────────────────────────────────────── | |
| 103 | ||
| 104 | get name () { return this._name } | |
| 105 | get corestore () { return this._corestore } | |
| 106 | ||
| 107 | /** Read-only handle to the UDS transport (or null if disabled). */ | |
| 108 | get uds () { return this._uds } | |
| 109 | ||
| 110 | /** Read-only handle to the HTTP transport (or null if disabled). */ | |
| 111 | get http () { return this._http } | |
| 112 | ||
| 113 | /** Read-only handle to the swarm transport (or null if disabled). */ | |
| 114 | get swarm () { return this._swarm } | |
| 115 | ||
| 116 | /** | |
| 117 | * Mounted modules, accessible by name. Use `svc.modules.orgs` after | |
| 118 | * `svc.mount('orgs', orgsInstance)`. | |
| 119 | */ | |
| 120 | get modules () { | |
| 121 | // Lazily build a property-style accessor object on top of the map | |
| 122 | if (!this._modulesProxy) { | |
| 123 | const map = this._mounts | |
| 124 | this._modulesProxy = new Proxy({}, { | |
| 125 | get (_target, prop) { | |
| 126 | const m = map.get(prop) | |
| 127 | return m ? m.instance : undefined | |
| 128 | }, | |
| 129 | has (_target, prop) { | |
| 130 | return map.has(prop) | |
| 131 | }, | |
| 132 | ownKeys () { | |
| 133 | return Array.from(map.keys()) | |
| 134 | }, | |
| 135 | getOwnPropertyDescriptor (_target, prop) { | |
| 136 | if (map.has(prop)) { | |
| 137 | return { enumerable: true, configurable: true, value: map.get(prop).instance } | |
| 138 | } | |
| 139 | } | |
| 140 | }) | |
| 141 | } | |
| 142 | return this._modulesProxy | |
| 143 | } | |
| 144 | ||
| 145 | /** Map iteration: list mount entries as { name, instance }. */ | |
| 146 | _listMounts () { | |
| 147 | const out = [] | |
| 148 | for (const [name, m] of this._mounts) out.push({ name, instance: m.instance }) | |
| 149 | return out | |
| 150 | } | |
| 151 | ||
| 152 | // ── Registration ──────────────────────────────────────────── | |
| 153 | ||
| 154 | /** | |
| 155 | * Register an RPC handler. The handler is invoked from any RPC transport | |
| 156 | * with `(ctx, params)` and should return a JSON-serializable result. | |
| 157 | * | |
| 158 | * @param {string} name - method name (e.g. "list-orgs") | |
| 159 | * @param {(ctx: object, params: any) => Promise<any>|any} fn | |
| 160 | */ | |
| 161 | handle (name, fn) { | |
| 162 | if (typeof name !== 'string' || !name) throw new Error('handle requires a name') | |
| 163 | if (typeof fn !== 'function') throw new Error('handle requires a function') | |
| 164 | if (this._handlers.has(name)) throw new Error(`handler "${name}" already registered`) | |
| 165 | this._handlers.set(name, fn) | |
| 166 | } | |
| 167 | ||
| 168 | /** Whether a handler is registered. */ | |
| 169 | hasHandler (name) { return this._handlers.has(name) } | |
| 170 | ||
| 171 | /** List registered handler names. */ | |
| 172 | listHandlers () { return Array.from(this._handlers.keys()).sort() } | |
| 173 | ||
| 174 | /** | |
| 175 | * Mount a module instance. Modules are accessible via `svc.modules.<name>`. | |
| 176 | * If the module exposes a `.base` (Autobase consumer) and the swarm | |
| 177 | * transport is enabled, the base is replicated on every swarm connection. | |
| 178 | * | |
| 179 | * @param {string} name | |
| 180 | * @param {object} instance | |
| 181 | */ | |
| 182 | mount (name, instance) { | |
| 183 | if (typeof name !== 'string' || !name) throw new Error('mount requires a name') | |
| 184 | if (!instance) throw new Error('mount requires an instance') | |
| 185 | if (this._mounts.has(name)) throw new Error(`mount "${name}" already registered`) | |
| 186 | this._mounts.set(name, { instance }) | |
| 187 | } | |
| 188 | ||
| 189 | /** Whether a module is mounted. */ | |
| 190 | hasMount (name) { return this._mounts.has(name) } | |
| 191 | ||
| 192 | /** List mounted module names. */ | |
| 193 | listMounts () { return Array.from(this._mounts.keys()).sort() } | |
| 194 | ||
| 195 | // ── Dispatch ──────────────────────────────────────────────── | |
| 196 | ||
| 197 | /** | |
| 198 | * Dispatch a method call to its handler. Used by all RPC transports. | |
| 199 | * Throws NoSuchMethodError (code: 'ENOMETHOD') if the method isn't registered. | |
| 200 | */ | |
| 201 | async dispatch (method, params, ctx) { | |
| 202 | const handler = this._handlers.get(method) | |
| 203 | if (!handler) throw new NoSuchMethodError(method) | |
| 204 | return handler(ctx || {}, params) | |
| 205 | } | |
| 206 | ||
| 207 | // ── Lifecycle ─────────────────────────────────────────────── | |
| 208 | ||
| 209 | async _open () { | |
| 210 | if (this._uds) await this._uds.listen() | |
| 211 | if (this._http) await this._http.listen() | |
| 212 | if (this._swarm) await this._swarm.listen() | |
| 213 | } | |
| 214 | ||
| 215 | /** | |
| 216 | * Alias for `ready()` — feels more natural for a daemon ("start listening"). | |
| 217 | */ | |
| 218 | async listen () { | |
| 219 | await this.ready() | |
| 220 | } | |
| 221 | ||
| 222 | async _close () { | |
| 223 | const errors = [] | |
| 224 | if (this._swarm) try { await this._swarm.close() } catch (e) { errors.push(e) } | |
| 225 | if (this._http) try { await this._http.close() } catch (e) { errors.push(e) } | |
| 226 | if (this._uds) try { await this._uds.close() } catch (e) { errors.push(e) } | |
| 227 | this._swarm = null | |
| 228 | this._http = null | |
| 229 | this._uds = null | |
| 230 | if (errors.length) { | |
| 231 | const msg = errors.map(e => e.message).join('; ') | |
| 232 | throw new Error('Service close errors: ' + msg) | |
| 233 | } | |
| 234 | } | |
| 235 | } |
| … | … | @@ -0,0 +1,181 @@ |
| 1 | /** | |
| 2 | * HTTP transport for hyper-svc. | |
| 3 | * | |
| 4 | * Local-only RPC. Browsers (or curl, or any HTTP client) on the same machine | |
| 5 | * POST a JSON body to /rpc/<method> and receive a JSON response. | |
| 6 | * | |
| 7 | * Wire protocol: | |
| 8 | * POST /rpc/<method> | |
| 9 | * Content-Type: application/json | |
| 10 | * { ...params... } | |
| 11 | * | |
| 12 | * → 200 application/json { result: ... } | |
| 13 | * 400 application/json { error: ... } (bad request / handler threw) | |
| 14 | * 404 application/json { error: 'unknown method' } | |
| 15 | * | |
| 16 | * Other routes are NOT handled by hyper-svc — apps wire their own SSR pages | |
| 17 | * via the underlying http.Server (exposed as `.server` on this class). Any | |
| 18 | * request not matching POST /rpc/<method> falls through to user-registered | |
| 19 | * 'request' listeners or, if none, returns 404. | |
| 20 | */ | |
| 21 | ||
| 22 | import http from 'http' | |
| 23 | import b4a from 'b4a' | |
| 24 | ||
| 25 | export class HttpServer { | |
| 26 | /** | |
| 27 | * @param {object} opts | |
| 28 | * @param {number} [opts.port=0] - 0 = random port | |
| 29 | * @param {string} [opts.host='127.0.0.1'] - bind address (local-only by default) | |
| 30 | * @param {(method: string, params: any, ctx: object) => Promise<any>} opts.dispatch - request handler | |
| 31 | */ | |
| 32 | constructor (opts = {}) { | |
| 33 | if (typeof opts.dispatch !== 'function') throw new Error('HttpServer requires opts.dispatch') | |
| 34 | ||
| 35 | this._port = opts.port || 0 | |
| 36 | this._host = opts.host || '127.0.0.1' | |
| 37 | this._dispatch = opts.dispatch | |
| 38 | // Optional consumer-provided ctx builder. Called per /rpc/* request to | |
| 39 | // populate fields like identity from request headers/cookies. Receives | |
| 40 | // (req) and may return either an object or a Promise<object>. The | |
| 41 | // returned fields are merged into the default ctx ({ transport: 'http', | |
| 42 | // identity: null, req, res }). | |
| 43 | this._ctxFactory = typeof opts.ctxFactory === 'function' ? opts.ctxFactory : null | |
| 44 | this._listening = false | |
| 45 | ||
| 46 | // Create the server eagerly so apps can register routes BEFORE listen(). | |
| 47 | // Our /rpc/* listener is installed first so it has first dibs on requests. | |
| 48 | this._server = http.createServer() | |
| 49 | this._server.on('request', (req, res) => this._onRequest(req, res)) | |
| 50 | } | |
| 51 | ||
| 52 | /** | |
| 53 | * Update or set the ctxFactory after construction. Useful when the | |
| 54 | * factory needs references that don't exist at constructor time | |
| 55 | * (e.g. session stores created by the consumer in a later step). | |
| 56 | */ | |
| 57 | setCtxFactory (fn) { | |
| 58 | if (fn != null && typeof fn !== 'function') { | |
| 59 | throw new Error('ctxFactory must be a function or null') | |
| 60 | } | |
| 61 | this._ctxFactory = fn | |
| 62 | } | |
| 63 | ||
| 64 | /** Underlying http.Server. Apps register their own routes here. */ | |
| 65 | get server () { return this._server } | |
| 66 | get listening () { return this._listening } | |
| 67 | get port () { | |
| 68 | if (!this._listening || !this._server) return null | |
| 69 | const addr = this._server.address() | |
| 70 | return addr ? addr.port : null | |
| 71 | } | |
| 72 | get host () { return this._host } | |
| 73 | ||
| 74 | async listen () { | |
| 75 | if (this._listening) return | |
| 76 | ||
| 77 | await new Promise((resolve, reject) => { | |
| 78 | this._server.once('error', reject) | |
| 79 | this._server.listen(this._port, this._host, () => { | |
| 80 | this._server.removeListener('error', reject) | |
| 81 | resolve() | |
| 82 | }) | |
| 83 | }) | |
| 84 | ||
| 85 | this._listening = true | |
| 86 | } | |
| 87 | ||
| 88 | async _onRequest (req, res) { | |
| 89 | if (req.method === 'POST' && req.url.startsWith('/rpc/')) { | |
| 90 | const method = req.url.slice('/rpc/'.length).split('?')[0] | |
| 91 | let body | |
| 92 | try { | |
| 93 | body = await collectBody(req) | |
| 94 | } catch (err) { | |
| 95 | return jsonError(res, 400, 'failed to read body: ' + err.message) | |
| 96 | } | |
| 97 | ||
| 98 | let params = {} | |
| 99 | if (body.length > 0) { | |
| 100 | try { | |
| 101 | params = JSON.parse(b4a.toString(body, 'utf8')) | |
| 102 | } catch { | |
| 103 | return jsonError(res, 400, 'request body must be JSON') | |
| 104 | } | |
| 105 | } | |
| 106 | ||
| 107 | let ctx = { | |
| 108 | transport: 'http', | |
| 109 | identity: null, // anonymous by default — apps populate via ctxFactory | |
| 110 | req, | |
| 111 | res | |
| 112 | } | |
| 113 | ||
| 114 | if (this._ctxFactory) { | |
| 115 | try { | |
| 116 | const extra = await this._ctxFactory(req) | |
| 117 | if (extra && typeof extra === 'object') { | |
| 118 | ctx = { ...ctx, ...extra } | |
| 119 | } | |
| 120 | } catch (err) { | |
| 121 | return jsonError(res, 500, 'ctxFactory failed: ' + (err.message || String(err))) | |
| 122 | } | |
| 123 | } | |
| 124 | ||
| 125 | try { | |
| 126 | const result = await this._dispatch(method, params, ctx) | |
| 127 | if (result === undefined) { | |
| 128 | return jsonOk(res, { result: null }) | |
| 129 | } | |
| 130 | return jsonOk(res, { result }) | |
| 131 | } catch (err) { | |
| 132 | if (err && err.code === 'ENOMETHOD') { | |
| 133 | return jsonError(res, 404, err.message) | |
| 134 | } | |
| 135 | return jsonError(res, 400, err.message || String(err)) | |
| 136 | } | |
| 137 | } | |
| 138 | ||
| 139 | // Not an /rpc/ request. App-owned routes live in OTHER 'request' | |
| 140 | // listeners (registered on this._server by the consumer). If at least | |
| 141 | // one other listener is present, leave the response to them — they're | |
| 142 | // responsible for handling and 404-ing as needed. | |
| 143 | // | |
| 144 | // If we're the ONLY 'request' listener, nobody else will handle this, | |
| 145 | // so we 404 it synchronously. Doing this synchronously avoids a race | |
| 146 | // where setImmediate fires before an async user handler can respond. | |
| 147 | if (this._server.listenerCount('request') <= 1) { | |
| 148 | res.writeHead(404, { 'Content-Type': 'text/plain' }) | |
| 149 | res.end('Not found') | |
| 150 | } | |
| 151 | // Otherwise: leave the response to the user's listener. | |
| 152 | } | |
| 153 | ||
| 154 | async close () { | |
| 155 | if (!this._server || !this._listening) { | |
| 156 | this._listening = false | |
| 157 | return | |
| 158 | } | |
| 159 | await new Promise((resolve) => this._server.close(() => resolve())) | |
| 160 | this._listening = false | |
| 161 | } | |
| 162 | } | |
| 163 | ||
| 164 | function collectBody (req) { | |
| 165 | return new Promise((resolve, reject) => { | |
| 166 | const parts = [] | |
| 167 | req.on('data', (c) => parts.push(c)) | |
| 168 | req.on('end', () => resolve(b4a.concat(parts))) | |
| 169 | req.on('error', reject) | |
| 170 | }) | |
| 171 | } | |
| 172 | ||
| 173 | function jsonOk (res, body) { | |
| 174 | res.writeHead(200, { 'Content-Type': 'application/json' }) | |
| 175 | res.end(JSON.stringify(body)) | |
| 176 | } | |
| 177 | ||
| 178 | function jsonError (res, status, message) { | |
| 179 | res.writeHead(status, { 'Content-Type': 'application/json' }) | |
| 180 | res.end(JSON.stringify({ error: message })) | |
| 181 | } |
| … | … | @@ -0,0 +1,142 @@ |
| 1 | /** | |
| 2 | * Hyperswarm transport for hyper-svc. | |
| 3 | * | |
| 4 | * Joins a topic, and on every connection: | |
| 5 | * 1. corestore.replicate(conn) — replicates all hypercores in the corestore | |
| 6 | * 2. for each mounted module exposing a `.base` (Autobase consumer), also | |
| 7 | * base.replicate(conn) — belt-and-suspenders against the autobase | |
| 8 | * replication gotcha (corestore.replicate alone is unreliable in tests | |
| 9 | * and we don't want to chase that bug in production) | |
| 10 | * 3. for each mounted module exposing `attachProtocol(mux)` (mux-based | |
| 11 | * protocols like hyper-overlay), lazily build `Protomux.from(conn)` | |
| 12 | * once per connection and call attachProtocol on each consumer. | |
| 13 | * Protomux.from reuses any protomux already attached by corestore/autobase | |
| 14 | * replication, so channels multiplex cleanly over the same transport. | |
| 15 | * | |
| 16 | * No RPC over swarm today — modules communicate either via replication or | |
| 17 | * via their own mux channels. Adding a new mux-based protocol only requires | |
| 18 | * exposing `attachProtocol(mux)` on the mounted module. | |
| 19 | */ | |
| 20 | ||
| 21 | import Hyperswarm from 'hyperswarm' | |
| 22 | import Protomux from 'protomux' | |
| 23 | import b4a from 'b4a' | |
| 24 | ||
| 25 | export class SwarmTransport { | |
| 26 | /** | |
| 27 | * @param {object} opts | |
| 28 | * @param {Buffer|string} opts.topic - 32-byte topic to join (hex string or buffer) | |
| 29 | * @param {object} opts.corestore - corestore to replicate | |
| 30 | * @param {() => Iterable<{ name: string, instance: object }>} opts.mounts - returns the current mount list (called on each connection so late mounts work) | |
| 31 | * @param {object} [opts.swarm] - hyperswarm options (e.g., bootstrap nodes for testnet) | |
| 32 | * @param {boolean} [opts.server=true] | |
| 33 | * @param {boolean} [opts.client=true] | |
| 34 | */ | |
| 35 | constructor (opts = {}) { | |
| 36 | if (!opts.topic) throw new Error('SwarmTransport requires opts.topic') | |
| 37 | if (!opts.corestore) throw new Error('SwarmTransport requires opts.corestore') | |
| 38 | if (typeof opts.mounts !== 'function') throw new Error('SwarmTransport requires opts.mounts (function)') | |
| 39 | ||
| 40 | this._topic = typeof opts.topic === 'string' ? b4a.from(opts.topic, 'hex') : opts.topic | |
| 41 | if (this._topic.length !== 32) throw new Error('topic must be 32 bytes') | |
| 42 | ||
| 43 | this._corestore = opts.corestore | |
| 44 | this._getMounts = opts.mounts | |
| 45 | this._swarmOpts = opts.swarm || {} | |
| 46 | this._joinAsServer = opts.server !== false | |
| 47 | this._joinAsClient = opts.client !== false | |
| 48 | ||
| 49 | this._swarm = null | |
| 50 | this._discovery = null | |
| 51 | this._listening = false | |
| 52 | this._connections = new Set() | |
| 53 | } | |
| 54 | ||
| 55 | get topic () { return this._topic } | |
| 56 | get listening () { return this._listening } | |
| 57 | get swarm () { return this._swarm } | |
| 58 | get connections () { return this._connections.size } | |
| 59 | ||
| 60 | async listen () { | |
| 61 | if (this._listening) return | |
| 62 | ||
| 63 | this._swarm = new Hyperswarm(this._swarmOpts) | |
| 64 | this._swarm.on('connection', (conn, info) => this._onConnection(conn, info)) | |
| 65 | ||
| 66 | this._discovery = this._swarm.join(this._topic, { | |
| 67 | server: this._joinAsServer, | |
| 68 | client: this._joinAsClient | |
| 69 | }) | |
| 70 | await this._discovery.flushed() | |
| 71 | this._listening = true | |
| 72 | } | |
| 73 | ||
| 74 | _onConnection (conn, info) { | |
| 75 | this._connections.add(conn) | |
| 76 | conn.on('close', () => this._connections.delete(conn)) | |
| 77 | conn.on('error', () => { /* connection errors are routine — ignore */ }) | |
| 78 | ||
| 79 | // Always replicate the corestore. Hypercore handles muxing internally. | |
| 80 | try { | |
| 81 | this._corestore.replicate(conn) | |
| 82 | } catch (err) { | |
| 83 | // Don't crash on replication setup errors — log via stderr | |
| 84 | // eslint-disable-next-line no-console | |
| 85 | console.error('[hyper-svc/swarm] corestore.replicate failed:', err.message) | |
| 86 | } | |
| 87 | ||
| 88 | // Belt and suspenders: also call base.replicate on every mounted Autobase | |
| 89 | // consumer. This is the documented workaround for the test-time gotcha | |
| 90 | // where corestore.replicate alone doesn't propagate writer cores. | |
| 91 | // | |
| 92 | // In the same pass, wire up mux-based protocols: any mount exposing | |
| 93 | // `attachProtocol(mux)` (hyper-overlay and future consumers) gets attached | |
| 94 | // to a single Protomux per connection. Protomux.from reuses any protomux | |
| 95 | // corestore/autobase replication already set up. | |
| 96 | let mux = null | |
| 97 | for (const { name, instance } of this._getMounts()) { | |
| 98 | if (!instance) continue | |
| 99 | ||
| 100 | const base = instance.base | |
| 101 | if (base && typeof base.replicate === 'function') { | |
| 102 | try { | |
| 103 | base.replicate(conn) | |
| 104 | } catch (err) { | |
| 105 | // eslint-disable-next-line no-console | |
| 106 | console.error(`[hyper-svc/swarm] ${name}.base.replicate failed:`, err.message) | |
| 107 | } | |
| 108 | } | |
| 109 | ||
| 110 | if (typeof instance.attachProtocol === 'function') { | |
| 111 | if (!mux) { | |
| 112 | try { | |
| 113 | mux = Protomux.from(conn) | |
| 114 | } catch (err) { | |
| 115 | // eslint-disable-next-line no-console | |
| 116 | console.error('[hyper-svc/swarm] Protomux.from failed:', err.message) | |
| 117 | } | |
| 118 | } | |
| 119 | if (mux) { | |
| 120 | try { | |
| 121 | instance.attachProtocol(mux) | |
| 122 | } catch (err) { | |
| 123 | // eslint-disable-next-line no-console | |
| 124 | console.error(`[hyper-svc/swarm] ${name}.attachProtocol failed:`, err.message) | |
| 125 | } | |
| 126 | } | |
| 127 | } | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | async close () { | |
| 132 | if (!this._swarm) return | |
| 133 | for (const conn of this._connections) { | |
| 134 | try { conn.destroy() } catch {} | |
| 135 | } | |
| 136 | this._connections.clear() | |
| 137 | try { await this._swarm.destroy() } catch {} | |
| 138 | this._swarm = null | |
| 139 | this._discovery = null | |
| 140 | this._listening = false | |
| 141 | } | |
| 142 | } |
| … | … | @@ -0,0 +1,145 @@ |
| 1 | /** | |
| 2 | * UDS (Unix Domain Socket) transport for hyper-svc. | |
| 3 | * | |
| 4 | * Local-only RPC. Trusts the local user. CLIs on the same machine connect | |
| 5 | * to ~/.local/share/<name>/svc.sock (or wherever the daemon was told to bind) | |
| 6 | * and exchange length-prefixed JSON frames. | |
| 7 | * | |
| 8 | * Wire protocol per connection: | |
| 9 | * client → server: { id, method, params } | |
| 10 | * server → client: { id, result } OR { id, error } | |
| 11 | * | |
| 12 | * Multiple in-flight requests are matched by id. Server is fully async — | |
| 13 | * a slow handler doesn't block other requests on the same connection. | |
| 14 | */ | |
| 15 | ||
| 16 | import net from 'net' | |
| 17 | import { unlink, stat } from 'fs/promises' | |
| 18 | import { encodeFrame, FrameDecoder } from '../frame.js' | |
| 19 | ||
| 20 | export class UdsServer { | |
| 21 | /** | |
| 22 | * @param {object} opts | |
| 23 | * @param {string} opts.path - filesystem path for the UNIX socket | |
| 24 | * @param {(method: string, params: any, ctx: object) => Promise<any>} opts.dispatch - request handler | |
| 25 | */ | |
| 26 | constructor (opts = {}) { | |
| 27 | if (!opts.path) throw new Error('UdsServer requires opts.path') | |
| 28 | if (typeof opts.dispatch !== 'function') throw new Error('UdsServer requires opts.dispatch') | |
| 29 | ||
| 30 | this._path = opts.path | |
| 31 | this._dispatch = opts.dispatch | |
| 32 | this._server = null | |
| 33 | this._connections = new Set() | |
| 34 | this._listening = false | |
| 35 | } | |
| 36 | ||
| 37 | get path () { return this._path } | |
| 38 | get listening () { return this._listening } | |
| 39 | ||
| 40 | async listen () { | |
| 41 | if (this._listening) return | |
| 42 | ||
| 43 | // Best-effort cleanup of a stale socket file (server crashed without unlinking). | |
| 44 | // ONLY remove if it's actually a socket — never touch regular files / dirs. | |
| 45 | try { | |
| 46 | const s = await stat(this._path) | |
| 47 | if (s.isSocket()) { | |
| 48 | // Try to connect first — if a daemon is already running, refuse to clobber. | |
| 49 | const inUse = await this._probe() | |
| 50 | if (inUse) throw new Error(`socket ${this._path} is already in use`) | |
| 51 | await unlink(this._path) | |
| 52 | } | |
| 53 | } catch (err) { | |
| 54 | if (err.code !== 'ENOENT' && err.message.indexOf('already in use') === -1) { | |
| 55 | // stat failed for some reason other than missing — ignore, let listen() fail naturally | |
| 56 | } | |
| 57 | if (err.message && err.message.indexOf('already in use') !== -1) throw err | |
| 58 | } | |
| 59 | ||
| 60 | this._server = net.createServer((sock) => this._onConnection(sock)) | |
| 61 | ||
| 62 | await new Promise((resolve, reject) => { | |
| 63 | this._server.once('error', reject) | |
| 64 | this._server.listen(this._path, () => { | |
| 65 | this._server.removeListener('error', reject) | |
| 66 | resolve() | |
| 67 | }) | |
| 68 | }) | |
| 69 | ||
| 70 | this._listening = true | |
| 71 | } | |
| 72 | ||
| 73 | _probe () { | |
| 74 | return new Promise((resolve) => { | |
| 75 | const sock = net.connect(this._path) | |
| 76 | sock.once('connect', () => { | |
| 77 | sock.destroy() | |
| 78 | resolve(true) | |
| 79 | }) | |
| 80 | sock.once('error', () => { | |
| 81 | sock.destroy() | |
| 82 | resolve(false) | |
| 83 | }) | |
| 84 | }) | |
| 85 | } | |
| 86 | ||
| 87 | _onConnection (sock) { | |
| 88 | this._connections.add(sock) | |
| 89 | sock.on('close', () => this._connections.delete(sock)) | |
| 90 | ||
| 91 | const decoder = new FrameDecoder((req) => this._handleRequest(sock, req)) | |
| 92 | ||
| 93 | sock.on('data', (chunk) => { | |
| 94 | try { | |
| 95 | decoder.push(chunk) | |
| 96 | } catch (err) { | |
| 97 | // Bad frame — close the connection rather than try to recover | |
| 98 | try { sock.destroy(err) } catch {} | |
| 99 | } | |
| 100 | }) | |
| 101 | ||
| 102 | sock.on('error', () => { /* swallow — close handler runs anyway */ }) | |
| 103 | } | |
| 104 | ||
| 105 | async _handleRequest (sock, req) { | |
| 106 | const id = req && req.id | |
| 107 | if (typeof id !== 'number' && typeof id !== 'string') { | |
| 108 | // Malformed — best effort error response, but the client probably can't match it | |
| 109 | try { | |
| 110 | sock.write(encodeFrame({ id: null, error: 'request missing id' })) | |
| 111 | } catch {} | |
| 112 | return | |
| 113 | } | |
| 114 | ||
| 115 | const ctx = { | |
| 116 | transport: 'uds', | |
| 117 | identity: { type: 'local', userId: process.env.USER || null, trust: 'local' } | |
| 118 | } | |
| 119 | ||
| 120 | try { | |
| 121 | const result = await this._dispatch(req.method, req.params, ctx) | |
| 122 | try { | |
| 123 | sock.write(encodeFrame({ id, result })) | |
| 124 | } catch {} | |
| 125 | } catch (err) { | |
| 126 | try { | |
| 127 | sock.write(encodeFrame({ id, error: err.message || String(err) })) | |
| 128 | } catch {} | |
| 129 | } | |
| 130 | } | |
| 131 | ||
| 132 | async close () { | |
| 133 | if (!this._server) return | |
| 134 | for (const sock of this._connections) { | |
| 135 | try { sock.destroy() } catch {} | |
| 136 | } | |
| 137 | this._connections.clear() | |
| 138 | await new Promise((resolve) => this._server.close(() => resolve())) | |
| 139 | this._server = null | |
| 140 | this._listening = false | |
| 141 | ||
| 142 | // Clean up the socket file (best-effort) | |
| 143 | try { await unlink(this._path) } catch {} | |
| 144 | } | |
| 145 | } |
| … | … | @@ -0,0 +1,1592 @@ |
| 1 | { | |
| 2 | "name": "hyper-svc", | |
| 3 | "version": "0.1.0", | |
| 4 | "lockfileVersion": 3, | |
| 5 | "requires": true, | |
| 6 | "packages": { | |
| 7 | "": { | |
| 8 | "name": "hyper-svc", | |
| 9 | "version": "0.1.0", | |
| 10 | "dependencies": { | |
| 11 | "b4a": "^1.6.0", | |
| 12 | "hyperswarm": "^4.0.0", | |
| 13 | "ready-resource": "^1.0.0" | |
| 14 | }, | |
| 15 | "devDependencies": { | |
| 16 | "@hyperswarm/testnet": "^3.0.0", | |
| 17 | "brittle": "^3.0.0", | |
| 18 | "corestore": "^7.0.0", | |
| 19 | "hyper-rooms": "file:../hyper-rooms" | |
| 20 | } | |
| 21 | }, | |
| 22 | "../hyper-rooms": { | |
| 23 | "version": "0.1.0", | |
| 24 | "dev": true, | |
| 25 | "dependencies": { | |
| 26 | "autobase": "^7.20.0", | |
| 27 | "b4a": "^1.6.0", | |
| 28 | "hyperbee": "^2.20.0", | |
| 29 | "hypercore-crypto": "^3.4.0", | |
| 30 | "ready-resource": "^1.1.0" | |
| 31 | }, | |
| 32 | "devDependencies": { | |
| 33 | "autobase-test-helpers": "^2.0.0", | |
| 34 | "brittle": "^3.0.0", | |
| 35 | "corestore": "^7.0.0" | |
| 36 | } | |
| 37 | }, | |
| 38 | "node_modules/@hyperswarm/secret-stream": { | |
| 39 | "version": "6.9.1", | |
| 40 | "resolved": "https://registry.npmjs.org/@hyperswarm/secret-stream/-/secret-stream-6.9.1.tgz", | |
| 41 | "integrity": "sha512-xb0S5y3YJwBakD77JOGBHlBxdp63mHClZoXBYoLv+9wH8e054ESKlmQptWqjJK5dv5VMUIVYOJB4MaOpB0JdGw==", | |
| 42 | "license": "Apache-2.0", | |
| 43 | "dependencies": { | |
| 44 | "b4a": "^1.1.0", | |
| 45 | "hypercore-crypto": "^3.3.1", | |
| 46 | "noise-curve-ed": "^2.0.1", | |
| 47 | "noise-handshake": "^4.0.0", | |
| 48 | "sodium-secretstream": "^1.1.0", | |
| 49 | "sodium-universal": "^5.0.0", | |
| 50 | "streamx": "^2.14.0", | |
| 51 | "timeout-refresh": "^2.0.0", | |
| 52 | "unslab": "^1.3.0" | |
| 53 | } | |
| 54 | }, | |
| 55 | "node_modules/@hyperswarm/testnet": { | |
| 56 | "version": "3.1.4", | |
| 57 | "resolved": "https://registry.npmjs.org/@hyperswarm/testnet/-/testnet-3.1.4.tgz", | |
| 58 | "integrity": "sha512-Jw8JIZ9kdZ4R9jwJHsqctj5YeRqecw3aeCg2NS12HBXCqMioQIYzVqHs/8BWdOQCf34+BEXkwMQjba9CzaLwkg==", | |
| 59 | "dev": true, | |
| 60 | "license": "Apache-2.0", | |
| 61 | "dependencies": { | |
| 62 | "hyperdht": "^6.6.0" | |
| 63 | }, | |
| 64 | "bin": { | |
| 65 | "hyperswarm-testnet": "bin.js" | |
| 66 | } | |
| 67 | }, | |
| 68 | "node_modules/@jridgewell/resolve-uri": { | |
| 69 | "version": "3.1.2", | |
| 70 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", | |
| 71 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", | |
| 72 | "dev": true, | |
| 73 | "license": "MIT", | |
| 74 | "engines": { | |
| 75 | "node": ">=6.0.0" | |
| 76 | } | |
| 77 | }, | |
| 78 | "node_modules/@jridgewell/sourcemap-codec": { | |
| 79 | "version": "1.5.5", | |
| 80 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", | |
| 81 | "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", | |
| 82 | "dev": true, | |
| 83 | "license": "MIT" | |
| 84 | }, | |
| 85 | "node_modules/@jridgewell/trace-mapping": { | |
| 86 | "version": "0.3.31", | |
| 87 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", | |
| 88 | "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", | |
| 89 | "dev": true, | |
| 90 | "license": "MIT", | |
| 91 | "dependencies": { | |
| 92 | "@jridgewell/resolve-uri": "^3.1.0", | |
| 93 | "@jridgewell/sourcemap-codec": "^1.4.14" | |
| 94 | } | |
| 95 | }, | |
| 96 | "node_modules/@types/istanbul-lib-coverage": { | |
| 97 | "version": "2.0.6", | |
| 98 | "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", | |
| 99 | "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", | |
| 100 | "dev": true, | |
| 101 | "license": "MIT" | |
| 102 | }, | |
| 103 | "node_modules/adaptive-timeout": { | |
| 104 | "version": "1.0.1", | |
| 105 | "resolved": "https://registry.npmjs.org/adaptive-timeout/-/adaptive-timeout-1.0.1.tgz", | |
| 106 | "integrity": "sha512-+seGiBtHqbnyZ0Quq/ZGxxwP66153WBABawa07/QeyuPFzbduPBjiGQAyCiriiLDzt3dkbMBskSlfqtRwblK8Q==", | |
| 107 | "license": "Apache-2.0", | |
| 108 | "dependencies": { | |
| 109 | "xache": "^1.2.1" | |
| 110 | } | |
| 111 | }, | |
| 112 | "node_modules/b4a": { | |
| 113 | "version": "1.8.0", | |
| 114 | "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", | |
| 115 | "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", | |
| 116 | "license": "Apache-2.0", | |
| 117 | "peerDependencies": { | |
| 118 | "react-native-b4a": "*" | |
| 119 | }, | |
| 120 | "peerDependenciesMeta": { | |
| 121 | "react-native-b4a": { | |
| 122 | "optional": true | |
| 123 | } | |
| 124 | } | |
| 125 | }, | |
| 126 | "node_modules/bare-abort": { | |
| 127 | "version": "2.0.13", | |
| 128 | "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz", | |
| 129 | "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==", | |
| 130 | "dev": true, | |
| 131 | "license": "Apache-2.0" | |
| 132 | }, | |
| 133 | "node_modules/bare-addon-resolve": { | |
| 134 | "version": "1.10.0", | |
| 135 | "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", | |
| 136 | "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", | |
| 137 | "license": "Apache-2.0", | |
| 138 | "dependencies": { | |
| 139 | "bare-module-resolve": "^1.10.0", | |
| 140 | "bare-semver": "^1.0.0" | |
| 141 | }, | |
| 142 | "peerDependencies": { | |
| 143 | "bare-url": "*" | |
| 144 | }, | |
| 145 | "peerDependenciesMeta": { | |
| 146 | "bare-url": { | |
| 147 | "optional": true | |
| 148 | } | |
| 149 | } | |
| 150 | }, | |
| 151 | "node_modules/bare-ansi-escapes": { | |
| 152 | "version": "2.2.3", | |
| 153 | "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz", | |
| 154 | "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==", | |
| 155 | "license": "Apache-2.0", | |
| 156 | "dependencies": { | |
| 157 | "bare-stream": "^2.6.5" | |
| 158 | }, | |
| 159 | "peerDependencies": { | |
| 160 | "bare-buffer": "*" | |
| 161 | }, | |
| 162 | "peerDependenciesMeta": { | |
| 163 | "bare-buffer": { | |
| 164 | "optional": true | |
| 165 | } | |
| 166 | } | |
| 167 | }, | |
| 168 | "node_modules/bare-assert": { | |
| 169 | "version": "1.2.0", | |
| 170 | "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz", | |
| 171 | "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==", | |
| 172 | "license": "Apache-2.0", | |
| 173 | "dependencies": { | |
| 174 | "bare-inspect": "^3.1.2" | |
| 175 | } | |
| 176 | }, | |
| 177 | "node_modules/bare-bundle": { | |
| 178 | "version": "1.10.0", | |
| 179 | "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.10.0.tgz", | |
| 180 | "integrity": "sha512-4LVlnJAHr00Hh6Vu6ZUJS38rcEtJT3b3vChXSsBsJ2mk1TN0lQ+gzd+Dw5L0aV7uqDZv84smuwW+O02X7PfDlw==", | |
| 181 | "dev": true, | |
| 182 | "license": "Apache-2.0", | |
| 183 | "peerDependencies": { | |
| 184 | "bare-buffer": "*", | |
| 185 | "bare-url": "*" | |
| 186 | }, | |
| 187 | "peerDependenciesMeta": { | |
| 188 | "bare-buffer": { | |
| 189 | "optional": true | |
| 190 | }, | |
| 191 | "bare-url": { | |
| 192 | "optional": true | |
| 193 | } | |
| 194 | } | |
| 195 | }, | |
| 196 | "node_modules/bare-cov": { | |
| 197 | "version": "1.2.1", | |
| 198 | "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.1.tgz", | |
| 199 | "integrity": "sha512-SHNqb/WG9udUEj8m5qGSehoUQ9o0Xzv8nrf4HIdmG+KNU/XdhPLhTP5VPrHnSAMAXhAzEB4Q+2IYZ0x9qPzN1g==", | |
| 200 | "dev": true, | |
| 201 | "license": "Apache-2.0", | |
| 202 | "dependencies": { | |
| 203 | "bare-fs": "^4.1.2", | |
| 204 | "bare-inspector": "^4.0.1", | |
| 205 | "bare-path": "^3.0.0", | |
| 206 | "bare-process": "^4.2.1", | |
| 207 | "bare-url": "^2.1.5", | |
| 208 | "bare-v8-to-istanbul": "^1.0.2", | |
| 209 | "picomatch": "^4.0.2", | |
| 210 | "which-runtime": "^1.2.1" | |
| 211 | } | |
| 212 | }, | |
| 213 | "node_modules/bare-crypto": { | |
| 214 | "version": "1.13.4", | |
| 215 | "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.4.tgz", | |
| 216 | "integrity": "sha512-JiCZ5l2YOG1y8J7yy1BCAKTCZrPnHLb7pDRIdurBTOn5oIwBQDIv8iH5Pl2V85vzjl1NZXRfNY4HZLsE942jJA==", | |
| 217 | "dev": true, | |
| 218 | "license": "Apache-2.0", | |
| 219 | "dependencies": { | |
| 220 | "bare-assert": "^1.2.0", | |
| 221 | "bare-stream": "^2.6.3" | |
| 222 | }, | |
| 223 | "peerDependencies": { | |
| 224 | "bare-buffer": "*" | |
| 225 | }, | |
| 226 | "peerDependenciesMeta": { | |
| 227 | "bare-buffer": { | |
| 228 | "optional": true | |
| 229 | } | |
| 230 | } | |
| 231 | }, | |
| 232 | "node_modules/bare-debug-log": { | |
| 233 | "version": "2.0.0", | |
| 234 | "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz", | |
| 235 | "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==", | |
| 236 | "dev": true, | |
| 237 | "license": "Apache-2.0", | |
| 238 | "dependencies": { | |
| 239 | "bare-os": "^3.0.1" | |
| 240 | } | |
| 241 | }, | |
| 242 | "node_modules/bare-dns": { | |
| 243 | "version": "2.1.4", | |
| 244 | "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz", | |
| 245 | "integrity": "sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==", | |
| 246 | "dev": true, | |
| 247 | "license": "Apache-2.0", | |
| 248 | "engines": { | |
| 249 | "bare": ">=1.7.0" | |
| 250 | } | |
| 251 | }, | |
| 252 | "node_modules/bare-encoding": { | |
| 253 | "version": "1.0.3", | |
| 254 | "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz", | |
| 255 | "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==", | |
| 256 | "dev": true, | |
| 257 | "license": "Apache-2.0", | |
| 258 | "peerDependencies": { | |
| 259 | "bare-buffer": "*" | |
| 260 | }, | |
| 261 | "peerDependenciesMeta": { | |
| 262 | "bare-buffer": { | |
| 263 | "optional": true | |
| 264 | } | |
| 265 | } | |
| 266 | }, | |
| 267 | "node_modules/bare-env": { | |
| 268 | "version": "3.0.0", | |
| 269 | "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.0.tgz", | |
| 270 | "integrity": "sha512-0u964P5ZLAxTi+lW4Kjp7YRJQ5gZr9ycYOtjLxsSrupgMz3sn5Z9n4SH/JIifHwvadsf1brA2JAjP+9IOWwTiw==", | |
| 271 | "dev": true, | |
| 272 | "license": "Apache-2.0", | |
| 273 | "dependencies": { | |
| 274 | "bare-os": "^3.0.1" | |
| 275 | } | |
| 276 | }, | |
| 277 | "node_modules/bare-events": { | |
| 278 | "version": "2.8.2", | |
| 279 | "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", | |
| 280 | "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", | |
| 281 | "license": "Apache-2.0", | |
| 282 | "peerDependencies": { | |
| 283 | "bare-abort-controller": "*" | |
| 284 | }, | |
| 285 | "peerDependenciesMeta": { | |
| 286 | "bare-abort-controller": { | |
| 287 | "optional": true | |
| 288 | } | |
| 289 | } | |
| 290 | }, | |
| 291 | "node_modules/bare-format": { | |
| 292 | "version": "1.0.2", | |
| 293 | "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz", | |
| 294 | "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==", | |
| 295 | "dev": true, | |
| 296 | "license": "Apache-2.0", | |
| 297 | "dependencies": { | |
| 298 | "bare-inspect": "^3.0.0" | |
| 299 | } | |
| 300 | }, | |
| 301 | "node_modules/bare-fs": { | |
| 302 | "version": "4.7.0", | |
| 303 | "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.0.tgz", | |
| 304 | "integrity": "sha512-xzqKsCFxAek9aezYhjJuJRXBIaYlg/0OGDTZp+T8eYmYMlm66cs6cYko02drIyjN2CBbi+I6L7YfXyqpqtKRXA==", | |
| 305 | "dev": true, | |
| 306 | "license": "Apache-2.0", | |
| 307 | "dependencies": { | |
| 308 | "bare-events": "^2.5.4", | |
| 309 | "bare-path": "^3.0.0", | |
| 310 | "bare-stream": "^2.6.4", | |
| 311 | "bare-url": "^2.2.2", | |
| 312 | "fast-fifo": "^1.3.2" | |
| 313 | }, | |
| 314 | "engines": { | |
| 315 | "bare": ">=1.16.0" | |
| 316 | }, | |
| 317 | "peerDependencies": { | |
| 318 | "bare-buffer": "*" | |
| 319 | }, | |
| 320 | "peerDependenciesMeta": { | |
| 321 | "bare-buffer": { | |
| 322 | "optional": true | |
| 323 | } | |
| 324 | } | |
| 325 | }, | |
| 326 | "node_modules/bare-hrtime": { | |
| 327 | "version": "2.1.1", | |
| 328 | "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.1.tgz", | |
| 329 | "integrity": "sha512-VMb3tHo05gsnbu3OXTmkDiwTjMlOsbQmKoysKqKEyR09m77TuDrYFbj3Q5GGk10dAKsUHrnXmwCaeJqzVpB5ZA==", | |
| 330 | "dev": true, | |
| 331 | "license": "Apache-2.0" | |
| 332 | }, | |
| 333 | "node_modules/bare-http-parser": { | |
| 334 | "version": "1.1.3", | |
| 335 | "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.1.3.tgz", | |
| 336 | "integrity": "sha512-+dhVvQi6brHq14L/XHNRQ+TLuVE76VjRmMt61wVEtS+Od8xUslfMHWJN/ZjIIt3RtTG6vPuA+x9cOh7KrkBJsA==", | |
| 337 | "dev": true, | |
| 338 | "license": "Apache-2.0" | |
| 339 | }, | |
| 340 | "node_modules/bare-http1": { | |
| 341 | "version": "4.5.6", | |
| 342 | "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.5.6.tgz", | |
| 343 | "integrity": "sha512-31OAwMkSU+z1VuUOCk65hx3aWQgzCfH/zQ6LGxbJtmiy2Czsw0+uvOBM9YkqaL6zUSTSYG2pLbL0v/TjME3Buw==", | |
| 344 | "dev": true, | |
| 345 | "license": "Apache-2.0", | |
| 346 | "dependencies": { | |
| 347 | "bare-events": "^2.6.0", | |
| 348 | "bare-http-parser": "^1.1.1", | |
| 349 | "bare-stream": "^2.10.0", | |
| 350 | "bare-tcp": "^2.2.0" | |
| 351 | }, | |
| 352 | "peerDependencies": { | |
| 353 | "bare-buffer": "*", | |
| 354 | "bare-url": "*" | |
| 355 | }, | |
| 356 | "peerDependenciesMeta": { | |
| 357 | "bare-buffer": { | |
| 358 | "optional": true | |
| 359 | }, | |
| 360 | "bare-url": { | |
| 361 | "optional": true | |
| 362 | } | |
| 363 | } | |
| 364 | }, | |
| 365 | "node_modules/bare-https": { | |
| 366 | "version": "2.1.3", | |
| 367 | "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-2.1.3.tgz", | |
| 368 | "integrity": "sha512-0TI/mJXQGYXmG7UUyWEG+KCJusayIAQLywUjFAskDoKuxfqVnGF+M/mTMrEV8J64DaIdU+5x761FvgmT7M68tA==", | |
| 369 | "dev": true, | |
| 370 | "license": "Apache-2.0", | |
| 371 | "dependencies": { | |
| 372 | "bare-http1": "^4.4.0", | |
| 373 | "bare-tcp": "^2.2.0", | |
| 374 | "bare-tls": "^2.0.0" | |
| 375 | } | |
| 376 | }, | |
| 377 | "node_modules/bare-inspect": { | |
| 378 | "version": "3.1.4", | |
| 379 | "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.4.tgz", | |
| 380 | "integrity": "sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==", | |
| 381 | "license": "Apache-2.0", | |
| 382 | "dependencies": { | |
| 383 | "bare-ansi-escapes": "^2.1.0", | |
| 384 | "bare-type": "^1.0.0" | |
| 385 | }, | |
| 386 | "engines": { | |
| 387 | "bare": ">=1.18.0" | |
| 388 | } | |
| 389 | }, | |
| 390 | "node_modules/bare-inspector": { | |
| 391 | "version": "4.0.3", | |
| 392 | "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-4.0.3.tgz", | |
| 393 | "integrity": "sha512-yYk6+7tTVKJKHOja5+QBn989ETv8oVsmwoH0nCM+QYC6w5MC03GC4aQNK+lipAZSz1EaAj42WZ8Wewjpy0L9TA==", | |
| 394 | "dev": true, | |
| 395 | "license": "Apache-2.0", | |
| 396 | "dependencies": { | |
| 397 | "bare-events": "^2.1.0", | |
| 398 | "bare-http1": "^4.0.0", | |
| 399 | "bare-stream": "^2.0.0", | |
| 400 | "bare-url": "^2.0.0", | |
| 401 | "bare-ws": "^2.0.0" | |
| 402 | }, | |
| 403 | "engines": { | |
| 404 | "bare": ">=1.2.0" | |
| 405 | }, | |
| 406 | "peerDependencies": { | |
| 407 | "bare-tcp": "*" | |
| 408 | }, | |
| 409 | "peerDependenciesMeta": { | |
| 410 | "bare-tcp": { | |
| 411 | "optional": true | |
| 412 | } | |
| 413 | } | |
| 414 | }, | |
| 415 | "node_modules/bare-module": { | |
| 416 | "version": "6.1.3", | |
| 417 | "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.1.3.tgz", | |
| 418 | "integrity": "sha512-5XWsVHsvtWMH4tK4DQWgpNTV0t/sg3ZrAaQLIxrwjrS5+u8Q9vEgc/zQ4QaDPWDse/y/5h+d+YG1Q0JfSMt0zA==", | |
| 419 | "dev": true, | |
| 420 | "license": "Apache-2.0", | |
| 421 | "dependencies": { | |
| 422 | "bare-bundle": "^1.3.0", | |
| 423 | "bare-module-lexer": "^1.0.0", | |
| 424 | "bare-module-resolve": "^1.8.0", | |
| 425 | "bare-path": "^3.0.0", | |
| 426 | "bare-url": "^2.0.1" | |
| 427 | }, | |
| 428 | "engines": { | |
| 429 | "bare": ">=1.23.0" | |
| 430 | }, | |
| 431 | "peerDependencies": { | |
| 432 | "bare-buffer": "*" | |
| 433 | }, | |
| 434 | "peerDependenciesMeta": { | |
| 435 | "bare-buffer": { | |
| 436 | "optional": true | |
| 437 | } | |
| 438 | } | |
| 439 | }, | |
| 440 | "node_modules/bare-module-lexer": { | |
| 441 | "version": "1.4.7", | |
| 442 | "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.4.7.tgz", | |
| 443 | "integrity": "sha512-0klU4eMsjh/wcxi8FdHmNom2j2F4kmkXOhyJFL9qTaSFp2lE3m6BtbKgMHY8R5miqC9r8/IfA8wzXnC5Os14WA==", | |
| 444 | "dev": true, | |
| 445 | "license": "Apache-2.0", | |
| 446 | "dependencies": { | |
| 447 | "require-addon": "^1.0.2" | |
| 448 | }, | |
| 449 | "peerDependencies": { | |
| 450 | "bare-buffer": "*" | |
| 451 | }, | |
| 452 | "peerDependenciesMeta": { | |
| 453 | "bare-buffer": { | |
| 454 | "optional": true | |
| 455 | } | |
| 456 | } | |
| 457 | }, | |
| 458 | "node_modules/bare-module-resolve": { | |
| 459 | "version": "1.12.1", | |
| 460 | "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.1.tgz", | |
| 461 | "integrity": "sha512-hbmAPyFpEq8FoZMd5sFO3u6MC5feluWoGE8YKlA8fCrl6mNtx68Wjg4DTiDJcqRJaovTvOYKfYngoBUnbaT7eg==", | |
| 462 | "license": "Apache-2.0", | |
| 463 | "dependencies": { | |
| 464 | "bare-semver": "^1.0.0" | |
| 465 | }, | |
| 466 | "peerDependencies": { | |
| 467 | "bare-url": "*" | |
| 468 | }, | |
| 469 | "peerDependenciesMeta": { | |
| 470 | "bare-url": { | |
| 471 | "optional": true | |
| 472 | } | |
| 473 | } | |
| 474 | }, | |
| 475 | "node_modules/bare-net": { | |
| 476 | "version": "2.3.1", | |
| 477 | "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.1.tgz", | |
| 478 | "integrity": "sha512-MypSqDKpDU2Xt7FIfazn5yGvRnV09gFcIPHGWstW0gxuzA4tucTcwJSZeos97C4F89vtU5oGwXDN/HrGN6Y4Jw==", | |
| 479 | "dev": true, | |
| 480 | "license": "Apache-2.0", | |
| 481 | "dependencies": { | |
| 482 | "bare-events": "^2.2.2", | |
| 483 | "bare-pipe": "^4.0.0", | |
| 484 | "bare-stream": "^2.0.0", | |
| 485 | "bare-tcp": "^2.0.0" | |
| 486 | } | |
| 487 | }, | |
| 488 | "node_modules/bare-os": { | |
| 489 | "version": "3.8.7", | |
| 490 | "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz", | |
| 491 | "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==", | |
| 492 | "devOptional": true, | |
| 493 | "license": "Apache-2.0", | |
| 494 | "engines": { | |
| 495 | "bare": ">=1.14.0" | |
| 496 | } | |
| 497 | }, | |
| 498 | "node_modules/bare-path": { | |
| 499 | "version": "3.0.0", | |
| 500 | "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", | |
| 501 | "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", | |
| 502 | "devOptional": true, | |
| 503 | "license": "Apache-2.0", | |
| 504 | "dependencies": { | |
| 505 | "bare-os": "^3.0.1" | |
| 506 | } | |
| 507 | }, | |
| 508 | "node_modules/bare-pipe": { | |
| 509 | "version": "4.1.5", | |
| 510 | "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.5.tgz", | |
| 511 | "integrity": "sha512-6OfxaG8JSkRh3Gc4hzHRsxNt+yu2PpN7lrv1V+T78GdknWQkVGwiEvu4m+1nbfk8cMVQ0TGxRvQ90XA4rhnTuw==", | |
| 512 | "dev": true, | |
| 513 | "license": "Apache-2.0", | |
| 514 | "dependencies": { | |
| 515 | "bare-events": "^2.0.0", | |
| 516 | "bare-stream": "^2.0.0" | |
| 517 | }, | |
| 518 | "engines": { | |
| 519 | "bare": ">=1.16.0" | |
| 520 | } | |
| 521 | }, | |
| 522 | "node_modules/bare-process": { | |
| 523 | "version": "4.4.1", | |
| 524 | "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.4.1.tgz", | |
| 525 | "integrity": "sha512-JfcTtymq6akqM2bdyTsP4A4GYJceBzb91k/ECmFps75uFf6uO33SM1bOZsRAqyRXExabsQJLn5S41NBl8HTM4A==", | |
| 526 | "dev": true, | |
| 527 | "license": "Apache-2.0", | |
| 528 | "dependencies": { | |
| 529 | "bare-abort": "^2.0.13", | |
| 530 | "bare-env": "^3.0.0", | |
| 531 | "bare-events": "^2.3.1", | |
| 532 | "bare-hrtime": "^2.0.0", | |
| 533 | "bare-os": "^3.7.1", | |
| 534 | "bare-signals": "^4.0.0", | |
| 535 | "bare-stdio": "^1.0.1" | |
| 536 | } | |
| 537 | }, | |
| 538 | "node_modules/bare-semver": { | |
| 539 | "version": "1.0.3", | |
| 540 | "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.3.tgz", | |
| 541 | "integrity": "sha512-HS/A30bi2+PiRJfU6R4+Kp+6KeLSCSByjYM2iiobOKzLAvtu1CT+S8xWfiU7wz0erknjkUoC+yXy108tzIuP5Q==", | |
| 542 | "license": "Apache-2.0" | |
| 543 | }, | |
| 544 | "node_modules/bare-signals": { | |
| 545 | "version": "4.2.0", | |
| 546 | "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-4.2.0.tgz", | |
| 547 | "integrity": "sha512-fNHMOdQIlYuTvMB3Oh9Apk99hLKn351+Ir8vz+khiPTcOqIyGG4uWWjdLTzxWdYGsA0eT+We3y0K74hjj2nq7A==", | |
| 548 | "dev": true, | |
| 549 | "license": "Apache-2.0", | |
| 550 | "dependencies": { | |
| 551 | "bare-events": "^2.5.3", | |
| 552 | "bare-os": "^3.3.1" | |
| 553 | }, | |
| 554 | "engines": { | |
| 555 | "bare": ">=1.7.0" | |
| 556 | } | |
| 557 | }, | |
| 558 | "node_modules/bare-stdio": { | |
| 559 | "version": "1.0.2", | |
| 560 | "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.2.tgz", | |
| 561 | "integrity": "sha512-3WJDqtvVGP4f+j68kyEC05umOYNwKJ1xG+YAXL8yZ605WgNqiRhVaFq+mVIhBt2eKNp7pa5vCQdhOt1pNh79SA==", | |
| 562 | "dev": true, | |
| 563 | "license": "Apache-2.0", | |
| 564 | "dependencies": { | |
| 565 | "bare-fs": "^4.5.2", | |
| 566 | "bare-pipe": "^4.1.5", | |
| 567 | "bare-tty": "^5.0.3" | |
| 568 | } | |
| 569 | }, | |
| 570 | "node_modules/bare-stream": { | |
| 571 | "version": "2.13.0", | |
| 572 | "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.0.tgz", | |
| 573 | "integrity": "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==", | |
| 574 | "license": "Apache-2.0", | |
| 575 | "dependencies": { | |
| 576 | "streamx": "^2.25.0", | |
| 577 | "teex": "^1.0.1" | |
| 578 | }, | |
| 579 | "peerDependencies": { | |
| 580 | "bare-abort-controller": "*", | |
| 581 | "bare-buffer": "*", | |
| 582 | "bare-events": "*" | |
| 583 | }, | |
| 584 | "peerDependenciesMeta": { | |
| 585 | "bare-abort-controller": { | |
| 586 | "optional": true | |
| 587 | }, | |
| 588 | "bare-buffer": { | |
| 589 | "optional": true | |
| 590 | }, | |
| 591 | "bare-events": { | |
| 592 | "optional": true | |
| 593 | } | |
| 594 | } | |
| 595 | }, | |
| 596 | "node_modules/bare-stylize": { | |
| 597 | "version": "0.0.1", | |
| 598 | "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz", | |
| 599 | "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==", | |
| 600 | "dev": true, | |
| 601 | "license": "Apache-2.0", | |
| 602 | "dependencies": { | |
| 603 | "bare-ansi-escapes": "^2.2.3", | |
| 604 | "bare-process": "^4.2.1" | |
| 605 | } | |
| 606 | }, | |
| 607 | "node_modules/bare-subprocess": { | |
| 608 | "version": "5.2.3", | |
| 609 | "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz", | |
| 610 | "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==", | |
| 611 | "dev": true, | |
| 612 | "license": "Apache-2.0", | |
| 613 | "dependencies": { | |
| 614 | "bare-env": "^3.0.0", | |
| 615 | "bare-events": "^2.5.4", | |
| 616 | "bare-os": "^3.0.1", | |
| 617 | "bare-pipe": "^4.0.0", | |
| 618 | "bare-url": "^2.2.2" | |
| 619 | }, | |
| 620 | "engines": { | |
| 621 | "bare": ">=1.7.0" | |
| 622 | }, | |
| 623 | "peerDependencies": { | |
| 624 | "bare-buffer": "*" | |
| 625 | }, | |
| 626 | "peerDependenciesMeta": { | |
| 627 | "bare-buffer": { | |
| 628 | "optional": true | |
| 629 | } | |
| 630 | } | |
| 631 | }, | |
| 632 | "node_modules/bare-tcp": { | |
| 633 | "version": "2.2.7", | |
| 634 | "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.7.tgz", | |
| 635 | "integrity": "sha512-rjpqNQ2cOCkNo3NeYA/W4GTK3DRkl8sDHO3uos+AEswUjLC8XXMQF8WrJCSjlIowCbS6NUVxKE92X5RGXjyefg==", | |
| 636 | "dev": true, | |
| 637 | "license": "Apache-2.0", | |
| 638 | "dependencies": { | |
| 639 | "bare-dns": "^2.0.4", | |
| 640 | "bare-events": "^2.5.4", | |
| 641 | "bare-stream": "^2.6.4" | |
| 642 | }, | |
| 643 | "engines": { | |
| 644 | "bare": ">=1.16.0" | |
| 645 | } | |
| 646 | }, | |
| 647 | "node_modules/bare-tls": { | |
| 648 | "version": "2.2.3", | |
| 649 | "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-2.2.3.tgz", | |
| 650 | "integrity": "sha512-dPYBGEXtgLceRFMfGaF2/rqR86/xMxMyrM9ootO/gaRKL/z2uNHJs7aP7IOBtnJF8eUCt5qwMzbKfrKgDIxPLg==", | |
| 651 | "dev": true, | |
| 652 | "license": "Apache-2.0", | |
| 653 | "dependencies": { | |
| 654 | "bare-net": "^2.0.1", | |
| 655 | "bare-stream": "^2.6.4" | |
| 656 | }, | |
| 657 | "engines": { | |
| 658 | "bare": ">=1.7.0" | |
| 659 | } | |
| 660 | }, | |
| 661 | "node_modules/bare-tty": { | |
| 662 | "version": "5.1.0", | |
| 663 | "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.1.0.tgz", | |
| 664 | "integrity": "sha512-EZLvW4A+XiJgI3TW+e1pMME9PIJsfEXe/DA/WSKzIkq/v7Yarpv/rvG6Z5pGnpo4V/Bd+qopwnCLSR71hMMBYA==", | |
| 665 | "dev": true, | |
| 666 | "license": "Apache-2.0", | |
| 667 | "dependencies": { | |
| 668 | "bare-events": "^2.2.0", | |
| 669 | "bare-signals": "^4.0.0", | |
| 670 | "bare-stream": "^2.0.0" | |
| 671 | }, | |
| 672 | "engines": { | |
| 673 | "bare": ">=1.16.0" | |
| 674 | } | |
| 675 | }, | |
| 676 | "node_modules/bare-type": { | |
| 677 | "version": "1.1.0", | |
| 678 | "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.0.tgz", | |
| 679 | "integrity": "sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==", | |
| 680 | "license": "Apache-2.0", | |
| 681 | "engines": { | |
| 682 | "bare": ">=1.2.0" | |
| 683 | } | |
| 684 | }, | |
| 685 | "node_modules/bare-url": { | |
| 686 | "version": "2.4.0", | |
| 687 | "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", | |
| 688 | "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", | |
| 689 | "devOptional": true, | |
| 690 | "license": "Apache-2.0", | |
| 691 | "peer": true, | |
| 692 | "dependencies": { | |
| 693 | "bare-path": "^3.0.0" | |
| 694 | } | |
| 695 | }, | |
| 696 | "node_modules/bare-utils": { | |
| 697 | "version": "1.6.0", | |
| 698 | "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz", | |
| 699 | "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==", | |
| 700 | "dev": true, | |
| 701 | "license": "Apache-2.0", | |
| 702 | "dependencies": { | |
| 703 | "bare-debug-log": "^2.0.0", | |
| 704 | "bare-encoding": "^1.0.0", | |
| 705 | "bare-format": "^1.0.0", | |
| 706 | "bare-inspect": "^3.0.0", | |
| 707 | "bare-stylize": "^0.0.1", | |
| 708 | "bare-type": "^1.0.6" | |
| 709 | } | |
| 710 | }, | |
| 711 | "node_modules/bare-v8-to-istanbul": { | |
| 712 | "version": "1.0.2", | |
| 713 | "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.2.tgz", | |
| 714 | "integrity": "sha512-CM0wPgyQtXTQ7h4q8NjbTZ/FH5he0Jkiwgpq0lFZZA5tkVZzmQ/9ul4R7E+cNlGZJIZlQZ9v4CwbgVaFG3zOAA==", | |
| 715 | "dev": true, | |
| 716 | "license": "Apache-2.0", | |
| 717 | "dependencies": { | |
| 718 | "bare-assert": "^1.0.2", | |
| 719 | "bare-fs": "^4.1.2", | |
| 720 | "bare-module": "^6.1.2", | |
| 721 | "bare-path": "^3.0.0", | |
| 722 | "bare-process": "^4.2.0", | |
| 723 | "bare-url": "^2.1.5", | |
| 724 | "bare-utils": "^1.2.0", | |
| 725 | "v8-to-istanbul": "^9.3.0", | |
| 726 | "which-runtime": "^1.2.1" | |
| 727 | } | |
| 728 | }, | |
| 729 | "node_modules/bare-ws": { | |
| 730 | "version": "2.1.0", | |
| 731 | "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-2.1.0.tgz", | |
| 732 | "integrity": "sha512-2gEWlPK9iyBchACdIY6oQXgmDz3KLrChdwrPgmU3IVXOFTnxTqSUT27WE/+izd4QojHj/SsqDkQiD2HDvuTdAA==", | |
| 733 | "dev": true, | |
| 734 | "license": "Apache-2.0", | |
| 735 | "dependencies": { | |
| 736 | "bare-crypto": "^1.2.0", | |
| 737 | "bare-events": "^2.3.1", | |
| 738 | "bare-http1": "^4.0.0", | |
| 739 | "bare-https": "^2.0.0", | |
| 740 | "bare-stream": "^2.1.2" | |
| 741 | }, | |
| 742 | "peerDependencies": { | |
| 743 | "bare-buffer": "*", | |
| 744 | "bare-url": "*" | |
| 745 | }, | |
| 746 | "peerDependenciesMeta": { | |
| 747 | "bare-buffer": { | |
| 748 | "optional": true | |
| 749 | }, | |
| 750 | "bare-url": { | |
| 751 | "optional": true | |
| 752 | } | |
| 753 | } | |
| 754 | }, | |
| 755 | "node_modules/big-sparse-array": { | |
| 756 | "version": "1.0.3", | |
| 757 | "resolved": "https://registry.npmjs.org/big-sparse-array/-/big-sparse-array-1.0.3.tgz", | |
| 758 | "integrity": "sha512-6RjV/3mSZORlMdpUaQ6rUSpG637cZm0//E54YYGtQg1c1O+AbZP8UTdJ/TchsDZcTVLmyWZcseBfp2HBeXUXOQ==", | |
| 759 | "dev": true, | |
| 760 | "license": "MIT" | |
| 761 | }, | |
| 762 | "node_modules/bits-to-bytes": { | |
| 763 | "version": "1.3.0", | |
| 764 | "resolved": "https://registry.npmjs.org/bits-to-bytes/-/bits-to-bytes-1.3.0.tgz", | |
| 765 | "integrity": "sha512-OJoHTpFXS9bXHBCekGTByf3MqM8CGblBDIduKQeeVVeiU9dDWywSSirXIBYGgg3d1zbVuvnMa1vD4r6PA0kOKg==", | |
| 766 | "license": "ISC", | |
| 767 | "dependencies": { | |
| 768 | "b4a": "^1.5.0" | |
| 769 | } | |
| 770 | }, | |
| 771 | "node_modules/blind-relay": { | |
| 772 | "version": "1.4.0", | |
| 773 | "resolved": "https://registry.npmjs.org/blind-relay/-/blind-relay-1.4.0.tgz", | |
| 774 | "integrity": "sha512-6xt7fDfCs6eGmNNym6I9N42jmjcMQn2qwwOVnkP9ZnrkXFk6c4/tdO1xqRmDEzKzV8gigd+DVdCUG/RUYnen7Q==", | |
| 775 | "license": "Apache-2.0", | |
| 776 | "dependencies": { | |
| 777 | "b4a": "^1.6.4", | |
| 778 | "bare-events": "^2.2.0", | |
| 779 | "bits-to-bytes": "^1.3.0", | |
| 780 | "compact-encoding": "^2.12.0", | |
| 781 | "compact-encoding-bitfield": "^1.0.0", | |
| 782 | "protomux": "^3.5.1", | |
| 783 | "sodium-universal": "^5.0.0", | |
| 784 | "streamx": "^2.15.1" | |
| 785 | } | |
| 786 | }, | |
| 787 | "node_modules/bogon": { | |
| 788 | "version": "1.2.0", | |
| 789 | "resolved": "https://registry.npmjs.org/bogon/-/bogon-1.2.0.tgz", | |
| 790 | "integrity": "sha512-FqOBr/1VMzCOsoJd+fzNUMarUYki2+TKt07A2+xaulsNx4r53iJ7MV5k0jbqg7W2U0CsLqxCZOrFibdG6h6HSg==", | |
| 791 | "license": "MIT", | |
| 792 | "dependencies": { | |
| 793 | "compact-encoding": "^2.11.0", | |
| 794 | "compact-encoding-net": "^1.2.0" | |
| 795 | } | |
| 796 | }, | |
| 797 | "node_modules/brittle": { | |
| 798 | "version": "3.19.1", | |
| 799 | "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz", | |
| 800 | "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==", | |
| 801 | "dev": true, | |
| 802 | "license": "Apache-2.0", | |
| 803 | "dependencies": { | |
| 804 | "b4a": "^1.6.0", | |
| 805 | "bare-assert": "^1.0.2", | |
| 806 | "bare-cov": "^1.1.0", | |
| 807 | "bare-fs": "^4.1.6", | |
| 808 | "bare-os": "^3.6.1", | |
| 809 | "bare-path": "^3.0.0", | |
| 810 | "bare-process": "^4.2.1", | |
| 811 | "bare-subprocess": "^5.0.0", | |
| 812 | "bare-url": "^2.1.6", | |
| 813 | "error-stack-parser": "^2.1.4", | |
| 814 | "globbie": "^1.0.2", | |
| 815 | "paparam": "^1.6.2", | |
| 816 | "same-object": "^1.0.2", | |
| 817 | "test-tmp": "^1.4.0", | |
| 818 | "tmatch": "^5.0.0" | |
| 819 | }, | |
| 820 | "bin": { | |
| 821 | "brittle": "bin/node.js", | |
| 822 | "brittle-bare": "bin/bare.js", | |
| 823 | "brittle-node": "bin/node.js", | |
| 824 | "brittle-pear": "bin/pear.js" | |
| 825 | } | |
| 826 | }, | |
| 827 | "node_modules/compact-encoding": { | |
| 828 | "version": "2.19.2", | |
| 829 | "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-2.19.2.tgz", | |
| 830 | "integrity": "sha512-/YjhHQE/5L4F7l5Bht69dRbP9RV6zoJPeowi8bMKQxNKe3Nh6hOY8pBGoVE9fz5GaWfEd8fWJ2aU9sB4KZuMYg==", | |
| 831 | "license": "Apache-2.0", | |
| 832 | "dependencies": { | |
| 833 | "b4a": "^1.3.0" | |
| 834 | } | |
| 835 | }, | |
| 836 | "node_modules/compact-encoding-bitfield": { | |
| 837 | "version": "1.0.0", | |
| 838 | "resolved": "https://registry.npmjs.org/compact-encoding-bitfield/-/compact-encoding-bitfield-1.0.0.tgz", | |
| 839 | "integrity": "sha512-3nMVKUg+PF72UHfainmCL8uKvyWfxsjqOtUY+HiMPGLPCTjnwzoKfFAMo1Ad7nwTPdjBqtGK5b3BOFTFW4EBTg==", | |
| 840 | "license": "ISC", | |
| 841 | "dependencies": { | |
| 842 | "compact-encoding": "^2.4.1" | |
| 843 | } | |
| 844 | }, | |
| 845 | "node_modules/compact-encoding-net": { | |
| 846 | "version": "1.2.0", | |
| 847 | "resolved": "https://registry.npmjs.org/compact-encoding-net/-/compact-encoding-net-1.2.0.tgz", | |
| 848 | "integrity": "sha512-LVXpNpF7PGQeHRVVLGgYWzuVoYAaDZvKUsUxRioGfkotzvOh4AzoQF1HBH3zMNaSnx7gJXuUr3hkjnijaH/Eng==", | |
| 849 | "license": "ISC", | |
| 850 | "dependencies": { | |
| 851 | "compact-encoding": "^2.4.1" | |
| 852 | } | |
| 853 | }, | |
| 854 | "node_modules/convert-source-map": { | |
| 855 | "version": "2.0.0", | |
| 856 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", | |
| 857 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", | |
| 858 | "dev": true, | |
| 859 | "license": "MIT" | |
| 860 | }, | |
| 861 | "node_modules/corestore": { | |
| 862 | "version": "7.9.2", | |
| 863 | "resolved": "https://registry.npmjs.org/corestore/-/corestore-7.9.2.tgz", | |
| 864 | "integrity": "sha512-dyIktVSVLu0skZ1UM4yODbNbZ46dBpTHypzwlwET1btVuS3YIf66Zz8Tx8h3Xa8nbLO2USGqrYtvPYzSRj2wCA==", | |
| 865 | "dev": true, | |
| 866 | "license": "MIT", | |
| 867 | "dependencies": { | |
| 868 | "b4a": "^1.6.7", | |
| 869 | "hypercore": "^11.19.0", | |
| 870 | "hypercore-crypto": "^3.4.2", | |
| 871 | "hypercore-errors": "^1.4.0", | |
| 872 | "hypercore-id-encoding": "^1.3.0", | |
| 873 | "ready-resource": "^1.1.1", | |
| 874 | "sodium-universal": "^5.0.1", | |
| 875 | "which-runtime": "^1.2.1" | |
| 876 | } | |
| 877 | }, | |
| 878 | "node_modules/device-file": { | |
| 879 | "version": "2.3.1", | |
| 880 | "resolved": "https://registry.npmjs.org/device-file/-/device-file-2.3.1.tgz", | |
| 881 | "integrity": "sha512-bmON44lwxJPle9N2OcH4tqM44pMGZKT8G6OkzXkz0urvqQ9LKkoQdTS6w0ztYfmLdUE27R4UUmx0+y2gEz4Jug==", | |
| 882 | "dev": true, | |
| 883 | "license": "Apache-2.0", | |
| 884 | "dependencies": { | |
| 885 | "b4a": "^1.6.7", | |
| 886 | "bare-fs": "^4.0.1", | |
| 887 | "bare-path": "^3.0.0", | |
| 888 | "fd-lock": "^2.1.0", | |
| 889 | "fs-native-extensions": "^1.4.0", | |
| 890 | "ready-resource": "^1.2.0" | |
| 891 | } | |
| 892 | }, | |
| 893 | "node_modules/dht-rpc": { | |
| 894 | "version": "6.26.3", | |
| 895 | "resolved": "https://registry.npmjs.org/dht-rpc/-/dht-rpc-6.26.3.tgz", | |
| 896 | "integrity": "sha512-KuLfRv/hecUHipQcTXHpVv4/N4Jhpww5sLdsrn3Edm5oHwzK9SgNV34hNt7a2aVZCOcG5SfP4AvcQ7pI+y9YNg==", | |
| 897 | "license": "MIT", | |
| 898 | "dependencies": { | |
| 899 | "adaptive-timeout": "^1.0.1", | |
| 900 | "b4a": "^1.6.1", | |
| 901 | "bare-events": "^2.2.0", | |
| 902 | "compact-encoding": "^2.11.0", | |
| 903 | "compact-encoding-net": "^1.2.0", | |
| 904 | "fast-fifo": "^1.1.0", | |
| 905 | "kademlia-routing-table": "^1.0.1", | |
| 906 | "nat-sampler": "^1.0.1", | |
| 907 | "sodium-universal": "^5.0.0", | |
| 908 | "streamx": "^2.13.2", | |
| 909 | "time-ordered-set": "^2.0.0", | |
| 910 | "udx-native": "^1.5.3" | |
| 911 | } | |
| 912 | }, | |
| 913 | "node_modules/error-stack-parser": { | |
| 914 | "version": "2.1.4", | |
| 915 | "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", | |
| 916 | "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", | |
| 917 | "dev": true, | |
| 918 | "license": "MIT", | |
| 919 | "dependencies": { | |
| 920 | "stackframe": "^1.3.4" | |
| 921 | } | |
| 922 | }, | |
| 923 | "node_modules/events-universal": { | |
| 924 | "version": "1.0.1", | |
| 925 | "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", | |
| 926 | "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", | |
| 927 | "license": "Apache-2.0", | |
| 928 | "dependencies": { | |
| 929 | "bare-events": "^2.7.0" | |
| 930 | } | |
| 931 | }, | |
| 932 | "node_modules/fast-fifo": { | |
| 933 | "version": "1.3.2", | |
| 934 | "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", | |
| 935 | "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", | |
| 936 | "license": "MIT" | |
| 937 | }, | |
| 938 | "node_modules/fd-lock": { | |
| 939 | "version": "2.1.1", | |
| 940 | "resolved": "https://registry.npmjs.org/fd-lock/-/fd-lock-2.1.1.tgz", | |
| 941 | "integrity": "sha512-H3VkcWFl39Rk0xBokDcUyBcVs6VrYTUo/DhDWMzJOF99wXWDAvmvq/GlLTS2NTehsznZhp3fXVyrtB2ldDXhwg==", | |
| 942 | "dev": true, | |
| 943 | "license": "Apache-2.0", | |
| 944 | "dependencies": { | |
| 945 | "bare-fs": "^4.5.0", | |
| 946 | "fs-native-extensions": "^1.4.4", | |
| 947 | "ready-resource": "^1.2.0", | |
| 948 | "resource-on-exit": "^1.0.0" | |
| 949 | } | |
| 950 | }, | |
| 951 | "node_modules/flat-tree": { | |
| 952 | "version": "1.13.0", | |
| 953 | "resolved": "https://registry.npmjs.org/flat-tree/-/flat-tree-1.13.0.tgz", | |
| 954 | "integrity": "sha512-fT3HIuCPwHhFgJ20QYzDHgUG0zMmFg5cHvFiFo5h+QMSJ28TihsEVY0f8HGliuO+pOzmvjMx1odToeaEWkTnyQ==", | |
| 955 | "dev": true, | |
| 956 | "license": "MIT" | |
| 957 | }, | |
| 958 | "node_modules/fs-native-extensions": { | |
| 959 | "version": "1.4.5", | |
| 960 | "resolved": "https://registry.npmjs.org/fs-native-extensions/-/fs-native-extensions-1.4.5.tgz", | |
| 961 | "integrity": "sha512-ekV0T//iDm4AvhOcuPaHpxub4DI7HvY5ucLJVDvi7T2J+NZkQ9S6MuvgP0yeQvoqNUaAGyLjVYb1905BF9bpmg==", | |
| 962 | "dev": true, | |
| 963 | "license": "Apache-2.0", | |
| 964 | "dependencies": { | |
| 965 | "require-addon": "^1.1.0", | |
| 966 | "which-runtime": "^1.2.0" | |
| 967 | } | |
| 968 | }, | |
| 969 | "node_modules/generate-object-property": { | |
| 970 | "version": "2.0.0", | |
| 971 | "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-2.0.0.tgz", | |
| 972 | "integrity": "sha512-KwuURPyqn2Mz8DdV29pJwQu0Y7tcsbkULr82eeOcY/ZllFK6I9Wm8dsRByIu7CKWlFi9BdW1b3mcXMp/kQBQsw==", | |
| 973 | "dev": true, | |
| 974 | "license": "MIT", | |
| 975 | "dependencies": { | |
| 976 | "is-property": "^1.0.0" | |
| 977 | } | |
| 978 | }, | |
| 979 | "node_modules/generate-string": { | |
| 980 | "version": "1.0.1", | |
| 981 | "resolved": "https://registry.npmjs.org/generate-string/-/generate-string-1.0.1.tgz", | |
| 982 | "integrity": "sha512-IfTY0dKZM43ACyGvXkbG7De7WY7MxTS5VO6Juhe8oJKpCmrYYXoqp/cJMskkpi0k9H8wuXq0H+eI898/BCqvXg==", | |
| 983 | "dev": true, | |
| 984 | "license": "MIT" | |
| 985 | }, | |
| 986 | "node_modules/globbie": { | |
| 987 | "version": "1.0.3", | |
| 988 | "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.3.tgz", | |
| 989 | "integrity": "sha512-hcryJmKcftf82xfBWTWxuUITWIIoYO3ec104V17SMHGFl6VLbm1d1Ju9LX9L+jyJTwICpemX/dmPI/HGYoKr1A==", | |
| 990 | "dev": true, | |
| 991 | "license": "Apache-2.0", | |
| 992 | "dependencies": { | |
| 993 | "bare-fs": "^4.1.2", | |
| 994 | "bare-path": "^3.0.0", | |
| 995 | "bare-process": "^4.2.1", | |
| 996 | "picomatch": "^4.0.2" | |
| 997 | } | |
| 998 | }, | |
| 999 | "node_modules/hyper-rooms": { | |
| 1000 | "resolved": "../hyper-rooms", | |
| 1001 | "link": true | |
| 1002 | }, | |
| 1003 | "node_modules/hypercore": { | |
| 1004 | "version": "11.28.1", | |
| 1005 | "resolved": "https://registry.npmjs.org/hypercore/-/hypercore-11.28.1.tgz", | |
| 1006 | "integrity": "sha512-d8jqUilG7Gri8wT+sRdkFTYRPMHGyvBz/NCYHf5rX7+LoivRNRxQm5nWChT7caiUKbcsDak6h7IiEQ5wbD1wCg==", | |
| 1007 | "dev": true, | |
| 1008 | "license": "MIT", | |
| 1009 | "dependencies": { | |
| 1010 | "@hyperswarm/secret-stream": "^6.0.0", | |
| 1011 | "b4a": "^1.1.0", | |
| 1012 | "bare-events": "^2.2.0", | |
| 1013 | "big-sparse-array": "^1.0.3", | |
| 1014 | "compact-encoding": "^2.11.0", | |
| 1015 | "fast-fifo": "^1.3.0", | |
| 1016 | "flat-tree": "^1.9.0", | |
| 1017 | "hypercore-crypto": "^3.2.1", | |
| 1018 | "hypercore-errors": "^1.5.0", | |
| 1019 | "hypercore-id-encoding": "^1.2.0", | |
| 1020 | "hypercore-storage": "^2.8.0", | |
| 1021 | "is-options": "^1.0.1", | |
| 1022 | "nanoassert": "^2.0.0", | |
| 1023 | "protomux": "^3.5.0", | |
| 1024 | "quickbit-universal": "^2.2.0", | |
| 1025 | "random-array-iterator": "^1.0.0", | |
| 1026 | "safety-catch": "^1.0.1", | |
| 1027 | "sodium-universal": "^5.0.1", | |
| 1028 | "streamx": "^2.12.4", | |
| 1029 | "unslab": "^1.3.0", | |
| 1030 | "z32": "^1.0.0" | |
| 1031 | } | |
| 1032 | }, | |
| 1033 | "node_modules/hypercore-crypto": { | |
| 1034 | "version": "3.6.1", | |
| 1035 | "resolved": "https://registry.npmjs.org/hypercore-crypto/-/hypercore-crypto-3.6.1.tgz", | |
| 1036 | "integrity": "sha512-ltIz2uDwy9pO/ZGTvqcjzyBkvt6O4cVm4r/nNxh0GFs/RbQtqP/i4wCvLEdmU7ptgtnw7fI67WYD1aHPuv4OVA==", | |
| 1037 | "license": "MIT", | |
| 1038 | "dependencies": { | |
| 1039 | "b4a": "^1.6.6", | |
| 1040 | "compact-encoding": "^2.15.0", | |
| 1041 | "sodium-universal": "^5.0.0" | |
| 1042 | } | |
| 1043 | }, | |
| 1044 | "node_modules/hypercore-errors": { | |
| 1045 | "version": "1.5.0", | |
| 1046 | "resolved": "https://registry.npmjs.org/hypercore-errors/-/hypercore-errors-1.5.0.tgz", | |
| 1047 | "integrity": "sha512-5KQ/SuDxsvet+7qWA35Ay6zdD9WyAHQoyWHGcPUTbmJBd300gvNIJoi3oma7kp4TTCSzii6qYumNZe/s0j/saQ==", | |
| 1048 | "dev": true, | |
| 1049 | "license": "Apache-2.0", | |
| 1050 | "dependencies": { | |
| 1051 | "hypercore-id-encoding": "^1.3.0" | |
| 1052 | } | |
| 1053 | }, | |
| 1054 | "node_modules/hypercore-id-encoding": { | |
| 1055 | "version": "1.3.0", | |
| 1056 | "resolved": "https://registry.npmjs.org/hypercore-id-encoding/-/hypercore-id-encoding-1.3.0.tgz", | |
| 1057 | "integrity": "sha512-W6sHdGo5h7LXEsoWfKf/KfuROZmZRQDlGqJF2EPHW+noCK66Vvr0+zE6cL0vqQi18s0kQPeN7Sq3QyR0Ytc2VQ==", | |
| 1058 | "license": "Apache-2.0", | |
| 1059 | "dependencies": { | |
| 1060 | "b4a": "^1.5.3", | |
| 1061 | "z32": "^1.0.0" | |
| 1062 | } | |
| 1063 | }, | |
| 1064 | "node_modules/hypercore-storage": { | |
| 1065 | "version": "2.8.0", | |
| 1066 | "resolved": "https://registry.npmjs.org/hypercore-storage/-/hypercore-storage-2.8.0.tgz", | |
| 1067 | "integrity": "sha512-s35HllFMYvrkSHvkIIklVFmCI/bRVNO7H3pi4mKvWl0XLyhGGGM+NtJ7fpsh82oPRCmL9XtMRO+pT6LiMAO8MA==", | |
| 1068 | "dev": true, | |
| 1069 | "license": "Apache-2.0", | |
| 1070 | "dependencies": { | |
| 1071 | "b4a": "^1.6.7", | |
| 1072 | "bare-fs": "^4.0.1", | |
| 1073 | "bare-path": "^3.0.0", | |
| 1074 | "compact-encoding": "^2.16.0", | |
| 1075 | "device-file": "^2.1.2", | |
| 1076 | "flat-tree": "^1.12.1", | |
| 1077 | "hypercore-crypto": "^3.4.2", | |
| 1078 | "hyperschema": "^1.7.0", | |
| 1079 | "index-encoder": "^3.3.2", | |
| 1080 | "resolve-reject-promise": "^1.0.0", | |
| 1081 | "rocksdb-native": "^3.11.0", | |
| 1082 | "scope-lock": "^1.2.4", | |
| 1083 | "streamx": "^2.21.1" | |
| 1084 | } | |
| 1085 | }, | |
| 1086 | "node_modules/hyperdht": { | |
| 1087 | "version": "6.29.6", | |
| 1088 | "resolved": "https://registry.npmjs.org/hyperdht/-/hyperdht-6.29.6.tgz", | |
| 1089 | "integrity": "sha512-bMMtw02fhiALdsTVLNZ/VFmMZuEY8kxb0/Rprl6oYF4/TCS653i3FDjtJeRbqtnAzwAj6dVZ/EwhoNOSCFGaYQ==", | |
| 1090 | "license": "MIT", | |
| 1091 | "dependencies": { | |
| 1092 | "@hyperswarm/secret-stream": "^6.6.2", | |
| 1093 | "b4a": "^1.3.1", | |
| 1094 | "bare-events": "^2.2.0", | |
| 1095 | "blind-relay": "^1.3.0", | |
| 1096 | "bogon": "^1.0.0", | |
| 1097 | "compact-encoding": "^2.4.1", | |
| 1098 | "dht-rpc": "^6.15.1", | |
| 1099 | "hypercore-crypto": "^3.3.0", | |
| 1100 | "hypercore-id-encoding": "^1.2.0", | |
| 1101 | "noise-curve-ed": "^2.0.0", | |
| 1102 | "noise-handshake": "^4.0.0", | |
| 1103 | "record-cache": "^1.1.1", | |
| 1104 | "safety-catch": "^1.0.1", | |
| 1105 | "signal-promise": "^1.0.3", | |
| 1106 | "sodium-universal": "^5.0.1", | |
| 1107 | "streamx": "^2.16.1", | |
| 1108 | "unslab": "^1.3.0", | |
| 1109 | "xache": "^1.1.0" | |
| 1110 | }, | |
| 1111 | "bin": { | |
| 1112 | "hyperdht": "bin.js" | |
| 1113 | } | |
| 1114 | }, | |
| 1115 | "node_modules/hyperschema": { | |
| 1116 | "version": "1.20.1", | |
| 1117 | "resolved": "https://registry.npmjs.org/hyperschema/-/hyperschema-1.20.1.tgz", | |
| 1118 | "integrity": "sha512-7gnvaTNUs8FqdrU3NNE6nzhOneGyDlUlpROu9YFlHA61fE8ppKKf2gLZjZjsx/enNt+VFN4ITxy8ijypfyWBLw==", | |
| 1119 | "dev": true, | |
| 1120 | "license": "Apache-2.0", | |
| 1121 | "dependencies": { | |
| 1122 | "bare-fs": "^4.0.1", | |
| 1123 | "compact-encoding": "^2.19.0", | |
| 1124 | "generate-object-property": "^2.0.0", | |
| 1125 | "generate-string": "^1.0.1" | |
| 1126 | } | |
| 1127 | }, | |
| 1128 | "node_modules/hyperswarm": { | |
| 1129 | "version": "4.17.0", | |
| 1130 | "resolved": "https://registry.npmjs.org/hyperswarm/-/hyperswarm-4.17.0.tgz", | |
| 1131 | "integrity": "sha512-oe86sK961Ueg7rvDN/veFwG8xH+Iv6vObPhGDkPJcDVxk/NduW41ZhAcVDnHzRbm7S0eLU7WaDUvehOYoKSpRQ==", | |
| 1132 | "license": "MIT", | |
| 1133 | "dependencies": { | |
| 1134 | "b4a": "^1.3.1", | |
| 1135 | "bare-events": "^2.2.0", | |
| 1136 | "hyperdht": "^6.21.0", | |
| 1137 | "safety-catch": "^1.0.2", | |
| 1138 | "shuffled-priority-queue": "^2.1.0", | |
| 1139 | "streamx": "^2.22.1", | |
| 1140 | "unslab": "^1.3.0" | |
| 1141 | } | |
| 1142 | }, | |
| 1143 | "node_modules/index-encoder": { | |
| 1144 | "version": "3.5.0", | |
| 1145 | "resolved": "https://registry.npmjs.org/index-encoder/-/index-encoder-3.5.0.tgz", | |
| 1146 | "integrity": "sha512-idZ1cxtZz2dRV6rUiaP9Xo99UjXbSzjcMacoQmxUMu/A7fEQcNPngvwDJYeWelQUS5XFlY71/or70lKn6XnwbQ==", | |
| 1147 | "dev": true, | |
| 1148 | "license": "Apache-2.0", | |
| 1149 | "dependencies": { | |
| 1150 | "b4a": "^1.6.4" | |
| 1151 | } | |
| 1152 | }, | |
| 1153 | "node_modules/is-options": { | |
| 1154 | "version": "1.0.2", | |
| 1155 | "resolved": "https://registry.npmjs.org/is-options/-/is-options-1.0.2.tgz", | |
| 1156 | "integrity": "sha512-u+Ai74c8Q74aS8BuHwPdI1jptGOT1FQXgCq8/zv0xRuE+wRgSMEJLj8lVO8Zp9BeGb29BXY6AsNPinfqjkr7Fg==", | |
| 1157 | "dev": true, | |
| 1158 | "license": "MIT", | |
| 1159 | "dependencies": { | |
| 1160 | "b4a": "^1.1.1" | |
| 1161 | } | |
| 1162 | }, | |
| 1163 | "node_modules/is-property": { | |
| 1164 | "version": "1.0.2", | |
| 1165 | "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", | |
| 1166 | "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", | |
| 1167 | "dev": true, | |
| 1168 | "license": "MIT" | |
| 1169 | }, | |
| 1170 | "node_modules/kademlia-routing-table": { | |
| 1171 | "version": "1.0.6", | |
| 1172 | "resolved": "https://registry.npmjs.org/kademlia-routing-table/-/kademlia-routing-table-1.0.6.tgz", | |
| 1173 | "integrity": "sha512-Ve6jwIlUCYvUzBnXnzVRHDZCFgXURW9gmF3r7n05kZs/2rNbLHXwGdcq0qIaSwdmJCvtosgR4JensnVU65hzNQ==", | |
| 1174 | "license": "MIT", | |
| 1175 | "dependencies": { | |
| 1176 | "bare-events": "^2.2.0" | |
| 1177 | } | |
| 1178 | }, | |
| 1179 | "node_modules/nanoassert": { | |
| 1180 | "version": "2.0.0", | |
| 1181 | "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz", | |
| 1182 | "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==", | |
| 1183 | "license": "ISC" | |
| 1184 | }, | |
| 1185 | "node_modules/nat-sampler": { | |
| 1186 | "version": "1.0.1", | |
| 1187 | "resolved": "https://registry.npmjs.org/nat-sampler/-/nat-sampler-1.0.1.tgz", | |
| 1188 | "integrity": "sha512-yQvyNN7xbqR8crTKk3U8gRgpcV1Az+vfCEijiHu9oHHsnIl8n3x+yXNHl42M6L3czGynAVoOT9TqBfS87gDdcw==", | |
| 1189 | "license": "MIT" | |
| 1190 | }, | |
| 1191 | "node_modules/noise-curve-ed": { | |
| 1192 | "version": "2.1.0", | |
| 1193 | "resolved": "https://registry.npmjs.org/noise-curve-ed/-/noise-curve-ed-2.1.0.tgz", | |
| 1194 | "integrity": "sha512-zAzJx+VwZM3w6EA1hTmDhJfvAnCeBQn/1FAeZ0LtGxCcCtlAK/uJXQVF/eDVUOaAZ286lHlx77WJ+qj9SmsRRg==", | |
| 1195 | "license": "ISC", | |
| 1196 | "dependencies": { | |
| 1197 | "b4a": "^1.1.0", | |
| 1198 | "nanoassert": "^2.0.0", | |
| 1199 | "sodium-universal": "^5.0.0" | |
| 1200 | } | |
| 1201 | }, | |
| 1202 | "node_modules/noise-handshake": { | |
| 1203 | "version": "4.2.0", | |
| 1204 | "resolved": "https://registry.npmjs.org/noise-handshake/-/noise-handshake-4.2.0.tgz", | |
| 1205 | "integrity": "sha512-9O/VTNX/E2/AToyMTTDU0J/4WhaXMTdqc2DHs9vf+snoZ0cenSBq0dNYTVV1snYYEkmo6QeRrYMxtqtoYnY+LA==", | |
| 1206 | "license": "Apache-2.0", | |
| 1207 | "dependencies": { | |
| 1208 | "b4a": "^1.1.0", | |
| 1209 | "nanoassert": "^2.0.0", | |
| 1210 | "sodium-universal": "^5.0.0" | |
| 1211 | } | |
| 1212 | }, | |
| 1213 | "node_modules/paparam": { | |
| 1214 | "version": "1.10.1", | |
| 1215 | "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.10.1.tgz", | |
| 1216 | "integrity": "sha512-viyQI64VIja0Za3njIzhoEP8ZVkgPowhZPuG0E96NwBfYJ6ZIyrrlhWGtFPkdN7eYLl2L8CTWL+wla0evm1KKQ==", | |
| 1217 | "dev": true, | |
| 1218 | "license": "Apache-2.0" | |
| 1219 | }, | |
| 1220 | "node_modules/picomatch": { | |
| 1221 | "version": "4.0.4", | |
| 1222 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", | |
| 1223 | "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", | |
| 1224 | "dev": true, | |
| 1225 | "license": "MIT", | |
| 1226 | "engines": { | |
| 1227 | "node": ">=12" | |
| 1228 | }, | |
| 1229 | "funding": { | |
| 1230 | "url": "https://github.com/sponsors/jonschlinkert" | |
| 1231 | } | |
| 1232 | }, | |
| 1233 | "node_modules/protomux": { | |
| 1234 | "version": "3.10.1", | |
| 1235 | "resolved": "https://registry.npmjs.org/protomux/-/protomux-3.10.1.tgz", | |
| 1236 | "integrity": "sha512-jgBqx8ZyaBWea/DFG4eOu1scOaeBwcnagiRC1XFVrjeGt7oAb0Pk5udPpBUpJ4DJBRjra50jD6YcZiQQTRqaaA==", | |
| 1237 | "license": "MIT", | |
| 1238 | "dependencies": { | |
| 1239 | "b4a": "^1.3.1", | |
| 1240 | "compact-encoding": "^2.5.1", | |
| 1241 | "queue-tick": "^1.0.0", | |
| 1242 | "safety-catch": "^1.0.1", | |
| 1243 | "unslab": "^1.3.0" | |
| 1244 | } | |
| 1245 | }, | |
| 1246 | "node_modules/queue-tick": { | |
| 1247 | "version": "1.0.1", | |
| 1248 | "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", | |
| 1249 | "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", | |
| 1250 | "license": "MIT" | |
| 1251 | }, | |
| 1252 | "node_modules/quickbit-native": { | |
| 1253 | "version": "2.4.8", | |
| 1254 | "resolved": "https://registry.npmjs.org/quickbit-native/-/quickbit-native-2.4.8.tgz", | |
| 1255 | "integrity": "sha512-FcCcqI+nIAWGknqhtrYT5TSD7t/N+Xd8ctM+2PrIIBuwOi5hx0SxAvuPtzLIEMfT/2h9+fhBakUe2uALOHX6yw==", | |
| 1256 | "dev": true, | |
| 1257 | "license": "Apache-2.0", | |
| 1258 | "optional": true, | |
| 1259 | "dependencies": { | |
| 1260 | "require-addon": "^1.1.0" | |
| 1261 | } | |
| 1262 | }, | |
| 1263 | "node_modules/quickbit-universal": { | |
| 1264 | "version": "2.2.0", | |
| 1265 | "resolved": "https://registry.npmjs.org/quickbit-universal/-/quickbit-universal-2.2.0.tgz", | |
| 1266 | "integrity": "sha512-w02i1R8n7+6pEKTud8DfF8zbFY9o7RtPlUc3jWbtCkDKvhbx/AvV7oNnz4/TcmsPGpSJS+fq5Ud6RH6+YPvSGg==", | |
| 1267 | "dev": true, | |
| 1268 | "license": "ISC", | |
| 1269 | "dependencies": { | |
| 1270 | "b4a": "^1.6.0", | |
| 1271 | "simdle-universal": "^1.1.0" | |
| 1272 | }, | |
| 1273 | "optionalDependencies": { | |
| 1274 | "quickbit-native": "^2.2.0" | |
| 1275 | } | |
| 1276 | }, | |
| 1277 | "node_modules/random-array-iterator": { | |
| 1278 | "version": "1.0.0", | |
| 1279 | "resolved": "https://registry.npmjs.org/random-array-iterator/-/random-array-iterator-1.0.0.tgz", | |
| 1280 | "integrity": "sha512-u7xCM93XqKEvPTP6xZp2ehttcAemKnh73oKNf1FvzuVCfpt6dILDt1Kxl1LeBjm2iNIeR49VGFhy4Iz3yOun+Q==", | |
| 1281 | "dev": true, | |
| 1282 | "license": "MIT" | |
| 1283 | }, | |
| 1284 | "node_modules/ready-resource": { | |
| 1285 | "version": "1.2.0", | |
| 1286 | "resolved": "https://registry.npmjs.org/ready-resource/-/ready-resource-1.2.0.tgz", | |
| 1287 | "integrity": "sha512-nfcco/8iAFV0M+2PYnmIc+/xY0iRb35d42HFHQ7AfjulbGEAFa+XWpByfwSyeVeiBoMLLFVMv1HixxNCqzSQ1g==", | |
| 1288 | "license": "MIT", | |
| 1289 | "dependencies": { | |
| 1290 | "bare-events": "^2.2.0" | |
| 1291 | } | |
| 1292 | }, | |
| 1293 | "node_modules/record-cache": { | |
| 1294 | "version": "1.2.0", | |
| 1295 | "resolved": "https://registry.npmjs.org/record-cache/-/record-cache-1.2.0.tgz", | |
| 1296 | "integrity": "sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==", | |
| 1297 | "license": "MIT", | |
| 1298 | "dependencies": { | |
| 1299 | "b4a": "^1.3.1" | |
| 1300 | } | |
| 1301 | }, | |
| 1302 | "node_modules/refcounter": { | |
| 1303 | "version": "1.0.0", | |
| 1304 | "resolved": "https://registry.npmjs.org/refcounter/-/refcounter-1.0.0.tgz", | |
| 1305 | "integrity": "sha512-1WosVzUy0kPUaPMEtlNDwm99UsteALIhXXR8rerELoa63WkYIXAl0hxgwPFrIYBRWZPGUyekQ04FRtPJ7dHk9w==", | |
| 1306 | "dev": true, | |
| 1307 | "license": "Apache-2.0" | |
| 1308 | }, | |
| 1309 | "node_modules/require-addon": { | |
| 1310 | "version": "1.2.0", | |
| 1311 | "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.2.0.tgz", | |
| 1312 | "integrity": "sha512-VNPDZlYgIYQwWp9jMTzljx+k0ZtatKlcvOhktZ/anNPI3dQ9NXk7cq2U4iJ1wd9IrytRnYhyEocFWbkdPb+MYA==", | |
| 1313 | "license": "Apache-2.0", | |
| 1314 | "dependencies": { | |
| 1315 | "bare-addon-resolve": "^1.3.0" | |
| 1316 | }, | |
| 1317 | "engines": { | |
| 1318 | "bare": ">=1.10.0" | |
| 1319 | } | |
| 1320 | }, | |
| 1321 | "node_modules/resolve-reject-promise": { | |
| 1322 | "version": "1.1.0", | |
| 1323 | "resolved": "https://registry.npmjs.org/resolve-reject-promise/-/resolve-reject-promise-1.1.0.tgz", | |
| 1324 | "integrity": "sha512-LWsTOA91AqzBTjSGgX79Tc130pwcBK6xjpJEO+qRT5IKZ6bGnHKcc8QL3upUBcWuU8OTIDzKK2VNSwmmlqvAVg==", | |
| 1325 | "dev": true, | |
| 1326 | "license": "MIT" | |
| 1327 | }, | |
| 1328 | "node_modules/resource-on-exit": { | |
| 1329 | "version": "1.0.0", | |
| 1330 | "resolved": "https://registry.npmjs.org/resource-on-exit/-/resource-on-exit-1.0.0.tgz", | |
| 1331 | "integrity": "sha512-ViJwJAknCkLRJRPR+9SISQQ7R5eRgtdIHLJsM2hHx1MweAJbJxJ5XnMjjq0Lc7ZGv44ufzAqds1nKxiVkdy4ag==", | |
| 1332 | "dev": true, | |
| 1333 | "license": "Apache-2.0" | |
| 1334 | }, | |
| 1335 | "node_modules/rocksdb-native": { | |
| 1336 | "version": "3.15.0", | |
| 1337 | "resolved": "https://registry.npmjs.org/rocksdb-native/-/rocksdb-native-3.15.0.tgz", | |
| 1338 | "integrity": "sha512-czYx0hOzKAy+HYmJROZm7thvJ1tHFWHNkGI3nomumXWzrJghJdrWo4MIhehGX4OTff0lnOCoB2zu6ekzIKY0rQ==", | |
| 1339 | "dev": true, | |
| 1340 | "license": "Apache-2.0", | |
| 1341 | "dependencies": { | |
| 1342 | "compact-encoding": "^2.15.0", | |
| 1343 | "ready-resource": "^1.0.0", | |
| 1344 | "refcounter": "^1.0.0", | |
| 1345 | "require-addon": "^1.0.2", | |
| 1346 | "resolve-reject-promise": "^1.1.0", | |
| 1347 | "signal-promise": "^1.0.3", | |
| 1348 | "streamx": "^2.16.1" | |
| 1349 | }, | |
| 1350 | "engines": { | |
| 1351 | "bare": ">=1.16.0" | |
| 1352 | } | |
| 1353 | }, | |
| 1354 | "node_modules/safety-catch": { | |
| 1355 | "version": "1.0.3", | |
| 1356 | "resolved": "https://registry.npmjs.org/safety-catch/-/safety-catch-1.0.3.tgz", | |
| 1357 | "integrity": "sha512-Zq+J1TefpoEq/HTUabo0YXX5MNvttjWYODGohgPBO2jfko8Wqx3JYMgE823szDFVamdH5PlpByvfiWScTdSYDA==", | |
| 1358 | "license": "MIT" | |
| 1359 | }, | |
| 1360 | "node_modules/same-object": { | |
| 1361 | "version": "1.0.2", | |
| 1362 | "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz", | |
| 1363 | "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==", | |
| 1364 | "dev": true, | |
| 1365 | "license": "MIT" | |
| 1366 | }, | |
| 1367 | "node_modules/scope-lock": { | |
| 1368 | "version": "1.2.4", | |
| 1369 | "resolved": "https://registry.npmjs.org/scope-lock/-/scope-lock-1.2.4.tgz", | |
| 1370 | "integrity": "sha512-BpSd8VCuCxW9ZitcdIC/vjs3gMaP9bRBL5nkHcyfX2VrS52n13/rHuBA2xJ/S/4DPuRdAO/Bk8pWd8eD/gHCIA==", | |
| 1371 | "dev": true, | |
| 1372 | "license": "Apache-2.0" | |
| 1373 | }, | |
| 1374 | "node_modules/shuffled-priority-queue": { | |
| 1375 | "version": "2.1.0", | |
| 1376 | "resolved": "https://registry.npmjs.org/shuffled-priority-queue/-/shuffled-priority-queue-2.1.0.tgz", | |
| 1377 | "integrity": "sha512-xhdh7fHyMsr0m/w2kDfRJuBFRS96b9l8ZPNWGaQ+PMvnUnZ/Eh+gJJ9NsHBd7P9k0399WYlCLzsy18EaMfyadA==", | |
| 1378 | "license": "MIT", | |
| 1379 | "dependencies": { | |
| 1380 | "unordered-set": "^2.0.1" | |
| 1381 | } | |
| 1382 | }, | |
| 1383 | "node_modules/signal-promise": { | |
| 1384 | "version": "1.0.3", | |
| 1385 | "resolved": "https://registry.npmjs.org/signal-promise/-/signal-promise-1.0.3.tgz", | |
| 1386 | "integrity": "sha512-WBgv0UnIq2C+Aeh0/n+IRpP6967eIx9WpynTUoiW3isPpfe1zu2LJzyfXdo9Tgef8yR/sGjcMvoUXD7EYdiz+g==", | |
| 1387 | "license": "MIT" | |
| 1388 | }, | |
| 1389 | "node_modules/simdle-native": { | |
| 1390 | "version": "1.3.9", | |
| 1391 | "resolved": "https://registry.npmjs.org/simdle-native/-/simdle-native-1.3.9.tgz", | |
| 1392 | "integrity": "sha512-Isc8sP4OiiIU0mpslD4GHEnR0VQWvR/54WN7YtwEDkNdTJVWtpmvsSvsgRlw5BNGxdYXlVRegdnrSu10H/PhvA==", | |
| 1393 | "dev": true, | |
| 1394 | "license": "Apache-2.0", | |
| 1395 | "optional": true, | |
| 1396 | "dependencies": { | |
| 1397 | "b4a": "^1.6.0", | |
| 1398 | "require-addon": "^1.1.0" | |
| 1399 | } | |
| 1400 | }, | |
| 1401 | "node_modules/simdle-universal": { | |
| 1402 | "version": "1.1.2", | |
| 1403 | "resolved": "https://registry.npmjs.org/simdle-universal/-/simdle-universal-1.1.2.tgz", | |
| 1404 | "integrity": "sha512-3n3w1bs+uwgHKQjt6arez83EywNlhZzYvNOhvAASTl/8KqNIcqr6aHyGt3JRlfuUC7iB0tomJRPlJ2cRGIpBzA==", | |
| 1405 | "dev": true, | |
| 1406 | "license": "ISC", | |
| 1407 | "dependencies": { | |
| 1408 | "b4a": "^1.6.0" | |
| 1409 | }, | |
| 1410 | "optionalDependencies": { | |
| 1411 | "simdle-native": "^1.1.1" | |
| 1412 | } | |
| 1413 | }, | |
| 1414 | "node_modules/sodium-native": { | |
| 1415 | "version": "5.1.0", | |
| 1416 | "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz", | |
| 1417 | "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==", | |
| 1418 | "license": "MIT", | |
| 1419 | "dependencies": { | |
| 1420 | "bare-assert": "^1.2.0", | |
| 1421 | "require-addon": "^1.1.0", | |
| 1422 | "which-runtime": "^1.2.1" | |
| 1423 | }, | |
| 1424 | "engines": { | |
| 1425 | "bare": ">=1.16.0" | |
| 1426 | } | |
| 1427 | }, | |
| 1428 | "node_modules/sodium-secretstream": { | |
| 1429 | "version": "1.2.0", | |
| 1430 | "resolved": "https://registry.npmjs.org/sodium-secretstream/-/sodium-secretstream-1.2.0.tgz", | |
| 1431 | "integrity": "sha512-q/DbraNFXm1KfCiiZvapmz5UC3OlpirYFIvBK2MhGaOFSb3gRyk8OXTi17UI9SGfshQNCpsVvlopogbzZNyW6Q==", | |
| 1432 | "license": "MIT", | |
| 1433 | "dependencies": { | |
| 1434 | "b4a": "^1.1.1", | |
| 1435 | "sodium-universal": "^5.0.0" | |
| 1436 | } | |
| 1437 | }, | |
| 1438 | "node_modules/sodium-universal": { | |
| 1439 | "version": "5.0.1", | |
| 1440 | "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz", | |
| 1441 | "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==", | |
| 1442 | "license": "MIT", | |
| 1443 | "dependencies": { | |
| 1444 | "sodium-native": "^5.0.1" | |
| 1445 | }, | |
| 1446 | "peerDependencies": { | |
| 1447 | "sodium-javascript": "~0.8.0" | |
| 1448 | }, | |
| 1449 | "peerDependenciesMeta": { | |
| 1450 | "sodium-javascript": { | |
| 1451 | "optional": true | |
| 1452 | } | |
| 1453 | } | |
| 1454 | }, | |
| 1455 | "node_modules/stackframe": { | |
| 1456 | "version": "1.3.4", | |
| 1457 | "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", | |
| 1458 | "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", | |
| 1459 | "dev": true, | |
| 1460 | "license": "MIT" | |
| 1461 | }, | |
| 1462 | "node_modules/streamx": { | |
| 1463 | "version": "2.25.0", | |
| 1464 | "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", | |
| 1465 | "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", | |
| 1466 | "license": "MIT", | |
| 1467 | "dependencies": { | |
| 1468 | "events-universal": "^1.0.0", | |
| 1469 | "fast-fifo": "^1.3.2", | |
| 1470 | "text-decoder": "^1.1.0" | |
| 1471 | } | |
| 1472 | }, | |
| 1473 | "node_modules/teex": { | |
| 1474 | "version": "1.0.1", | |
| 1475 | "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", | |
| 1476 | "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", | |
| 1477 | "license": "MIT", | |
| 1478 | "dependencies": { | |
| 1479 | "streamx": "^2.12.5" | |
| 1480 | } | |
| 1481 | }, | |
| 1482 | "node_modules/test-tmp": { | |
| 1483 | "version": "1.4.0", | |
| 1484 | "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz", | |
| 1485 | "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==", | |
| 1486 | "dev": true, | |
| 1487 | "license": "MIT", | |
| 1488 | "dependencies": { | |
| 1489 | "bare-fs": "^4.0.1", | |
| 1490 | "bare-os": "^3.3.0", | |
| 1491 | "bare-path": "^3.0.0" | |
| 1492 | } | |
| 1493 | }, | |
| 1494 | "node_modules/text-decoder": { | |
| 1495 | "version": "1.2.7", | |
| 1496 | "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", | |
| 1497 | "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", | |
| 1498 | "license": "Apache-2.0", | |
| 1499 | "dependencies": { | |
| 1500 | "b4a": "^1.6.4" | |
| 1501 | } | |
| 1502 | }, | |
| 1503 | "node_modules/time-ordered-set": { | |
| 1504 | "version": "2.0.1", | |
| 1505 | "resolved": "https://registry.npmjs.org/time-ordered-set/-/time-ordered-set-2.0.1.tgz", | |
| 1506 | "integrity": "sha512-VJEKmgSN2UiOLB8BpN8Sh2b9LGMHTP5OPrQRpnKjvOheOyzk0mufbjzjKTIG2gO4A+Y+vDJ+0TcLbpUmMLsg8A==", | |
| 1507 | "license": "MIT" | |
| 1508 | }, | |
| 1509 | "node_modules/timeout-refresh": { | |
| 1510 | "version": "2.0.1", | |
| 1511 | "resolved": "https://registry.npmjs.org/timeout-refresh/-/timeout-refresh-2.0.1.tgz", | |
| 1512 | "integrity": "sha512-SVqEcMZBsZF9mA78rjzCrYrUs37LMJk3ShZ851ygZYW1cMeIjs9mL57KO6Iv5mmjSQnOe/29/VAfGXo+oRCiVw==", | |
| 1513 | "license": "MIT" | |
| 1514 | }, | |
| 1515 | "node_modules/tmatch": { | |
| 1516 | "version": "5.0.0", | |
| 1517 | "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz", | |
| 1518 | "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==", | |
| 1519 | "dev": true, | |
| 1520 | "license": "ISC", | |
| 1521 | "engines": { | |
| 1522 | "node": ">=8" | |
| 1523 | } | |
| 1524 | }, | |
| 1525 | "node_modules/udx-native": { | |
| 1526 | "version": "1.19.2", | |
| 1527 | "resolved": "https://registry.npmjs.org/udx-native/-/udx-native-1.19.2.tgz", | |
| 1528 | "integrity": "sha512-RNYh+UhfryCsF5hE2ZOuIqcZ+qdipXK3UsarwxWJwsUQZFE3ybwz0mPjwb5ev1PMBcjFahWiepS/q0wwL51c2g==", | |
| 1529 | "license": "Apache-2.0", | |
| 1530 | "dependencies": { | |
| 1531 | "b4a": "^1.5.0", | |
| 1532 | "bare-events": "^2.2.0", | |
| 1533 | "require-addon": "^1.1.0", | |
| 1534 | "streamx": "^2.22.0" | |
| 1535 | }, | |
| 1536 | "engines": { | |
| 1537 | "bare": ">=1.17.4" | |
| 1538 | } | |
| 1539 | }, | |
| 1540 | "node_modules/unordered-set": { | |
| 1541 | "version": "2.0.1", | |
| 1542 | "resolved": "https://registry.npmjs.org/unordered-set/-/unordered-set-2.0.1.tgz", | |
| 1543 | "integrity": "sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==", | |
| 1544 | "license": "MIT" | |
| 1545 | }, | |
| 1546 | "node_modules/unslab": { | |
| 1547 | "version": "1.3.0", | |
| 1548 | "resolved": "https://registry.npmjs.org/unslab/-/unslab-1.3.0.tgz", | |
| 1549 | "integrity": "sha512-YATkfKAFj47kTzmiQrWXMyRvaVrHsW6MEALa4bm+FhiA2YG4oira+Z3DXN6LrYOYn2Y8eO94Lwl9DOHjs1FpoQ==", | |
| 1550 | "license": "Apache-2.0", | |
| 1551 | "dependencies": { | |
| 1552 | "b4a": "^1.6.6" | |
| 1553 | } | |
| 1554 | }, | |
| 1555 | "node_modules/v8-to-istanbul": { | |
| 1556 | "version": "9.3.0", | |
| 1557 | "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", | |
| 1558 | "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", | |
| 1559 | "dev": true, | |
| 1560 | "license": "ISC", | |
| 1561 | "dependencies": { | |
| 1562 | "@jridgewell/trace-mapping": "^0.3.12", | |
| 1563 | "@types/istanbul-lib-coverage": "^2.0.1", | |
| 1564 | "convert-source-map": "^2.0.0" | |
| 1565 | }, | |
| 1566 | "engines": { | |
| 1567 | "node": ">=10.12.0" | |
| 1568 | } | |
| 1569 | }, | |
| 1570 | "node_modules/which-runtime": { | |
| 1571 | "version": "1.3.2", | |
| 1572 | "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.3.2.tgz", | |
| 1573 | "integrity": "sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==", | |
| 1574 | "license": "Apache-2.0" | |
| 1575 | }, | |
| 1576 | "node_modules/xache": { | |
| 1577 | "version": "1.2.1", | |
| 1578 | "resolved": "https://registry.npmjs.org/xache/-/xache-1.2.1.tgz", | |
| 1579 | "integrity": "sha512-igRS6jPreJ54ABdzhh4mCDXcz+XMaWO2q1ABRV2yWYuk29jlp8VT7UBdCqNkX7rpYBbXsebVVKkwIuYZjyZNqA==", | |
| 1580 | "license": "MIT" | |
| 1581 | }, | |
| 1582 | "node_modules/z32": { | |
| 1583 | "version": "1.1.0", | |
| 1584 | "resolved": "https://registry.npmjs.org/z32/-/z32-1.1.0.tgz", | |
| 1585 | "integrity": "sha512-1WUHy+VS6d0HPNspDxvLssBbeQjXMjSnpv0vH82vRAUfg847NmX3OXozp/hRP5jPhxBbrVzrgvAt+UsGNzRFQQ==", | |
| 1586 | "license": "MIT", | |
| 1587 | "dependencies": { | |
| 1588 | "b4a": "^1.5.3" | |
| 1589 | } | |
| 1590 | } | |
| 1591 | } | |
| 1592 | } |
| … | … | @@ -0,0 +1,22 @@ |
| 1 | { | |
| 2 | "name": "hyper-svc", | |
| 3 | "version": "0.1.0", | |
| 4 | "description": "Reusable per-app daemon library — UDS + HTTP for local RPC, Hyperswarm for peer mirroring", | |
| 5 | "type": "module", | |
| 6 | "main": "index.js", | |
| 7 | "scripts": { | |
| 8 | "test": "brittle test/*.test.js" | |
| 9 | }, | |
| 10 | "dependencies": { | |
| 11 | "b4a": "^1.6.0", | |
| 12 | "hyperswarm": "^4.0.0", | |
| 13 | "protomux": "^3.0.0", | |
| 14 | "ready-resource": "^1.0.0" | |
| 15 | }, | |
| 16 | "devDependencies": { | |
| 17 | "brittle": "^3.0.0", | |
| 18 | "corestore": "^7.0.0", | |
| 19 | "hyper-rooms": "file:../hyper-rooms", | |
| 20 | "@hyperswarm/testnet": "^3.0.0" | |
| 21 | } | |
| 22 | } |
| … | … | @@ -0,0 +1,431 @@ |
| 1 | import test from 'brittle' | |
| 2 | import Corestore from 'corestore' | |
| 3 | import { Room, jsonSchema } from 'hyper-rooms' | |
| 4 | import createTestnet from '@hyperswarm/testnet' | |
| 5 | import { mkdtempSync } from 'fs' | |
| 6 | import { join } from 'path' | |
| 7 | import { tmpdir } from 'os' | |
| 8 | import b4a from 'b4a' | |
| 9 | ||
| 10 | import { Service, Client, encodeFrame, FrameDecoder } from '../index.js' | |
| 11 | ||
| 12 | // ── Helpers ──────────────────────────────────────────────────── | |
| 13 | ||
| 14 | function tmpStore () { | |
| 15 | return new Corestore(mkdtempSync(join(tmpdir(), 'hyper-svc-store-'))) | |
| 16 | } | |
| 17 | ||
| 18 | function tmpSockPath () { | |
| 19 | return join(mkdtempSync(join(tmpdir(), 'hyper-svc-sock-')), 'svc.sock') | |
| 20 | } | |
| 21 | ||
| 22 | // A trivial Room schema for swarm replication tests. | |
| 23 | const counterSchema = jsonSchema({ | |
| 24 | validate (op) { | |
| 25 | if (op.type !== 'inc') throw new Error('only inc supported') | |
| 26 | }, | |
| 27 | async apply (batch, op) { | |
| 28 | const cur = await batch.get('counter') | |
| 29 | const next = (cur ? cur.value.n : 0) + (op.delta || 1) | |
| 30 | await batch.put('counter', { n: next }) | |
| 31 | } | |
| 32 | }) | |
| 33 | ||
| 34 | class CounterRoom { | |
| 35 | constructor (corestore, opts = {}) { | |
| 36 | this._room = new Room(corestore, { | |
| 37 | schema: counterSchema, | |
| 38 | namespace: 'counter', | |
| 39 | bootstrapKey: opts.bootstrapKey || null | |
| 40 | }) | |
| 41 | } | |
| 42 | async ready () { await this._room.ready() } | |
| 43 | async close () { await this._room.close() } | |
| 44 | get keyHex () { return this._room.keyHex } | |
| 45 | get base () { return this._room.base } | |
| 46 | async inc (delta = 1) { | |
| 47 | await this._room.append({ type: 'inc', delta }) | |
| 48 | } | |
| 49 | async value () { | |
| 50 | await this._room.base.update() | |
| 51 | const e = await this._room.view.get('counter') | |
| 52 | return e ? e.value.n : 0 | |
| 53 | } | |
| 54 | } | |
| 55 | ||
| 56 | // ── Framing ─────────────────────────────────────────────────── | |
| 57 | ||
| 58 | test('frame round-trip', (t) => { | |
| 59 | const original = { id: 1, method: 'list', params: { limit: 10 } } | |
| 60 | const frame = encodeFrame(original) | |
| 61 | ||
| 62 | let decoded | |
| 63 | const dec = new FrameDecoder((obj) => { decoded = obj }) | |
| 64 | dec.push(frame) | |
| 65 | ||
| 66 | t.alike(decoded, original) | |
| 67 | }) | |
| 68 | ||
| 69 | test('frame decoder handles multiple frames in one chunk', (t) => { | |
| 70 | const a = encodeFrame({ id: 1, method: 'one' }) | |
| 71 | const b = encodeFrame({ id: 2, method: 'two' }) | |
| 72 | ||
| 73 | const seen = [] | |
| 74 | const dec = new FrameDecoder((obj) => seen.push(obj)) | |
| 75 | dec.push(b4a.concat([a, b])) | |
| 76 | ||
| 77 | t.is(seen.length, 2) | |
| 78 | t.is(seen[0].id, 1) | |
| 79 | t.is(seen[1].id, 2) | |
| 80 | }) | |
| 81 | ||
| 82 | test('frame decoder handles a frame split across chunks', (t) => { | |
| 83 | const frame = encodeFrame({ id: 1, method: 'split', params: { x: 'hello world' } }) | |
| 84 | const seen = [] | |
| 85 | const dec = new FrameDecoder((obj) => seen.push(obj)) | |
| 86 | ||
| 87 | // Split arbitrarily | |
| 88 | dec.push(frame.subarray(0, 3)) // partial header | |
| 89 | t.is(seen.length, 0) | |
| 90 | dec.push(frame.subarray(3, 6)) // rest of header + tiny payload | |
| 91 | t.is(seen.length, 0) | |
| 92 | dec.push(frame.subarray(6)) // rest of payload | |
| 93 | t.is(seen.length, 1) | |
| 94 | t.is(seen[0].method, 'split') | |
| 95 | }) | |
| 96 | ||
| 97 | test('frame decoder rejects malformed JSON', (t) => { | |
| 98 | const dec = new FrameDecoder(() => {}) | |
| 99 | // Length 5, payload "{bad}" — invalid JSON | |
| 100 | const bad = b4a.alloc(9) | |
| 101 | bad.writeUInt32BE(5, 0) | |
| 102 | bad.write('{bad}', 4, 5, 'utf8') | |
| 103 | // SyntaxError is a "native" error so we use exception.all | |
| 104 | t.exception.all(() => dec.push(bad)) | |
| 105 | }) | |
| 106 | ||
| 107 | // ── In-process dispatch ────────────────────────────────────── | |
| 108 | ||
| 109 | test('in-process dispatch (no transports)', async (t) => { | |
| 110 | const svc = new Service({ name: 'test' }) | |
| 111 | svc.handle('echo', async (ctx, params) => params) | |
| 112 | svc.handle('hello', async (ctx, params) => `hi ${params.name}`) | |
| 113 | ||
| 114 | await svc.ready() | |
| 115 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 116 | ||
| 117 | t.alike(await svc.dispatch('echo', { foo: 1 }), { foo: 1 }) | |
| 118 | t.is(await svc.dispatch('hello', { name: 'alice' }), 'hi alice') | |
| 119 | }) | |
| 120 | ||
| 121 | test('dispatch unknown method throws ENOMETHOD', async (t) => { | |
| 122 | const svc = new Service({ name: 'test' }) | |
| 123 | await svc.ready() | |
| 124 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 125 | ||
| 126 | try { | |
| 127 | await svc.dispatch('nope', {}) | |
| 128 | t.fail('should have thrown') | |
| 129 | } catch (err) { | |
| 130 | t.is(err.code, 'ENOMETHOD') | |
| 131 | t.ok(err.message.includes('nope')) | |
| 132 | } | |
| 133 | }) | |
| 134 | ||
| 135 | test('handle rejects duplicate names', async (t) => { | |
| 136 | const svc = new Service({ name: 'test' }) | |
| 137 | svc.handle('foo', () => 1) | |
| 138 | t.exception(() => svc.handle('foo', () => 2)) | |
| 139 | await svc.close() | |
| 140 | }) | |
| 141 | ||
| 142 | test('listHandlers + listMounts', async (t) => { | |
| 143 | const svc = new Service({ name: 'test' }) | |
| 144 | svc.handle('a', () => 1) | |
| 145 | svc.handle('b', () => 2) | |
| 146 | svc.mount('m1', { hello: 'world' }) | |
| 147 | ||
| 148 | t.alike(svc.listHandlers(), ['a', 'b']) | |
| 149 | t.alike(svc.listMounts(), ['m1']) | |
| 150 | t.is(svc.modules.m1.hello, 'world') | |
| 151 | await svc.close() | |
| 152 | }) | |
| 153 | ||
| 154 | // ── UDS transport ──────────────────────────────────────────── | |
| 155 | ||
| 156 | test('UDS round-trip', async (t) => { | |
| 157 | const sock = tmpSockPath() | |
| 158 | const svc = new Service({ name: 'test', uds: sock }) | |
| 159 | svc.handle('echo', async (ctx, params) => { | |
| 160 | return { from: ctx.transport, params } | |
| 161 | }) | |
| 162 | await svc.listen() | |
| 163 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 164 | ||
| 165 | const client = await Client.discover({ uds: sock }) | |
| 166 | t.is(client.kind, 'uds') | |
| 167 | t.teardown(async () => { try { await client.close() } catch {} }) | |
| 168 | ||
| 169 | const result = await client.call('echo', { x: 42 }) | |
| 170 | t.is(result.from, 'uds') | |
| 171 | t.alike(result.params, { x: 42 }) | |
| 172 | }) | |
| 173 | ||
| 174 | test('UDS handler error propagates', async (t) => { | |
| 175 | const sock = tmpSockPath() | |
| 176 | const svc = new Service({ name: 'test', uds: sock }) | |
| 177 | svc.handle('boom', async () => { throw new Error('kaboom') }) | |
| 178 | await svc.listen() | |
| 179 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 180 | ||
| 181 | const client = await Client.discover({ uds: sock }) | |
| 182 | t.teardown(async () => { try { await client.close() } catch {} }) | |
| 183 | ||
| 184 | try { | |
| 185 | await client.call('boom', {}) | |
| 186 | t.fail('should have thrown') | |
| 187 | } catch (err) { | |
| 188 | t.ok(err.message.includes('kaboom')) | |
| 189 | } | |
| 190 | }) | |
| 191 | ||
| 192 | test('UDS unknown method propagates', async (t) => { | |
| 193 | const sock = tmpSockPath() | |
| 194 | const svc = new Service({ name: 'test', uds: sock }) | |
| 195 | await svc.listen() | |
| 196 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 197 | ||
| 198 | const client = await Client.discover({ uds: sock }) | |
| 199 | t.teardown(async () => { try { await client.close() } catch {} }) | |
| 200 | ||
| 201 | try { | |
| 202 | await client.call('missing', {}) | |
| 203 | t.fail('should have thrown') | |
| 204 | } catch (err) { | |
| 205 | t.ok(err.message.includes('missing') || err.message.includes('unknown')) | |
| 206 | } | |
| 207 | }) | |
| 208 | ||
| 209 | test('UDS pipelined requests', async (t) => { | |
| 210 | const sock = tmpSockPath() | |
| 211 | const svc = new Service({ name: 'test', uds: sock }) | |
| 212 | svc.handle('slow', async (ctx, params) => { | |
| 213 | await new Promise(r => setTimeout(r, 30)) | |
| 214 | return params.n * 2 | |
| 215 | }) | |
| 216 | await svc.listen() | |
| 217 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 218 | ||
| 219 | const client = await Client.discover({ uds: sock }) | |
| 220 | t.teardown(async () => { try { await client.close() } catch {} }) | |
| 221 | ||
| 222 | // Fire 5 calls in parallel — they should pipeline and all return correct values | |
| 223 | const results = await Promise.all([ | |
| 224 | client.call('slow', { n: 1 }), | |
| 225 | client.call('slow', { n: 2 }), | |
| 226 | client.call('slow', { n: 3 }), | |
| 227 | client.call('slow', { n: 4 }), | |
| 228 | client.call('slow', { n: 5 }) | |
| 229 | ]) | |
| 230 | t.alike(results, [2, 4, 6, 8, 10]) | |
| 231 | }) | |
| 232 | ||
| 233 | // ── HTTP transport ─────────────────────────────────────────── | |
| 234 | ||
| 235 | test('HTTP round-trip', async (t) => { | |
| 236 | const svc = new Service({ name: 'test', http: { port: 0 } }) | |
| 237 | svc.handle('echo', async (ctx, params) => { | |
| 238 | return { from: ctx.transport, params } | |
| 239 | }) | |
| 240 | await svc.listen() | |
| 241 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 242 | ||
| 243 | const port = svc.http.port | |
| 244 | const client = await Client.discover({ http: `http://127.0.0.1:${port}` }) | |
| 245 | t.is(client.kind, 'http') | |
| 246 | t.teardown(async () => { try { await client.close() } catch {} }) | |
| 247 | ||
| 248 | const result = await client.call('echo', { y: 'hi' }) | |
| 249 | t.is(result.from, 'http') | |
| 250 | t.alike(result.params, { y: 'hi' }) | |
| 251 | }) | |
| 252 | ||
| 253 | test('HTTP unknown method returns 404', async (t) => { | |
| 254 | const svc = new Service({ name: 'test', http: { port: 0 } }) | |
| 255 | await svc.listen() | |
| 256 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 257 | ||
| 258 | const client = await Client.discover({ http: `http://127.0.0.1:${svc.http.port}` }) | |
| 259 | t.teardown(async () => { try { await client.close() } catch {} }) | |
| 260 | ||
| 261 | try { | |
| 262 | await client.call('nope', {}) | |
| 263 | t.fail('should have thrown') | |
| 264 | } catch (err) { | |
| 265 | t.ok(err.message.toLowerCase().includes('unknown') || err.message.includes('nope')) | |
| 266 | } | |
| 267 | }) | |
| 268 | ||
| 269 | test('HTTP app routes coexist with /rpc', async (t) => { | |
| 270 | const svc = new Service({ name: 'test', http: { port: 0 } }) | |
| 271 | svc.handle('ping', async () => 'pong') | |
| 272 | ||
| 273 | // App registers its own SSR route | |
| 274 | svc.http.server.on('request', (req, res) => { | |
| 275 | if (req.url === '/hello') { | |
| 276 | res.writeHead(200, { 'Content-Type': 'text/plain' }) | |
| 277 | res.end('hello world') | |
| 278 | } | |
| 279 | }) | |
| 280 | ||
| 281 | await svc.listen() | |
| 282 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 283 | ||
| 284 | // RPC still works | |
| 285 | const client = await Client.discover({ http: `http://127.0.0.1:${svc.http.port}` }) | |
| 286 | t.teardown(async () => { try { await client.close() } catch {} }) | |
| 287 | t.is(await client.call('ping', {}), 'pong') | |
| 288 | ||
| 289 | // App route also works | |
| 290 | const text = await fetchText(`http://127.0.0.1:${svc.http.port}/hello`) | |
| 291 | t.is(text, 'hello world') | |
| 292 | }) | |
| 293 | ||
| 294 | // ── Discovery ──────────────────────────────────────────────── | |
| 295 | ||
| 296 | test('discover prefers UDS when both are reachable', async (t) => { | |
| 297 | const sock = tmpSockPath() | |
| 298 | const svc = new Service({ name: 'test', uds: sock, http: { port: 0 } }) | |
| 299 | svc.handle('echo', async (ctx) => ctx.transport) | |
| 300 | await svc.listen() | |
| 301 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 302 | ||
| 303 | const client = await Client.discover({ | |
| 304 | uds: sock, | |
| 305 | http: `http://127.0.0.1:${svc.http.port}` | |
| 306 | }) | |
| 307 | t.is(client.kind, 'uds') | |
| 308 | t.is(await client.call('echo', {}), 'uds') | |
| 309 | await client.close() | |
| 310 | }) | |
| 311 | ||
| 312 | test('discover falls back to HTTP when UDS unreachable', async (t) => { | |
| 313 | // Daemon only listens on HTTP | |
| 314 | const svc = new Service({ name: 'test', http: { port: 0 } }) | |
| 315 | svc.handle('echo', async (ctx) => ctx.transport) | |
| 316 | await svc.listen() | |
| 317 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 318 | ||
| 319 | const client = await Client.discover({ | |
| 320 | uds: '/tmp/this-socket-does-not-exist-' + Date.now() + '.sock', | |
| 321 | http: `http://127.0.0.1:${svc.http.port}` | |
| 322 | }) | |
| 323 | t.is(client.kind, 'http') | |
| 324 | t.is(await client.call('echo', {}), 'http') | |
| 325 | await client.close() | |
| 326 | }) | |
| 327 | ||
| 328 | test('discover throws when nothing is reachable', async (t) => { | |
| 329 | try { | |
| 330 | await Client.discover({}) | |
| 331 | t.fail('should have thrown') | |
| 332 | } catch (err) { | |
| 333 | t.ok(err.message.includes('no daemon') || err.message.includes('reachable')) | |
| 334 | } | |
| 335 | }) | |
| 336 | ||
| 337 | // ── Mounts ─────────────────────────────────────────────────── | |
| 338 | ||
| 339 | test('mounted modules accessible via svc.modules', async (t) => { | |
| 340 | const svc = new Service({ name: 'test' }) | |
| 341 | const orgs = { greet: () => 'hi from orgs' } | |
| 342 | svc.mount('orgs', orgs) | |
| 343 | ||
| 344 | svc.handle('greet', async () => svc.modules.orgs.greet()) | |
| 345 | await svc.ready() | |
| 346 | t.teardown(async () => { try { await svc.close() } catch {} }) | |
| 347 | ||
| 348 | t.is(await svc.dispatch('greet', {}), 'hi from orgs') | |
| 349 | }) | |
| 350 | ||
| 351 | // ── Swarm replication ──────────────────────────────────────── | |
| 352 | ||
| 353 | test('swarm replicates a mounted Room across two services', async (t) => { | |
| 354 | const testnet = await createTestnet(3, t.teardown) | |
| 355 | const topic = b4a.alloc(32, 'topic-svc-test-rep') | |
| 356 | ||
| 357 | // Service A — owns the bootstrap room | |
| 358 | const storeA = tmpStore() | |
| 359 | const counterA = new CounterRoom(storeA) | |
| 360 | await counterA.ready() | |
| 361 | await counterA.inc(1) | |
| 362 | ||
| 363 | const svcA = new Service({ | |
| 364 | name: 'a', | |
| 365 | corestore: storeA, | |
| 366 | swarm: { | |
| 367 | topic, | |
| 368 | swarm: { bootstrap: testnet.bootstrap } | |
| 369 | } | |
| 370 | }) | |
| 371 | svcA.mount('counter', counterA) | |
| 372 | await svcA.listen() | |
| 373 | ||
| 374 | // Service B — joins with the same key | |
| 375 | const storeB = tmpStore() | |
| 376 | const counterB = new CounterRoom(storeB, { bootstrapKey: counterA.keyHex }) | |
| 377 | await counterB.ready() | |
| 378 | ||
| 379 | const svcB = new Service({ | |
| 380 | name: 'b', | |
| 381 | corestore: storeB, | |
| 382 | swarm: { | |
| 383 | topic, | |
| 384 | swarm: { bootstrap: testnet.bootstrap } | |
| 385 | } | |
| 386 | }) | |
| 387 | svcB.mount('counter', counterB) | |
| 388 | await svcB.listen() | |
| 389 | ||
| 390 | t.teardown(async () => { | |
| 391 | try { await svcA.close() } catch {} | |
| 392 | try { await svcB.close() } catch {} | |
| 393 | try { await counterA.close() } catch {} | |
| 394 | try { await counterB.close() } catch {} | |
| 395 | try { await storeA.close() } catch {} | |
| 396 | try { await storeB.close() } catch {} | |
| 397 | }) | |
| 398 | ||
| 399 | // Wait for B to receive A's writes (replication is async) | |
| 400 | const ok = await waitFor(async () => (await counterB.value()) === 1, 5000) | |
| 401 | t.ok(ok, 'B received A\'s data via swarm replication') | |
| 402 | ||
| 403 | // Now A increments again, B should also see it | |
| 404 | await counterA.inc(2) | |
| 405 | const ok2 = await waitFor(async () => (await counterB.value()) === 3, 5000) | |
| 406 | t.ok(ok2, 'B received A\'s second write') | |
| 407 | }) | |
| 408 | ||
| 409 | // ── Helpers ─────────────────────────────────────────────────── | |
| 410 | ||
| 411 | async function waitFor (predicate, timeoutMs) { | |
| 412 | const start = Date.now() | |
| 413 | while (Date.now() - start < timeoutMs) { | |
| 414 | if (await predicate()) return true | |
| 415 | await new Promise(r => setTimeout(r, 50)) | |
| 416 | } | |
| 417 | return false | |
| 418 | } | |
| 419 | ||
| 420 | function fetchText (url) { | |
| 421 | return new Promise((resolve, reject) => { | |
| 422 | import('http').then(({ default: http }) => { | |
| 423 | http.get(url, (res) => { | |
| 424 | const parts = [] | |
| 425 | res.on('data', (c) => parts.push(c)) | |
| 426 | res.on('end', () => resolve(b4a.concat(parts).toString('utf8'))) | |
| 427 | res.on('error', reject) | |
| 428 | }).on('error', reject) | |
| 429 | }) | |
| 430 | }) | |
| 431 | } |