mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
The json backend now serves two layouts. single (the default) keeps the whole unit as one document at <root>/<name>.json; per-record keeps one version-stamped document per record at <root>/<name>/<table>/<key>.json (plus global.json), so one write rewrites one record instead of the whole unit. The per-record unit is stateless — the directory is the state, loadAll re-reads the tree, and every write is a single durable file operation — while single keeps its authoritative in-memory state and whole-file publish. Records keys must be path-safe ([a-zA-Z0-9_-]+); an unsafe key rejects. A record document that is malformed or stamped with another version reads as an absent record: one bad or stale file never bricks the unit, and a version bump discards stale records instead of migrating them. DomainSpec and KvUnitDescriptor gain the optional layout field (defineDomain validates it, descriptorOf projects it).
124 lines
5.0 KiB
TypeScript
124 lines
5.0 KiB
TypeScript
/**
|
|
* 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 (`<table>/<key>.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<string, Map<string, unknown>>
|
|
}
|
|
|
|
/**
|
|
* 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<string, Record<string, unknown>> = {}
|
|
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; version mismatch rejects.
|
|
* @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<string, unknown>
|
|
if (
|
|
typeof unit !== 'object' || unit === null ||
|
|
(unit as Record<string, unknown>)['name'] !== descriptor.name ||
|
|
typeof (unit as Record<string, unknown>)['version'] !== 'number'
|
|
) {
|
|
throw new StorageError('malformed-medium', `unit '${descriptor.name}': missing or foreign unit header`)
|
|
}
|
|
const version = (unit as Record<string, unknown>)['version'] as number
|
|
if (version !== descriptor.version) {
|
|
throw new StorageError(
|
|
'version-mismatch',
|
|
`unit '${descriptor.name}': stored version ${version} != expected ${descriptor.version}`,
|
|
)
|
|
}
|
|
if (typeof tables !== 'object' || tables === null) {
|
|
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<string, unknown>)[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<string, unknown>)))
|
|
}
|
|
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 a different version is FOREIGN and reads
|
|
* as absent — the per-record contract: one bad or stale record file must not
|
|
* brick the whole unit, and a version bump discards stale records instead of
|
|
* migrating them (the whole-unit format rejects instead, because there is
|
|
* exactly one document).
|
|
* @param text - Raw per-record document content.
|
|
* @param version - Expected unit version; a mismatch discards the document.
|
|
* @returns the record value, or `undefined` for a foreign document.
|
|
*/
|
|
export function parseRecord(text: string, version: 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<string, unknown>
|
|
if (stamped !== version) return undefined
|
|
return record
|
|
}
|