/**
* Boot-time, version-gated store migration (the in-place upgrade entry point).
*
* On daemon boot, AFTER the v2 per-org HyperOrgs is constructed + ready and
* BEFORE the daemon starts serving, call migrateStore(). If the store's schema
* version is behind TARGET_VERSION it opens the v1 sources in-place (old
* single-orgs autobase + old per-org issue trackers), runs the registered
* migrations through the generic runner (which bumps the version after each
* succeeds → resumable / fail-stop), and closes the sources.
*
* Pure w.r.t. persistence: the caller injects getVersion/setVersion (see
* version-file.js for the daemon's file-backed pair). This keeps migrateStore
* unit-testable with an in-memory version cell.
*/
import { runMigrations } from './runner.js'
import { MIGRATIONS, TARGET_VERSION } from './migrations.js'
import { openOldOrgsBase, makeOpenOldOrgTracker } from './open-old-sources.js'
/**
* @param {object} opts
* @param {Corestore} opts.corestore the daemon's corestore (NOT pre-namespaced)
* @param {HyperOrgs} opts.newOrgs the v2 per-org HyperOrgs (ready), the target
* @param {() => Promise<number|null>} opts.getVersion
* @param {(v:number) => Promise<void>} opts.setVersion
* @param {string} [opts.orgsNamespace='orgs'] v1 orgs autobase namespace
* @param {(name:string)=>(Buffer|string|null)} [opts.trackerBootstrapKeyFor]
* @param {(msg:string)=>void} [opts.log]
* @returns {Promise<{ skipped:boolean, startVersion?:number, version?:number, ran?:number[], result?:any }>}
*/
export async function migrateStore (opts = {}) {
const { corestore, newOrgs, getVersion, setVersion, log, isMirror } = opts
if (!corestore || !newOrgs) throw new Error('migrateStore requires { corestore, newOrgs }')
if (typeof getVersion !== 'function' || typeof setVersion !== 'function') {
throw new Error('migrateStore requires { getVersion, setVersion }')
}
// ORIGIN-ONLY (#52 Phase 4.4). The orgs migration (#37) SPLITS the single
// orgs autobase into one autobase PER org — minting NEW cores with NEW keys.
// If a MIRROR ran this, it would mint its OWN divergent per-org autobases
// instead of mounting the origin's → split-brain. The origin owns the new
// cores and conveys their keys through the replicated OrgEventLog
// (reference-by-key, the Keet pattern); the mirror's OrgPuller mounts them
// via mountOrg. So a mirror NEVER self-migrates — it converges by replication.
if (isMirror) {
if (log) log('schema migration skipped: mirror (orgs arrive via OrgPuller/mountOrg from origin)')
return { skipped: true, reason: 'mirror', version: (await getVersion()) || 0, ran: [] }
}
const current = (await getVersion()) || 0
if (current >= TARGET_VERSION) {
return { skipped: true, startVersion: current, version: current, ran: [] }
}
if (log) log('schema migration: store at v' + current + ', target v' + TARGET_VERSION)
const oldOrgsBase = await openOldOrgsBase(corestore, { namespace: opts.orgsNamespace || 'orgs' })
const trackerOpener = makeOpenOldOrgTracker(corestore, { bootstrapKeyFor: opts.trackerBootstrapKeyFor })
let result
try {
result = await runMigrations({
getVersion,
setVersion,
migrations: MIGRATIONS,
ctx: {
oldOrgsBase,
newOrgs,
openOldOrgTracker: (name) => trackerOpener.open(name),
log
},
log
})
} finally {
await trackerOpener.closeAll()
try { await oldOrgsBase.close() } catch {}
}
if (log) log('schema migration done: v' + result.startVersion + ' -> v' + result.version + ' (ran ' + result.ran.join(',') + ')')
return { skipped: false, ...result, result }
}