OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-store / lib / overlay-repo-substrate.js

45 lines · javascript ref:
// OverlayRepoSubstrate — present an OverlayRepoStore as the KV substrate RepoBase expects
// (`{ set, del, get, list, listNow }`), so RepoBase's record ops read/write content-addressed
// FILES instead of an append-only autobase. This is the seam that moves the multi-DID path's
// records off the StateBase (see hyper-proto-peer/docs/DESIGN-multidid-record-decoupling.md).
//
// RepoBase keys are "<collection>/<rkey>" (the FIRST "/" separates; atproto NSIDs + rkeys forbid
// "/"). Reserved "__"-prefixed / single-segment control keys (__members__, __commit__/<rev>, …)
// NEVER reach here — swarm-device writes those directly to the StateBase — so this substrate is
// records-only and throws on a non-record key (fail-loud if that assumption is ever violated).
//
// listNow == list: file reads are always current, so there is no blocking base.update() to avoid
// (RepoBase.listRecords({now:true}) uses listNow for its liveness-critical, non-blocking path).

const SEP = '/'

function split (key) {
  const k = String(key)
  const i = k.indexOf(SEP)
  if (i < 0) throw new Error('OverlayRepoSubstrate: expected a "collection/rkey" record key, got ' + JSON.stringify(key))
  return { collection: k.slice(0, i), rkey: k.slice(i + 1) }
}

export class OverlayRepoSubstrate {
  constructor (store) {
    if (!store || typeof store.put !== 'function' || typeof store.records !== 'function') {
      throw new Error('OverlayRepoSubstrate: an OverlayRepoStore (put/get/del/records) is required')
    }
    this.store = store
  }

  async set (key, value) { const { collection, rkey } = split(key); await this.store.put(collection, rkey, value) }
  async del (key) { const { collection, rkey } = split(key); await this.store.del(collection, rkey) }
  async get (key) { const { collection, rkey } = split(key); return this.store.get(collection, rkey) }

  // Return { key: record } for every record whose key starts with `prefix` ("" = all,
  // "<collection>/" = one collection) — the shape RepoBase.listRecords folds over.
  async list (prefix = '') {
    const out = {}
    for (const { key, record } of await this.store.records()) if (key.startsWith(prefix)) out[key] = record
    return out
  }

  async listNow (prefix = '') { return this.list(prefix) } // files are always current — no base.update() to block on
}