OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-orgs / examples / per-org-invite.js

113 lines · javascript ref:
#!/usr/bin/env node
/**
 * Per-org-invite example — FORWARD-LOOKING SPEC (issue #35)
 *
 * This example demonstrates the desired post-refactor flow:
 *   - One autobase per org (Keet-rooms pattern)
 *   - `createOrgInvite(name, opts)` returns a bearer invite scoped
 *     to a single org
 *   - `joinOrg(invite)` consumes it, mounts ONLY that org's autobase,
 *     and starts replicating just that org's data
 *
 * Today this example will print a clear "NOT IMPLEMENTED" line and
 * exit before the join step — because `createOrgInvite` and
 * `joinOrg` do not exist on `HyperOrgs` yet. The matching failing
 * test is `test/scope-isolation.test.js` (SPEC #35).
 *
 * Once the per-org-autobase refactor lands, this example runs
 * end-to-end successfully and prints the isolation proof.
 *
 * Usage:
 *   node examples/per-org-invite.js
 *
 * Storage: ephemeral temp dirs, cleaned up on exit.
 */

import Corestore from 'corestore'
import { mkdtempSync, rmSync } from 'fs'
import { join } from 'path'
import { tmpdir } from 'os'
import { replicateAndSync } from 'autobase-test-helpers'
import { HyperOrgs } from '../index.js'

const dirs = []
function tmpStore () {
  const dir = mkdtempSync(join(tmpdir(), 'hyper-orgs-spec-'))
  dirs.push(dir)
  return new Corestore(dir)
}

async function main () {
  // ── PRIMARY (org owner) ─────────────────────────────────────────
  const storeA = tmpStore()
  const owner = new HyperOrgs(storeA, { namespace: 'spec-demo' })
  await owner.ready()

  await owner.create('alpha', 'group', { displayName: 'Alpha' })
  await owner.create('beta',  'group', { displayName: 'Beta'  })
  await owner.create('gamma', 'group', { displayName: 'Gamma' })
  await owner.base.update()
  console.log('[owner] created 3 orgs: alpha, beta, gamma')

  // ── FUTURE API #1 — createOrgInvite ─────────────────────────────
  if (typeof owner.createOrgInvite !== 'function') {
    console.log('')
    console.log('  ⚠  NOT IMPLEMENTED YET: HyperOrgs.createOrgInvite(orgName, opts)')
    console.log('     Required by post-mortem #35. This example will run')
    console.log('     end-to-end once the per-org-autobase refactor lands.')
    console.log('     See test/scope-isolation.test.js for the failing spec.')
    console.log('')
    await cleanup({ owner, storeA })
    process.exit(0)
  }

  const { invitation: alphaInvite } = await owner.createOrgInvite('alpha', {
    role: 'contributor',
    maxUses: 1
  })
  console.log('[owner] issued bearer invite for alpha (contributor, 1 use)')

  // ── JOINER ──────────────────────────────────────────────────────
  const storeB = tmpStore()
  const joiner = new HyperOrgs(storeB, { namespace: 'spec-demo-joiner' })
  await joiner.ready()

  // FUTURE API #2 — joinOrg
  await joiner.joinOrg(alphaInvite)
  console.log('[joiner] consumed alpha invite')

  // Sync only alpha's per-org base (post-refactor accessor).
  await replicateAndSync([owner.getOrgBase('alpha'), joiner.base])

  // ── Isolation assertions ────────────────────────────────────────
  const alphaSeen = !!(await joiner.get('alpha'))
  const betaSeen  = !!(await joiner.get('beta'))
  const gammaSeen = !!(await joiner.get('gamma'))

  console.log('')
  console.log('Joiner view after alpha-only join:')
  console.log('  alpha:', alphaSeen ? 'visible (expected)' : 'MISSING (broken)')
  console.log('  beta:',  betaSeen  ? 'VISIBLE — SCOPE LEAK' : 'hidden (expected)')
  console.log('  gamma:', gammaSeen ? 'VISIBLE — SCOPE LEAK' : 'hidden (expected)')

  const ok = alphaSeen && !betaSeen && !gammaSeen
  console.log('')
  console.log(ok ? '✓ isolation holds' : '✗ isolation broken')

  await cleanup({ owner, storeA, joiner, storeB })
  process.exit(ok ? 0 : 1)
}

async function cleanup ({ owner, storeA, joiner, storeB }) {
  try { if (owner) await owner.close() } catch {}
  try { if (joiner) await joiner.close() } catch {}
  try { if (storeA) await storeA.close() } catch {}
  try { if (storeB) await storeB.close() } catch {}
  for (const d of dirs) { try { rmSync(d, { recursive: true, force: true }) } catch {} }
}

main().catch((err) => {
  console.error('example failed:', err)
  process.exit(1)
})