OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-issues commits / 10d0b22

HyperIssues: wrap an existing Room (linked-core composites)
Constructor accepts { room, ownsRoom }: when `room` is given, operate the issue API over that existing Room instead of creating one, and close() only tears down the Room when ownsRoom is true. Lets a daemon expose org issues over an org Room (built with composeSchemas([orgsSchema, issuesSchema])) without the issue API owning/closing the org's autobase. Self-created Rooms default ownsRoom:true (unchanged behaviour). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10d0b22037f8e8b614a33943b5094d836538e768
by plankatron <fleeky@gmail.com> · 2026-05-27T01:42:45.000Z

Changed files (2) +65 -7

lib/hyper-issues.js +22 -7
……@@ -23,17 +23,32 @@ import { issuesSchema, DEFAULT_LABELS } from './schema.js'
2323
2424export class HyperIssues {
2525 /**
26 * @param {Corestore} corestore
26 * @param {Corestore} corestore - ignored when `options.room` is given
2727 * @param {object} options
2828 * @param {string} [options.namespace='issues']
2929 * @param {Buffer|string|null} [options.bootstrapKey=null]
30 * @param {Room} [options.room] - wrap an EXISTING Room instead of
31 * creating one. Used for "linked-core" composites: an org Room built
32 * with composeSchemas([orgsSchema, issuesSchema]) already handles
33 * issue ops, and a HyperIssues wrapping it provides the issue API
34 * over that one autobase. The Room's schema MUST handle issue ops.
35 * @param {boolean} [options.ownsRoom] - whether close() closes the
36 * Room. Defaults to true when this instance created the Room, false
37 * when wrapping an external one (so closing the issue API doesn't
38 * tear down the org that owns the Room).
3039 */
3140 constructor (corestore, options = {}) {
32 this._room = new Room(corestore, {
33 schema: issuesSchema,
34 namespace: options.namespace || 'issues',
35 bootstrapKey: options.bootstrapKey || null
36 })
41 if (options.room) {
42 this._room = options.room
43 this._ownsRoom = options.ownsRoom === true
44 } else {
45 this._room = new Room(corestore, {
46 schema: issuesSchema,
47 namespace: options.namespace || 'issues',
48 bootstrapKey: options.bootstrapKey || null
49 })
50 this._ownsRoom = options.ownsRoom !== false
51 }
3752 }
3853
3954 // ── Lifecycle / properties ──────────────────────────────────
……@@ -43,7 +58,7 @@ export class HyperIssues {
4358 }
4459
4560 async sync () { await this._room.base.update() }
46 async close () { await this._room.close() }
61 async close () { if (this._ownsRoom) await this._room.close() }
4762
4863 get key () { return this._room.key }
4964 get keyHex () { return this._room.keyHex }
test/room-wrap.test.js +43 -0
……@@ -0,0 +1,43 @@
1import test from 'brittle'
2import Corestore from 'corestore'
3import { mkdtempSync } from 'fs'
4import { join } from 'path'
5import { tmpdir } from 'os'
6import { Room } from 'hyper-rooms'
7import { HyperIssues } from '../index.js'
8import { issuesSchema } from '../lib/schema.js'
9
10function tmp () { return mkdtempSync(join(tmpdir(), 'issues-wrap-')) }
11
12test('HyperIssues wraps an external Room and does NOT own it', async (t) => {
13 const store = new Corestore(tmp())
14 const room = new Room(store, { schema: issuesSchema, namespace: 'shared' })
15 await room.ready()
16
17 const issues = new HyperIssues(null, { room, ownsRoom: false })
18 await issues.ready()
19
20 const id = await issues.create({ title: 'wrapped', author: 'alice' })
21 const got = await issues.get(id)
22 t.is(got && got.title, 'wrapped', 'issue created/read through the wrapper')
23
24 await issues.close() // must NOT close the borrowed room
25 t.absent(room.base.closed, 'borrowed room still open after wrapper close')
26
27 // room remains usable directly after the wrapper closed
28 await room.append({ type: 'create-issue', id: 999, title: 'direct', userId: 'bob', timestamp: Date.now() })
29 await room.base.update()
30 const direct = await room.view.get('issue:999')
31 t.is(direct && direct.value.title, 'direct', 'room still usable after wrapper close')
32
33 await room.close()
34})
35
36test('HyperIssues owns the Room it creates (close tears it down)', async (t) => {
37 const issues = new HyperIssues(new Corestore(tmp()), { namespace: 'owned' })
38 await issues.ready()
39 await issues.create({ title: 'x', author: 'a' })
40 const base = issues.base
41 await issues.close()
42 t.ok(base.closed, 'self-created room closed by wrapper close')
43})