writeRow
uses node:crypto
Write value as row id of table (a new row, with a new id, if there's none), as the table's own writes are:
checked against its row type first (the reason, if it doesn't fit), recorded with its schema, and refused when it
would share a unique field with another row (that row named). Its id.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { encode } from "#codebase/tcmts/cli/codec"; import { tableSchemaOf, toJson, withWritableStore } from "#codebase/tcmts/cli/data_pages"; import { checkValue } from "#codebase/tcmts/core/schema"; import { randomBytes } from "node:crypto"; export function writeRow( cb: Codebase, table: string, id: string | undefined, value: unknown, ): string { const t = tableSchemaOf(cb, table); if (!t) throw new Error( `there's no table "${table}" declared in the codebase: its row type is what writes are checked against`, ); const bad = checkValue(t.s, value, "row", t.defs); if (bad) throw new TypeError(`table "${table}": ${bad}`); return withWritableStore(cb, (db) => { db.prepare( "INSERT OR IGNORE INTO schemas (tbl, hash, schema, first_seen) VALUES (?, ?, ?, ?)", ).run(table, t.hash, JSON.stringify({ s: t.s, defs: t.defs }), Date.now()); let row = id; if (!row) do row = randomBytes(9).toString("base64url"); while ( row.startsWith("-") || db.prepare("SELECT 1 FROM rows WHERE tbl = ? AND id = ?").get(table, row) ); const field = (f: string) => `json_extract(data, '$."${f}"')`; const scalar = (v: unknown) => typeof v === "boolean" ? Number(v) : v === null || ["string", "number"].includes(typeof v) ? v : encode(v); // (a unique field another row has: said, never a silent replacement) for (const f of t.unique ?? []) { const v = (value as Record<string, unknown>)[f]; const other = db .prepare(`SELECT id FROM rows WHERE tbl = ? AND ${field(f)} = ? AND id != ?`) .get(table, scalar(v) as string | number | null, row) as { id: string } | undefined; if (other) throw new Error( `table "${table}": another row (${other.id}) already has ${f} = ${JSON.stringify(toJson(v))}`, ); } db.prepare( "INSERT INTO rows (tbl, id, data, schema_hash, updated) VALUES (?, ?, ?, ?, ?) ON CONFLICT (tbl, id) DO UPDATE SET data = excluded.data, schema_hash = excluded.schema_hash, updated = excluded.updated", ).run(table, row, encode(value, `a ${table} row`), t.hash, Date.now()); return row; }); }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.codec.encode
- tcmts.cli.data_pages.tableSchemaOf
- tcmts.cli.data_pages.toJson
- tcmts.cli.data_pages.withWritableStore
- tcmts.core.schema.checkValue