55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
export const DEFAULT_ROW_LIMIT = 100
|
|
export const MAX_ROW_LIMIT = 1000
|
|
|
|
export const clampLimit = (
|
|
requested: number | undefined,
|
|
fallback = DEFAULT_ROW_LIMIT,
|
|
max = MAX_ROW_LIMIT
|
|
): number => {
|
|
if (requested === undefined) {
|
|
return fallback
|
|
}
|
|
return Math.max(1, Math.min(Math.trunc(requested), max))
|
|
}
|
|
|
|
export const truncateRows = <T>(rows: T[], limit: number): { rows: T[]; truncated: boolean } =>
|
|
rows.length > limit
|
|
? { rows: rows.slice(0, limit), truncated: true }
|
|
: { rows, truncated: false }
|
|
|
|
// Slice a string to at most `maxBytes` UTF-8 bytes without splitting a
|
|
// multi-byte sequence (back off to a code-point boundary).
|
|
export const sliceUtf8 = (s: string, maxBytes: number): string => {
|
|
const buf = Buffer.from(s, 'utf8')
|
|
if (buf.length <= maxBytes) {
|
|
return s
|
|
}
|
|
let end = maxBytes
|
|
while (end > 0 && (buf[end] & 0xc0) === 0x80) {
|
|
end -= 1
|
|
}
|
|
return buf.subarray(0, end).toString('utf8')
|
|
}
|
|
|
|
// Serialize `value` to JSON and, if it exceeds `maxBytes` (UTF-8), return a
|
|
// truncated marker object whose own serialization stays within `maxBytes`.
|
|
export const truncateJsonBudget = (
|
|
value: unknown,
|
|
maxBytes: number
|
|
): { body: unknown; truncated: boolean } => {
|
|
const text = JSON.stringify(value)
|
|
if (text === undefined) {
|
|
return { body: value, truncated: false }
|
|
}
|
|
const bytes = Buffer.byteLength(text, 'utf8')
|
|
if (bytes <= maxBytes) {
|
|
return { body: value, truncated: false }
|
|
}
|
|
const RESERVE = 256
|
|
const preview = sliceUtf8(text, Math.max(0, maxBytes - RESERVE))
|
|
return {
|
|
body: { _truncated: true, _originalBytes: bytes, _maxBytes: maxBytes, preview },
|
|
truncated: true
|
|
}
|
|
}
|