← Files DataARCHIVED FILE
skills/publish-artifact-to-sites/scripts/publication-snapshot-index.mjs
9.37 KB · Oct 5, 2026 · 12:04 UTC
import { isUtf8 } from "node:buffer";
import { createHash } from "node:crypto";
import { parseJsonBytes } from "../../../templates/data-app/base/src/streaming-json.js";
const digest = bytes => createHash("sha256").update(bytes).digest("hex");
const invalid = () => new Error("The reviewed snapshot must contain valid UTF-8 JSON, object queries, and arrays of object rows.");
const isWhitespace = byte => byte === 32 || byte === 9 || byte === 10 || byte === 13;
// Iterate one row at a time from an already validated index. Retain byte spans
// only for the active row; an array with millions of rows needs no offset list.
export function* publicationSnapshotRows(bytes, range) {
if (!range) return;
let cursor = range[0] + 1;
const end = range[1] - 1;
while (cursor < end) {
while (isWhitespace(bytes[cursor]) || bytes[cursor] === 44) cursor += 1;
if (cursor >= end) break;
const start = cursor;
if (bytes[cursor] !== 123) throw invalid();
let depth = 0, string = false, escaped = false;
for (; cursor < end; cursor += 1) {
const byte = bytes[cursor];
if (string) {
if (escaped) escaped = false;
else if (byte === 92) escaped = true;
else if (byte === 34) string = false;
} else if (byte === 34) string = true;
else if (byte === 123 || byte === 91) depth += 1;
else if (byte === 125 || byte === 93) {
depth -= 1;
if (!depth) { cursor += 1; break; }
}
}
if (depth || string) throw invalid();
yield bytes.subarray(start, cursor);
}
}
// Record byte spans, not a second copy of the snapshot's rows. Only root,
// queries, and query objects retain member information; all other nesting is
// visited with an explicit stack. Maps preserve JSON's last-key-wins behavior.
export function createPublicationSnapshotIndex(input) {
const bytes = Buffer.isBuffer(input) ? input : Buffer.from(input.buffer, input.byteOffset, input.byteLength);
if (!isUtf8(bytes)) throw invalid();
let cursor = bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 3 : 0, root;
const stack = [], replacements = [];
const whitespace = () => {
while (isWhitespace(bytes[cursor])) cursor += 1;
};
function stringEnd() {
if (bytes[cursor++] !== 34) throw invalid();
while (cursor < bytes.length) {
const character = bytes[cursor++];
if (character === 34) return cursor;
if (character < 32) throw invalid();
if (character === 92) {
const escaped = bytes[cursor++];
if (escaped === 117) {
for (let i = 0; i < 4; i += 1) {
const hex = bytes[cursor++];
if (!(hex >= 48 && hex <= 57 || hex >= 65 && hex <= 70 || hex >= 97 && hex <= 102)) throw invalid();
}
} else if (![34, 92, 47, 98, 102, 110, 114, 116].includes(escaped)) throw invalid();
}
}
throw invalid();
}
function complete(value) {
const parent = stack.at(-1);
if (!parent) { root = value; return; }
parent.count += 1;
parent.state = 4;
if (parent.scope === "root" && (parent.key === "queries" || parent.key === "generatedAt")) parent.members.set(parent.key, value);
if (parent.scope === "queries") parent.members.set(parent.key, value);
if (parent.scope === "query" && parent.key === "rows") parent.rows = value;
if (parent.scope === "query" && parent.key === "source") parent.source = value;
if (parent.scope === "source" && parent.key === "executedAt") parent.executedAt = value;
if (parent.scope === "rows" && value.type !== "object") parent.objectRows = false;
}
function close() {
const frame = stack.pop();
const value = { type: frame.type, start: frame.start, end: ++cursor };
if (frame.scope === "root" || frame.scope === "queries") value.members = frame.members;
if (frame.scope === "query") { value.rows = frame.rows; value.source = frame.source; value.empty = frame.count === 0; }
if (frame.scope === "source") { value.executedAt = frame.executedAt; value.empty = frame.count === 0; }
if (frame.scope === "rows") { value.rowCount = frame.count; value.objectRows = frame.objectRows; }
complete(value);
}
while (true) {
whitespace();
const frame = stack.at(-1);
if (!frame && root) break;
if (frame) {
const closing = frame.type === "object" ? 125 : 93;
if (frame.state === 4) {
if (bytes[cursor] === closing) { close(); continue; }
if (bytes[cursor++] !== 44) throw invalid();
frame.state = 1;
continue;
}
if (frame.state === 0 && bytes[cursor] === closing) { close(); continue; }
if (frame.type === "object" && frame.state !== 3) {
const start = cursor, end = stringEnd();
frame.key = frame.scope ? parseJsonBytes(bytes.subarray(start, end), { fatal: true }) : null;
whitespace();
if (bytes[cursor++] !== 58) throw invalid();
frame.state = 3;
continue;
}
}
const start = cursor, character = bytes[cursor];
if (character === 123 || character === 91) {
const type = character === 123 ? "object" : "array";
let scope = null;
if (!frame && type === "object") scope = "root";
else if (frame?.scope === "root" && frame.key === "queries" && type === "object") scope = "queries";
else if (frame?.scope === "queries" && type === "object") scope = "query";
else if (frame?.scope === "query" && frame.key === "rows" && type === "array") scope = "rows";
else if (frame?.scope === "query" && frame.key === "source" && type === "object") scope = "source";
stack.push({ type, scope, start, state: 0, count: 0, objectRows: true,
...(scope === "root" || scope === "queries" ? { members: new Map() } : {}) });
cursor += 1;
continue;
}
let type;
if (character === 34) { stringEnd(); type = "string"; }
else if (character === 116 || character === 102 || character === 110) {
const word = character === 116 ? "true" : character === 102 ? "false" : "null";
if (bytes.toString("ascii", cursor, cursor + word.length) !== word) throw invalid();
cursor += word.length;
type = word === "null" ? "null" : "boolean";
} else {
while (cursor < bytes.length && !isWhitespace(bytes[cursor]) && bytes[cursor] !== 44 && bytes[cursor] !== 93 && bytes[cursor] !== 125) cursor += 1;
let value;
// Native conversion is inexpensive for bounded scalar tokens. A single
// decimal spelling may itself span windows, so never decode an unbounded
// token even though the index retains only its normalized numeric value.
if (cursor - start <= 64 * 1024) {
const token = bytes.toString("ascii", start, cursor);
if (!/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/u.test(token)) throw invalid();
value = Number(token);
} else {
try { value = parseJsonBytes(bytes.subarray(start, cursor), { fatal: true }); }
catch { throw invalid(); }
if (typeof value !== "number") throw invalid();
}
// Match the former JSON.parse -> JSON.stringify API normalization, while
// keeping the immutable source asset byte-for-byte unchanged.
if (!Number.isFinite(value) || Object.is(value, -0)) replacements.push({ start, end: cursor, text: Number.isFinite(value) ? "0" : "null" });
type = "number";
}
complete({ type, start, end: cursor });
}
if (cursor !== bytes.length || root.type !== "object") throw invalid();
const queries = root.members.get("queries");
if (queries?.type !== "object") throw invalid();
let rowCount = 0;
const entries = [];
for (const [queryId, query] of queries.members) {
if (query.type !== "object" || query.rows && (query.rows.type !== "array" || !query.rows.objectRows)) throw invalid();
rowCount += query.rows?.rowCount ?? 0;
entries.push([queryId, { rows: query.rows ? [query.rows.start, query.rows.end] : null,
end: query.end - 1, empty: query.empty,
source: query.source ? { range: [query.source.start, query.source.end],
object: query.source.type === "object", empty: query.source.empty ?? false,
executedAt: query.source.executedAt ? [query.source.executedAt.start, query.source.executedAt.end] : null } : null }]);
}
const generatedAt = root.members.get("generatedAt");
const snapshotIndex = { version: 1, sha256: digest(bytes), bytes: bytes.length,
generatedAt: generatedAt ? [generatedAt.start, generatedAt.end] : null,
end: root.end - 1, queries: Object.fromEntries(entries),
...(replacements.length ? { replacements } : {}) };
const edits = [...replacements];
for (const query of Object.values(snapshotIndex.queries)) {
if (!query.rows) edits.push({ start: query.end, end: query.end, text: `${query.empty ? "" : ","}"rows":[]` });
}
if (!edits.length) return { snapshotIndex, snapshotResponse: { sha256: snapshotIndex.sha256, bytes: bytes.length,
queryCount: entries.length, rowCount, encoding: "indexed-raw-v1" } };
edits.sort((a, b) => a.start - b.start);
const hash = createHash("sha256");
let position = 0, responseBytes = 0;
const append = value => { hash.update(value); responseBytes += Buffer.byteLength(value); };
for (const edit of edits) {
append(bytes.subarray(position, edit.start));
append(edit.text);
position = edit.end;
}
append(bytes.subarray(position));
return { snapshotIndex, snapshotResponse: { sha256: hash.digest("hex"), bytes: responseBytes,
queryCount: entries.length, rowCount, encoding: "indexed-raw-v1" } };
}
SHA-256: a3f56c49fafe6db2c45be8c064ee8abd7d73cbf16cf5abe327b585957a10da63