Files
orca/src/shared/json-text-structure-limit.ts
T
Neil 94b4116a10 Bound AI Vault cache loading and keep atomic saves responsive (#24789)
* Bound AI Vault cache loading and cooperative atomic saves

Preserve schema 3 caches across compatible releases while limiting bytes, JSON structure, and newest unique rows. Keep in-process entries authoritative and retain a valid prior snapshot when the newest row cannot fit.

Credits @AmethystLiang for the original PR10708 cache bounds and cooperative persistence intent.

* Use checked cache JSON properties in cooperative serialization

Preserves lazy own-property access and all serializer bounds, yields and errors.
2026-10-02 17:38:02 -07:00

121 lines
3.5 KiB
TypeScript

export type JsonTextStructureLimits = Readonly<{
structuralTokens: number
nestingDepth: number
}>
export class JsonTextStructureCapacityError extends Error {
constructor(
readonly resource: keyof JsonTextStructureLimits,
readonly limit: number
) {
super(
resource === 'structuralTokens'
? `JSON structure exceeds ${limit} tokens`
: `JSON nesting exceeds ${limit} levels`
)
this.name = 'JsonTextStructureCapacityError'
}
}
export function assertJsonTextStructureWithinLimits(
content: string,
limits: JsonTextStructureLimits
): void {
new JsonTextStructureValidator(limits).consume(content)
}
/** Carries string/escape and structure state across bounded chunks. */
export class JsonTextStructureValidator {
private structuralTokens = 0
private depth = 0
private maximumDepth = 0
private inString = false
private escaped = false
constructor(private readonly limits: JsonTextStructureLimits) {
assertLimit(limits.structuralTokens)
assertLimit(limits.nestingDepth)
}
consume(content: string): void {
let linearString = false
for (let index = 0; index < content.length; index += 1) {
const character = content[index]
if (this.inString) {
if (this.escaped) {
this.escaped = false
continue
}
if (!linearString) {
const quote = content.indexOf('"', index)
const end = quote === -1 ? content.length : quote
let backslashes = 0
for (let at = end - 1; at >= index && content[at] === '\\'; at -= 1) {
backslashes++
}
if (quote === -1) {
this.escaped = backslashes % 2 !== 0
return
}
index = quote
if (backslashes % 2 === 0) {
this.inString = false
} else {
// Escape-heavy strings scan linearly instead of repeating native searches.
linearString = true
}
continue
}
if (character === '\\') {
this.escaped = true
} else if (character === '"') {
this.inString = false
linearString = false
}
continue
}
if (character === '"') {
this.inString = true
continue
}
if (!isStructuralToken(character)) {
continue
}
this.structuralTokens++
if (this.structuralTokens > this.limits.structuralTokens) {
throw new JsonTextStructureCapacityError('structuralTokens', this.limits.structuralTokens)
}
if (character === '{' || character === '[') {
this.depth++
this.maximumDepth = Math.max(this.maximumDepth, this.depth)
if (this.depth > this.limits.nestingDepth) {
throw new JsonTextStructureCapacityError('nestingDepth', this.limits.nestingDepth)
}
} else if (character === '}' || character === ']') {
this.depth = Math.max(0, this.depth - 1)
}
}
}
usage(): { structuralTokens: number; nestingDepth: number } {
return { structuralTokens: this.structuralTokens, nestingDepth: this.maximumDepth }
}
}
function assertLimit(value: number): void {
if (!Number.isSafeInteger(value) || value < 0) {
throw new RangeError('JSON structure limits must be non-negative safe integers')
}
}
function isStructuralToken(character: string | undefined): boolean {
return (
character === '{' ||
character === '}' ||
character === '[' ||
character === ']' ||
character === ',' ||
character === ':'
)
}