12 changed files with 463 additions and 16 deletions
@ -0,0 +1,48 @@
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from 'vitest' |
||||
import { |
||||
approxLibraryIndexEventBytes, |
||||
getLibraryIndexCacheBudget, |
||||
LIBRARY_INDEX_CACHE_DEFAULTS |
||||
} from '@/lib/library-index-cache-config' |
||||
import { ExtendedKind } from '@/constants' |
||||
import type { Event } from 'nostr-tools' |
||||
|
||||
function sampleIndexEvent(tagCount: number): Event { |
||||
const tags: string[][] = [ |
||||
['d', 'sample-book'], |
||||
['title', 'Sample Book'] |
||||
] |
||||
for (let i = 0; i < tagCount; i++) { |
||||
tags.push(['a', `30041:${'a'.repeat(64)}:chapter-${i}`, 'wss://relay.example', 'b'.repeat(64)]) |
||||
} |
||||
return { |
||||
id: 'c'.repeat(64), |
||||
kind: ExtendedKind.PUBLICATION, |
||||
pubkey: 'a'.repeat(64), |
||||
created_at: 1_700_000_000, |
||||
content: '', |
||||
tags, |
||||
sig: 'd'.repeat(128) |
||||
} |
||||
} |
||||
|
||||
describe('library-index-cache-config', () => { |
||||
it('approxLibraryIndexEventBytes returns positive size', () => { |
||||
const bytes = approxLibraryIndexEventBytes(sampleIndexEvent(3)) |
||||
expect(bytes).toBeGreaterThan(100) |
||||
}) |
||||
|
||||
it('getLibraryIndexCacheBudget returns platform defaults', () => { |
||||
const budget = getLibraryIndexCacheBudget() |
||||
expect(budget.maxEntries).toBeGreaterThanOrEqual(LIBRARY_INDEX_CACHE_DEFAULTS.maxEntriesMobile) |
||||
expect(budget.maxBytes).toBeGreaterThanOrEqual(LIBRARY_INDEX_CACHE_DEFAULTS.maxMbMobile * 1024 * 1024) |
||||
}) |
||||
|
||||
it('5000 mercury-sized indexes land near documented desktop budget', () => { |
||||
const avgMercuryBytes = 14_131 |
||||
const est5000Mb = (avgMercuryBytes * LIBRARY_INDEX_CACHE_DEFAULTS.maxEntriesDesktop) / (1024 * 1024) |
||||
expect(est5000Mb).toBeGreaterThan(50) |
||||
expect(est5000Mb).toBeLessThan(120) |
||||
expect(LIBRARY_INDEX_CACHE_DEFAULTS.maxMbDesktop).toBeGreaterThanOrEqual(Math.ceil(est5000Mb)) |
||||
}) |
||||
}) |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
import { isImwaldElectron, isMobileBrowserProfile } from '@/lib/client-platform' |
||||
import type { Event } from 'nostr-tools' |
||||
|
||||
/** Platform caps for the dedicated Library kind-30040 index LRU store (separate from EVENT_ARCHIVE). */ |
||||
export const LIBRARY_INDEX_CACHE_DEFAULTS = { |
||||
maxEntriesMobile: 400, |
||||
maxEntriesDesktop: 5000, |
||||
maxEntriesElectron: 5000, |
||||
maxMbMobile: 40, |
||||
maxMbDesktop: 96, |
||||
maxMbElectron: 128 |
||||
} as const |
||||
|
||||
export function approxLibraryIndexEventBytes(ev: Event): number { |
||||
try { |
||||
return new Blob([JSON.stringify(ev)]).size |
||||
} catch { |
||||
return 2048 |
||||
} |
||||
} |
||||
|
||||
export function getLibraryIndexCacheBudget(): { maxEntries: number; maxBytes: number } { |
||||
if (isImwaldElectron()) { |
||||
return { |
||||
maxEntries: LIBRARY_INDEX_CACHE_DEFAULTS.maxEntriesElectron, |
||||
maxBytes: LIBRARY_INDEX_CACHE_DEFAULTS.maxMbElectron * 1024 * 1024 |
||||
} |
||||
} |
||||
if (isMobileBrowserProfile()) { |
||||
return { |
||||
maxEntries: LIBRARY_INDEX_CACHE_DEFAULTS.maxEntriesMobile, |
||||
maxBytes: LIBRARY_INDEX_CACHE_DEFAULTS.maxMbMobile * 1024 * 1024 |
||||
} |
||||
} |
||||
return { |
||||
maxEntries: LIBRARY_INDEX_CACHE_DEFAULTS.maxEntriesDesktop, |
||||
maxBytes: LIBRARY_INDEX_CACHE_DEFAULTS.maxMbDesktop * 1024 * 1024 |
||||
} |
||||
} |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
import { ExtendedKind } from '@/constants' |
||||
import { |
||||
approxLibraryIndexEventBytes, |
||||
getLibraryIndexCacheBudget |
||||
} from '@/lib/library-index-cache-config' |
||||
import logger from '@/lib/logger' |
||||
import indexedDb from '@/services/indexed-db.service' |
||||
import type { Event } from 'nostr-tools' |
||||
|
||||
export async function loadLibraryIndexCacheEvents(): Promise<Event[]> { |
||||
try { |
||||
return await indexedDb.getLibraryPublicationIndexCacheEvents() |
||||
} catch (e) { |
||||
if (import.meta.env.DEV) { |
||||
logger.warn('[Library] index IDB read failed', { |
||||
message: e instanceof Error ? e.message : String(e) |
||||
}) |
||||
} |
||||
return [] |
||||
} |
||||
} |
||||
|
||||
export async function persistLibraryIndexCacheEvents(events: Event[]): Promise<void> { |
||||
const kind30040 = events.filter((ev) => ev.kind === ExtendedKind.PUBLICATION) |
||||
if (kind30040.length === 0) return |
||||
try { |
||||
const budget = getLibraryIndexCacheBudget() |
||||
await indexedDb.mergeLibraryPublicationIndexCacheEvents(kind30040, budget) |
||||
} catch (e) { |
||||
if (import.meta.env.DEV) { |
||||
logger.warn('[Library] index IDB write failed', { |
||||
message: e instanceof Error ? e.message : String(e) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
export async function getLibraryIndexCacheFootprint(): Promise<{ count: number; bytes: number }> { |
||||
try { |
||||
return await indexedDb.getLibraryPublicationIndexCacheFootprint() |
||||
} catch { |
||||
return { count: 0, bytes: 0 } |
||||
} |
||||
} |
||||
|
||||
export async function clearLibraryIndexIdbCache(): Promise<void> { |
||||
await indexedDb.clearLibraryPublicationIndexCacheStore() |
||||
} |
||||
|
||||
export { approxLibraryIndexEventBytes, getLibraryIndexCacheBudget } |
||||
Loading…
Reference in new issue