p2p-db / site / index.html
1,184 words
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
<!doctype html>
<html lang="en" data-releases="../releases.json">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>p2p-db.git — ird-p2p-db, encrypted append-only p2p database for ClojureScript</title>
<style>
  :root {
    color-scheme: dark;
    --paper: #12161c; --ink: #e8edf2; --ink-soft: #9aa7b3;
    --line: #263241; --card: #182029; --code-bg: #1f2a36; --accent: #7fc4a8;
  }
  @media (prefers-color-scheme: light) {
    :root {
      color-scheme: light;
      --paper: #f8f9fa; --ink: #232a30; --ink-soft: #61707d;
      --line: #dde4ea; --card: #ffffff; --code-bg: #eef2f5; --accent: #35836a;
    }
  }
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: var(--paper); color: var(--ink);
    font: 16.5px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
      "Helvetica Neue", Arial, sans-serif;
    padding: 64px 22px 90px;
  }
  main { max-width: 660px; margin: 0 auto; }
  h1 { font-size: 40px; font-weight: 600; letter-spacing: -.4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
  .tag { margin: 14px 0 34px; font-size: 18px; color: var(--ink-soft); }
  h2 { font-size: 20px; font-weight: 600; margin: 36px 0 10px; }
  p { margin: 10px 0; }
  a { color: var(--accent); }
  pre {
    background: var(--card); border: 1px solid var(--line); border-radius: 10px;
    padding: 13px 16px; overflow-x: auto; margin: 14px 0;
    font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  }
  code { font: 15px ui-monospace, SFMono-Regular, Menlo, monospace;
    background: var(--code-bg); border-radius: 5px; padding: 1px 6px; }
  pre code { background: none; padding: 0; }
  ul { padding-left: 22px; } li { margin: 6px 0; }
  footer { margin-top: 48px; padding-top: 20px; border-top: 1px solid var(--line);
    font-size: 14px; color: var(--ink-soft); }
  footer a { color: inherit; }
</style>
</head>
<body>
<main>
  <h1>p2p-db.git</h1>
  <p class="tag"><strong>ird-p2p-db</strong> — an encrypted, append-only,
  peer-to-peer database for ClojureScript on Node.js (22+) and in the browser, built on Helia +
  OrbitDB and designed to pair with <a href="../p2p-cljs/">p2p-cljs</a>: rooms
  are ephemeral messaging, this is the shared history that survives churn.
  Every holder of the secret appends sealed records and replicates them peer to
  peer on a private libp2p swarm — no server, no account. core.async channels
  end to end.</p>

  <h2>Use as a dependency</h2>
  <p>No registry — a source library served straight from this domain, consumed
  as a git dependency (with <a href="https://shadow-cljs.github.io/docs/UsersGuide.html">shadow-cljs</a>
  reading <code>deps.edn</code>); <code>ird-p2p-cljs</code> comes along
  transitively:</p>
  <pre><code>;; deps.edn
{:deps {ro.ird/ird-p2p-db {:git/url "https://ird.cod-sursa.ro/p2p-db.git"
                           :git/tag "<span data-rel="p2p-db.tag">v0.2.1</span>" :git/sha "<span data-rel="p2p-db.tag_sha_short">0000000</span>"}
        thheller/shadow-cljs {:mvn/version "3.5.0"}}}</code></pre>
  <p>The latest release is <code><span data-rel="p2p-db.tag">v0.2.1</span></code>
  (<code><span data-rel="p2p-db.tag_sha_short">0000000</span></code>). To pin that exact commit:</p>
  <pre><code>ro.ird/ird-p2p-db {:git/url "https://ird.cod-sursa.ro/p2p-db.git"
                   :git/sha "<span data-rel="p2p-db.tag_sha">0000000000000000000000000000000000000000</span>"}</code></pre>
  <p>Add the Helia/OrbitDB stack to your <code>package.json</code> dependencies
  at the exact versions the suite proves — <code>helia</code> 7.1.12,
  <code>@orbitdb/core</code> 4.0.0, <code>@libp2p/gossipsub</code> 17.1.1 and the
  libp2p 3.3.x pieces listed in the repo's <code>README.md</code>; ClojureScript
  and core.async come from <code>deps.edn</code>. These coordinates are filled
  from <a href="../releases.json">releases.json</a>, which scripts and agents can
  read directly (<code>head</code>, <code>tag</code>, <code>tag_sha</code> per repo).</p>

  <h2>Quickstart</h2>
  <pre><code>(require '[cljs.core.async :refer [go <!]]
         '[ird.p2p.async :refer [<?]]
         '[ird.p2p.db.core :as db])

(def secret (db/random-secret))            ; share it out of band, e.g. in an invite link's fragment

(go
  (let [notes (<? (db/open {:secret secret :app-salt "myapp/v1" :name "notes"}))]
    (go (loop []
          (when-let [r (<! (db/entries-stream notes))]   ; history first, then live records
            (println (:payload r) (:clock r))
            (recur))))
    (<? (db/dial notes ["/ip4/192.168.1.20/tcp/4001/p2p/12D3Koo…"]))
    (<? (db/append notes {:text "salut"}))))</code></pre>
  <p>Every async call returns a core.async channel delivering one value — the
  result or an <code>ex-info</code> error. Records are sealed with AES-256-GCM
  under a key derived from the secret before OrbitDB signs, hashes and syncs
  them; write access is the secret itself (a shared OrbitDB identity derived
  from it).</p>
  <p>The swarm reaches nothing public — no bootstrap list, no DHT, no gateways —
  and reachability is yours to arrange. Node peers meet over tcp or websockets
  by explicit dial (<code>:dial</code> at open, <code>db/dial</code> later). A
  browser can dial but never listen, so browser peers, and anything behind a
  NAT, come in through a circuit relay v2 hop you run: a Node peer opened with
  <code>:relay-hop? true</code> on a websockets address browsers can reach
  (<code>wss</code> for <code>https</code> pages), named in <code>:relay</code>
  by every peer that reserves a slot on it and so becomes reachable at
  <code>&lt;relay&gt;/p2p-circuit/p2p/&lt;its id&gt;</code>. The relay is
  configuration you supply, exactly like <code>:dial</code> — never a built-in
  address, no relay discovery, no NAT hole punching, and the ird rooms relay is
  not one; it forwards the noise-encrypted stream and terminates nothing.</p>
  <p><code>:mdns? true</code> (opt-in, Node only) is LAN discovery that actually
  connects: the peer announces itself and queries under the service tag
  <code>_ird-p2p-db._udp.local</code> — not libp2p's default, so only peers of
  this library answer or are dialed — and dials every peer that answers, again
  whenever one answers while not connected (a restarted peer comes back by
  itself). mDNS announces the listen addresses, so peers on different machines
  must <code>:listen</code> on a LAN address; the loopback default only lets
  peers of one machine meet. Every such peer on the LAN is dialed, whatever
  database it holds — a connection, not access.</p>
  <p><code>:gater</code> is opt-in inbound connection gating: a function of the
  connecting peer's multiaddr string (it always ends in
  <code>/p2p/&lt;its id&gt;</code>; <code>&lt;relay&gt;/p2p-circuit/p2p/&lt;id&gt;</code>
  when relayed) returning true to let it in — <code>false</code>,
  <code>nil</code> or a throw refuses, and a refused peer gets no connection at
  all. Without it every inbound connection is accepted. Outbound dials are
  never gated, and gating is about connections, not identity: whoever you let
  in (or dial) that holds the secret reads and writes everything.</p>
  <p>Node 22+, and <code>engines</code> stays <code>node &gt;=22</code>: Node
  18–21 was investigated and is blocked upstream (<code>@orbitdb/core</code>
  4.0.0 declares it, and the libp2p 3 line under it needs Node 22 whatever it
  declares) — the README's Versions section records the evidence. The exact key
  schedule and AAD, what peers and the network can see, the limits (not a blob
  store, ordering, persistence), browser peers and the proven version set are
  in the repo's <code>README.md</code>.</p>

  <h2>Clone</h2>
  <pre>git clone https://ird.cod-sursa.ro/p2p-db.git</pre>
  <p><a href="browse/">browse the source in your browser →</a></p>

  <h2>What's inside</h2>
  <ul>
    <li><code>src/ird/p2p/db/core.cljs</code> — the public API: open, append, entries, entries-stream, peers-stream, dial, close</li>
    <li><code>src/ird/p2p/db/crypto.cljs</code> — the key schedule (HKDF labels), sealed records, the opaque manifest name</li>
    <li><code>src/ird/p2p/db/identity.cljs</code> — the shared OrbitDB identity: two Ed25519 keys derived from the secret</li>
    <li><code>src/ird/p2p/db/errors.cljs</code> — the error values</li>
    <li><code>src/ird/p2p/db/impl/</code> — the only namespaces with JavaScript interop (Helia on a private swarm — the Node or the browser variant — and OrbitDB)</li>
    <li><code>browser/vendor-entry.mjs</code> — the esbuild entry that bundles the ESM-only stack for a browser build</li>
    <li><code>test/</code> — key schedule and records, identity, the zero-interop gate, two real nodes replicating over loopback, <code>:mdns?</code> peers meeting by discovery alone, <code>:gater</code> letting one peer in and refusing another, a bounded 600-record soak with a restart, and a headless-Chrome browser peer replicating with a Node peer through a relay</li>
  </ul>

  <h2>License</h2>
  <p>MIT.</p>

  <footer>Part of <a href="../">ird.cod-sursa.ro</a> — source published on IPFS,
  cloned over plain HTTP.</footer>
</main>
<script>
// Fill release coordinates from the site's releases.json (written by the
// assembler from the mirrors it publishes); the static text is the fallback.
(function () {
  var els = document.querySelectorAll('[data-rel]');
  if (!els.length || !window.fetch) return;
  fetch(document.documentElement.getAttribute('data-releases') || 'releases.json')
    .then(function (r) { return r.ok ? r.json() : null; })
    .then(function (j) {
      if (!j || !j.apps) return;
      Array.prototype.forEach.call(els, function (el) {
        var k = el.getAttribute('data-rel').split('.'), a = j.apps[k[0]];
        var v = a && a[k[1]];
        if (typeof v === 'string' && v) el.textContent = v;
      });
    })
    .catch(function () {});
})();
</script>
</body>
</html>

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/p2p-db.git