/**
* composeSchemas — combine multiple Room schemas into one.
*
* Lets a single Room (one Autobase + one Hyperbee view) carry several
* op-domains. Used for "linked-core" composites — e.g. an org Room that
* also handles issue ops, so issues replicate WITH the org autobase
* instead of living in a separate autobase whose bootstrap key must be
* conveyed to every joiner.
*
* All composed schemas must share the same op wire-encoding. jsonSchema
* uses plain JSON, which is op-agnostic, so we reuse the first schema's
* encodeOp/decodeOp. validateOp and apply run EVERY schema in order;
* each domain schema's switch ignores op.types it doesn't own (no
* default-throw), so they coexist.
*
* Constraint: op.types should be DISJOINT across composed schemas. A
* shared op.type runs ALL handlers — safe only when those writes are
* idempotent/compatible (e.g. two schemas both writing the same
* meta:<key> value). Diverging semantics on a shared type is a bug.
*
* @param {Array<{encodeOp,decodeOp,validateOp,apply}>} schemas
* @returns {{encodeOp,decodeOp,validateOp,apply}}
*/
export function composeSchemas (schemas) {
if (!Array.isArray(schemas) || schemas.length === 0) {
throw new Error('composeSchemas requires a non-empty array of schemas')
}
for (const s of schemas) {
for (const fn of ['encodeOp', 'decodeOp', 'validateOp', 'apply']) {
if (typeof s[fn] !== 'function') {
throw new Error('composeSchemas: each schema must provide ' + fn)
}
}
}
const [first] = schemas
return {
encodeOp: (op) => first.encodeOp(op),
decodeOp: (buf) => first.decodeOp(buf),
validateOp (op) {
for (const s of schemas) s.validateOp(op)
},
async apply (batch, op, host) {
for (const s of schemas) await s.apply(batch, op, host)
}
}
}