OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-orgs commits / 912cd19

orgs: setMeta/getMeta/listMeta over the existing set-meta op
Thin typed accessors backing the org-scoped peer-coordinate announce: a writer publishes meta:peer:<serverKey> -> { serverKey, repoEventLog } into the org autobase so every org peer can boot a reverse RepoPuller toward it (the org analogue of the IdentityRegistry __server__/__repoeventlog__ announce, #16). No schema change — reuses the set-meta op. Test: record-meta.test.js (6/6). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
912cd19c42a976721bd2713edff0da7d7212dcf6
by plankatron <fleeky@gmail.com> · 2026-07-18T01:11:26.000Z

Changed files (3) +85 -1

lib/hyper-orgs.js +37 -0
……@@ -213,6 +213,43 @@ export class HyperOrgs {
213213 return this.get(name)
214214 }
215215
216 // ── Record meta (generic set-meta op) ────────────────────────
217 // Thin typed accessors over the schema's `set-meta` op (meta:{key} →
218 // {value}). The daemon uses these to publish per-daemon origin coordinates
219 // (serverKey + repoEventLog) into the org autobase, so every org peer can
220 // boot a reverse RepoPuller toward the others — the org-scoped analogue of
221 // the IdentityRegistry mirror-writer announce (#16, __server__/__repoeventlog__).
222 // Only a WRITER on the org can setMeta; readers replicate + read it.
223
224 async setMeta (orgName, key, value) {
225 const room = this._requireRoom(orgName)
226 await room.append({ type: 'set-meta', key, value, timestamp: Date.now() })
227 await room.base.update()
228 return value
229 }
230
231 async getMeta (orgName, key) {
232 const room = this._requireRoom(orgName)
233 await room.base.update()
234 const e = await room.view.get(`meta:${key}`)
235 return e ? e.value.value : null
236 }
237
238 // List meta entries whose key starts with `prefix` (e.g. 'peer:'), returning
239 // [{ key, value }] with the `meta:` view-prefix stripped from key. Suffixed
240 // keys are strictly > the bare prefix, so `gt` (matching the grants scan
241 // convention) never misses an entry.
242 async listMeta (orgName, prefix = '') {
243 const room = this._requireRoom(orgName)
244 await room.base.update()
245 const out = []
246 const lo = `meta:${prefix}`
247 for await (const entry of room.view.createReadStream({ gt: lo, lt: lo + '\xff' })) {
248 out.push({ key: entry.key.slice('meta:'.length), value: entry.value.value })
249 }
250 return out
251 }
252
216253 async delete (name) {
217254 const org = await this.get(name)
218255 if (!org) throw new Error(`Organization "${name}" not found`)
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "hyper-orgs",
3 "version": "0.2.1",
3 "version": "0.2.2",
44 "description": "P2P organization management — a hyper-rooms schema for orgs, members, resources, grants, invites, and access requests",
55 "type": "module",
66 "main": "index.js",
test/record-meta.test.js +47 -0
……@@ -0,0 +1,47 @@
1import test from 'brittle'
2import Corestore from 'corestore'
3import { mkdtempSync } from 'fs'
4import { join } from 'path'
5import { tmpdir } from 'os'
6import { HyperOrgs } from '../index.js'
7
8function tmpStore () {
9 const dir = mkdtempSync(join(tmpdir(), 'hyper-orgs-meta-'))
10 return new Corestore(dir)
11}
12
13async function makeOrgs (t, namespace = 'meta') {
14 const store = tmpStore()
15 const orgs = new HyperOrgs(store, { namespace })
16 await orgs.ready()
17 t.teardown(async () => {
18 try { await orgs.close() } catch {}
19 try { await store.close() } catch {}
20 })
21 return orgs
22}
23
24// setMeta/getMeta/listMeta back the org-scoped peer-coordinate announce: a
25// writer publishes meta:peer:<serverKey> -> { serverKey, repoEventLog } into
26// the org autobase so every org peer can boot a reverse RepoPuller toward it.
27test('setMeta / getMeta / listMeta — peer coordinates round-trip', async (t) => {
28 const orgs = await makeOrgs(t, 'meta-1')
29 await orgs.create('house', 'group')
30
31 const coordsA = { serverKey: 'a'.repeat(64), repoEventLog: 'c'.repeat(64) }
32 const coordsB = { serverKey: 'b'.repeat(64), repoEventLog: 'd'.repeat(64) }
33 await orgs.setMeta('house', 'peer:' + coordsA.serverKey, coordsA)
34 await orgs.setMeta('house', 'peer:' + coordsB.serverKey, coordsB)
35 // a non-peer meta entry must NOT show up under the 'peer:' prefix scan
36 await orgs.setMeta('house', 'schema-version', 2)
37
38 t.alike(await orgs.getMeta('house', 'peer:' + coordsA.serverKey), coordsA, 'getMeta returns the stored value')
39 t.is(await orgs.getMeta('house', 'nope'), null, 'missing meta → null')
40
41 const peers = await orgs.listMeta('house', 'peer:')
42 t.is(peers.length, 2, 'listMeta returns only the peer: entries (schema-version excluded)')
43 const byServer = new Map(peers.map(p => [p.value.serverKey, p.value]))
44 t.alike(byServer.get('a'.repeat(64)), coordsA, 'peer A coords intact')
45 t.alike(byServer.get('b'.repeat(64)), coordsB, 'peer B coords intact')
46 t.ok(peers.every(p => p.key.startsWith('peer:')), 'meta: view-prefix stripped, peer: retained')
47})