← Public packages
@kentcdodds/sentry-triage
Sentry triage wakes Cole (Grok Bot) per issue: one active wake per repo (lease + queue), loop-safe Discord. Cole may spawn Cursor for isolated repo work.
src/list-storage.ts
98 lines · 3.0 KB · TypeScript/**
* Paginate packageStorage.list until the prefix is exhausted.
*
* Live packageStorage returns a page object shaped like
* `{ entries, truncated, nextStartAfter, pageSize }` (default page ≈ 250).
* A single `list({ prefix })` therefore silently drops later keys — which is
* how newer `awaiting-seer` issue records fell past the grace window without
* ever being seen by `./sweep-stale-awaiting` or `./triage-report`.
*/
export async function listStorageEntries(
storage: {
list: (opts: Record<string, unknown>) => Promise<unknown>
},
opts: { prefix: string; pageSize?: number; maxPages?: number } = {
prefix: '',
},
) {
const prefix = String(opts.prefix ?? '')
const pageSize = Math.min(
1000,
Math.max(1, Math.floor(Number(opts.pageSize) || 256)),
)
const maxPages = Math.min(
200,
Math.max(1, Math.floor(Number(opts.maxPages) || 40)),
)
const all: unknown[] = []
let startAfter: string | undefined
for (let page = 0; page < maxPages; page++) {
const listed = await storage.list({
prefix,
limit: pageSize,
...(startAfter ? { startAfter } : {}),
})
const entries = normalizeListEntries(listed)
all.push(...entries)
const next = nextStartAfterOf(listed)
const truncated = isListTruncated(listed)
if (!truncated || !next || next === startAfter) break
startAfter = next
}
return all
}
export function normalizeListEntries(listed: unknown): unknown[] {
if (Array.isArray(listed)) return listed
if (!listed || typeof listed !== 'object') return []
const obj = listed as Record<string, unknown>
const candidates = [obj.keys, obj.entries, obj.items]
for (const candidate of candidates) {
if (Array.isArray(candidate)) return candidate
}
return []
}
export function nextStartAfterOf(listed: unknown): string | null {
if (!listed || typeof listed !== 'object') return null
const obj = listed as Record<string, unknown>
for (const key of ['nextStartAfter', 'cursor', 'continuationToken', 'nextCursor']) {
const value = obj[key]
if (typeof value === 'string' && value) return value
}
return null
}
export function isListTruncated(listed: unknown): boolean {
if (!listed || typeof listed !== 'object') return false
const obj = listed as Record<string, unknown>
if (obj.truncated === true) return true
if (obj.list_complete === false) return true
return false
}
/**
* Resolve a list entry into a stored record, fetching by key when the page
* only returned key metadata.
*/
export async function recordFromListEntry(
storage: { get: (key: string) => Promise<unknown> },
entry: unknown,
) {
if (entry && typeof entry === 'object' && 'value' in entry) {
const value = (entry as { value?: unknown }).value
if (value != null) return value
}
const key =
typeof entry === 'string'
? entry
: entry && typeof entry === 'object'
? String(
(entry as { key?: unknown; name?: unknown }).key ??
(entry as { name?: unknown }).name ??
'',
) || null
: null
if (!key) return null
return (await storage.get(key)) ?? null
}