OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / hyper-social / hyper-wiki / lib / wiki-store.js

129 lines · javascript ref:
import Hyperdrive from 'hyperdrive'
import b4a from 'b4a'

class WikiStore {
  constructor (store, opts = {}) {
    this._store = store // Corestore instance (shared)
    this._key = opts.key || null
    this._drive = null
    this._ready = false
  }

  async ready () {
    if (this._ready) return

    this._drive = this._key
      ? new Hyperdrive(this._store, this._key)
      : new Hyperdrive(this._store)

    await this._drive.ready()
    this._ready = true
  }

  get key () {
    return this._drive?.key || null
  }

  get discoveryKey () {
    return this._drive?.discoveryKey || null
  }

  _pagePath (slug) {
    return `/wiki/${slug}.md`
  }

  _metaPath (slug) {
    return `/wiki/${slug}.meta.json`
  }

  async put (slug, content, meta = {}) {
    await this.ready()

    const now = Date.now()
    const existing = await this.getMeta(slug)

    const metadata = {
      title: meta.title || existing?.title || slug,
      author: meta.author || existing?.author || '',
      created: existing?.created || now,
      updated: now,
      tags: meta.tags || existing?.tags || [],
      version: (existing?.version || 0) + 1
    }

    await this._drive.put(this._pagePath(slug), b4a.from(content))
    await this._drive.put(this._metaPath(slug), b4a.from(JSON.stringify(metadata)))

    return metadata
  }

  async get (slug) {
    await this.ready()

    const buf = await this._drive.get(this._pagePath(slug))
    if (!buf) return null

    const meta = await this.getMeta(slug)
    return {
      slug,
      content: b4a.toString(buf),
      ...meta
    }
  }

  async getMeta (slug) {
    await this.ready()

    const buf = await this._drive.get(this._metaPath(slug))
    if (!buf) return null

    try {
      return JSON.parse(b4a.toString(buf))
    } catch {
      return null
    }
  }

  async del (slug) {
    await this.ready()

    await this._drive.del(this._pagePath(slug))
    await this._drive.del(this._metaPath(slug))
  }

  async exists (slug) {
    await this.ready()
    const entry = await this._drive.entry(this._pagePath(slug))
    return entry !== null
  }

  async list () {
    await this.ready()

    const pages = []
    for await (const entry of this._drive.list('/wiki/')) {
      const name = entry.key
      if (name.endsWith('.md')) {
        const slug = name.replace('/wiki/', '').replace('.md', '')
        const meta = await this.getMeta(slug)
        pages.push({
          slug,
          title: meta?.title || slug,
          updated: meta?.updated || 0,
          version: meta?.version || 1
        })
      }
    }

    pages.sort((a, b) => b.updated - a.updated)
    return pages
  }

  async close () {
    if (this._drive) await this._drive.close()
    this._ready = false
  }
}

export { WikiStore }