initial commit

This commit is contained in:
Hunter
2026-07-29 14:20:30 -04:00
commit 6702030406
27 changed files with 14828 additions and 0 deletions

164
app/composables/useDump.ts Normal file
View File

@@ -0,0 +1,164 @@
import { ref, shallowRef, computed } from 'vue'
import type { Query } from '../lib/dump-store'
import type { TreeChild } from '../lib/dump-store'
export interface RowView {
index: number
address: number
type: string
typeId: number
path: string
name: string
container: string
outer: number
offset: number
}
let worker: Worker | null = null
let seq = 0
const pending = new Map<number, { resolve: (v: any) => void; reject: (e: any) => void }>()
const loading = ref(false)
const progress = ref(0)
const error = ref('')
const label = ref('')
const dumpKey = ref('')
const count = ref(0)
const skipped = ref(0)
const types = shallowRef<string[]>([])
const typeCounts = shallowRef<Int32Array>(new Int32Array(0))
const sharedAddressDigits = ref(0)
const total = ref(0)
const queryMs = ref(0)
const parseMs = ref(0)
function ensureWorker(): Worker {
if (worker) return worker
worker = new Worker(new URL('../workers/dump.worker.ts', import.meta.url), { type: 'module' })
worker.onmessage = (e) => {
const { id, result, error: err, event, fraction } = e.data
if (event === 'progress') {
progress.value = fraction
return
}
const p = pending.get(id)
if (!p) return
pending.delete(id)
err ? p.reject(new Error(err)) : p.resolve(result)
}
return worker
}
function call<T = any>(op: string, payload: Record<string, unknown> = {}, transfer: Transferable[] = []): Promise<T> {
const w = ensureWorker()
const id = ++seq
return new Promise<T>((resolve, reject) => {
pending.set(id, { resolve, reject })
w.postMessage({ id, op, ...payload }, transfer)
})
}
export function useDump() {
async function loadFile(file: File) {
loading.value = true
error.value = ''
progress.value = 0
try {
const buffer = await file.arrayBuffer()
// Transferred, not copied: a 60 MB structured clone is a visible stall.
const r = await call('parse', { buffer, name: file.name }, [buffer])
applyMeta(r)
} catch (e) {
error.value = (e as Error).message
} finally {
loading.value = false
progress.value = 0
}
}
async function restore(key: string) {
loading.value = true
error.value = ''
try {
applyMeta(await call('restore', { key }))
} catch (e) {
error.value = (e as Error).message
} finally {
loading.value = false
}
}
/** Unload the current dump without deleting it from the cache. */
async function reset() {
await call('close').catch(() => {})
loading.value = false
error.value = ''
progress.value = 0
label.value = ''
dumpKey.value = ''
types.value = []
typeCounts.value = new Int32Array(0)
skipped.value = 0
sharedAddressDigits.value = 0
total.value = 0
queryMs.value = 0
parseMs.value = 0
// Last: `ready` and the app's reset watcher both hang off this.
count.value = 0
}
function applyMeta(r: any) {
count.value = r.count
types.value = r.types
skipped.value = r.skipped
sharedAddressDigits.value = r.sharedAddressDigits
label.value = r.label
dumpKey.value = r.key
parseMs.value = r.parseMs ?? 0
}
async function runQuery(q: Query) {
const plain: Query = {
...q,
typeIds: q.typeIds ? Array.from(q.typeIds) : undefined,
}
const r = await call('query', { query: plain })
total.value = r.total
typeCounts.value = r.typeCounts
queryMs.value = r.ms
return r
}
const fetchWindow = (start: number, end: number) =>
call<{ start: number; rows: RowView[] }>('window', { start, end })
const fetchChildren = (prefix: string) =>
call<{ prefix: string; children: TreeChild[] }>('children', { prefix })
const resolveAddress = (address: number) =>
call<{ row: RowView | null }>('resolve', { address })
return {
loadFile,
restore,
reset,
runQuery,
fetchWindow,
fetchChildren,
resolveAddress,
loading,
progress,
error,
label,
dumpKey,
count,
skipped,
types,
typeCounts,
sharedAddressDigits,
total,
queryMs,
parseMs,
ready: computed(() => count.value > 0),
}
}