testing local-first datastores
1import type { BrowserDatastoreAdapter, BrowserStoreBenchmarkResults, BrowserBenchmarkResult, BrowserTestData } from '../harness/browser-types.js';
2
3export async function runBrowserBenchmarks(
4 adapter: BrowserDatastoreAdapter,
5 testData: BrowserTestData,
6 onProgress?: (phase: string, durationMs?: number, failed?: boolean, error?: string) => void
7): Promise<BrowserStoreBenchmarkResults> {
8 const results: BrowserStoreBenchmarkResults = {
9 storeName: adapter.name,
10 userAgent: navigator.userAgent,
11 init: { name: 'init', durationMs: 0 },
12 writes: {
13 allUrls: { name: 'addUrls', durationMs: 0 },
14 allImages: { name: 'addImages', durationMs: 0 },
15 allDocuments: { name: 'addDocuments', durationMs: 0 },
16 allMetadata: { name: 'addMetadata', durationMs: 0 }
17 },
18 reads: {
19 recentUrls: { name: 'getRecentUrls', durationMs: 0 },
20 randomImages: { name: 'getRandomImages', durationMs: 0 },
21 randomDocuments: { name: 'getRandomDocuments', durationMs: 0 }
22 },
23 storage: {
24 totalBytes: 0
25 }
26 };
27
28 try {
29 // Init
30 onProgress?.('Initializing datastore');
31 const initStart = performance.now();
32 await adapter.init();
33 results.init.durationMs = performance.now() - initStart;
34 onProgress?.('init', results.init.durationMs);
35
36 // Write: URLs
37 onProgress?.('Writing URLs');
38 const urlsStart = performance.now();
39 await adapter.addUrls(testData.urls);
40 results.writes.allUrls.durationMs = performance.now() - urlsStart;
41 results.writes.allUrls.itemCount = testData.urls.length;
42 onProgress?.('addUrls', results.writes.allUrls.durationMs);
43
44 // Write: Images
45 onProgress?.('Writing images');
46 const imagesStart = performance.now();
47 let imageBytes = 0;
48 for (const img of testData.images) {
49 await adapter.addImage(img.id, img.data);
50 imageBytes += img.data.byteLength;
51 }
52 results.writes.allImages.durationMs = performance.now() - imagesStart;
53 results.writes.allImages.itemCount = testData.images.length;
54 results.writes.allImages.bytesProcessed = imageBytes;
55 onProgress?.('addImages', results.writes.allImages.durationMs);
56
57 // Write: Documents
58 onProgress?.('Writing documents');
59 const docsStart = performance.now();
60 let docBytes = 0;
61 for (const doc of testData.documents) {
62 await adapter.addDocument(doc.id, doc.content);
63 docBytes += new TextEncoder().encode(doc.content).byteLength;
64 }
65 results.writes.allDocuments.durationMs = performance.now() - docsStart;
66 results.writes.allDocuments.itemCount = testData.documents.length;
67 results.writes.allDocuments.bytesProcessed = docBytes;
68 onProgress?.('addDocuments', results.writes.allDocuments.durationMs);
69
70 // Write: Metadata
71 onProgress?.('Writing metadata');
72 const metaStart = performance.now();
73 await adapter.addMetadata(testData.metadata);
74 results.writes.allMetadata.durationMs = performance.now() - metaStart;
75 results.writes.allMetadata.itemCount = testData.metadata.length;
76 onProgress?.('addMetadata', results.writes.allMetadata.durationMs);
77
78 // Read: Recent URLs
79 onProgress?.('Reading recent URLs');
80 const recentUrlsStart = performance.now();
81 await adapter.getRecentUrls(100);
82 results.reads.recentUrls.durationMs = performance.now() - recentUrlsStart;
83 results.reads.recentUrls.itemCount = 100;
84 onProgress?.('getRecentUrls', results.reads.recentUrls.durationMs);
85
86 // Read: Random images
87 onProgress?.('Reading random images');
88 const imageIds = testData.images.slice(0, 10).map(img => img.id);
89 const randomImagesStart = performance.now();
90 const imgs = await adapter.getImages(imageIds);
91 results.reads.randomImages.durationMs = performance.now() - randomImagesStart;
92 results.reads.randomImages.itemCount = imgs.size;
93 let imgBytes = 0;
94 for (const data of imgs.values()) {
95 imgBytes += data.byteLength;
96 }
97 results.reads.randomImages.bytesProcessed = imgBytes;
98 onProgress?.('getImages', results.reads.randomImages.durationMs);
99
100 // Read: Random documents
101 onProgress?.('Reading random documents');
102 const docIds = testData.documents.slice(0, Math.min(1000, testData.documents.length)).map(doc => doc.id);
103 const randomDocsStart = performance.now();
104 const docs = await adapter.getDocuments(docIds);
105 results.reads.randomDocuments.durationMs = performance.now() - randomDocsStart;
106 results.reads.randomDocuments.itemCount = docs.size;
107 let docSize = 0;
108 for (const content of docs.values()) {
109 docSize += new TextEncoder().encode(content).byteLength;
110 }
111 results.reads.randomDocuments.bytesProcessed = docSize;
112 onProgress?.('getDocuments', results.reads.randomDocuments.durationMs);
113
114 // Storage usage
115 onProgress?.('Measuring storage');
116 results.storage.totalBytes = await adapter.getStorageUsage();
117
118 // Cleanup
119 await adapter.cleanup();
120
121 } catch (error) {
122 console.error('Benchmark error:', error);
123 const err = error as Error;
124 return {
125 ...results,
126 init: { ...results.init, failed: true, error: err.message }
127 };
128 }
129
130 return results;
131}