refactor: split format helpers into core

This commit is contained in:
admin committed 2026-06-16 19:54:22 +05:00
1 parent df64404d21
commit 32b9b3f2c5
6 files changed
+122 -24

No files matched your search

+54
View File
@@ -0,0 +1,54 @@
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
}
}
+8
View File
@@ -1,5 +1,13 @@
export type { ConnectionSource, ResolvedConnection, SshConfig } from './config/baseSchema.js'
export { baseConnectionShape, sshConfigSchema } from './config/baseSchema.js'
export {
clampLimit,
DEFAULT_ROW_LIMIT,
MAX_ROW_LIMIT,
sliceUtf8,
truncateJsonBudget,
truncateRows
} from './format.js'
export { errorMessage, fail, ok } from './mcp/respond.js'
export type { Tunnel } from './net/tunnel.js'
export { buildSshAuth, expandHome, openTunnel } from './net/tunnel.js'
+52
View File
@@ -0,0 +1,52 @@
import { describe, expect, it } from 'vitest'
import { clampLimit, sliceUtf8, truncateJsonBudget, truncateRows } from '../../src/format.js'
describe('clampLimit', () => {
it('returns fallback when undefined', () => {
expect(clampLimit(undefined)).toBe(100)
expect(clampLimit(undefined, 7)).toBe(7)
})
it('clamps to [1, max]', () => {
expect(clampLimit(0)).toBe(1)
expect(clampLimit(99999)).toBe(1000)
expect(clampLimit(5000, 100, 2000)).toBe(2000)
})
})
describe('truncateRows', () => {
it('does not truncate when under limit', () => {
expect(truncateRows([1, 2], 5)).toEqual({ rows: [1, 2], truncated: false })
})
it('truncates and flags when over limit', () => {
expect(truncateRows([1, 2, 3], 2)).toEqual({ rows: [1, 2], truncated: true })
})
})
describe('truncateJsonBudget', () => {
it('passes small payloads through untouched', () => {
const v = { a: 1 }
expect(truncateJsonBudget(v, 1000)).toEqual({ body: v, truncated: false })
})
it('marks oversized payloads truncated', () => {
const big = { s: 'x'.repeat(500) }
const out = truncateJsonBudget(big, 100)
expect(out.truncated).toBe(true)
expect((out.body as { _truncated: boolean })._truncated).toBe(true)
})
})
describe('sliceUtf8', () => {
it('returns the string unchanged when it fits', () => {
expect(sliceUtf8('hello', 100)).toBe('hello')
})
it('caps to a real UTF-8 byte budget', () => {
const out = sliceUtf8('abcdef', 3)
expect(Buffer.byteLength(out, 'utf8')).toBeLessThanOrEqual(3)
expect(out).toBe('abc')
})
it('does not split a multi-byte code point at the boundary', () => {
const out = sliceUtf8('aéé', 3)
expect(Buffer.byteLength(out, 'utf8')).toBeLessThanOrEqual(3)
expect(out).toBe('aé')
})
})