OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-note / lib / export.js

97 lines · javascript ref:
/**
 * Data Export Module
 *
 * Exports notebooks, notes, and media metadata as portable JSON structures.
 * Used by the /api/export endpoints for backup and migration.
 */

import fs from 'bare-fs'
import path from 'bare-path'
import { extractMediaHashes } from './multiwriter/notebook-apply.js'

// Read app version from package.json at module load time
let _version = '0.0.0'
try {
  const _dir = path.dirname(new URL(import.meta.url).pathname)
  const _pkg = JSON.parse(fs.readFileSync(path.join(_dir, '..', 'package.json'), 'utf-8'))
  _version = _pkg.version || _version
} catch {}
export const APP_VERSION = _version

/**
 * Build export manifest describing the export contents.
 */
export function buildExportManifest (notebooks, version) {
  return {
    version: version || '0.0.0',
    format: 'hypernote-export-v1',
    exportedAt: new Date().toISOString(),
    notebooks: notebooks.map(nb => ({
      slug: nb.slug,
      name: nb.name,
      noteCount: nb.noteCount || 0
    }))
  }
}

/**
 * Export a single notebook as a portable data structure.
 * Returns { metadata, notes: [{ id, slug, title, content, tags, ... }], mediaMap: { hash: { size, mimeType } } }
 */
export async function exportNotebook (noteManager, mediaStore, slug) {
  const metadata = await noteManager.getNotebook(slug)
  if (!metadata) return null

  const notes = await noteManager.listNotes(slug)
  const mediaMap = {}

  // Collect all media hashes referenced in note content
  for (const note of notes) {
    const hashes = extractMediaHashes(note.content || '')
    for (const hash of hashes) {
      if (mediaMap[hash]) continue
      const info = await mediaStore.getInfo(hash)
      if (info) {
        mediaMap[hash] = { size: info.size, mimeType: info.mimeType }
      }
    }
  }

  return {
    metadata: {
      slug: metadata.slug,
      name: metadata.name,
      description: metadata.description || '',
      visibility: metadata.visibility || 'public',
      createdAt: metadata.createdAt,
      updatedAt: metadata.updatedAt
    },
    notes: notes.map(n => ({
      id: n.id,
      slug: n.slug,
      title: n.title,
      content: n.content || '',
      tags: n.tags || [],
      type: n.type || 'markdown',
      createdAt: n.createdAt,
      updatedAt: n.updatedAt
    })),
    mediaMap
  }
}

/**
 * Export all notebooks.
 */
export async function exportAll (noteManager, mediaStore, version) {
  const notebooks = await noteManager.listNotebooks()
  const manifest = buildExportManifest(notebooks, version)

  const data = {}
  for (const nb of notebooks) {
    data[nb.slug] = await exportNotebook(noteManager, mediaStore, nb.slug)
  }

  return { manifest, notebooks: data }
}