UNPKG

@pgxsinkit/pgwasm

Version:
95 lines (86 loc) • 3.91 kB
/** * The build marker (ADR-0063 decision 1, build permanence). * * pgwasm records the Postgres build in every data directory it creates, as the file `PGWASM_BUILD` in * the data directory itself (next to `PG_VERSION`), so it works on every storage and travels inside * every Store backup. Its content is one line of JSON: * * {"pgwasm":1,"build":"c","dataFormat":1} * * `pgwasm` is the marker's own format version; a newer one than this release knows is unreadable * (fail closed). A directory without a marker predates marking: it is the C build's, which the build * says through `claimsUnmarkedDirectories` rather than shared code naming a build. Existing unmarked * directories are never backfilled; only a directory pgwasm creates gets a marker. */ import type { BuildIdentity, DataDirEntry } from "../build/seam"; import { BuildMarkerUnreadableError, BuildMismatchError, DataFormatMismatchError, type RecordedBuild } from "../errors"; /** The marker's path inside the data directory. */ export const BUILD_MARKER_PATH = "/PGWASM_BUILD"; /** Postgres' own version file: its presence means the directory holds a cluster. */ export const PG_VERSION_PATH = "/PG_VERSION"; const MARKER_FORMAT = 1; const encoder = new TextEncoder(); const decoder = new TextDecoder(); /** The marker's bytes for a build. */ export function encodeBuildMarker(build: BuildIdentity): Uint8Array { return encoder.encode( `${JSON.stringify({ pgwasm: MARKER_FORMAT, build: build.name, dataFormat: build.dataFormat })}\n`, ); } /** Parse a marker; throws {@link BuildMarkerUnreadableError} for anything but a valid one. */ export function decodeBuildMarker(bytes: Uint8Array): { build: string; dataFormat: number } { const raw = decoder.decode(bytes); let parsed: unknown; try { parsed = JSON.parse(raw); } catch { throw new BuildMarkerUnreadableError(raw, "not JSON"); } if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) { throw new BuildMarkerUnreadableError(raw, "not a JSON object"); } const { pgwasm, build, dataFormat } = parsed as Record<string, unknown>; if (typeof pgwasm !== "number" || !Number.isInteger(pgwasm) || pgwasm < 1) { throw new BuildMarkerUnreadableError(raw, "no marker format version"); } if (pgwasm > MARKER_FORMAT) { throw new BuildMarkerUnreadableError(raw, `marker format ${pgwasm} is newer than this pgwasm reads`); } if (typeof build !== "string" || build === "") { throw new BuildMarkerUnreadableError(raw, "no build name"); } if (typeof dataFormat !== "number" || !Number.isInteger(dataFormat) || dataFormat < 1) { throw new BuildMarkerUnreadableError(raw, "no data format"); } return { build, dataFormat }; } /** * Check what a data directory or backup records against the build about to open it. `marker` is the * marker's bytes (absent when there is none); `hasCluster` is whether `PG_VERSION` exists. Throws the * typed refusal when the build may not open it. */ export function checkBuildMarker( build: BuildIdentity, marker: Uint8Array | undefined, hasCluster: boolean, source: "data-directory" | "backup", ): void { if (marker === undefined) { if (hasCluster && !build.claimsUnmarkedDirectories) { throw new BuildMismatchError(build, "unmarked", source); } return; } const recorded = decodeBuildMarker(marker); if (recorded.build !== build.name) { const found: RecordedBuild = recorded; throw new BuildMismatchError(build, found, source); } if (recorded.dataFormat !== build.dataFormat) { throw new DataFormatMismatchError(build, recorded.dataFormat, source); } } /** The marker entry of a data-directory image, if it has one. */ export function findEntry(entries: readonly DataDirEntry[], path: string): DataDirEntry | undefined { return entries.find((entry) => entry.path === path && entry.type === "file"); }