Build a DatabaseVersioning from an ordered version history, for
Database.create(plugin, { versioning: createVersionUpgrader(entries) }).
currentVersion = entries.length - 1 (exposed as db.version) and the current
schema is foldSchemas(entries). Each entry's handler (when present) touches
exactly ONE persisted quadrant — document (shared+persistent) or settings
(nonShared+persistent) — the guard enforces it, so each quadrant advances
INDEPENDENTLY from its own stamped version.
The version each quadrant was last saved at is read from the blob's save
METADATA (schemaVersions, not an ECS resource) and handed to handle. The two
persisted blobs can be saved separately and arrive at DIFFERENT versions; the
upgrader replays only each quadrant's handlers, from its own version up to current:
reject (null) if EITHER quadrant is newer than this app — a load can never
down-convert a blob a newer client wrote;
for each version with a handler, if that handler's quadrant is behind, stage
ONLY that quadrant's schemas to the previous version (so the other quadrant,
possibly already ahead, is left untouched) and run the handler.
The database's commit path then auto-normalizes every remaining additive/minor
change to the CURRENT schema (never down-converts), so a purely additive/minor
upgrade needs no handler at all.
A blob NEWER than this app is rejected non-destructively (the live db is left
untouched). Rejection is not a throw — db.fromData resolves with
{ loaded: false, … } so the caller can react (e.g. prompt the user to update).
You MUST also add the assertVersioning guard test — nothing at runtime ties
entries to the plugin schema, so without it a drift surfaces only as a load-time
throw (or, for untyped schemas, silently).
Build a DatabaseVersioning from an ordered version history, for
Database.create(plugin, { versioning: createVersionUpgrader(entries) }).currentVersion = entries.length - 1(exposed asdb.version) and the current schema isfoldSchemas(entries). Each entry'shandler(when present) touches exactly ONE persisted quadrant —document(shared+persistent) orsettings(nonShared+persistent) — the guard enforces it, so each quadrant advances INDEPENDENTLY from its own stamped version.The version each quadrant was last saved at is read from the blob's save METADATA (
schemaVersions, not an ECS resource) and handed tohandle. The two persisted blobs can be saved separately and arrive at DIFFERENT versions; the upgrader replays only each quadrant's handlers, from its own version up to current:null) if EITHER quadrant is newer than this app — a load can never down-convert a blob a newer client wrote;The database's commit path then auto-normalizes every remaining additive/minor change to the CURRENT schema (never down-converts), so a purely additive/minor upgrade needs no handler at all.
A blob NEWER than this app is rejected non-destructively (the live db is left untouched). Rejection is not a throw —
db.fromDataresolves with{ loaded: false, … }so the caller can react (e.g. prompt the user to update).You MUST also add the assertVersioning guard test — nothing at runtime ties
entriesto the plugin schema, so without it a drift surfaces only as a load-time throw (or, for untyped schemas, silently).