← Files Biological Sequence & Alignment ViewerARCHIVED FILE
src/workbench-session-validation.ts
28.1 KB · Sep 30, 2026 · 23:01 UTC
import { z } from "zod";
import { SEQUENCE_VIEWER_LIMITS, utf8ByteLength } from "./runtime-contract";
import { CHROMATOGRAM_LIMITS } from "./sequence/formats/chromatogram";
import {
sequenceInterfaceSettingsSchema,
validateSequenceInterfaceSettingsForSource,
} from "./sequence/interface-state";
import type { SequenceTrack } from "./sequence/tracks";
import type { WorkbenchSession } from "./workbench-state";
import { isSafeWorkspaceProvenancePath } from "./viewer-operations";
const chromatogramBaseConfidenceSchema = z
.array(z.number().int().min(0).max(255))
.max(CHROMATOGRAM_LIMITS.maxBaseCalls);
const chromatogramSignalSchema = z
.array(z.number().int().min(-32_768).max(65_535))
.max(CHROMATOGRAM_LIMITS.maxSamples);
const chromatogramSchema = z
.object({
baseConfidences: z
.object({
A: chromatogramBaseConfidenceSchema,
C: chromatogramBaseConfidenceSchema,
G: chromatogramBaseConfidenceSchema,
T: chromatogramBaseConfidenceSchema,
})
.strict()
.optional(),
channels: z
.object({
A: chromatogramSignalSchema,
C: chromatogramSignalSchema,
G: chromatogramSignalSchema,
T: chromatogramSignalSchema,
})
.strict(),
format: z.enum(["abif", "scf"]),
peakLocations: z
.array(z.number().int().nonnegative())
.max(CHROMATOGRAM_LIMITS.maxBaseCalls),
quality: z
.array(z.number().int().min(0).max(255).nullable())
.max(CHROMATOGRAM_LIMITS.maxBaseCalls)
.optional(),
qualityEncoding: z.enum(["phred", "source-confidence"]).optional(),
sampleCount: z
.number()
.int()
.positive()
.max(CHROMATOGRAM_LIMITS.maxSamples),
})
.strict();
const uuidSchema = z.string().uuid();
const parametersSchema = z.record(z.string().max(500), z.unknown());
const jobSchema = z
.object({
completedAt: z.number().int().nonnegative().optional(),
error: z.string().max(10_000).optional(),
id: uuidSchema,
kind: z.enum([
"align",
"distance-matrix",
"guide-tree",
"orfs",
"primers",
"quality-report",
"restriction-analysis",
"statistics",
"translation",
]),
message: z.string().max(10_000),
parameters: parametersSchema,
progress: z.number().min(0).max(1),
result: parametersSchema.optional(),
startedAt: z.number().int().nonnegative(),
status: z.enum(["cancelled", "completed", "failed", "running"]),
})
.strict();
const artifactSchema = z
.object({
content: z.string(),
createdAt: z.number().int().nonnegative(),
format: z.string().min(1).max(100),
id: uuidSchema,
mediaType: z.string().min(1).max(200),
name: z.string().min(1).max(255),
provenance: z
.object({
engine: z.string().min(1).max(500),
parameters: parametersSchema,
sourceRevision: z.number().int().nonnegative(),
})
.strict(),
})
.strict()
.superRefine(({ content, name }, context) => {
if (
utf8ByteLength(content) > SEQUENCE_VIEWER_LIMITS.session.maxArtifactBytes
) {
context.addIssue({
code: "custom",
message: `Artifact ${name} exceeds the bounded artifact size.`,
});
}
});
const trackFeatureSegmentSchema = z
.object({
end: z.number().int().positive(),
start: z.number().int().positive(),
})
.strict()
.refine(({ end, start }) => end >= start, "Track feature segment range is reversed.");
const trackFeatureSchema = z
.object({
attributes: z.record(z.string().max(500), z.string().max(10_000)),
codingSegments: z
.array(trackFeatureSegmentSchema)
.max(10_000)
.optional(),
end: z.number().int().positive(),
id: z.string().min(1).max(2_000),
label: z.string().max(2_000).optional(),
phase: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional(),
reference: z.string().max(2_000),
score: z.number().finite().optional(),
segments: z
.array(trackFeatureSegmentSchema)
.max(10_000)
.optional(),
source: z.string().max(2_000).optional(),
start: z.number().int().positive(),
strand: z.enum(["+", "-", ".", "?"]),
type: z.string().min(1).max(500),
})
.strict()
.refine(({ end, start }) => end >= start, "Track feature range is reversed.")
.superRefine((feature, context) => {
for (const [kind, segments] of [
["segments", feature.segments],
["codingSegments", feature.codingSegments],
] as const) {
for (const [index, segment] of (segments ?? []).entries()) {
if (segment.start < feature.start || segment.end > feature.end) {
context.addIssue({
code: "custom",
message: "Track feature segment extends outside its feature.",
path: [kind, index],
});
}
}
}
});
const variantSchema = z
.object({
alternateAlleles: z.array(z.string().max(10_000)).max(1_000),
filters: z.array(z.string().max(1_000)).max(1_000),
format: z.string().max(10_000).optional(),
id: z.string().min(1).max(2_000),
info: z.record(
z.string().max(500),
z.union([z.string().max(10_000), z.literal(true)]),
),
position: z.number().int().positive(),
quality: z.number().finite().optional(),
rawFilter: z.string().max(10_000).optional(),
rawId: z.string().max(2_000).optional(),
rawInfo: z.string().max(100_000).optional(),
rawQuality: z.string().max(10_000).optional(),
reference: z.string().max(2_000),
referenceAllele: z.string().min(1).max(100_000),
sampleValues: z.array(z.string().max(100_000)).max(10_000).optional(),
samples: z.record(z.string().max(2_000), z.string().max(100_000)),
})
.strict();
const vcfHeaderSchema = z
.object({
columns: z.array(z.string().min(1).max(2_000)).min(8).max(10_009),
metaLines: z.array(z.string().max(256 * 1_024)).max(4_096),
sampleNames: z.array(z.string().min(1).max(2_000)).max(10_000),
})
.strict()
.superRefine((header, context) => {
if (
utf8ByteLength([...header.metaLines, header.columns.join("\t")].join("\n")) >
SEQUENCE_VIEWER_LIMITS.session.maxSessionBytes / 2
) {
context.addIssue({
code: "custom",
message: "VCF metadata exceeds its bounded header budget.",
});
}
if (header.metaLines.some((line) => !line.startsWith("##"))) {
context.addIssue({
code: "custom",
message: "VCF metadata lines must retain their original prefix.",
path: ["metaLines"],
});
}
if (
header.sampleNames.length !== Math.max(0, header.columns.length - 9) ||
header.sampleNames.some((name, index) => name !== header.columns[index + 9])
) {
context.addIssue({
code: "custom",
message: "VCF sample columns do not match their preserved header.",
path: ["sampleNames"],
});
}
if (new Set(header.sampleNames).size !== header.sampleNames.length) {
context.addIssue({
code: "custom",
message: "VCF sample names must be unique.",
path: ["sampleNames"],
});
}
});
const readSchema = z
.object({
cigar: z.string().max(100_000),
end: z.number().int().positive(),
flags: z.number().int().nonnegative(),
id: z.string().min(1).max(2_000),
insertSize: z.number().int(),
mappingQuality: z.number().int().min(0).max(255),
matePosition: z.number().int().positive().optional(),
mateReference: z.string().max(2_000).optional(),
position: z.number().int().positive(),
quality: z
.array(z.number().int().min(0).max(255))
.max(1_000_000)
.optional(),
reference: z.string().max(2_000),
sequence: z.string().max(1_000_000),
strand: z.enum(["+", "-"]),
tags: z.record(
z.string().max(100),
z.union([z.string().max(100_000), z.number().finite()]),
),
})
.strict()
.refine(
({ end, position }) => end >= position,
"Track read range is reversed.",
);
const trackSchema = z
.object({
features: z
.array(trackFeatureSchema)
.max(SEQUENCE_VIEWER_LIMITS.input.maxTrackItems)
.optional(),
format: z.enum(["bam", "bed", "cram", "gff3", "gtf", "sam", "vcf"]),
id: z.string().min(1).max(2_000),
kind: z.enum(["annotations", "reads", "variants"]),
mapping: z
.object({
matchedReference: z.string().max(2_000).nullable(),
requestedReference: z.string().max(2_000).nullable(),
status: z.enum(["matched", "unmatched", "unresolved"]),
unmatchedReferences: z.array(z.string().max(2_000)).max(100),
})
.strict(),
name: z.string().min(1).max(2_000),
reads: z
.array(readSchema)
.max(SEQUENCE_VIEWER_LIMITS.input.maxTrackItems)
.optional(),
source: z
.object({
contentHash: z.string().max(500).optional(),
displayName: z.string().min(1).max(2_000),
workspacePath: z
.string()
.max(4_096)
.refine(isSafeWorkspaceProvenancePath)
.optional(),
})
.strict(),
summary: z
.object({
itemCount: z.number().int().nonnegative(),
materializedItemCount: z.number().int().nonnegative().optional(),
references: z.array(z.string().max(2_000)).max(100_000),
truncated: z.boolean(),
})
.strict(),
variants: z
.array(variantSchema)
.max(SEQUENCE_VIEWER_LIMITS.input.maxTrackItems)
.optional(),
vcfHeader: vcfHeaderSchema.optional(),
})
.strict()
.superRefine((track, context) => {
const items =
track.kind === "annotations"
? track.features
: track.kind === "reads"
? track.reads
: track.variants;
if (items == null) {
context.addIssue({
code: "custom",
message: `Track ${track.name} has no ${track.kind} payload.`,
});
} else if (
track.summary.materializedItemCount != null &&
items.length !== track.summary.materializedItemCount
) {
context.addIssue({
code: "custom",
message: `Track ${track.name} materialized item count is inconsistent.`,
});
} else if (
track.summary.materializedItemCount == null &&
!track.summary.truncated &&
items.length !== track.summary.itemCount
) {
context.addIssue({
code: "custom",
message: `Track ${track.name} item count is inconsistent.`,
});
} else if (
track.summary.itemCount <
(track.summary.materializedItemCount ?? items.length)
) {
context.addIssue({
code: "custom",
message: `Track ${track.name} source item count is inconsistent.`,
});
}
if (track.vcfHeader != null) {
if (track.format !== "vcf" || track.kind !== "variants") {
context.addIssue({
code: "custom",
message: "Only VCF variant tracks can retain VCF headers.",
path: ["vcfHeader"],
});
}
for (const [index, variant] of (track.variants ?? []).entries()) {
const sampleNames = track.vcfHeader.sampleNames;
if (
Object.keys(variant.samples).length !== sampleNames.length ||
sampleNames.some((name) => !Object.hasOwn(variant.samples, name))
) {
context.addIssue({
code: "custom",
message: "VCF sample genotypes do not match their preserved header.",
path: ["variants", index, "samples"],
});
}
if (
variant.sampleValues != null &&
(variant.sampleValues.length !== sampleNames.length ||
sampleNames.some(
(name, sampleIndex) =>
variant.samples[name] !== variant.sampleValues?.[sampleIndex],
))
) {
context.addIssue({
code: "custom",
message: "VCF sample values do not match their preserved header.",
path: ["variants", index, "sampleValues"],
});
}
}
}
});
const sequenceViewSchema = z
.object({
geneticCodeId: z.number().int().positive(),
interface: sequenceInterfaceSettingsSchema.optional(),
layout: z.enum(["circular", "linear", "split"]),
orientation: z.enum(["forward", "reverse-complement"]),
paletteId: z.string().min(1).max(100),
selectedFeatureId: z.string().max(2_000).nullable(),
selectedRecordId: z.string().max(2_000),
selection: z
.object({
end: z.number().int().positive(),
recordId: z.string().max(2_000),
segments: z
.array(
z
.object({
end: z.number().int().positive(),
start: z.number().int().positive(),
})
.strict()
.refine(({ end, start }) => end >= start),
)
.min(1)
.max(1_000)
.optional(),
start: z.number().int().positive(),
})
.strict()
.nullable(),
showFeatures: z.boolean(),
showQuality: z.boolean(),
showTranslation: z.boolean(),
synchronizedViews: z.boolean(),
viewport: z
.object({
end: z.number().int().positive(),
start: z.number().int().positive(),
})
.strict()
.nullable(),
wrapWidth: z.number().int().min(20).max(500),
})
.strict();
const alignmentViewSchema = z
.object({
analysisScope: z.string().max(100),
cellWidth: z.number().int().min(8).max(100),
colorMode: z.string().max(100),
enabledMetricTracks: z
.array(
z.enum([
"gap",
"identity",
"mismatch",
"modality-conservation",
"rna-structure",
"sequence-logo",
]),
)
.max(6)
.optional(),
referenceMode: z.string().max(100),
residuePalette: z.string().max(100).nullable(),
rowFilter: z.string().max(500),
rowSortDirection: z.enum(["asc", "desc"]).optional(),
rowSortKey: z
.enum(["coverage", "identity", "label", "length", "mismatches", "source"])
.optional(),
searchScope: z.string().max(100),
selectedColumns: z
.object({
end: z.number().int().nonnegative(),
start: z.number().int().nonnegative(),
})
.strict()
.nullable(),
selectedRows: z.array(z.string().max(2_000)).max(100_000),
showAnnotationTracks: z.boolean(),
showIdenticalAsDots: z.boolean(),
showRnaStructureOverlays: z.boolean(),
showSequenceLogoHelp: z.boolean().optional(),
})
.strict();
const viewSchema = z.discriminatedUnion("mode", [
z
.object({ mode: z.literal("sequence"), sequence: sequenceViewSchema })
.strict(),
z
.object({ alignment: alignmentViewSchema, mode: z.literal("alignment") })
.strict(),
]);
const sessionSchema = z
.object({
artifacts: z
.array(artifactSchema)
.max(SEQUENCE_VIEWER_LIMITS.session.maxArtifacts),
createdAt: z.number().int().nonnegative(),
dirty: z.boolean(),
jobs: z.array(jobSchema).max(100),
revision: z.number().int().nonnegative(),
schemaVersion: z.literal(1),
snapshot: z
.object({
alignmentDocument: z.unknown().optional(),
sequenceDocument: z.unknown().optional(),
})
.strict()
.optional(),
source: z
.object({
fileName: z.string().max(2_000).nullable(),
format: z.string().min(1).max(100),
stateKey: z.string().min(1).max(4_096).nullable().optional(),
})
.strict(),
tracks: z.array(trackSchema).max(128),
view: viewSchema,
})
.strict();
export function parseAndValidateWorkbenchSession(
value: string,
): WorkbenchSession {
if (utf8ByteLength(value) > SEQUENCE_VIEWER_LIMITS.session.maxSessionBytes) {
throw new Error("Workbench session exceeds the bounded session size.");
}
let raw: unknown;
try {
raw = JSON.parse(value);
} catch {
throw new Error(
"This is not valid Biological Sequence Viewer session JSON.",
);
}
const parsed = sessionSchema.parse(raw);
if (parsed.view.mode === "sequence") {
if (parsed.snapshot?.alignmentDocument != null) {
throw new Error(
"A Sequence-mode session cannot contain an alignment snapshot.",
);
}
if (parsed.snapshot?.sequenceDocument != null) {
validateSequenceDocument(parsed.snapshot.sequenceDocument);
validateSequenceViewState(
parsed.view.sequence,
parsed.snapshot.sequenceDocument,
parsed.tracks,
);
} else if (parsed.view.sequence.interface != null) {
validateSequenceInterfaceSettingsForSource({
settings: parsed.view.sequence.interface,
tracks: parsed.tracks,
});
}
} else {
if (parsed.snapshot?.sequenceDocument != null) {
throw new Error(
"An Alignment-mode session cannot contain a sequence snapshot.",
);
}
if (parsed.snapshot?.alignmentDocument != null) {
validateAlignmentDocument(parsed.snapshot.alignmentDocument);
validateAlignmentViewState(
parsed.view.alignment,
parsed.snapshot.alignmentDocument,
);
}
}
return parsed as WorkbenchSession;
}
function validateSequenceViewState(
view: z.infer<typeof sequenceViewSchema>,
snapshot: unknown,
tracks: ReadonlyArray<SequenceTrack>,
): void {
const document = requireObject(snapshot, "sequence snapshot");
const records = requireArray(document.records, "sequence records").map(
(record) => requireObject(record, "sequence record"),
);
const selectedRecord = records.find(({ id }) => id === view.selectedRecordId);
if (selectedRecord == null) {
throw new Error("Sequence session selected record is not in its snapshot.");
}
const length = requireInteger(
selectedRecord.length,
"sequence record length",
);
if (view.interface != null) {
validateSequenceInterfaceSettingsForSource({
record: {
evidenceCoordinatesStale:
selectedRecord.evidenceCoordinatesStale === true,
length,
sourceLabel: requireString(
selectedRecord.sourceLabel,
"sequence source label",
),
},
records: records.map((record) => ({
sourceLabel: requireString(record.sourceLabel, "sequence source label"),
})),
settings: view.interface,
tracks,
});
}
if (
view.viewport != null &&
(view.viewport.start > view.viewport.end || view.viewport.end > length)
) {
throw new Error(
"Sequence session viewport is outside its selected record.",
);
}
if (view.selectedFeatureId != null) {
const features = requireArray(selectedRecord.features, "sequence features");
if (
!features.some(
(feature) =>
requireObject(feature, "sequence feature").id ===
view.selectedFeatureId,
)
) {
throw new Error(
"Sequence session selected feature is not in its record.",
);
}
}
const selection = view.selection;
if (selection == null) return;
if (selection.recordId !== view.selectedRecordId) {
throw new Error(
"Sequence session selection belongs to a different record.",
);
}
if (selection.start > length || selection.end > length) {
throw new Error(
"Sequence session selection is outside its selected record.",
);
}
if (selection.segments == null) {
if (selection.start > selection.end) {
throw new Error("Linear sequence session selection is reversed.");
}
return;
}
const first = selection.segments[0];
const last = selection.segments.at(-1);
if (
first == null ||
last == null ||
first.start !== selection.start ||
last.end !== selection.end ||
selection.segments.some((segment) => segment.end > length)
) {
throw new Error(
"Sequence session selection segments are inconsistent with their source coordinates.",
);
}
const ordered = [...selection.segments].sort(
(left, right) => left.start - right.start,
);
for (let index = 1; index < ordered.length; index += 1) {
if (ordered[index]!.start <= ordered[index - 1]!.end) {
throw new Error("Sequence session selection segments overlap.");
}
}
}
function validateAlignmentViewState(
view: z.infer<typeof alignmentViewSchema>,
snapshot: unknown,
): void {
const document = requireObject(snapshot, "alignment snapshot");
const rows = requireArray(document.rows, "alignment rows").map((row) =>
requireObject(row, "alignment row"),
);
const rowIds = new Set(rows.map(({ id }) => id));
if (view.selectedRows.some((rowId) => !rowIds.has(rowId))) {
throw new Error("Alignment session selected rows are not in its snapshot.");
}
const alignedLength = requireInteger(
document.alignedLength,
"alignment length",
);
if (
view.selectedColumns != null &&
(view.selectedColumns.start >= view.selectedColumns.end ||
view.selectedColumns.end > alignedLength)
) {
throw new Error(
"Alignment session selected columns are outside its snapshot.",
);
}
}
function validateSequenceDocument(value: unknown): void {
const document = requireObject(value, "sequence snapshot");
assertExactKeys(
document,
[
"classification",
"fastqSummary",
"fileName",
"format",
"kind",
"recordInventory",
"records",
"warnings",
],
"sequence snapshot",
);
const records = requireArray(document.records, "sequence records");
if (records.length > SEQUENCE_VIEWER_LIMITS.input.maxSequenceRecords) {
throw new Error("Sequence snapshot has too many records.");
}
let totalResidues = 0;
for (const rawRecord of records) {
const record = requireObject(rawRecord, "sequence record");
if (
record.evidenceCoordinatesStale !== undefined &&
typeof record.evidenceCoordinatesStale !== "boolean"
) {
throw new Error(
"Sequence evidence-coordinate stale status must be boolean when present.",
);
}
const sequence = requireString(record.sequence, "sequence record sequence");
const length = requireInteger(record.length, "sequence record length");
if (sequence.length !== length) {
throw new Error("Sequence snapshot record length is inconsistent.");
}
totalResidues += length;
const features = requireArray(record.features, "sequence features");
for (const rawFeature of features) {
const feature = requireObject(rawFeature, "sequence feature");
const start = requireInteger(feature.start, "feature start");
const end = requireInteger(feature.end, "feature end");
if (start < 1 || end < start || end > length) {
throw new Error("Sequence snapshot feature coordinates are invalid.");
}
}
if (record.quality != null) {
const quality = requireObject(record.quality, "sequence quality");
if (
requireString(quality.ascii, "quality ASCII").length !== length ||
requireArray(quality.phred, "quality scores").length !== length
) {
throw new Error("Sequence snapshot quality length is inconsistent.");
}
}
if (record.chromatogram !== undefined) {
validateSequenceChromatogram(record.chromatogram, sequence);
}
}
if (totalResidues > SEQUENCE_VIEWER_LIMITS.input.maxTotalResidues) {
throw new Error("Sequence snapshot exceeds the residue budget.");
}
}
function validateSequenceChromatogram(value: unknown, sequence: string): void {
const chromatogram = chromatogramSchema.parse(value);
if (
sequence.length === 0 ||
sequence.length > CHROMATOGRAM_LIMITS.maxBaseCalls ||
chromatogram.peakLocations.length !== sequence.length
) {
throw new Error(
"Chromatogram base-call count is inconsistent with its sequence.",
);
}
for (const channel of Object.values(chromatogram.channels)) {
if (channel.length !== chromatogram.sampleCount) {
throw new Error(
"Chromatogram signal channel length is inconsistent with its sample count.",
);
}
if (chromatogram.format === "scf" && channel.some((sample) => sample < 0)) {
throw new Error(
"SCF chromatogram signal must contain unsigned sample values.",
);
}
}
let previousPeak = -1;
for (const peak of chromatogram.peakLocations) {
if (peak < previousPeak || peak >= chromatogram.sampleCount) {
throw new Error(
"Chromatogram peaks must be ordered zero-based sample coordinates within the signal.",
);
}
previousPeak = peak;
}
if (chromatogram.baseConfidences != null) {
if (chromatogram.format !== "scf") {
throw new Error(
"Per-base source confidence channels are supported only for SCF chromatograms.",
);
}
if (
Object.values(chromatogram.baseConfidences).some(
(confidence) => confidence.length !== sequence.length,
)
) {
throw new Error(
"Chromatogram confidence channel length is inconsistent with its sequence.",
);
}
}
if (chromatogram.quality == null) {
if (chromatogram.qualityEncoding != null) {
throw new Error(
"Chromatogram quality encoding requires source quality values.",
);
}
return;
}
if (chromatogram.quality.length !== sequence.length) {
throw new Error(
"Chromatogram source quality length is inconsistent with its sequence.",
);
}
const expectedEncoding =
chromatogram.format === "abif" ? "phred" : "source-confidence";
if (chromatogram.qualityEncoding !== expectedEncoding) {
throw new Error(
"Chromatogram quality encoding does not match its source format.",
);
}
for (const [index, quality] of chromatogram.quality.entries()) {
const base = sequence[index];
const isCanonicalBase =
base === "A" || base === "C" || base === "G" || base === "T";
if (chromatogram.format === "abif") {
if (quality == null) {
throw new Error("ABIF Phred quality values cannot be null.");
}
} else if (
(quality == null && isCanonicalBase) ||
(quality != null && !isCanonicalBase) ||
(isCanonicalBase &&
chromatogram.baseConfidences != null &&
quality !== chromatogram.baseConfidences[base][index])
) {
throw new Error(
"SCF called-base confidence does not match its source base call.",
);
}
}
}
function validateAlignmentDocument(value: unknown): void {
const document = requireObject(value, "alignment snapshot");
const rows = requireArray(document.rows, "alignment rows");
const alignedLength = requireInteger(
document.alignedLength,
"alignment length",
);
if (
rows.length > SEQUENCE_VIEWER_LIMITS.input.maxMsaRows ||
rows.length * alignedLength > SEQUENCE_VIEWER_LIMITS.input.maxMsaCells
) {
throw new Error("Alignment snapshot exceeds the matrix budget.");
}
for (const rawRow of rows) {
const row = requireObject(rawRow, "alignment row");
if (
requireString(row.alignedSequence, "aligned sequence").length !==
alignedLength
) {
throw new Error("Alignment snapshot contains unequal row widths.");
}
}
for (const rawTrack of requireArray(
document.annotations,
"alignment annotations",
)) {
const track = requireObject(rawTrack, "alignment annotation");
if (
requireString(track.values, "alignment annotation values").length !==
alignedLength
) {
throw new Error("Alignment snapshot annotation width is inconsistent.");
}
}
}
function requireObject(value: unknown, label: string): Record<string, unknown> {
if (value == null || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`${label} must be an object.`);
}
return value as Record<string, unknown>;
}
function requireArray(value: unknown, label: string): Array<unknown> {
if (!Array.isArray(value)) throw new Error(`${label} must be an array.`);
return value;
}
function requireString(value: unknown, label: string): string {
if (typeof value !== "string") throw new Error(`${label} must be text.`);
return value;
}
function requireInteger(value: unknown, label: string): number {
if (!Number.isInteger(value) || (value as number) < 0) {
throw new Error(`${label} must be a nonnegative integer.`);
}
return value as number;
}
function assertExactKeys(
value: Record<string, unknown>,
allowed: Array<string>,
label: string,
): void {
const allowedSet = new Set(allowed);
const unexpected = Object.keys(value).filter((key) => !allowedSet.has(key));
if (unexpected.length > 0) {
throw new Error(
`${label} contains unsupported fields: ${unexpected.join(", ")}.`,
);
}
}
SHA-256: ab9580d6643a40ac6779baf853f6f05a9d1bf914626952e67c3ea0234e26656a