/**
* Read a legacy org issue tracker (issues v1): yield each issue (with its
* comments attached) and, separately, each label, from the linearized view.
*
* In v1, an org's issues lived in their OWN separate autobase
* (`issues:org:<name>`), a HyperIssues whose Hyperbee view is keyed:
* issue:{id} -> { id, title, body, author, status, labels, images, createdAt, updatedAt }
* comment:{issueId}:{cid} -> { body, author, createdAt, editedAt?, editedBy? }
* label:{name} -> { color, description, displayName }
*
* v2 (#42 linked-core) moves org issues INTO the org's own room. This reader
* is the migration SOURCE: it streams those v1 records read-only so
* migrateIssues can replay them into the org room via createWithId/
* commentWithId/createLabel.
*
* Generic over anything exposing a Hyperbee `.view` (a HyperIssues, a
* hyper-rooms Room, or a raw Autobase). Read-only — it linearizes the view
* to latest before reading but never appends.
*
* Pairs with migrateIssues (which takes async-iterables of these records).
*
* @param {object} base - has `.view` (Hyperbee) and optionally `.update()`
*/
import { linearize } from './linearize.js'
function requireView (base, who) {
if (!base) throw new Error(who + ' requires an opened issue tracker/room')
const view = base.view
if (!view || typeof view.createReadStream !== 'function') {
throw new Error(who + ': base.view must be a Hyperbee')
}
return view
}
/**
* Yield each issue record, with its comments attached as `comments: [...]`.
* @yields { id, title, body, author, status, labels, images, createdAt, updatedAt, comments }
*/
export async function * readOldIssues (base) {
const view = requireView(base, 'readOldIssues')
// Linearize to the latest state before reading (pulls in replicated ops on
// a follower; no-op on a single writer with nothing pending).
await linearize(base)
for await (const entry of view.createReadStream({ gt: 'issue:', lt: 'issue:\xff' })) {
const issue = entry && entry.value
if (!issue || typeof issue.id !== 'number') continue
const comments = []
// comment:{id}: .. comment:{id}:\xff — same range hyper-issues getComments
// uses; id 1 vs 11 don't overlap (the trailing ':' guards the prefix).
for await (const c of view.createReadStream({ gt: 'comment:' + issue.id + ':', lt: 'comment:' + issue.id + ':\xff' })) {
if (!c || !c.value) continue
comments.push({ commentId: c.key.split(':').slice(2).join(':'), ...c.value })
}
yield { ...issue, comments }
}
}
/**
* Yield each label record.
* @yields { name, color, description, displayName }
*/
export async function * readOldLabels (base) {
const view = requireView(base, 'readOldLabels')
await linearize(base)
for await (const entry of view.createReadStream({ gt: 'label:', lt: 'label:\xff' })) {
if (!entry || !entry.value) continue
yield { name: entry.key.slice('label:'.length), ...entry.value }
}
}