Files
bare-operating-system/packages/bare-os-booter/lib/p2p/bare-os-protomux-alias-registry.js
T
2026-08-18 18:11:28 -04:00

109 lines
3.2 KiB
JavaScript

/**
* Validated Protomux channel alias registry: logical name → actual channel name.
* Each actual channel name may be claimed by at most one logical alias at a time.
* Re-registering the same logical with a different actual overwrites and records history.
*/
const NAME_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,127}$/
/**
* @param {string} s
*/
export function assertValidProtomuxAliasName(s) {
const t = String(s || '').trim()
if (!t || t.length > 128) {
throw new Error('protomux alias: name length must be 1..128')
}
if (!NAME_RE.test(t)) {
throw new Error(
'protomux alias: name must match ' + NAME_RE.source + ' (logical/actual)'
)
}
return t
}
export function createBareOsProtomuxAliasRegistry() {
/** @type {Map<string, string>} */
const logicalToActual = new Map()
/** @type {Record<string, string>} */
const legacyMap = Object.create(null)
/** @type {{ atMs: number, logical: string, previousActual: string | null, actual: string }[]} */
const changeLog = []
/** @type {Map<string, string[]>} */
const actualToLogicals = new Map()
function reindexActual(logical, oldAct, newAct) {
if (oldAct) {
const arr = actualToLogicals.get(oldAct)
if (arr) {
const i = arr.indexOf(logical)
if (i >= 0) arr.splice(i, 1)
if (!arr.length) actualToLogicals.delete(oldAct)
}
}
if (newAct) {
const arr = actualToLogicals.get(newAct) || []
if (!arr.includes(logical)) arr.push(logical)
actualToLogicals.set(newAct, arr)
}
}
return {
/**
* @param {string} logicalName
* @param {string} actualName
* @returns {{ ok: boolean, conflict?: string, replaced?: boolean }}
*/
register(logicalName, actualName) {
const a = assertValidProtomuxAliasName(logicalName)
const b = assertValidProtomuxAliasName(actualName)
const prev = logicalToActual.get(a) || null
if (prev === b) {
return { ok: true, replaced: false }
}
const others = actualToLogicals.get(b) || []
if (others.some((o) => o !== a)) {
return {
ok: false,
conflict: `actual ${b} already mapped from logical: ${others.filter((o) => o !== a).join(', ')}`
}
}
reindexActual(a, prev, b)
logicalToActual.set(a, b)
legacyMap[a] = b
changeLog.push({
atMs: Date.now(),
logical: a,
previousActual: prev,
actual: b
})
if (changeLog.length > 256) changeLog.splice(0, changeLog.length - 256)
return { ok: true, replaced: Boolean(prev) }
},
get(logicalName) {
const a = String(logicalName || '').trim()
return logicalToActual.get(a) || null
},
snapshot() {
const aliases = {}
for (const [k, v] of logicalToActual.entries()) aliases[k] = v
return {
schema: 2,
aliases,
changeLogTail: changeLog.slice(-32),
reverseIndex: Object.fromEntries(
[...actualToLogicals.entries()].map(([act, logs]) => [act, [...logs]])
),
atMs: Date.now()
}
},
/** @returns {Record<string, string>} same object reference; updated on each register */
legacyMapView() {
return legacyMap
}
}
}