/** * On-disk JSON unit format: the file is always the current net state, kept * human-readable (pretty-printed, stable key order from insertion) — that * legibility is this backend's reason to exist. `single`-layout units are * one document with a unit header; `per-record`-layout units are a directory * with one version-stamped document per record (`/.json`) plus a * `global.json` for the global slot, so a write rewrites one record instead * of the whole unit. * @module @deepseek-ai/dsh-storage-json/src/format */ import { StorageError } from '@deepseek-ai/dsh-storage' import type { KvUnitDescriptor } from '@deepseek-ai/dsh-storage' /** In-memory authoritative state of one unit; the file is its projection. `global` is `null` until first written. */ export interface UnitState { version: number global: unknown tables: Map> } /** * Serialize a unit state to file content. * @param name - Unit name, stamped into the header. * @param state - Authoritative in-memory state. * @returns pretty-printed JSON document with a trailing newline. */ export function serialize(name: string, state: UnitState): string { const tables: Record> = {} for (const [table, records] of state.tables) { tables[table] = Object.fromEntries(records) } const document = { unit: { name, version: state.version }, global: state.global, tables, } return `${JSON.stringify(document, null, 2)}\n` } /** * Parse file content into unit state, validating shape and version. * @param text - Raw file content. * @param descriptor - Expected identity and accepted current or compatible versions. * @returns the parsed state. */ export function parse(text: string, descriptor: KvUnitDescriptor): UnitState { let document: unknown try { document = JSON.parse(text) } catch (error) { throw new StorageError('malformed-medium', `unit '${descriptor.name}': file is not valid JSON`, { cause: error }) } if (typeof document !== 'object' || document === null) { throw new StorageError('malformed-medium', `unit '${descriptor.name}': file is not a JSON object`) } const { unit, global: globalValue, tables } = document as Record if ( typeof unit !== 'object' || unit === null || (unit as Record)['name'] !== descriptor.name || typeof (unit as Record)['version'] !== 'number' ) { throw new StorageError('malformed-medium', `unit '${descriptor.name}': missing or foreign unit header`) } const version = (unit as Record)['version'] as number if (version !== descriptor.version && !descriptor.compatibleVersions?.includes(version)) { throw new StorageError( 'version-mismatch', `unit '${descriptor.name}': stored version ${version} is not accepted by version ${descriptor.version}`, ) } // `typeof` alone admits an array: reading one as a table map reports every // declared table as absent, and the next publish replaces the file with that // empty view, discarding records this reader could not see. if (typeof tables !== 'object' || tables === null || Array.isArray(tables)) { throw new StorageError('malformed-medium', `unit '${descriptor.name}': tables is not an object`) } const state: UnitState = { version, global: globalValue ?? null, tables: new Map() } for (const table of descriptor.tables) { const records = (tables as Record)[table] if (records === undefined) { state.tables.set(table, new Map()) continue } if (typeof records !== 'object' || records === null || Array.isArray(records)) { throw new StorageError('malformed-medium', `unit '${descriptor.name}': table '${table}' is not an object`) } state.tables.set(table, new Map(Object.entries(records as Record))) } return state } /** * Serialize one per-record document: the unit's version stamp plus the * record value, pretty-printed like the whole-unit document. * @param version - Unit format version, stamped into the header. * @param value - The record value (or the global singleton value). * @returns pretty-printed JSON document with a trailing newline. */ export function serializeRecord(version: number, value: unknown): string { return `${JSON.stringify({ version, record: value }, null, 2)}\n` } /** * Parse one per-record document, validating its version stamp. A document * that is malformed or stamped with an unaccepted version is FOREIGN and * reads as absent — the per-record contract: one bad or stale record file * must not brick the whole unit, and an unaccepted version stamp discards the * record instead of migrating it (the whole-unit format rejects instead, * because there is exactly one document). * @param text - Raw per-record document content. * @param versions - Accepted unit versions (the current one plus the * descriptor's compatibleVersions); any other stamp discards the * document. * @returns the record value, or `undefined` for a foreign document. */ export function parseRecord(text: string, versions: readonly number[]): unknown { let document: unknown try { document = JSON.parse(text) } catch { return undefined } if (typeof document !== 'object' || document === null) return undefined const { version: stamped, record } = document as Record if (typeof stamped !== 'number' || !versions.includes(stamped)) return undefined return record }