OverviewFilesCommitsIssuesDocsReleasesPeers

favela-gateway commits / a2b6278

0.1.0: the gateway — membrane + fediverse as roles of one process (hyper-social #80, G1)
- lib/gateway.js: startGateway({ roles, membrane, fediverse }) starts each role through atproto-bridge's startMembrane and ap-bridge's startApBridge, each on its own data dir and swarm identity. A role that fails is reported down and the rest serve; no role up = refuse. One verb map (collisions refused), one status, stop steps in reverse start order, named by role. - bin/favela-gateway.js: owns the process — `admin` passthrough to ap-bridge's CLI, both standalone config loaders on the same env names, ONE control socket (GATEWAY_SOCK) registered as favela-gateway only (the old svc.sock paths and names are not used, so an old app's release cannot restart this process), release-ready -> bounded shutdown -> 75, signals. - CI: favela-surface's ci.yaml (locked dev graph, existing-files import guard). keet-identity-key 3.2.0 is a devDependency for the same reason it is one in ap-bridge: favela-identity's dev node_modules are symlinks into siblings' trees, so a vendored copy of keet-identity-key loses the sodium-hmac it found next to its real path; pgh's installed closure has it, the raw vendored tree does not. Verified: test/gateway.test.js 7/7 (both roles serve; one verb map; a down role leaves the other serving; config errors arrive per role; all-down refuses; unknown role refused; stop frees ports and keeps both swarm identities). CI dry run (hosted-history check relaxed: the two bridge commits are not pushed yet) OK, tests 34/34 against the vendored closure. The real entry on throwaway dirs: membrane 200, actor 200, status lists both roles, verbs of both answer on the one socket, no old socket created, `admin` reaches ap-bridge's CLI, release-ready -> 75 after 2.2 s, SIGTERM -> 0 after 1.9 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a2b627878ac1de1f2d2eeda4a89b0da63bc05bae
by plankatron <fleeky@gmail.com> · 2026-10-13T20:40:55.000Z

Changed files (8) +1672 -0

.gitignore +1 -0
……@@ -0,0 +1 @@
1node_modules/
.pear-git/ci.yaml +109 -0
……@@ -0,0 +1,109 @@
1# pear-git CI — auto-build + publish on `git push pear master`.
2#
3# favela-surface's ci.yaml (install -> vendor -> guard -> tests -> publish), which carries the two fixes
4# its dry run found: the import guard globs only files that exist, and the test runner installs the
5# LOCKED dev graph (fresh brittle pulls bare-type >=1.3, which needs a newer Bare than the fleet runs).
6#
7# The gateway's file: siblings (atproto-bridge, ap-bridge, hyper-svc) are vendored from the build
8# machine's working tree WITH their own node_modules, refusing any sibling whose HEAD is not in hosted
9# history — so atproto-bridge and ap-bridge must be pushed before the gateway.
10#
11# NO daemon-boot smoke: CI steps are synchronous with a hard timeout, and a step that boots a
12# long-running daemon orphans the child when the timeout fires.
13
14steps:
15 - name: install deps (root closure)
16 run: npm install --production
17 timeout: 300
18
19 # Vendor LAST among the npm operations. CI git-archives only this repo, so each file: dep is a
20 # dangling symlink until it is copied in from the build machine's working tree, and whatever is
21 # checked out there ships to the fleet. Refuse unless each sibling HEAD is reachable from a hosted
22 # branch.
23 #
24 # ⛔ The SOURCE comes from the file: spec, not the package name. Two siblings live off the flat
25 # layout: hyper-identity2 is `file:../packages/hyper-identity2` and hyperident is
26 # `file:../repos/hyperident`. Looking them up as hyper-fleeky/<name> (ap-bridge's rule) finds
27 # neither. The spec is resolved against the canonical checkout, because the export in /tmp has no
28 # meaningful `..`.
29 - name: "vendor file: siblings into the closure"
30 run: |
31 HF=/storage/projects/code/hypercore-kb/hyper-fleeky
32 [ -d "$HF" ] || HF="$HOME/hyper-fleeky"
33 SIBS=$(node -e 'const d=require("./package.json");const a={...d.dependencies,...d.devDependencies};console.log(Object.entries(a).filter(([,v])=>String(v).startsWith("file:../")).map(([k,v])=>k+"="+v.slice(8)).join(" "))')
34 echo "siblings (derived from package.json): $SIBS"
35 [ -n "$SIBS" ] || { echo "no file: siblings found — package.json unreadable?"; exit 1; }
36 rc=0
37 for pair in $SIBS; do
38 pkg=${pair%%=*}; rel=${pair#*=}
39 SRC="$HF/$rel"
40 if [ ! -d "$SRC" ]; then echo " $pkg: $SRC NOT FOUND on the build machine — refuse"; rc=1; continue; fi
41 GD="$HOME/git-repos/$(basename "$rel").git"
42 if [ ! -d "$GD" ]; then echo " $pkg: no hosted repo at $GD — refuse"; rc=1; continue; fi
43 LOCAL=$(git -C "$SRC" rev-parse HEAD 2>/dev/null)
44 REFS=$(git --git-dir="$GD" for-each-ref --contains "$LOCAL" --format='%(refname:short)' refs/heads 2>/dev/null | tr '\n' ' ')
45 if [ -z "$REFS" ]; then echo " $pkg: $(echo $LOCAL | cut -c1-7) NOT in hosted history — refuse (push it)"; rc=1; continue; fi
46 echo " $pkg ($rel): $(echo $LOCAL | cut -c1-7) ok (on: $REFS)"
47 rm -rf "node_modules/$pkg" && cp -rL "$SRC" "node_modules/$pkg"
48 done
49 exit $rc
50 timeout: 180
51
52 # Declare-your-deps guard: an undeclared runtime import resolves locally through hoisting and then
53 # crash-loops the --production closure with MODULE_NOT_FOUND. Checks the DECLARATION, not the
54 # disk. Runtime code is index.js, bin/ and lib/; `#node:` specifiers are package imports and are
55 # skipped by the pattern.
56 - name: every direct import is declared
57 run: |
58 rc=0
59 # Only files that exist: favela-surface has no bin/, and grep on an unmatched glob fails the step.
60 FILES=$(ls index.js bin/*.js lib/*.js 2>/dev/null)
61 for spec in $(grep -hoE "from '[a-z0-9@][^']*'" $FILES \
62 | sed -E "s/.*from '([^']*)'/\1/" | sort -u); do
63 case "$spec" in
64 @*) m=$(echo "$spec" | cut -d/ -f1,2) ;;
65 *) m=$(echo "$spec" | cut -d/ -f1) ;;
66 esac
67 if node -e "const d=require('./package.json');process.exit(({...d.dependencies,...d.devDependencies})['$m']?0:1)"; then echo " ok: $m"
68 else echo " UNDECLARED: $m (from '$spec') — resolves on disk but is not in package.json"; rc=1; fi
69 done
70 exit $rc
71 timeout: 60
72
73 # Tests AFTER vendoring, against the exact tree about to be published. npm must not touch this
74 # tree now (it re-resolves and deletes hand-vendored directories), so registry devDependencies go
75 # into a SEPARATE prefix, only what the tree lacks is copied in, the suite runs with the tree's own
76 # brittle (brittle-bare runs tests in-process), and exactly those additions are removed again.
77 - name: tests (offline, against the vendored closure)
78 run: |
79 B=$(mktemp -d)
80 # ⛔ The LOCKED dev graph, every package at the version package-lock.json records — not the ranges
81 # in package.json. Resolved fresh, brittle ^4 became 4.5.0 (2026-10-09), whose
82 # bare-structured-clone 2.x needs bare-type >=1.3, and that needs Bare >=1.32; this machine runs
83 # Bare 1.28 and the tree pins bare-type 1.1.1, so the runner died before a single test. The
84 # lockfile's dev graph (brittle 4.1.1) is the one the suite already passes with locally.
85 SPECS=$(node -p 'const l=require("./package-lock.json").packages;Object.entries(l).filter(([k,v])=>v.dev&&/^node_modules\/(@[^/]+\/)?[^/]+$/.test(k)&&v.version).map(([k,v])=>k.slice(13)+"@"+v.version).join(" ")')
86 echo "test-only devDependencies: $SPECS"
87 npm install --prefix "$B" --no-audit --no-fund --no-save $SPECS >/dev/null 2>&1 || { echo "could not install: $SPECS"; exit 1; }
88 ADDED=""
89 for p in $(cd "$B/node_modules" && ls -A | grep -v -E '^\.'); do
90 case "$p" in
91 @*) for q in $(ls -A "$B/node_modules/$p"); do
92 if [ ! -e "node_modules/$p/$q" ]; then mkdir -p "node_modules/$p" && cp -r "$B/node_modules/$p/$q" "node_modules/$p/$q" && ADDED="$ADDED $p/$q"; fi
93 done ;;
94 *) if [ ! -e "node_modules/$p" ]; then cp -r "$B/node_modules/$p" "node_modules/$p" && ADDED="$ADDED $p"; fi ;;
95 esac
96 done
97 echo "overlaid for the test run only:$ADDED"
98 # The runner's path comes from brittle's own bin map: brittle 4 moved it from bin/bare.js
99 # (ap-bridge's 3.x) to brittle-bare.js at the package root.
100 RUNNER=node_modules/brittle/$(node -p 'require("./node_modules/brittle/package.json").bin["brittle-bare"]')
101 bare "$RUNNER" test/*.test.js; rc=$?
102 for p in $ADDED; do rm -rf "node_modules/$p"; done
103 rm -rf "$B"
104 exit $rc
105 timeout: 900
106
107 - name: pgh publish (content-addressed -> installable app)
108 run: pgh publish . --accept-conflict "automated CI publish; dep version-splits inherited from per-app lockfiles across the pgh ecosystem"
109 timeout: 300
README.md +20 -0
……@@ -0,0 +1,20 @@
1# favela-gateway
2
3The favela gateway: the public-facing bridges as **roles of one process** (hyper-social #80).
4
5| role | what | public surface |
6|---|---|---|
7| `membrane` | atproto-bridge's keyless bsky membrane | `PDS_PORT` (3399) |
8| `fediverse` | ap-bridge, the ActivityPub bridge | `AP_PORT` (3400), admin `AP_ADMIN_PORT` (3401) |
9
10Planned: `lens` (the bsky custom feed, computed keylessly from the fronted repos) and `seeder`
11(holepunch blind-peer).
12
13- Every role keeps its **own** data dir and swarm identity; the gateway is pointed at the existing
14 dirs with the same env names the standalone apps use (`PDS_*`, `AP_*`).
15- One control socket, `GATEWAY_SOCK`: `status` for every role, the membrane's verbs, the fediverse
16 verbs, and `release-ready` (bounded shutdown, exit 75).
17- A role that fails to start is reported down; the others keep serving.
18- `favela-gateway admin …` is ap-bridge's operator CLI.
19
20`GATEWAY_ROLES` picks the roles (default `membrane,fediverse`).
bin/favela-gateway.js +87 -0
……@@ -0,0 +1,87 @@
1// favela-gateway — the entry pgh runs (`bare bin/favela-gateway.js`). See lib/gateway.js and
2// hyper-social #80.
3//
4// This file owns the PROCESS: the admin CLI passthrough, the one control socket and its sidecar name,
5// the OTA exit, the signal handlers and the bounded shutdown. The roles own nothing of that.
6//
7// ⛔ ITS OWN CONTROL SOCKET AND ITS OWN SIDECAR NAME. Not atproto-bridge's svc.sock, not ap-bridge's,
8// and neither old name is registered: the sidecar pushes release-ready to the path an app NAME is
9// registered under, so a later atproto-bridge or ap-bridge release would otherwise restart this
10// process. (favela-host learned this against favela-peer's svc.sock.)
11//
12// ⛔ THE SAME CONFIG LOADERS THE STANDALONE APPS USE, on the same env names. Pointing the gateway at a
13// host is setting the env the two apps had; nothing is reinterpreted on the way.
14
15import process from 'bare-process'
16import fs from 'bare-fs'
17import os from 'bare-os'
18import path from 'bare-path'
19import { Service } from 'hyper-svc'
20import { loadConfig as loadMembraneConfig } from 'atproto-bridge/pds/lib/config.js'
21import { createShutdown } from 'atproto-bridge/pds/lib/shutdown.js'
22import { maintainSidecarRegistration } from 'atproto-bridge/pds/lib/sidecar.js'
23import { loadConfig as loadApConfig, assertUsable } from 'ap-bridge/lib/config.js'
24import { startGateway } from '../lib/gateway.js'
25
26const log = (o) => console.log(JSON.stringify({ t: new Date().toISOString(), ...o }))
27
28function pkgVersion () {
29 try { return JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url).pathname, 'utf8')).version } catch { return null }
30}
31
32// ⛔ `favela-gateway admin …` is ap-bridge's operator CLI on the same admin.sock, checked BEFORE
33// anything opens — running it must not also bind the listeners of a gateway that is already running.
34if (process.argv[2] === 'admin') {
35 const { runAdminCli } = await import('ap-bridge/lib/admin-cli.js')
36 const cfg = loadApConfig()
37 const dir = cfg.dataDir || path.join(process.env.HOME || '.', '.config', 'ap-bridge')
38 process.exit(await runAdminCli(process.argv.slice(3), { uds: path.join(dir, 'admin.sock') }))
39}
40
41const roles = String(process.env.GATEWAY_ROLES || 'membrane,fediverse').split(',').map((s) => s.trim()).filter(Boolean)
42const sock = process.env.GATEWAY_SOCK || path.join(os.homedir(), '.config', 'favela-gateway', 'svc.sock')
43const name = process.env.PGH_APP_NAME || 'favela-gateway'
44const version = pkgVersion()
45
46// A role's config that cannot load (a missing key path, an unusable host) is that role's failure, not
47// the gateway's: it is handed through as an Error, which startGateway reports as the role being down.
48const configs = {}
49if (roles.includes('membrane')) {
50 try { configs.membrane = loadMembraneConfig() } catch (err) { configs.membrane = new Error('membrane config: ' + err.message) }
51}
52if (roles.includes('fediverse')) {
53 try { configs.fediverse = assertUsable(loadApConfig()) } catch (err) { configs.fediverse = new Error('fediverse config: ' + err.message) }
54}
55
56const gateway = await startGateway({ roles, log, version, ...configs })
57
58// The one control socket. A stale socket file from a killed process blocks bind; we only get here once
59// the supervisor decided no live instance owns this app, so an existing file is orphaned.
60fs.mkdirSync(path.dirname(sock), { recursive: true })
61try { if (fs.existsSync(sock)) fs.unlinkSync(sock) } catch {}
62const svc = new Service({ name, uds: sock })
63for (const [verb, fn] of Object.entries(gateway.handlers)) svc.handle(verb, async (ctx, params) => fn(params))
64
65// OTA — the app half of the push contract: the sidecar did the full install; respond ok FIRST, then a
66// clean bounded shutdown, then exit 75 so the supervisor respawns on the new tree.
67svc.handle('release-ready', async (ctx, params = {}) => {
68 log({ msg: 'ota: release-ready — sidecar installed; exiting 75 for supervisor respawn', version: (params && params.version) || '?' })
69 setImmediate(() => { shutdown(75) })
70 return { ok: true, willRespawn: true }
71})
72await svc.listen()
73const stopRegistration = maintainSidecarRegistration({ udsPath: sock, name, runtime: 'bare', version })
74log({ msg: 'gateway up', roles: Object.keys(gateway.roles), down: gateway.down, control: sock, version })
75
76// Bounded and timed (atproto-bridge's createShutdown): the caller's exit code wins, every step logs its
77// duration, and a deadline exits anyway naming the step it was stuck on.
78const shutdown = createShutdown({
79 exit: (code) => process.exit(code),
80 log,
81 steps: [
82 ['control', async () => { stopRegistration(); try { await svc.close() } catch {} }],
83 ...gateway.steps
84 ]
85})
86process.on('SIGINT', () => shutdown(0))
87process.on('SIGTERM', () => shutdown(0))
lib/gateway.js +93 -0
……@@ -0,0 +1,93 @@
1// The favela gateway — the public-facing bridges as ROLES of one process (hyper-social #80).
2//
3// membrane atproto-bridge's keyless bsky membrane (pds.7rnx.net, *.7rnx.net → PDS_PORT)
4// fediverse ap-bridge, the ActivityPub bridge (ap.7rnx.net → AP_PORT, admin → AP_ADMIN_PORT)
5// (G2: lens — the bsky custom feed; G3: seeder — holepunch blind-peer)
6//
7// ⛔ EVERY ROLE KEEPS ITS OWN STORES AND SWARM IDENTITY. The membrane's `swarm-keypair.json` is what
8// hyper-proto-peers allowlist; the fediverse role's `held/swarm-key` public key IS the published
9// bridgeKey registrants find it by. The gateway is pointed at the existing data dirs and opens
10// nothing of its own, so moving a bridge into it changes no identity anywhere.
11//
12// ⛔ A ROLE THAT FAILS TO START IS REPORTED, NOT FATAL. The membrane serving bsky is worth more than a
13// gateway that refuses to boot because the fediverse role's key file moved. Only when NO requested
14// role started does start throw: a gateway serving nothing should not look healthy to its supervisor.
15//
16// Nothing here owns the process: no control socket, no exit, no signal handlers. bin/favela-gateway.js
17// owns those, and serves `handlers` on the gateway's one control socket.
18
19import { startMembrane } from 'atproto-bridge/pds/lib/membrane.js'
20import { startApBridge } from 'ap-bridge/lib/bridge.js'
21
22const ROLES = {
23 membrane: async (cfg, opts) => startMembrane(cfg, opts),
24 fediverse: async (cfg, opts) => startApBridge(cfg, opts)
25}
26
27const defaultLog = (o) => console.log(JSON.stringify(o))
28
29/**
30 * @param {object} opts
31 * @param {string[]} opts.roles in start order; stopped in reverse
32 * @param {object} [opts.membrane] atproto-bridge loadConfig() shape
33 * @param {object} [opts.fediverse] ap-bridge assertUsable(loadConfig()) shape
34 * @param {Function} [opts.log]
35 * @param {Array} [opts.bootstrap] DHT bootstrap for every role's swarm (tests pass [])
36 * @param {string} [opts.version]
37 */
38export async function startGateway ({ roles, log = defaultLog, bootstrap = null, version = null, ...configs }) {
39 for (const name of roles) if (!ROLES[name]) throw new Error('unknown role: ' + name)
40
41 const running = {}
42 const down = {}
43 for (const name of roles) {
44 const roleLog = (o) => log({ role: name, ...o })
45 try {
46 // A config the entry could not load arrives as an Error: that role is down, for that reason.
47 if (configs[name] instanceof Error) throw configs[name]
48 running[name] = await ROLES[name](configs[name], { log: roleLog, bootstrap, version })
49 log({ msg: 'role up', role: name })
50 } catch (err) {
51 down[name] = (err && err.message) || String(err)
52 log({ msg: 'role DOWN — the other roles keep serving', role: name, error: down[name] })
53 }
54 }
55
56 // Stop in reverse start order, each step named by its role so a slow shutdown says whose it was.
57 const steps = []
58 for (const name of [...roles].reverse()) {
59 if (!running[name]) continue
60 for (const [step, fn] of running[name].steps) steps.push([name + ':' + step, fn])
61 }
62
63 if (!Object.keys(running).length) {
64 // Release whatever a half-started role left behind before refusing.
65 for (const [, fn] of steps) { try { await fn() } catch {} }
66 throw new Error('no role started: ' + JSON.stringify(down))
67 }
68
69 const status = async () => {
70 const out = {}
71 for (const name of roles) {
72 if (running[name]) out[name] = await running[name].status()
73 else out[name] = { down: true, error: down[name] }
74 }
75 return { ok: true, version, roles: out }
76 }
77
78 // ONE verb map. The roles' verbs do not overlap except `status`, which the gateway answers for all
79 // of them; a collision would be a bug, so it is refused here rather than silently shadowed.
80 const handlers = { status }
81 for (const name of roles) {
82 if (!running[name]) continue
83 for (const [verb, fn] of Object.entries(running[name].handlers)) {
84 if (verb === 'status') continue
85 if (handlers[verb]) throw new Error(`verb ${verb} is claimed by two roles`)
86 handlers[verb] = fn
87 }
88 }
89
90 return { roles: running, down, handlers, status, steps }
91}
92
93export default startGateway
package-lock.json +1168 -0
……@@ -0,0 +1,1168 @@
1{
2 "name": "favela-gateway",
3 "version": "0.1.0",
4 "lockfileVersion": 3,
5 "requires": true,
6 "packages": {
7 "": {
8 "name": "favela-gateway",
9 "version": "0.1.0",
10 "dependencies": {
11 "ap-bridge": "file:../ap-bridge",
12 "atproto-bridge": "file:../atproto-bridge",
13 "bare-fs": "^4.8.1",
14 "bare-os": "^3.9.3",
15 "bare-path": "^3.1.2",
16 "bare-process": "^4.5.1",
17 "hyper-svc": "file:../hyper-svc"
18 },
19 "bin": {
20 "favela-gateway": "bin/favela-gateway.js"
21 },
22 "devDependencies": {
23 "bare-http1": "^4.5.7",
24 "brittle": "^3.5.0",
25 "keet-identity-key": "3.2.0"
26 }
27 },
28 "../ap-bridge": {
29 "version": "0.10.15",
30 "license": "MIT",
31 "dependencies": {
32 "atproto-did-peer": "file:../atproto-did-peer",
33 "atproto-identity": "file:../atproto-identity",
34 "atproto-repo": "file:../atproto-repo",
35 "atproto-store": "file:../atproto-store",
36 "b4a": "^1.8.1",
37 "bare-crypto": "^1.5.0",
38 "bare-fs": "^4.7.4",
39 "bare-http1": "^4.5.7",
40 "bare-https": "^3.0.0",
41 "bare-os": "^3.9.3",
42 "bare-path": "^3.1.1",
43 "bare-process": "^4.5.1",
44 "bare-subprocess": "^5.0.0",
45 "compact-encoding": "^2.19.0",
46 "corestore": "^7.4.0",
47 "favela-identity": "file:../favela-identity",
48 "favela-markdown": "file:../favela-markdown",
49 "favela-peer": "file:../favela-peer",
50 "hyper-overlay": "file:../hyper-overlay",
51 "hyper-svc": "file:../hyper-svc",
52 "hypercore-crypto": "^3.7.0",
53 "hyperswarm": "^4.17.0",
54 "protomux": "^3.11.0",
55 "protomux-rpc": "^1.10.0"
56 },
57 "devDependencies": {
58 "anproto": "file:../anproto",
59 "brittle": "^3.0.0",
60 "keet-identity-key": "3.2.0"
61 }
62 },
63 "../atproto-bridge": {
64 "version": "0.9.52",
65 "hasInstallScript": true,
66 "dependencies": {
67 "@ipld/car": "^5.4.0",
68 "@ipld/dag-cbor": "^9.2.1",
69 "@noble/curves": "^1.9.7",
70 "@noble/hashes": "^1.5.0",
71 "@scure/bip32": "^1.5.0",
72 "@scure/bip39": "^1.4.0",
73 "atproto-did-peer": "file:../atproto-did-peer",
74 "atproto-identity": "file:../atproto-identity",
75 "atproto-repo": "file:../atproto-repo",
76 "atproto-store": "file:../atproto-store",
77 "b4a": "^1.6.0",
78 "bare-crypto": "^1.5.0",
79 "bare-fetch": "^3.0.1",
80 "bare-fs": "^4.7.3",
81 "bare-http1": "^4.5.7",
82 "bare-https": "^3.0.0",
83 "bare-os": "^3.9.3",
84 "bare-path": "^3.0.1",
85 "bare-process": "^4.5.0",
86 "bare-url": "^2.4.5",
87 "bare-ws": "^3.0.0",
88 "blind-pairing": "^2.3.1",
89 "compact-encoding": "^2.16.0",
90 "corestore": "^7.4.0",
91 "favela-community": "file:../favela-community",
92 "favela-identity": "file:../favela-identity",
93 "hyper-svc": "file:../hyper-svc",
94 "hyperbee": "^2.20.0",
95 "hypercore-crypto": "^3.0.0",
96 "hyperswarm": "^4.17.0",
97 "keet-identity-key": "^3.2.0",
98 "multiformats": "^13.3.0",
99 "protomux": "^3.11.0",
100 "protomux-rpc": "^1.10.0",
101 "random-access-memory": "^6.2.0",
102 "ws": "^8.18.0"
103 },
104 "bin": {
105 "atproto-bridge": "index.js"
106 },
107 "devDependencies": {
108 "@hyperswarm/testnet": "^3.0.0"
109 }
110 },
111 "../hyper-svc": {
112 "version": "0.1.9",
113 "dependencies": {
114 "b4a": "^1.6.0",
115 "bare-abstract-socket": "^1.0.0",
116 "bare-events": "^2.0.0",
117 "bare-fs": "^4.7.2",
118 "bare-http1": "^4.0.0",
119 "bare-net": "^1.0.0",
120 "bare-os": "^3.0.0",
121 "bare-path": "^3.0.0",
122 "bare-process": "^4.0.0",
123 "bare-stream": "^2.0.0",
124 "bare-url": "^2.0.0",
125 "compact-encoding": "^3.2.0",
126 "hypercore-crypto": "^3.0.0",
127 "hyperswarm": "^4.0.0",
128 "protomux": "^3.0.0",
129 "ready-resource": "^1.0.0"
130 },
131 "devDependencies": {
132 "@hyperswarm/testnet": "^3.0.0",
133 "brittle": "^3.0.0",
134 "corestore": "^7.0.0",
135 "hyper-rooms": "file:../hyper-rooms"
136 },
137 "engines": {
138 "bare": ">=1.0",
139 "node": ">=18"
140 }
141 },
142 "node_modules/@jridgewell/resolve-uri": {
143 "version": "3.1.2",
144 "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
145 "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
146 "dev": true,
147 "license": "MIT",
148 "engines": {
149 "node": ">=6.0.0"
150 }
151 },
152 "node_modules/@jridgewell/sourcemap-codec": {
153 "version": "1.6.0",
154 "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
155 "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
156 "dev": true,
157 "license": "MIT"
158 },
159 "node_modules/@jridgewell/trace-mapping": {
160 "version": "0.3.31",
161 "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
162 "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
163 "dev": true,
164 "license": "MIT",
165 "dependencies": {
166 "@jridgewell/resolve-uri": "^3.1.0",
167 "@jridgewell/sourcemap-codec": "^1.4.14"
168 }
169 },
170 "node_modules/@types/istanbul-lib-coverage": {
171 "version": "2.0.6",
172 "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
173 "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
174 "dev": true,
175 "license": "MIT"
176 },
177 "node_modules/ap-bridge": {
178 "resolved": "../ap-bridge",
179 "link": true
180 },
181 "node_modules/atproto-bridge": {
182 "resolved": "../atproto-bridge",
183 "link": true
184 },
185 "node_modules/b4a": {
186 "version": "1.9.0",
187 "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz",
188 "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==",
189 "license": "Apache-2.0",
190 "peerDependencies": {
191 "react-native-b4a": "*"
192 },
193 "peerDependenciesMeta": {
194 "react-native-b4a": {
195 "optional": true
196 }
197 }
198 },
199 "node_modules/bare-abort": {
200 "version": "2.0.13",
201 "resolved": "https://registry.npmjs.org/bare-abort/-/bare-abort-2.0.13.tgz",
202 "integrity": "sha512-zdc8l88eB11Jsz5rDd6sCAgv2kUFXgdrZWoMlgU6JMkfAi1/uuGFC3IEHswKbIRQTk5H3T5CMuechsXYxiaHlQ==",
203 "license": "Apache-2.0"
204 },
205 "node_modules/bare-addon-resolve": {
206 "version": "1.11.0",
207 "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.11.0.tgz",
208 "integrity": "sha512-bTUBSqBjzQQzo2/iIk6KxAClZfahy38EPsJV2bPJ+p2+kS8h+hT5TE5QggpkFUrCnLL4HDTA4DDM+YiNdyol0w==",
209 "dev": true,
210 "license": "Apache-2.0",
211 "dependencies": {
212 "bare-module-resolve": "^1.10.0",
213 "bare-semver": "^1.0.0"
214 },
215 "peerDependencies": {
216 "bare-url": "*"
217 },
218 "peerDependenciesMeta": {
219 "bare-url": {
220 "optional": true
221 }
222 }
223 },
224 "node_modules/bare-ansi-escapes": {
225 "version": "2.2.3",
226 "resolved": "https://registry.npmjs.org/bare-ansi-escapes/-/bare-ansi-escapes-2.2.3.tgz",
227 "integrity": "sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==",
228 "dev": true,
229 "license": "Apache-2.0",
230 "dependencies": {
231 "bare-stream": "^2.6.5"
232 },
233 "peerDependencies": {
234 "bare-buffer": "*"
235 },
236 "peerDependenciesMeta": {
237 "bare-buffer": {
238 "optional": true
239 }
240 }
241 },
242 "node_modules/bare-assert": {
243 "version": "1.2.0",
244 "resolved": "https://registry.npmjs.org/bare-assert/-/bare-assert-1.2.0.tgz",
245 "integrity": "sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==",
246 "dev": true,
247 "license": "Apache-2.0",
248 "dependencies": {
249 "bare-inspect": "^3.1.2"
250 }
251 },
252 "node_modules/bare-bundle": {
253 "version": "1.11.0",
254 "resolved": "https://registry.npmjs.org/bare-bundle/-/bare-bundle-1.11.0.tgz",
255 "integrity": "sha512-msaGQLaojz+EszDhf0qvCTmVWgJFSqbC5bUmEb9AKyVeglmJpx1EbCGqQTiXycCV7oA++MYqTagh/YdIZ//bmQ==",
256 "dev": true,
257 "license": "Apache-2.0",
258 "peerDependencies": {
259 "bare-buffer": "*",
260 "bare-url": "*"
261 },
262 "peerDependenciesMeta": {
263 "bare-buffer": {
264 "optional": true
265 },
266 "bare-url": {
267 "optional": true
268 }
269 }
270 },
271 "node_modules/bare-cov": {
272 "version": "1.2.3",
273 "resolved": "https://registry.npmjs.org/bare-cov/-/bare-cov-1.2.3.tgz",
274 "integrity": "sha512-M9XAEUxk3KKMMD2g8WwbrR0grc25RhkS95XnW6+PksSQdN1C43gYSOa0fQeun5Vo6rvTv501FEYyERKA43EzWg==",
275 "dev": true,
276 "license": "Apache-2.0",
277 "dependencies": {
278 "bare-fs": "^4.8.1",
279 "bare-inspector": "^6.1.0",
280 "bare-path": "^3.1.2",
281 "bare-process": "^4.5.1",
282 "bare-url": "^2.5.4",
283 "bare-v8-to-istanbul": "^1.0.4",
284 "picomatch": "^4.0.7",
285 "which-runtime": "^1.4.0"
286 }
287 },
288 "node_modules/bare-crypto": {
289 "version": "1.15.4",
290 "resolved": "https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.15.4.tgz",
291 "integrity": "sha512-4luuiLnxIJvM7ZyyW6pXbaaGoQDZcIkfcVZrPzJ3oIpdi2UIP37dROmzBWqI2lnrzfKoQlKWM8ryHmyG2svViw==",
292 "dev": true,
293 "license": "Apache-2.0",
294 "dependencies": {
295 "bare-assert": "^1.2.0",
296 "bare-stream": "^2.6.3"
297 },
298 "peerDependencies": {
299 "bare-buffer": "*"
300 },
301 "peerDependenciesMeta": {
302 "bare-buffer": {
303 "optional": true
304 }
305 }
306 },
307 "node_modules/bare-debug-log": {
308 "version": "2.0.0",
309 "resolved": "https://registry.npmjs.org/bare-debug-log/-/bare-debug-log-2.0.0.tgz",
310 "integrity": "sha512-Vi42PkMQsNV9PUpx2Gl1hikshx5O9FzMJ6o9Nnopseg7qLBBK7Nl31d0RHcfwLEAfmcPApytpc0ZFfq68u22FQ==",
311 "dev": true,
312 "license": "Apache-2.0",
313 "dependencies": {
314 "bare-os": "^3.0.1"
315 }
316 },
317 "node_modules/bare-dns": {
318 "version": "2.2.0",
319 "resolved": "https://registry.npmjs.org/bare-dns/-/bare-dns-2.2.0.tgz",
320 "integrity": "sha512-iEqjj122eXdbDP5gXwk/NG/XVI5xoGjQA24UlGBPafUeMIGesxUZ5rxDvjNXPBHDyTkO+H3IsV5izZ8T5IGAXA==",
321 "dev": true,
322 "license": "Apache-2.0",
323 "engines": {
324 "bare": ">=1.7.0"
325 },
326 "peerDependencies": {
327 "bare-buffer": "*"
328 },
329 "peerDependenciesMeta": {
330 "bare-buffer": {
331 "optional": true
332 }
333 }
334 },
335 "node_modules/bare-encoding": {
336 "version": "1.0.3",
337 "resolved": "https://registry.npmjs.org/bare-encoding/-/bare-encoding-1.0.3.tgz",
338 "integrity": "sha512-Kqf+t/azs13lUeyK4Tb7ha4wdLRXKWCXQ8w1rVmt7KtoPCPdHD/Xwt7LBIsCSwwGglrcmblo5VOLa5avkJqULA==",
339 "dev": true,
340 "license": "Apache-2.0",
341 "peerDependencies": {
342 "bare-buffer": "*"
343 },
344 "peerDependenciesMeta": {
345 "bare-buffer": {
346 "optional": true
347 }
348 }
349 },
350 "node_modules/bare-env": {
351 "version": "3.0.1",
352 "resolved": "https://registry.npmjs.org/bare-env/-/bare-env-3.0.1.tgz",
353 "integrity": "sha512-BdoLvnzaWR0YyyJreEx4zG2Od0AC/s9JSZ+EXyMZKunpn8zEgR/dGv37hdFUaY1bjDkcRKvcKkFyJIwlJ0CaYA==",
354 "license": "Apache-2.0",
355 "dependencies": {
356 "bare-os": "^3.0.1"
357 }
358 },
359 "node_modules/bare-events": {
360 "version": "2.9.2",
361 "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz",
362 "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==",
363 "license": "Apache-2.0",
364 "peerDependencies": {
365 "bare-abort-controller": "*"
366 },
367 "peerDependenciesMeta": {
368 "bare-abort-controller": {
369 "optional": true
370 }
371 }
372 },
373 "node_modules/bare-format": {
374 "version": "1.0.2",
375 "resolved": "https://registry.npmjs.org/bare-format/-/bare-format-1.0.2.tgz",
376 "integrity": "sha512-GswdhnOnP9QtwRbrf4wLApw5widkaLMsLe2XOs35fQD2YfEN1ApoGka+cZ7PfvzxMgfYXmMhj/2OGlVn5/Dxgw==",
377 "dev": true,
378 "license": "Apache-2.0",
379 "dependencies": {
380 "bare-inspect": "^3.0.0"
381 }
382 },
383 "node_modules/bare-fs": {
384 "version": "4.8.4",
385 "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.4.tgz",
386 "integrity": "sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==",
387 "license": "Apache-2.0",
388 "dependencies": {
389 "bare-events": "^2.5.4",
390 "bare-path": "^3.0.0",
391 "bare-stream": "^2.6.4",
392 "bare-url": "^2.2.2",
393 "fast-fifo": "^1.3.2"
394 },
395 "engines": {
396 "bare": ">=1.28.0"
397 },
398 "peerDependencies": {
399 "bare-buffer": "*"
400 },
401 "peerDependenciesMeta": {
402 "bare-buffer": {
403 "optional": true
404 }
405 }
406 },
407 "node_modules/bare-hrtime": {
408 "version": "2.1.2",
409 "resolved": "https://registry.npmjs.org/bare-hrtime/-/bare-hrtime-2.1.2.tgz",
410 "integrity": "sha512-ePu0axonbewvuUv9TFqjfvnZTaw+mL4JNx1q79dw8T3DIQEjqHs+pXyj92ghepa8K14KLtX3GdUuAVB+faaGDw==",
411 "license": "Apache-2.0"
412 },
413 "node_modules/bare-http-parser": {
414 "version": "2.1.4",
415 "resolved": "https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-2.1.4.tgz",
416 "integrity": "sha512-6CRjxqMdrEoKKBCS6FbdZUuVsRtyqq18BUae9l9N5okSV75S9lUAF9f5aFSKTNybBS/hcnlxFNocdpXbd2Ohaw==",
417 "dev": true,
418 "license": "Apache-2.0"
419 },
420 "node_modules/bare-http1": {
421 "version": "4.6.2",
422 "resolved": "https://registry.npmjs.org/bare-http1/-/bare-http1-4.6.2.tgz",
423 "integrity": "sha512-fnvBPNUH6KfdcEG7sKKKtvc3/9BEn3vkmJPgdtSLqGl6i2J9Csnl14mVv78ODPn8p3Zu2ZfmlG5XQzQQ45fwLw==",
424 "dev": true,
425 "license": "Apache-2.0",
426 "dependencies": {
427 "bare-events": "^2.6.0",
428 "bare-hrtime": "^2.1.1",
429 "bare-http-parser": "^2.1.0",
430 "bare-stream": "^2.10.0",
431 "bare-tcp": "^2.2.0"
432 },
433 "peerDependencies": {
434 "bare-buffer": "*",
435 "bare-url": "*"
436 },
437 "peerDependenciesMeta": {
438 "bare-buffer": {
439 "optional": true
440 },
441 "bare-url": {
442 "optional": true
443 }
444 }
445 },
446 "node_modules/bare-https": {
447 "version": "3.1.0",
448 "resolved": "https://registry.npmjs.org/bare-https/-/bare-https-3.1.0.tgz",
449 "integrity": "sha512-HNAS0q7CBAXGDFRy/xaYJEsAaVcvyDRXKU1b/KxS/zdcfBWsQVB0TntSzz3IlqZ5ZWHiaOOrMYAO/oIDgpTLkQ==",
450 "dev": true,
451 "license": "Apache-2.0",
452 "dependencies": {
453 "bare-http1": "^4.4.0",
454 "bare-tcp": "^2.2.0",
455 "bare-tls": "^3.0.0"
456 }
457 },
458 "node_modules/bare-inspect": {
459 "version": "3.1.10",
460 "resolved": "https://registry.npmjs.org/bare-inspect/-/bare-inspect-3.1.10.tgz",
461 "integrity": "sha512-Qu01XZNTIT2/bS9k7c6A3OXuAzBnWwDCejGri76BUltdFqVe6Et/IxVOSSbcDpreSSWwqQ5pAu/bxFE6hPDWRQ==",
462 "dev": true,
463 "license": "Apache-2.0",
464 "dependencies": {
465 "bare-ansi-escapes": "^2.1.0",
466 "bare-type": "^1.0.0"
467 },
468 "engines": {
469 "bare": ">=1.18.0"
470 }
471 },
472 "node_modules/bare-inspector": {
473 "version": "6.1.0",
474 "resolved": "https://registry.npmjs.org/bare-inspector/-/bare-inspector-6.1.0.tgz",
475 "integrity": "sha512-PRxmZ4gF+K3TLzGubgRFvzdECybTCSKackgNsAdd4e7SdvICxMkGj+p5iX7bSKYSmgDnxgCR+2Z6UXmWykKhvw==",
476 "dev": true,
477 "license": "Apache-2.0",
478 "dependencies": {
479 "bare-events": "^2.1.0",
480 "bare-http1": "^4.0.0",
481 "bare-stream": "^2.0.0",
482 "bare-url": "^2.0.0",
483 "bare-ws": "^3.0.0"
484 },
485 "engines": {
486 "bare": ">=1.29.0"
487 },
488 "peerDependencies": {
489 "bare-tcp": "*"
490 },
491 "peerDependenciesMeta": {
492 "bare-tcp": {
493 "optional": true
494 }
495 }
496 },
497 "node_modules/bare-module": {
498 "version": "6.4.0",
499 "resolved": "https://registry.npmjs.org/bare-module/-/bare-module-6.4.0.tgz",
500 "integrity": "sha512-Yn4V5g5EqGQL4LYUOmt7fjKzj2JPWyJOqE3lPoeZwfUH5rk4CKUfZj6JhDwbzhBYCqqmUgjgQ5aY8cihAPILLA==",
501 "dev": true,
502 "license": "Apache-2.0",
503 "dependencies": {
504 "bare-bundle": "^1.3.0",
505 "bare-module-lexer": "^1.0.0",
506 "bare-module-resolve": "^1.8.0",
507 "bare-path": "^3.0.0",
508 "bare-type-stripper": "^0.1.2",
509 "bare-url": "^2.0.1"
510 },
511 "engines": {
512 "bare": ">=1.29.4"
513 },
514 "peerDependencies": {
515 "bare-buffer": "*"
516 },
517 "peerDependenciesMeta": {
518 "bare-buffer": {
519 "optional": true
520 }
521 }
522 },
523 "node_modules/bare-module-lexer": {
524 "version": "1.7.0",
525 "resolved": "https://registry.npmjs.org/bare-module-lexer/-/bare-module-lexer-1.7.0.tgz",
526 "integrity": "sha512-LD5eiVhDyiMRfMtxK3E+0SeL95nkLNl37YUccXWEaErmfkJi596OECtUoH3X+YPomthwW2oQEch9c7aOOJ4ePA==",
527 "dev": true,
528 "license": "Apache-2.0",
529 "dependencies": {
530 "require-addon": "^1.0.2"
531 },
532 "engines": {
533 "bare": ">=1.0.0",
534 "node": "^22.21.0 || >=24.9.0"
535 },
536 "peerDependencies": {
537 "bare-buffer": "*"
538 },
539 "peerDependenciesMeta": {
540 "bare-buffer": {
541 "optional": true
542 }
543 }
544 },
545 "node_modules/bare-module-resolve": {
546 "version": "1.12.6",
547 "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.12.6.tgz",
548 "integrity": "sha512-tsoOqHmtF6VmNX4LYDBZwvQVkSmEagnbykNjK31LMxz/amyKfjCs4K976n7uJd1bvWfxbpLQ3Fy7qq8an6jR6Q==",
549 "dev": true,
550 "license": "Apache-2.0",
551 "dependencies": {
552 "bare-semver": "^1.0.0"
553 },
554 "peerDependencies": {
555 "bare-url": "*"
556 },
557 "peerDependenciesMeta": {
558 "bare-url": {
559 "optional": true
560 }
561 }
562 },
563 "node_modules/bare-net": {
564 "version": "2.3.3",
565 "resolved": "https://registry.npmjs.org/bare-net/-/bare-net-2.3.3.tgz",
566 "integrity": "sha512-q1noXFJKn+eNi6vXYv2Y+5FTnn+o7qpli4m2lUZTdK8cBjVs2zGaykuzTUIa29v70eOsQI9OAIyYwN3ODHtFaQ==",
567 "dev": true,
568 "license": "Apache-2.0",
569 "dependencies": {
570 "bare-events": "^2.2.2",
571 "bare-pipe": "^4.0.0",
572 "bare-stream": "^2.0.0",
573 "bare-tcp": "^2.0.0"
574 }
575 },
576 "node_modules/bare-os": {
577 "version": "3.9.3",
578 "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz",
579 "integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==",
580 "license": "Apache-2.0",
581 "engines": {
582 "bare": ">=1.14.0"
583 }
584 },
585 "node_modules/bare-path": {
586 "version": "3.1.2",
587 "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz",
588 "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==",
589 "license": "Apache-2.0"
590 },
591 "node_modules/bare-pipe": {
592 "version": "4.3.1",
593 "resolved": "https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.3.1.tgz",
594 "integrity": "sha512-3P4MYTgOys1Bbz5dgWzwfroNOOl8x1zWJHTHVlwsojThhV/uJPRf+qFrYYBOnI0vQiJJLJtHlw8a4Osj/kcsJQ==",
595 "license": "Apache-2.0",
596 "peer": true,
597 "dependencies": {
598 "bare-events": "^2.5.4",
599 "bare-stream": "^2.6.4"
600 },
601 "engines": {
602 "bare": ">=1.16.0"
603 },
604 "peerDependencies": {
605 "bare-buffer": "*"
606 },
607 "peerDependenciesMeta": {
608 "bare-buffer": {
609 "optional": true
610 }
611 }
612 },
613 "node_modules/bare-posix": {
614 "version": "1.0.1",
615 "resolved": "https://registry.npmjs.org/bare-posix/-/bare-posix-1.0.1.tgz",
616 "integrity": "sha512-b4quA8dyRVvX0aFIAIiev5zFATHwIUGr42Gmt+CqiBNg/bdsNkrj4bbkeotMOMiljJS4DCME5kywJO/TFwKo1A==",
617 "license": "Apache-2.0"
618 },
619 "node_modules/bare-process": {
620 "version": "4.5.1",
621 "resolved": "https://registry.npmjs.org/bare-process/-/bare-process-4.5.1.tgz",
622 "integrity": "sha512-CaAvy1trputD49mtwfJ6G75vydhnirLrW/F3Sznp4H556e1uZn8YMo9ELicBTrGYy7RBNUgPl9bpB/ERzRCiDw==",
623 "license": "Apache-2.0",
624 "dependencies": {
625 "bare-abort": "^2.0.13",
626 "bare-env": "^3.0.0",
627 "bare-events": "^2.3.1",
628 "bare-hrtime": "^2.0.0",
629 "bare-os": "^3.7.1",
630 "bare-posix": "^1.0.1",
631 "bare-signals": "^5.0.0",
632 "bare-stdio": "^1.0.1"
633 }
634 },
635 "node_modules/bare-semver": {
636 "version": "1.1.0",
637 "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.1.0.tgz",
638 "integrity": "sha512-1Hw5qJ7hXdVt3uPUqjeFTuxyvBUJauvz5A1I2jk8gzjZMHp04n//6nV9MDbG9CMw78JHY2lGV0w6s//LrASm2w==",
639 "dev": true,
640 "license": "Apache-2.0"
641 },
642 "node_modules/bare-signals": {
643 "version": "5.0.0",
644 "resolved": "https://registry.npmjs.org/bare-signals/-/bare-signals-5.0.0.tgz",
645 "integrity": "sha512-8Gn8bBFUh2AUCJ9wWWFjSGWIo1HIUSIQnXRJGSm/f7GCDMqsuJhRmR1dT+HtDaDBkeu2l8Koxt2JLurfJ5yHVw==",
646 "license": "Apache-2.0",
647 "dependencies": {
648 "bare-events": "^2.5.3"
649 },
650 "engines": {
651 "bare": ">=1.7.0"
652 }
653 },
654 "node_modules/bare-stdio": {
655 "version": "1.0.3",
656 "resolved": "https://registry.npmjs.org/bare-stdio/-/bare-stdio-1.0.3.tgz",
657 "integrity": "sha512-8BRMx7RWMWCbBmKhyHgBco62J+Pgss7+EPI21L3R9+w0UpwDYWUUcbE0YTOesGyt7M6N7VtoshYbzwrP0pI52Q==",
658 "license": "Apache-2.0",
659 "dependencies": {
660 "bare-fs": "^4.5.2",
661 "bare-pipe": "^4.1.5",
662 "bare-tty": "^5.0.3"
663 }
664 },
665 "node_modules/bare-stream": {
666 "version": "2.13.4",
667 "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz",
668 "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==",
669 "license": "Apache-2.0",
670 "dependencies": {
671 "b4a": "^1.8.1",
672 "streamx": "^2.25.0",
673 "teex": "^1.0.1"
674 },
675 "peerDependencies": {
676 "bare-abort-controller": "*",
677 "bare-buffer": "*",
678 "bare-events": "*"
679 },
680 "peerDependenciesMeta": {
681 "bare-abort-controller": {
682 "optional": true
683 },
684 "bare-buffer": {
685 "optional": true
686 },
687 "bare-events": {
688 "optional": true
689 }
690 }
691 },
692 "node_modules/bare-stylize": {
693 "version": "0.0.1",
694 "resolved": "https://registry.npmjs.org/bare-stylize/-/bare-stylize-0.0.1.tgz",
695 "integrity": "sha512-l3MjmIl476bWijYWf3RbE+osl4iuXSOMudzp0vAqzIK7gPgn/+G3oAxp8Oin9CFF911KBP0LO9kts8Ci8mGZaQ==",
696 "dev": true,
697 "license": "Apache-2.0",
698 "dependencies": {
699 "bare-ansi-escapes": "^2.2.3",
700 "bare-process": "^4.2.1"
701 }
702 },
703 "node_modules/bare-subprocess": {
704 "version": "5.2.3",
705 "resolved": "https://registry.npmjs.org/bare-subprocess/-/bare-subprocess-5.2.3.tgz",
706 "integrity": "sha512-07wwswlV7M3sC9IykbZRZ/jHAkrXFWVLqdBWGv1y0ojCimtRD9hGwxdHmR5FUFmDUZLNsBmTYJNQqgio5+A85Q==",
707 "dev": true,
708 "license": "Apache-2.0",
709 "dependencies": {
710 "bare-env": "^3.0.0",
711 "bare-events": "^2.5.4",
712 "bare-os": "^3.0.1",
713 "bare-pipe": "^4.0.0",
714 "bare-url": "^2.2.2"
715 },
716 "engines": {
717 "bare": ">=1.7.0"
718 },
719 "peerDependencies": {
720 "bare-buffer": "*"
721 },
722 "peerDependenciesMeta": {
723 "bare-buffer": {
724 "optional": true
725 }
726 }
727 },
728 "node_modules/bare-tcp": {
729 "version": "2.6.1",
730 "resolved": "https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.6.1.tgz",
731 "integrity": "sha512-8f8UFP7O27gIGaVWJPysQ2am8LSD6YCuVSbnTbOrQjUD0RkWEZexvi2Q66P99MLxSephlyjoU0mLxWmozRJMnA==",
732 "dev": true,
733 "license": "Apache-2.0",
734 "dependencies": {
735 "bare-dns": "^2.0.4",
736 "bare-events": "^2.5.4",
737 "bare-stream": "^2.6.4"
738 },
739 "engines": {
740 "bare": ">=1.16.0"
741 },
742 "peerDependencies": {
743 "bare-pipe": "*"
744 },
745 "peerDependenciesMeta": {
746 "bare-pipe": {
747 "optional": true
748 }
749 }
750 },
751 "node_modules/bare-tls": {
752 "version": "3.1.11",
753 "resolved": "https://registry.npmjs.org/bare-tls/-/bare-tls-3.1.11.tgz",
754 "integrity": "sha512-lCQMepLYPPmdunO4MtkVe6l619Kdrt47/0HDGOgJY6zVvEeFACZD1hkh1kRw6zJk2NQHLcnebggYQZ/z6J/cPg==",
755 "dev": true,
756 "license": "Apache-2.0",
757 "dependencies": {
758 "bare-net": "^2.0.1",
759 "bare-stream": "^2.6.4"
760 },
761 "engines": {
762 "bare": ">=1.7.0"
763 }
764 },
765 "node_modules/bare-tty": {
766 "version": "5.2.1",
767 "resolved": "https://registry.npmjs.org/bare-tty/-/bare-tty-5.2.1.tgz",
768 "integrity": "sha512-vH9ggdu6q+HilCKcYCfSzwuN2QaR4+gGP+JiYN5zKMhMI6tKeKnZMCuE/AJoF9lbSo0FriPdZoKA5bO2b0Mk3w==",
769 "license": "Apache-2.0",
770 "dependencies": {
771 "bare-events": "^2.2.0",
772 "bare-signals": "^5.0.0",
773 "bare-stream": "^2.0.0"
774 },
775 "engines": {
776 "bare": ">=1.16.0"
777 }
778 },
779 "node_modules/bare-type": {
780 "version": "1.1.1",
781 "resolved": "https://registry.npmjs.org/bare-type/-/bare-type-1.1.1.tgz",
782 "integrity": "sha512-5HwbjDbnYr+Lwu5I59IqwOFcRcVo4jxo408t42gKhKnikw0ojwyPXPlx58AQP4dR2VvGw57ppX/qxYCn60Wujw==",
783 "dev": true,
784 "license": "Apache-2.0",
785 "engines": {
786 "bare": ">=1.2.0"
787 }
788 },
789 "node_modules/bare-type-stripper": {
790 "version": "0.1.7",
791 "resolved": "https://registry.npmjs.org/bare-type-stripper/-/bare-type-stripper-0.1.7.tgz",
792 "integrity": "sha512-kiYTY1PqJnCQmaGT4Q23gskKCTtxqinPdA0SKMO80rjyLMdZhkT+mALf+/UryvovV6ysXtYXGSNPZR4wFhCI/g==",
793 "dev": true,
794 "license": "Apache-2.0",
795 "dependencies": {
796 "require-addon": "^1.0.2"
797 },
798 "engines": {
799 "bare": ">=1.0.0",
800 "node": "^22.21.0 || >=24.9.0"
801 },
802 "peerDependencies": {
803 "bare-buffer": "*"
804 },
805 "peerDependenciesMeta": {
806 "bare-buffer": {
807 "optional": true
808 }
809 }
810 },
811 "node_modules/bare-url": {
812 "version": "2.5.5",
813 "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.5.tgz",
814 "integrity": "sha512-iM9qJCruvpt2rPaLsrumVDru9pogI7PHziHIa9kTNRFt8C8HuSa+kI9kFezK+NvHEFnnz71rKGZ1EAilq/3V/Q==",
815 "license": "Apache-2.0",
816 "peer": true,
817 "dependencies": {
818 "bare-path": "^3.0.0"
819 }
820 },
821 "node_modules/bare-utils": {
822 "version": "1.6.0",
823 "resolved": "https://registry.npmjs.org/bare-utils/-/bare-utils-1.6.0.tgz",
824 "integrity": "sha512-WhQEIkkAxkSnW7u1QgrI0AfNm5JpMruETXeYsb5qnkBJ0TTfNKygZmsh6rkoHBANaV+C/7Jed7bJP9OmEHG7rQ==",
825 "dev": true,
826 "license": "Apache-2.0",
827 "dependencies": {
828 "bare-debug-log": "^2.0.0",
829 "bare-encoding": "^1.0.0",
830 "bare-format": "^1.0.0",
831 "bare-inspect": "^3.0.0",
832 "bare-stylize": "^0.0.1",
833 "bare-type": "^1.0.6"
834 }
835 },
836 "node_modules/bare-v8-to-istanbul": {
837 "version": "1.0.4",
838 "resolved": "https://registry.npmjs.org/bare-v8-to-istanbul/-/bare-v8-to-istanbul-1.0.4.tgz",
839 "integrity": "sha512-aYdse9SdVep4NT18jyMehBShfZgvyQ36Tm+oha2nQIHGcxvdBk0xZLbcYsDPzCTqGdJ5SfXa3VvZUSWyoa3L4w==",
840 "dev": true,
841 "license": "Apache-2.0",
842 "dependencies": {
843 "bare-assert": "^1.2.0",
844 "bare-fs": "^4.8.1",
845 "bare-module": "^6.4.0",
846 "bare-path": "^3.1.2",
847 "bare-process": "^4.5.1",
848 "bare-url": "^2.5.4",
849 "bare-utils": "^1.6.0",
850 "v8-to-istanbul": "^9.3.0",
851 "which-runtime": "^1.4.0"
852 }
853 },
854 "node_modules/bare-ws": {
855 "version": "3.2.0",
856 "resolved": "https://registry.npmjs.org/bare-ws/-/bare-ws-3.2.0.tgz",
857 "integrity": "sha512-9PPJX5JrhuObWZGyhYCrQ1DbhjY1BLVALkZF56r0SYnfyN18ok84yUETcQ377aC2W6w8IqBewjQ1GlovtiXA1Q==",
858 "dev": true,
859 "license": "Apache-2.0",
860 "dependencies": {
861 "bare-crypto": "^1.2.0",
862 "bare-events": "^2.3.1",
863 "bare-http1": "^4.0.0",
864 "bare-https": "^3.0.0",
865 "bare-stream": "^2.1.2"
866 },
867 "peerDependencies": {
868 "bare-buffer": "*",
869 "bare-url": "*"
870 },
871 "peerDependenciesMeta": {
872 "bare-buffer": {
873 "optional": true
874 },
875 "bare-url": {
876 "optional": true
877 }
878 }
879 },
880 "node_modules/bip39-mnemonic": {
881 "version": "2.5.0",
882 "resolved": "https://registry.npmjs.org/bip39-mnemonic/-/bip39-mnemonic-2.5.0.tgz",
883 "integrity": "sha512-hgCxFHN129ta0bHl9VPykoqCPagBUr2oaXjfSbfeXAPe+y/byxwjPFFEFTMNO/1T/GSoMP1cOXHa2ZaprHG1kg==",
884 "dev": true,
885 "license": "Apache-2.0",
886 "dependencies": {
887 "b4a": "^1.6.6",
888 "nanoassert": "^2.0.0",
889 "sodium-universal": "^5.0.1"
890 }
891 },
892 "node_modules/brittle": {
893 "version": "3.19.1",
894 "resolved": "https://registry.npmjs.org/brittle/-/brittle-3.19.1.tgz",
895 "integrity": "sha512-4Ted1Mt9o9B6oIA6ImJJCtB/Fv++ZO3IDNQgRcHOXWSYGRUidgxchaGrUBaRn+4mlneXrgInPkCPzi0jO8qKbA==",
896 "dev": true,
897 "license": "Apache-2.0",
898 "dependencies": {
899 "b4a": "^1.6.0",
900 "bare-assert": "^1.0.2",
901 "bare-cov": "^1.1.0",
902 "bare-fs": "^4.1.6",
903 "bare-os": "^3.6.1",
904 "bare-path": "^3.0.0",
905 "bare-process": "^4.2.1",
906 "bare-subprocess": "^5.0.0",
907 "bare-url": "^2.1.6",
908 "error-stack-parser": "^2.1.4",
909 "globbie": "^1.0.2",
910 "paparam": "^1.6.2",
911 "same-object": "^1.0.2",
912 "test-tmp": "^1.4.0",
913 "tmatch": "^5.0.0"
914 },
915 "bin": {
916 "brittle": "bin/node.js",
917 "brittle-bare": "bin/bare.js",
918 "brittle-node": "bin/node.js",
919 "brittle-pear": "bin/pear.js"
920 }
921 },
922 "node_modules/compact-encoding": {
923 "version": "3.5.2",
924 "resolved": "https://registry.npmjs.org/compact-encoding/-/compact-encoding-3.5.2.tgz",
925 "integrity": "sha512-rgGcWdfxyuLwJHMUXI0OrYGpJe3VN4RZyovbxWVlxPYIsUZutOTfP3shmDyDi/iaybwcxMsTJDDuxQa4xRG0RQ==",
926 "dev": true,
927 "license": "Apache-2.0",
928 "dependencies": {
929 "b4a": "^1.3.0"
930 }
931 },
932 "node_modules/convert-source-map": {
933 "version": "2.0.0",
934 "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
935 "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
936 "dev": true,
937 "license": "MIT"
938 },
939 "node_modules/error-stack-parser": {
940 "version": "2.1.4",
941 "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
942 "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
943 "dev": true,
944 "license": "MIT",
945 "dependencies": {
946 "stackframe": "^1.3.4"
947 }
948 },
949 "node_modules/events-universal": {
950 "version": "1.0.1",
951 "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
952 "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
953 "license": "Apache-2.0",
954 "dependencies": {
955 "bare-events": "^2.7.0"
956 }
957 },
958 "node_modules/fast-fifo": {
959 "version": "1.3.2",
960 "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
961 "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
962 "license": "MIT"
963 },
964 "node_modules/globbie": {
965 "version": "1.0.6",
966 "resolved": "https://registry.npmjs.org/globbie/-/globbie-1.0.6.tgz",
967 "integrity": "sha512-GcxSSfWinZNZNW3+RsPBi2nW4jJcUJsOKiJvuXoeO3Y8pdd045NVPILCAoNCzYUsPw5XAUA/GXwRXF+ABcAeOQ==",
968 "dev": true,
969 "license": "Apache-2.0",
970 "dependencies": {
971 "bare-fs": "^4.7.4",
972 "bare-path": "^3.1.1",
973 "bare-process": "^4.5.1",
974 "picomatch": "^4.0.5"
975 }
976 },
977 "node_modules/hyper-svc": {
978 "resolved": "../hyper-svc",
979 "link": true
980 },
981 "node_modules/keet-identity-key": {
982 "version": "3.2.0",
983 "resolved": "https://registry.npmjs.org/keet-identity-key/-/keet-identity-key-3.2.0.tgz",
984 "integrity": "sha512-EMM+zOnDsdg/tOZ9gv+UlWjFYvMrTu+XYd2sNQ3+x5mGMxw5RjXvdXt/lh2OnGs04IemxKnHjk/hOMKjmYzhew==",
985 "dev": true,
986 "license": "Apache-2.0",
987 "dependencies": {
988 "b4a": "^1.6.4",
989 "bip39-mnemonic": "^2.2.0",
990 "compact-encoding": "^3.0.0",
991 "nanoassert": "^2.0.0",
992 "sodium-hmac": "^2.0.1",
993 "sodium-universal": "^5.0.1"
994 }
995 },
996 "node_modules/nanoassert": {
997 "version": "2.0.0",
998 "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-2.0.0.tgz",
999 "integrity": "sha512-7vO7n28+aYO4J+8w96AzhmU8G+Y/xpPDJz/se19ICsqj/momRbb9mh9ZUtkoJ5X3nTnPdhEJyc0qnM6yAsHBaA==",
1000 "dev": true,
1001 "license": "ISC"
1002 },
1003 "node_modules/paparam": {
1004 "version": "1.14.0",
1005 "resolved": "https://registry.npmjs.org/paparam/-/paparam-1.14.0.tgz",
1006 "integrity": "sha512-WQshqM/4Itr6jXK+sRku9oOuhkX9mV9i2ZALusT5H9QWAlZ8JVe5V+SI92xEyRsmtM30sv1NcMvnokPGFn/Aew==",
1007 "dev": true,
1008 "license": "Apache-2.0"
1009 },
1010 "node_modules/picomatch": {
1011 "version": "4.0.7",
1012 "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
1013 "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
1014 "dev": true,
1015 "license": "MIT",
1016 "engines": {
1017 "node": ">=12"
1018 },
1019 "funding": {
1020 "url": "https://github.com/sponsors/jonschlinkert"
1021 }
1022 },
1023 "node_modules/require-addon": {
1024 "version": "1.3.0",
1025 "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.3.0.tgz",
1026 "integrity": "sha512-j8pifaPNZI7ShKY3ihXZwF0qaEjua8zmlqPvDil0a8SBnyRvjCbAiEQuzO0UcoXH1sqU5U/ilXSjYd1W5NYwAA==",
1027 "dev": true,
1028 "license": "Apache-2.0",
1029 "dependencies": {
1030 "bare-addon-resolve": "^1.3.0"
1031 },
1032 "engines": {
1033 "bare": ">=1.10.0"
1034 }
1035 },
1036 "node_modules/same-object": {
1037 "version": "1.0.2",
1038 "resolved": "https://registry.npmjs.org/same-object/-/same-object-1.0.2.tgz",
1039 "integrity": "sha512-csHWhvUsLbIOHDM/nP+KHWM+BLPsIzWkFa8HbzaI0G7BqKXgx+7FJpKTGgLXyz5amfdY2OVBcmXTqYOMEk04og==",
1040 "dev": true,
1041 "license": "MIT"
1042 },
1043 "node_modules/sodium-hmac": {
1044 "version": "2.1.0",
1045 "resolved": "https://registry.npmjs.org/sodium-hmac/-/sodium-hmac-2.1.0.tgz",
1046 "integrity": "sha512-1g7d7v+/0g/XXSX9sp0++X1QB6M1knhomnsFiN7B42b+rV0VhQeyiRzoWtp7vYGLkzmLY77tWNnCef3juGRwfQ==",
1047 "dev": true,
1048 "license": "Apache-2.0",
1049 "dependencies": {
1050 "b4a": "^1.6.6",
1051 "sodium-universal": "^5.0.1"
1052 }
1053 },
1054 "node_modules/sodium-native": {
1055 "version": "5.1.0",
1056 "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-5.1.0.tgz",
1057 "integrity": "sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==",
1058 "dev": true,
1059 "license": "MIT",
1060 "dependencies": {
1061 "bare-assert": "^1.2.0",
1062 "require-addon": "^1.1.0",
1063 "which-runtime": "^1.2.1"
1064 },
1065 "engines": {
1066 "bare": ">=1.16.0"
1067 }
1068 },
1069 "node_modules/sodium-universal": {
1070 "version": "5.0.1",
1071 "resolved": "https://registry.npmjs.org/sodium-universal/-/sodium-universal-5.0.1.tgz",
1072 "integrity": "sha512-rv+aH+tnKB5H0MAc2UadHShLMslpJsc4wjdnHRtiSIEYpOetCgu8MS4ExQRia+GL/MK3uuCyZPeEsi+J3h+Q+Q==",
1073 "dev": true,
1074 "license": "MIT",
1075 "dependencies": {
1076 "sodium-native": "^5.0.1"
1077 },
1078 "peerDependencies": {
1079 "sodium-javascript": "~0.8.0"
1080 },
1081 "peerDependenciesMeta": {
1082 "sodium-javascript": {
1083 "optional": true
1084 }
1085 }
1086 },
1087 "node_modules/stackframe": {
1088 "version": "1.3.4",
1089 "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
1090 "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
1091 "dev": true,
1092 "license": "MIT"
1093 },
1094 "node_modules/streamx": {
1095 "version": "2.28.1",
1096 "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz",
1097 "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==",
1098 "license": "MIT",
1099 "dependencies": {
1100 "events-universal": "^1.0.0",
1101 "fast-fifo": "^1.3.2",
1102 "text-decoder": "^1.1.0"
1103 }
1104 },
1105 "node_modules/teex": {
1106 "version": "1.0.1",
1107 "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
1108 "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
1109 "license": "MIT",
1110 "dependencies": {
1111 "streamx": "^2.12.5"
1112 }
1113 },
1114 "node_modules/test-tmp": {
1115 "version": "1.4.0",
1116 "resolved": "https://registry.npmjs.org/test-tmp/-/test-tmp-1.4.0.tgz",
1117 "integrity": "sha512-GVggxGg+jXqP2Wbju50JVLo+9E+nIOPPyWqgr63EbOnNItIKu1cEbJpTWAJeflnyGqXOtcMI7ijHRp88GUkfDA==",
1118 "dev": true,
1119 "license": "MIT",
1120 "dependencies": {
1121 "bare-fs": "^4.0.1",
1122 "bare-os": "^3.3.0",
1123 "bare-path": "^3.0.0"
1124 }
1125 },
1126 "node_modules/text-decoder": {
1127 "version": "1.2.7",
1128 "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
1129 "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
1130 "license": "Apache-2.0",
1131 "dependencies": {
1132 "b4a": "^1.6.4"
1133 }
1134 },
1135 "node_modules/tmatch": {
1136 "version": "5.0.0",
1137 "resolved": "https://registry.npmjs.org/tmatch/-/tmatch-5.0.0.tgz",
1138 "integrity": "sha512-Ib9OtBkpHn07tXP04SlN1SYRxFgTk6wSM2EBmjjxug4u5RXPRVLkdFJSS1PmrQidaSB8Lru9nRtViQBsbxzE5Q==",
1139 "dev": true,
1140 "license": "ISC",
1141 "engines": {
1142 "node": ">=8"
1143 }
1144 },
1145 "node_modules/v8-to-istanbul": {
1146 "version": "9.3.0",
1147 "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
1148 "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
1149 "dev": true,
1150 "license": "ISC",
1151 "dependencies": {
1152 "@jridgewell/trace-mapping": "^0.3.12",
1153 "@types/istanbul-lib-coverage": "^2.0.1",
1154 "convert-source-map": "^2.0.0"
1155 },
1156 "engines": {
1157 "node": ">=10.12.0"
1158 }
1159 },
1160 "node_modules/which-runtime": {
1161 "version": "1.4.0",
1162 "resolved": "https://registry.npmjs.org/which-runtime/-/which-runtime-1.4.0.tgz",
1163 "integrity": "sha512-0ugbP4CJW4e2D20jvEcC4973dCgIaHI4Rw1PT+26U9zEve7FyYdWAIwUnoeOYvoCfn+wXHoHTKb1KhkYlb60Pw==",
1164 "dev": true,
1165 "license": "Apache-2.0"
1166 }
1167 }
1168}
package.json +82 -0
……@@ -0,0 +1,82 @@
1{
2 "name": "favela-gateway",
3 "version": "0.1.0",
4 "description": "The favela gateway: the bsky membrane (atproto-bridge) and the fediverse bridge (ap-bridge) in one process, keyless towards atproto. Later roles: the bsky lens and a blind seeder (hyper-social #80).",
5 "type": "module",
6 "main": "lib/gateway.js",
7 "bin": {
8 "favela-gateway": "bin/favela-gateway.js"
9 },
10 "scripts": {
11 "test": "brittle-bare test/*.test.js"
12 },
13 "dependencies": {
14 "ap-bridge": "file:../ap-bridge",
15 "atproto-bridge": "file:../atproto-bridge",
16 "bare-fs": "^4.8.1",
17 "bare-os": "^3.9.3",
18 "bare-path": "^3.1.2",
19 "bare-process": "^4.5.1",
20 "hyper-svc": "file:../hyper-svc"
21 },
22 "devDependencies": {
23 "bare-http1": "^4.5.7",
24 "brittle": "^3.5.0",
25 "keet-identity-key": "3.2.0"
26 },
27 "pgh": {
28 "bin": "favela-gateway",
29 "upgrade": "pgh://1dd36bdfa9cb7f7d84adbd07f017e7200f7f90a3a546cd0761fac2a1f8a13e86/favela-gateway",
30 "entrypoint": "bin/favela-gateway.js",
31 "type": "daemon",
32 "runtime": "bare",
33 "autostart": true,
34 "depends": [
35 "pgh-sidecar"
36 ],
37 "defaults": {
38 "env": {
39 "GATEWAY_ROLES": "membrane,fediverse",
40 "GATEWAY_SOCK": "${HOME}/.config/favela-gateway/svc.sock",
41 "PDS_BIND": "0.0.0.0",
42 "PDS_PORT": "3399",
43 "PDS_STORAGE": "${HOME}/.config/atproto-bridge/data",
44 "PDS_CRAWL_RELAY": "https://bsky.network",
45 "FAVELA_WEB_UPSTREAM": "http://127.0.0.1:4041",
46 "AP_BIND": "0.0.0.0",
47 "AP_PORT": "3400",
48 "AP_HOST": "ap.7rnx.net",
49 "AP_USERNAME": "p2p",
50 "AP_DATA_DIR": "${HOME}/.config/ap-bridge",
51 "AP_PRIVATE_KEY": "${HOME}/.config/ap-bridge/actor.key",
52 "AP_PUBLIC_KEY": "${HOME}/.config/ap-bridge/actor.pub",
53 "AP_ALSO_KNOWN_AS": "did:plc:wj62ebbusln6gxo5azo6ogzo",
54 "AP_DID": "did:plc:wj62ebbusln6gxo5azo6ogzo",
55 "AP_BACKFILL": "20",
56 "AP_REGISTRATION": "open",
57 "AP_ADMIN_PORT": "3401",
58 "AP_ADMIN_BIND": "0.0.0.0"
59 }
60 },
61 "readiness": {
62 "type": "uds",
63 "path": "${HOME}/.config/favela-gateway/svc.sock",
64 "timeoutMs": 30000
65 },
66 "service": {
67 "port": 3399,
68 "host": "0.0.0.0",
69 "requiresNetwork": true
70 },
71 "stage": {
72 "ignore": [
73 "test/"
74 ]
75 }
76 },
77 "overrides": {
78 "bare-assert": "1.2.0",
79 "bare-dns": "2.2.0",
80 "bare-type": "1.1.1"
81 }
82}
test/gateway.test.js +112 -0
……@@ -0,0 +1,112 @@
1// The gateway: the membrane and the fediverse bridge as roles of ONE process (hyper-social #80, G1).
2//
3// What G1 promises, each pinned here:
4// - both roles serve from one process, each on its own port and its own data dir;
5// - their control verbs answer from ONE handler map (the gateway's socket serves it);
6// - a role that fails to start is reported down and the other keeps serving;
7// - the stop steps free everything, so the gateway can start again in the same process.
8
9import test from 'brittle'
10import fs from 'bare-fs'
11import path from 'bare-path'
12import http from 'bare-http1'
13import { startGateway } from '../lib/gateway.js'
14import { TEST_PRIVATE_KEY, TEST_PUBLIC_KEY } from 'ap-bridge/test/fixtures/keys.js'
15
16// ⚠ SHORT paths: admin.sock lives in the ap data dir, and a unix socket path past ~107 bytes does not bind.
17const tmp = (p) => fs.mkdtempSync(path.join('/tmp', p))
18
19function membraneConfig () {
20 return { serverDid: 'did:web:pds.test', userDomain: '.test', frontedDids: [], feedRegistries: [], favelaWebUpstream: null, crawlRelay: null, crawlHostname: null, port: 0, bind: '127.0.0.1', storage: tmp('gwm-') }
21}
22
23function apConfig ({ keys = true } = {}) {
24 const dir = tmp('gwa-')
25 if (keys) {
26 fs.writeFileSync(path.join(dir, 'actor.key'), TEST_PRIVATE_KEY)
27 fs.writeFileSync(path.join(dir, 'actor.pub'), TEST_PUBLIC_KEY)
28 }
29 return { host: 'ap.test', username: 'p2p', bind: '127.0.0.1', port: 0, adminPort: 0, adminBind: '127.0.0.1', dataDir: dir, privateKeyPath: path.join(dir, 'actor.key'), publicKeyPath: path.join(dir, 'actor.pub'), registration: 'open', backfill: 0, did: null, favelaSocket: null }
30}
31
32function get (port, p) {
33 return new Promise((resolve, reject) => {
34 const req = http.request({ host: '127.0.0.1', port, path: p, method: 'GET' }, (res) => {
35 let body = ''
36 res.on('data', (c) => { body += c })
37 res.on('end', () => resolve({ status: res.statusCode, body }))
38 })
39 req.on('error', reject)
40 req.end()
41 })
42}
43
44const quiet = { log: () => {}, bootstrap: [] }
45async function stop (g) { for (const [, fn] of g.steps) await fn() }
46
47test('★ both roles serve from one process', async (t) => {
48 const g = await startGateway({ roles: ['membrane', 'fediverse'], membrane: membraneConfig(), fediverse: apConfig(), ...quiet })
49 t.teardown(() => stop(g))
50 t.is((await get(g.roles.membrane.port, '/xrpc/com.atproto.server.describeServer')).status, 200, 'the membrane answers')
51 t.is((await get(g.roles.fediverse.port, '/ap/actor')).status, 200, 'the fediverse actor answers')
52 const s = await g.handlers.status()
53 t.ok(s.ok, 'status ok')
54 t.alike(Object.keys(s.roles).sort(), ['fediverse', 'membrane'], 'and names both roles')
55})
56
57test('★ one handler map carries both roles\' verbs, and nothing collides', async (t) => {
58 const g = await startGateway({ roles: ['membrane', 'fediverse'], membrane: membraneConfig(), fediverse: apConfig(), ...quiet })
59 t.teardown(() => stop(g))
60 for (const verb of ['list-fronted', 'diagnose', 'probe-front', 'drop-front', 'allow-front', 'forget-did', 'set-seq-base', 'emit-sync', 'resolve-handle', 'list-repos']) {
61 t.is(typeof g.handlers[verb], 'function', 'membrane: ' + verb)
62 }
63 for (const verb of ['invite', 'notifications', 'reproject']) t.is(typeof g.handlers[verb], 'function', 'fediverse: ' + verb)
64 t.absent('release-ready' in g.handlers, 'release-ready belongs to the process owner, not to a role')
65 t.alike((await g.handlers['list-fronted']({})).fronted, [], 'a membrane verb answers')
66 t.is(typeof (await g.handlers.invite()), 'string', 'a fediverse verb answers')
67})
68
69test('⛔ a role that fails to start is reported down, and the other keeps serving', async (t) => {
70 const g = await startGateway({ roles: ['membrane', 'fediverse'], membrane: membraneConfig(), fediverse: apConfig({ keys: false }), ...quiet })
71 t.teardown(() => stop(g))
72 t.is((await get(g.roles.membrane.port, '/xrpc/com.atproto.server.describeServer')).status, 200, 'the membrane still serves')
73 t.absent(g.roles.fediverse, 'the fediverse role is not running')
74 const s = await g.handlers.status()
75 t.ok(s.roles.fediverse.down, 'status says it is down')
76 t.ok(/actor\.key|ENOENT|no such file/i.test(s.roles.fediverse.error), 'and why')
77 t.absent('invite' in g.handlers, 'and a down role offers no verbs')
78})
79
80test('a config the entry could not load arrives as an Error and marks only that role down', async (t) => {
81 const g = await startGateway({ roles: ['membrane', 'fediverse'], membrane: membraneConfig(), fediverse: new Error('fediverse config: AP_HOST is required'), ...quiet })
82 t.teardown(() => stop(g))
83 t.is((await g.handlers.status()).roles.fediverse.error, 'fediverse config: AP_HOST is required', 'the reason is the config error, verbatim')
84 t.ok(g.roles.membrane, 'the membrane is up')
85})
86
87test('⛔ if every requested role fails, the gateway refuses to start', async (t) => {
88 await t.exception(
89 startGateway({ roles: ['fediverse'], fediverse: apConfig({ keys: false }), ...quiet }),
90 /no role started/
91 )
92})
93
94test('⛔ an unknown role is refused rather than ignored', async (t) => {
95 await t.exception(startGateway({ roles: ['membrane', 'lens'], membrane: membraneConfig(), ...quiet }), /unknown role: lens/)
96})
97
98test('the stop steps free everything: the same ports start again in one process', async (t) => {
99 const m = membraneConfig()
100 const a = apConfig()
101 const one = await startGateway({ roles: ['membrane', 'fediverse'], membrane: m, fediverse: a, ...quiet })
102 const ports = { m: one.roles.membrane.port, a: one.roles.fediverse.port, admin: one.roles.fediverse.adminPort }
103 const names = one.steps.map(([n]) => n)
104 t.ok(names.indexOf('fediverse:server') < names.indexOf('membrane:server'), 'roles stop in reverse start order')
105 await stop(one)
106 const two = await startGateway({ roles: ['membrane', 'fediverse'], membrane: { ...m, port: ports.m }, fediverse: { ...a, port: ports.a, adminPort: ports.admin }, ...quiet })
107 t.teardown(() => stop(two))
108 t.is(two.roles.membrane.port, ports.m)
109 t.is(two.roles.fediverse.port, ports.a)
110 t.is(two.roles.fediverse.bridgeKey, one.roles.fediverse.bridgeKey, 'the published bridgeKey survives a restart')
111 t.is(two.roles.membrane.swarmKey, one.roles.membrane.swarmKey, 'and so does the membrane\'s swarm identity')
112})