perf(registratura): lightweight API mode strips base64 attachments from list
ROOT CAUSE: RegistryEntry stores file attachments as base64 strings in JSON.
A single 5MB PDF becomes ~6.7MB of base64. With 6 entries, the exportAll()
endpoint was sending 30-60MB of JSON on every page load taking 2+ minutes.
Fix: Added ?lightweight=true parameter to /api/storage GET endpoint.
When enabled, stripHeavyFields() recursively removes large 'data' and
'fileData' string fields (>1KB) from JSON values, replacing with '__stripped__'.
Changes:
- /api/storage route.ts: stripHeavyFields() + lightweight query param
- StorageService.export(): accepts { lightweight?: boolean } option
- DatabaseStorageAdapter.export(): passes lightweight flag to API
- LocalStorageAdapter.export(): accepts option (no-op, localStorage is fast)
- useStorage.exportAll(): passes options through
- registry-service.ts: getAllEntries() uses lightweight=true by default
- registry-service.ts: new getFullEntry() loads single entry with full data
- use-registry.ts: exports loadFullEntry() for on-demand full loading
- registratura-module.tsx: handleEdit/handleNavigateEntry load full entry
Result: List loading transfers ~100KB instead of 30-60MB. Editing loads
full data for a single entry on demand (~5-10MB for one entry vs all).
This commit is contained in:
@@ -1,10 +1,40 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/core/storage/prisma";
|
||||
|
||||
/**
|
||||
* Strip heavy base64 data from JSON values to enable fast list loading.
|
||||
* Targets: attachments[].data, versions[].fileData, and any top-level `data`
|
||||
* field that looks like base64 (>1KB). Replaces with "__stripped__" marker.
|
||||
*/
|
||||
function stripHeavyFields(value: unknown): unknown {
|
||||
if (!value || typeof value !== "object") return value;
|
||||
if (Array.isArray(value)) return value.map(stripHeavyFields);
|
||||
|
||||
const obj = value as Record<string, unknown>;
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
if (k === "data" && typeof v === "string" && v.length > 1024) {
|
||||
// Strip large base64 data fields, keep a marker
|
||||
result[k] = "__stripped__";
|
||||
} else if (k === "fileData" && typeof v === "string" && v.length > 1024) {
|
||||
result[k] = "__stripped__";
|
||||
} else if (Array.isArray(v)) {
|
||||
result[k] = v.map(stripHeavyFields);
|
||||
} else if (v && typeof v === "object") {
|
||||
result[k] = stripHeavyFields(v);
|
||||
} else {
|
||||
result[k] = v;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const namespace = searchParams.get("namespace");
|
||||
const key = searchParams.get("key");
|
||||
const lightweight = searchParams.get("lightweight") === "true";
|
||||
|
||||
if (!namespace) {
|
||||
return NextResponse.json(
|
||||
@@ -34,7 +64,11 @@ export async function GET(request: NextRequest) {
|
||||
// Return as a record { [key]: value }
|
||||
const result: Record<string, any> = {};
|
||||
for (const item of items) {
|
||||
result[item.key] = item.value;
|
||||
if (lightweight) {
|
||||
result[item.key] = stripHeavyFields(item.value);
|
||||
} else {
|
||||
result[item.key] = item.value;
|
||||
}
|
||||
}
|
||||
return NextResponse.json({ items: result });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user