/** Bounded serialization fragments: large escaped strings never form one copy. */ export function* jsonPieces(value: any, depth = 0): Generator { if (depth > 64) throw new Error('JSON nesting limit'); if (typeof value === 'string') { yield '"'; for (let offset = 0; offset < value.length;) { let end = Math.min(value.length, offset + 16384); if (end < value.length && /[\uD800-\uDBFF]/.test(value[end - 1])) end--; yield JSON.stringify(value.slice(offset, end)).slice(1, -1); offset = end; } yield '"'; } else if (Array.isArray(value)) { yield '['; for (let i = 0; i < value.length; i++) { if (i) yield ','; yield* jsonPieces(value[i] ?? null, depth + 1); } yield ']'; } else if (value && typeof value === 'object') { yield '{'; let first = true; for (const key of Object.keys(value)) { if (value[key] === undefined || typeof value[key] === 'function' || typeof value[key] === 'symbol') continue; if (!first) yield ','; first = false; yield JSON.stringify(key); yield ':'; yield* jsonPieces(value[key], depth + 1); } yield '}'; } else { yield JSON.stringify(value) ?? 'null'; } } export function jsonBytes(value: any, limit = Number.MAX_SAFE_INTEGER): number { let bytes = 0; for (const part of jsonPieces(value)) { bytes += Buffer.byteLength(part); if (bytes > limit) return bytes; } return bytes; }