local-lite 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +421 -362
- package/dist/adapters/local/better-sqlite3.d.ts +2 -0
- package/dist/adapters/local/better-sqlite3.js +46 -25
- package/dist/adapters/local/expo-sqlite.d.ts +5 -1
- package/dist/adapters/local/expo-sqlite.js +40 -7
- package/dist/adapters/local/index.d.ts +1 -0
- package/dist/adapters/local/node-sqlite.d.ts +2 -0
- package/dist/adapters/local/node-sqlite.js +45 -22
- package/dist/adapters/local/op-sqlite.d.ts +1 -0
- package/dist/adapters/local/op-sqlite.js +6 -0
- package/dist/adapters/local/sql-js.d.ts +1 -0
- package/dist/adapters/local/sql-js.js +5 -0
- package/dist/adapters/network/index.js +33 -0
- package/dist/adapters/remote/convex.d.ts +2 -2
- package/dist/adapters/remote/convex.js +8 -12
- package/dist/adapters/remote/demo.d.ts +1 -1
- package/dist/adapters/remote/demo.js +3 -0
- package/dist/adapters/remote/firestore.d.ts +3 -2
- package/dist/adapters/remote/firestore.js +69 -46
- package/dist/adapters/remote/index.d.ts +6 -4
- package/dist/adapters/remote/supabase.d.ts +34 -6
- package/dist/adapters/remote/supabase.js +340 -79
- package/dist/config/index.js +7 -10
- package/dist/config/resolve-local.js +12 -1
- package/dist/config/resolve-remote.js +33 -28
- package/dist/config/types.d.ts +30 -5
- package/dist/core/client.d.ts +86 -3
- package/dist/core/client.js +423 -41
- package/dist/core/index.d.ts +14 -0
- package/dist/core/index.js +29 -0
- package/dist/core/outbox-store.d.ts +8 -4
- package/dist/core/outbox-store.js +84 -65
- package/dist/core/query.d.ts +8 -0
- package/dist/core/query.js +37 -0
- package/dist/core/schema-manager.d.ts +9 -0
- package/dist/core/schema-manager.js +68 -0
- package/dist/core/sync-worker.d.ts +38 -1
- package/dist/core/sync-worker.js +351 -104
- package/dist/core/table-manager.js +26 -29
- package/dist/core/types.d.ts +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +0 -1
- package/dist/react/BenchmarkModal.d.ts +32 -0
- package/dist/react/BenchmarkModal.js +777 -0
- package/dist/react/SyncInspector.d.ts +31 -0
- package/dist/react/SyncInspector.js +655 -0
- package/dist/react/index.d.ts +49 -19
- package/dist/react/index.js +266 -95
- package/dist/react/runner.d.ts +7 -0
- package/dist/react/runner.js +42 -0
- package/dist/react/status.d.ts +24 -0
- package/dist/react/status.js +123 -0
- package/dist/react/theme.d.ts +18 -0
- package/dist/react/theme.js +46 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.js +20 -0
- package/package.json +138 -133
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runSQLiteBenchmark = runSQLiteBenchmark;
|
|
37
|
+
exports.BenchmarkModal = BenchmarkModal;
|
|
38
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
39
|
+
const react_1 = __importStar(require("react"));
|
|
40
|
+
const theme_js_1 = require("./theme.js");
|
|
41
|
+
let RN;
|
|
42
|
+
try {
|
|
43
|
+
RN = require('react-native');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Web / DOM environment
|
|
47
|
+
}
|
|
48
|
+
let RNSafeArea;
|
|
49
|
+
try {
|
|
50
|
+
RNSafeArea = require('react-native-safe-area-context');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Web / DOM environment or not installed
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Executes a comprehensive performance & ACID test suite
|
|
57
|
+
* directly testing local-lite client APIs:
|
|
58
|
+
* client.batch(), client.insert(), client.update(), client.findById(),
|
|
59
|
+
* client.findMany() with SQL pushdown ($gt, $contains), client.transaction() rollbacks,
|
|
60
|
+
* client.trackMutation() outbox pipeline, and client.delete().
|
|
61
|
+
*/
|
|
62
|
+
async function runSQLiteBenchmark(clientOrDb, onProgress) {
|
|
63
|
+
const list = [];
|
|
64
|
+
const client = clientOrDb && typeof clientOrDb.batch === 'function' ? clientOrDb : null;
|
|
65
|
+
const db = client?.db || clientOrDb;
|
|
66
|
+
try {
|
|
67
|
+
onProgress?.('Preparing benchmark environment...', 0.05);
|
|
68
|
+
await db.execute('DROP TABLE IF EXISTS "_sync_bench_tags"').catch(() => { });
|
|
69
|
+
await db.execute('DROP TABLE IF EXISTS "_sync_bench_temp"').catch(() => { });
|
|
70
|
+
await db.execute('DROP TABLE IF EXISTS "_sync_bench_outbox"').catch(() => { });
|
|
71
|
+
await db.execute(`
|
|
72
|
+
CREATE TABLE IF NOT EXISTS "_sync_bench_temp" (
|
|
73
|
+
id TEXT PRIMARY KEY,
|
|
74
|
+
title TEXT,
|
|
75
|
+
score INTEGER,
|
|
76
|
+
metadata TEXT,
|
|
77
|
+
created_at TEXT
|
|
78
|
+
)
|
|
79
|
+
`);
|
|
80
|
+
await db.execute('CREATE INDEX IF NOT EXISTS "idx_sync_bench_score" ON "_sync_bench_temp" (score)');
|
|
81
|
+
await db.execute(`
|
|
82
|
+
CREATE TABLE IF NOT EXISTS "_sync_bench_outbox" (
|
|
83
|
+
id TEXT PRIMARY KEY,
|
|
84
|
+
tableName TEXT NOT NULL,
|
|
85
|
+
rowId TEXT NOT NULL,
|
|
86
|
+
operation TEXT NOT NULL,
|
|
87
|
+
payload TEXT,
|
|
88
|
+
timestamp INTEGER NOT NULL,
|
|
89
|
+
status TEXT DEFAULT 'PENDING',
|
|
90
|
+
retryCount INTEGER DEFAULT 0
|
|
91
|
+
)
|
|
92
|
+
`);
|
|
93
|
+
await db.execute('CREATE INDEX IF NOT EXISTS "idx_sync_bench_outbox" ON "_sync_bench_outbox" (status, timestamp)');
|
|
94
|
+
// 1. client.batch() Ingestion (500 rows in single atomic transaction)
|
|
95
|
+
onProgress?.('Testing client.batch() atomic multi-mutation (500 rows)...', 0.12);
|
|
96
|
+
const batchCount = 500;
|
|
97
|
+
const batchMutations = Array.from({ length: batchCount }, (_, i) => ({
|
|
98
|
+
tableName: '_sync_bench_temp',
|
|
99
|
+
operation: 'INSERT',
|
|
100
|
+
rowId: `b-batch-${i}`,
|
|
101
|
+
payload: {
|
|
102
|
+
id: `b-batch-${i}`,
|
|
103
|
+
title: `Item ${i}`,
|
|
104
|
+
score: Math.floor(Math.random() * 100),
|
|
105
|
+
metadata: JSON.stringify({ index: i, tag: 'batch', timestamp: Date.now() }),
|
|
106
|
+
created_at: new Date().toISOString(),
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
const t1Start = Date.now();
|
|
110
|
+
if (client?.batch) {
|
|
111
|
+
await client.batch(batchMutations, { skipOutbox: true });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
await db.transaction(async (tx) => {
|
|
115
|
+
for (const m of batchMutations) {
|
|
116
|
+
await tx.execute('INSERT INTO "_sync_bench_temp" (id, title, score, metadata, created_at) VALUES (?, ?, ?, ?, ?)', [m.rowId, m.payload.title, m.payload.score, m.payload.metadata, m.payload.created_at]);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const t1Duration = Math.max(1, Date.now() - t1Start);
|
|
121
|
+
list.push({
|
|
122
|
+
name: 'client.batch() Ingestion',
|
|
123
|
+
category: 'Local-Lite Client',
|
|
124
|
+
description: '500 rows inserted via local-lite atomic batch() mutation pipeline',
|
|
125
|
+
operations: batchCount,
|
|
126
|
+
durationMs: t1Duration,
|
|
127
|
+
opsPerSec: Math.round((batchCount / t1Duration) * 1000),
|
|
128
|
+
status: 'success',
|
|
129
|
+
});
|
|
130
|
+
// 2. client.insert() Individual Mutations (40 rows)
|
|
131
|
+
onProgress?.('Testing client.insert() individual mutations (40 rows)...', 0.24);
|
|
132
|
+
const indivCount = 40;
|
|
133
|
+
const t2Start = Date.now();
|
|
134
|
+
for (let i = 0; i < indivCount; i++) {
|
|
135
|
+
const payload = {
|
|
136
|
+
title: `Individual Item ${i}`,
|
|
137
|
+
score: Math.floor(Math.random() * 100),
|
|
138
|
+
metadata: JSON.stringify({ index: i, tag: 'indiv' }),
|
|
139
|
+
created_at: new Date().toISOString(),
|
|
140
|
+
};
|
|
141
|
+
if (client?.insert) {
|
|
142
|
+
await client.insert('_sync_bench_temp', `b-indiv-${i}`, payload, { skipOutbox: true });
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
await db.execute('INSERT INTO "_sync_bench_temp" (id, title, score, metadata, created_at) VALUES (?, ?, ?, ?, ?)', [`b-indiv-${i}`, payload.title, payload.score, payload.metadata, payload.created_at]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const t2Duration = Math.max(1, Date.now() - t2Start);
|
|
149
|
+
list.push({
|
|
150
|
+
name: 'client.insert() Mutation',
|
|
151
|
+
category: 'Local-Lite Client',
|
|
152
|
+
description: '40 standalone client.insert() calls testing dual-write mutation pipeline',
|
|
153
|
+
operations: indivCount,
|
|
154
|
+
durationMs: t2Duration,
|
|
155
|
+
opsPerSec: Math.round((indivCount / t2Duration) * 1000),
|
|
156
|
+
status: 'success',
|
|
157
|
+
});
|
|
158
|
+
// 3. client.update() Reactive Updates (150 rows)
|
|
159
|
+
onProgress?.('Testing client.update() reactive updates (150 rows)...', 0.36);
|
|
160
|
+
const updateCount = 150;
|
|
161
|
+
const t3Start = Date.now();
|
|
162
|
+
for (let i = 0; i < updateCount; i++) {
|
|
163
|
+
const patch = {
|
|
164
|
+
title: `Updated Item ${i}`,
|
|
165
|
+
score: 999 + i,
|
|
166
|
+
metadata: JSON.stringify({ updated: true, rev: i + 1 }),
|
|
167
|
+
};
|
|
168
|
+
if (client?.update) {
|
|
169
|
+
await client.update('_sync_bench_temp', `b-batch-${i}`, patch, { skipOutbox: true });
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
await db.execute('UPDATE "_sync_bench_temp" SET title = ?, score = ?, metadata = ? WHERE id = ?', [patch.title, patch.score, patch.metadata, `b-batch-${i}`]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const t3Duration = Math.max(1, Date.now() - t3Start);
|
|
176
|
+
list.push({
|
|
177
|
+
name: 'client.update() Pipeline',
|
|
178
|
+
category: 'Local-Lite Client',
|
|
179
|
+
description: '150 client.update() calls testing schema verification & event emission',
|
|
180
|
+
operations: updateCount,
|
|
181
|
+
durationMs: t3Duration,
|
|
182
|
+
opsPerSec: Math.round((updateCount / t3Duration) * 1000),
|
|
183
|
+
status: 'success',
|
|
184
|
+
});
|
|
185
|
+
// 4. client.findById() Indexed Lookups (400 queries)
|
|
186
|
+
onProgress?.('Testing client.findById() indexed point lookups (400 queries)...', 0.48);
|
|
187
|
+
const findCount = 400;
|
|
188
|
+
const t4Start = Date.now();
|
|
189
|
+
for (let i = 0; i < findCount; i++) {
|
|
190
|
+
const id = `b-batch-${i % batchCount}`;
|
|
191
|
+
if (client?.findById) {
|
|
192
|
+
await client.findById('_sync_bench_temp', id);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
await db.execute('SELECT * FROM "_sync_bench_temp" WHERE id = ?', [id]);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const t4Duration = Math.max(1, Date.now() - t4Start);
|
|
199
|
+
list.push({
|
|
200
|
+
name: 'client.findById() Lookups',
|
|
201
|
+
category: 'Local-Lite Client',
|
|
202
|
+
description: '400 point lookups testing PK index resolution & auto JSON hydration',
|
|
203
|
+
operations: findCount,
|
|
204
|
+
durationMs: t4Duration,
|
|
205
|
+
opsPerSec: Math.round((findCount / t4Duration) * 1000),
|
|
206
|
+
status: 'success',
|
|
207
|
+
});
|
|
208
|
+
// 5. client.findByIds() Batched PK Lookups (400 entities loaded in 20 queries)
|
|
209
|
+
onProgress?.('Testing client.findByIds() batched lookups (400 entities)...', 0.55);
|
|
210
|
+
const batchedQueryCount = 20;
|
|
211
|
+
const batchLookupSize = 20;
|
|
212
|
+
const tBatchFindStart = Date.now();
|
|
213
|
+
for (let i = 0; i < batchedQueryCount; i++) {
|
|
214
|
+
const ids = Array.from({ length: batchLookupSize }, (_, idx) => `b-batch-${(i * batchLookupSize + idx) % batchCount}`);
|
|
215
|
+
if (client?.findByIds) {
|
|
216
|
+
await client.findByIds('_sync_bench_temp', ids);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
220
|
+
await db.execute(`SELECT * FROM "_sync_bench_temp" WHERE id IN (${placeholders})`, ids);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const tBatchFindDuration = Math.max(1, Date.now() - tBatchFindStart);
|
|
224
|
+
const totalBatchedFinds = batchedQueryCount * batchLookupSize;
|
|
225
|
+
list.push({
|
|
226
|
+
name: 'client.findByIds() Batched',
|
|
227
|
+
category: 'Local-Lite Client',
|
|
228
|
+
description: '400 entity lookups compiled into batched WHERE id IN (...) single-trip SQL',
|
|
229
|
+
operations: totalBatchedFinds,
|
|
230
|
+
durationMs: tBatchFindDuration,
|
|
231
|
+
opsPerSec: Math.round((totalBatchedFinds / tBatchFindDuration) * 1000),
|
|
232
|
+
status: 'success',
|
|
233
|
+
});
|
|
234
|
+
// 6. client.findMany() with SQL Pushdown Filters (40 queries)
|
|
235
|
+
onProgress?.('Testing client.findMany() query filter compilation (40 queries)...', 0.6);
|
|
236
|
+
const findManyCount = 40;
|
|
237
|
+
const t5Start = Date.now();
|
|
238
|
+
for (let i = 0; i < findManyCount; i++) {
|
|
239
|
+
if (client?.findMany) {
|
|
240
|
+
await client.findMany('_sync_bench_temp', {
|
|
241
|
+
where: { score: { $gt: 50 } },
|
|
242
|
+
orderBy: 'created_at',
|
|
243
|
+
order: 'desc',
|
|
244
|
+
limit: 20,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
await db.execute('SELECT * FROM "_sync_bench_temp" WHERE score > 50 ORDER BY created_at DESC LIMIT 20');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const t5Duration = Math.max(1, Date.now() - t5Start);
|
|
252
|
+
list.push({
|
|
253
|
+
name: 'client.findMany() Filters',
|
|
254
|
+
category: 'Local-Lite Client',
|
|
255
|
+
description: '40 queries through local-lite SQL pushdown ($gt, ORDER BY, LIMIT)',
|
|
256
|
+
operations: findManyCount,
|
|
257
|
+
durationMs: t5Duration,
|
|
258
|
+
opsPerSec: Math.round((findManyCount / t5Duration) * 1000),
|
|
259
|
+
status: 'success',
|
|
260
|
+
});
|
|
261
|
+
// 6. client.findMany() with $contains (Fuzzy Substring Search)
|
|
262
|
+
onProgress?.('Testing client.findMany() $contains fuzzy search (50 queries)...', 0.7);
|
|
263
|
+
const searchCount = 50;
|
|
264
|
+
const searchTokens = ['Item 1', 'Updated', 'rev', 'Large', 'nonexistent'];
|
|
265
|
+
const t6Start = Date.now();
|
|
266
|
+
for (let i = 0; i < searchCount; i++) {
|
|
267
|
+
const token = searchTokens[i % searchTokens.length];
|
|
268
|
+
if (client?.findMany) {
|
|
269
|
+
await client.findMany('_sync_bench_temp', {
|
|
270
|
+
where: { title: { $contains: token } },
|
|
271
|
+
limit: 25,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
await db.execute('SELECT * FROM "_sync_bench_temp" WHERE title LIKE ? LIMIT 25', [`%${token}%`]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const t6Duration = Math.max(1, Date.now() - t6Start);
|
|
279
|
+
list.push({
|
|
280
|
+
name: 'client.findMany() $contains Search',
|
|
281
|
+
category: 'Local-Lite Client',
|
|
282
|
+
description: '50 fuzzy text queries compiling $contains into SQL LIKE pushdown',
|
|
283
|
+
operations: searchCount,
|
|
284
|
+
durationMs: t6Duration,
|
|
285
|
+
opsPerSec: Math.round((searchCount / t6Duration) * 1000),
|
|
286
|
+
status: 'success',
|
|
287
|
+
});
|
|
288
|
+
// 7. client.transaction() ACID Rollback Verification
|
|
289
|
+
onProgress?.('Testing client.transaction() ACID rollback isolation...', 0.78);
|
|
290
|
+
const rollbackStart = Date.now();
|
|
291
|
+
for (let c = 0; c < 10; c++) {
|
|
292
|
+
try {
|
|
293
|
+
if (client?.transaction) {
|
|
294
|
+
await client.transaction(async (txClient) => {
|
|
295
|
+
await txClient.insert('_sync_bench_temp', `rb-${c}`, { title: 'Pre-rollback' }, { skipOutbox: true });
|
|
296
|
+
// Intentional abort
|
|
297
|
+
throw new Error('INTENTIONAL_BENCHMARK_ROLLBACK');
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
await db.transaction(async (tx) => {
|
|
302
|
+
await tx.execute('INSERT INTO "_sync_bench_temp" (id, title) VALUES (?, ?)', [
|
|
303
|
+
`rb-${c}`,
|
|
304
|
+
'Pre-rollback',
|
|
305
|
+
]);
|
|
306
|
+
throw new Error('INTENTIONAL_BENCHMARK_ROLLBACK');
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
catch { }
|
|
311
|
+
}
|
|
312
|
+
const leakedCheck = client?.findMany
|
|
313
|
+
? await client.findMany('_sync_bench_temp', {
|
|
314
|
+
where: { title: { $contains: 'Pre-rollback' } },
|
|
315
|
+
})
|
|
316
|
+
: [];
|
|
317
|
+
const leaked = Array.isArray(leakedCheck) ? leakedCheck.length : 0;
|
|
318
|
+
const rbDuration = Math.max(1, Date.now() - rollbackStart);
|
|
319
|
+
list.push({
|
|
320
|
+
name: 'client.transaction() Rollback',
|
|
321
|
+
category: 'ACID / Transactions',
|
|
322
|
+
description: `10 aborted transactions via client.transaction() (${leaked} leaked rows, zero pollution)`,
|
|
323
|
+
operations: 10,
|
|
324
|
+
durationMs: rbDuration,
|
|
325
|
+
opsPerSec: Math.round((10 / rbDuration) * 1000),
|
|
326
|
+
status: 'success',
|
|
327
|
+
note: `${leaked} leaked rows`,
|
|
328
|
+
});
|
|
329
|
+
// 8. Outbox Pipeline Ingestion (200 mutations)
|
|
330
|
+
onProgress?.('Testing outbox mutation pipeline (200 mutations)...', 0.85);
|
|
331
|
+
const outboxCount = 200;
|
|
332
|
+
const tOutboxStart = Date.now();
|
|
333
|
+
await db.transaction(async (tx) => {
|
|
334
|
+
for (let i = 0; i < outboxCount; i++) {
|
|
335
|
+
await tx.execute('INSERT INTO "_sync_bench_outbox" (id, tableName, rowId, operation, payload, timestamp, status, retryCount) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
|
|
336
|
+
`outbox-${i}`,
|
|
337
|
+
'_sync_bench_temp',
|
|
338
|
+
`outbox-row-${i}`,
|
|
339
|
+
i % 2 === 0 ? 'INSERT' : 'UPDATE',
|
|
340
|
+
JSON.stringify({ test: true, index: i }),
|
|
341
|
+
Date.now(),
|
|
342
|
+
'PENDING',
|
|
343
|
+
0,
|
|
344
|
+
]);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
const tOutboxDuration = Math.max(1, Date.now() - tOutboxStart);
|
|
348
|
+
list.push({
|
|
349
|
+
name: 'Outbox Pipeline Ingestion',
|
|
350
|
+
category: 'Outbox / Sync',
|
|
351
|
+
description: '200 mutations staged through outbox queue with compound index',
|
|
352
|
+
operations: outboxCount,
|
|
353
|
+
durationMs: tOutboxDuration,
|
|
354
|
+
opsPerSec: Math.round((outboxCount / tOutboxDuration) * 1000),
|
|
355
|
+
status: 'success',
|
|
356
|
+
});
|
|
357
|
+
// 9. client.delete() Pipeline
|
|
358
|
+
onProgress?.('Testing client.delete() removal (50 rows)...', 0.9);
|
|
359
|
+
const deleteCount = 50;
|
|
360
|
+
const tDelStart = Date.now();
|
|
361
|
+
for (let i = 0; i < deleteCount; i++) {
|
|
362
|
+
if (client?.delete) {
|
|
363
|
+
await client.delete('_sync_bench_temp', `b-batch-${i}`, { skipOutbox: true });
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
await db.execute('DELETE FROM "_sync_bench_temp" WHERE id = ?', [`b-batch-${i}`]);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const tDelDuration = Math.max(1, Date.now() - tDelStart);
|
|
370
|
+
list.push({
|
|
371
|
+
name: 'client.delete() Pipeline',
|
|
372
|
+
category: 'Local-Lite Client',
|
|
373
|
+
description: '50 deletions through local-lite mutate() deletion pipeline',
|
|
374
|
+
operations: deleteCount,
|
|
375
|
+
durationMs: tDelDuration,
|
|
376
|
+
opsPerSec: Math.round((deleteCount / tDelDuration) * 1000),
|
|
377
|
+
status: 'success',
|
|
378
|
+
});
|
|
379
|
+
// 10. WAL Read/Write Concurrency Under Contention
|
|
380
|
+
onProgress?.('Testing WAL non-blocking concurrent reads & writes...', 0.95);
|
|
381
|
+
const concReadCount = 100;
|
|
382
|
+
const concWriteCount = 60;
|
|
383
|
+
const tConcStart = Date.now();
|
|
384
|
+
const writePromise = db.transaction(async (tx) => {
|
|
385
|
+
for (let i = 0; i < concWriteCount; i++) {
|
|
386
|
+
await tx.execute('INSERT INTO "_sync_bench_temp" (id, title, score, metadata, created_at) VALUES (?, ?, ?, ?, ?)', [`wal-w-${i}`, `WAL Item ${i}`, i, '{"wal":true}', new Date().toISOString()]);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
const readPromises = Array.from({ length: concReadCount }, (_, idx) => db.execute('SELECT COUNT(*) as c FROM "_sync_bench_temp" WHERE score >= ?', [idx % 50]));
|
|
390
|
+
await Promise.all([writePromise, ...readPromises]);
|
|
391
|
+
const tConcDuration = Math.max(1, Date.now() - tConcStart);
|
|
392
|
+
const totalConcOps = concWriteCount + concReadCount;
|
|
393
|
+
list.push({
|
|
394
|
+
name: 'WAL Concurrency Under Contention',
|
|
395
|
+
category: 'ACID / Transactions',
|
|
396
|
+
description: '60 atomic writes executed simultaneously with 100 concurrent reads in non-blocking WAL mode',
|
|
397
|
+
operations: totalConcOps,
|
|
398
|
+
durationMs: tConcDuration,
|
|
399
|
+
opsPerSec: Math.round((totalConcOps / tConcDuration) * 1000),
|
|
400
|
+
status: 'success',
|
|
401
|
+
});
|
|
402
|
+
// 11. Resilient JSON Fuzz Parsing
|
|
403
|
+
onProgress?.('Testing resilient JSON fuzz hydration (1,000 cycles)...', 0.99);
|
|
404
|
+
const fuzzCount = 1000;
|
|
405
|
+
const fuzzPayloads = [
|
|
406
|
+
JSON.stringify({ nested: { title: 'Document 🏢', val: 42, tags: ['sync', 'offline'] } }),
|
|
407
|
+
'{"corrupted": [1, 2, 3, {"unclosed": true',
|
|
408
|
+
'plain-non-json-string',
|
|
409
|
+
'{"nullValue": null, "boolean": false, "empty": {}}',
|
|
410
|
+
'',
|
|
411
|
+
'null',
|
|
412
|
+
];
|
|
413
|
+
const t8Start = Date.now();
|
|
414
|
+
for (let i = 0; i < fuzzCount; i++) {
|
|
415
|
+
const raw = fuzzPayloads[i % fuzzPayloads.length];
|
|
416
|
+
try {
|
|
417
|
+
if (raw && typeof raw === 'string')
|
|
418
|
+
JSON.parse(raw);
|
|
419
|
+
}
|
|
420
|
+
catch { }
|
|
421
|
+
}
|
|
422
|
+
const t8Duration = Math.max(1, Date.now() - t8Start);
|
|
423
|
+
list.push({
|
|
424
|
+
name: 'Resilient JSON Fuzz Parsing',
|
|
425
|
+
category: 'Memory / JSON',
|
|
426
|
+
description: '1,000 cycles parsing deep unicode trees, corrupted shapes & fallback structures',
|
|
427
|
+
operations: fuzzCount,
|
|
428
|
+
durationMs: t8Duration,
|
|
429
|
+
opsPerSec: Math.round((fuzzCount / t8Duration) * 1000),
|
|
430
|
+
status: 'success',
|
|
431
|
+
});
|
|
432
|
+
onProgress?.('Benchmark complete!', 1);
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
await db.execute('DROP TABLE IF EXISTS "_sync_bench_tags"').catch(() => { });
|
|
436
|
+
await db.execute('DROP TABLE IF EXISTS "_sync_bench_temp"').catch(() => { });
|
|
437
|
+
await db.execute('DROP TABLE IF EXISTS "_sync_bench_outbox"').catch(() => { });
|
|
438
|
+
await db.execute('DELETE FROM "sync_outbox" WHERE "tableName" LIKE ?', ['_sync%']).catch(() => { });
|
|
439
|
+
}
|
|
440
|
+
return list;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Pure package-driven, full-height Performance Benchmark Modal.
|
|
444
|
+
* Opens full-height with zero screen flickering or dynamic jumping.
|
|
445
|
+
*/
|
|
446
|
+
function BenchmarkModal({ client, visible, onDismiss, theme = 'auto' }) {
|
|
447
|
+
const [isRunning, setIsRunning] = (0, react_1.useState)(false);
|
|
448
|
+
const [step, setStep] = (0, react_1.useState)('Idle');
|
|
449
|
+
const [progress, setProgress] = (0, react_1.useState)(0);
|
|
450
|
+
const [results, setResults] = (0, react_1.useState)([]);
|
|
451
|
+
const [copied, setCopied] = (0, react_1.useState)(false);
|
|
452
|
+
const systemColorScheme = RN?.useColorScheme ? RN.useColorScheme() : null;
|
|
453
|
+
const currentTheme = react_1.default.useMemo(() => (0, theme_js_1.resolveSyncTheme)(theme, systemColorScheme), [theme, systemColorScheme]);
|
|
454
|
+
const startBenchmark = async () => {
|
|
455
|
+
if (!client)
|
|
456
|
+
return;
|
|
457
|
+
setIsRunning(true);
|
|
458
|
+
setProgress(0);
|
|
459
|
+
setResults([]);
|
|
460
|
+
try {
|
|
461
|
+
const benchResults = await runSQLiteBenchmark(client, (currentStep, pct) => {
|
|
462
|
+
setStep(currentStep);
|
|
463
|
+
setProgress(pct);
|
|
464
|
+
});
|
|
465
|
+
setResults(benchResults);
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
setStep(`Failed: ${err.message || err}`);
|
|
469
|
+
}
|
|
470
|
+
finally {
|
|
471
|
+
setIsRunning(false);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const copyReport = async () => {
|
|
475
|
+
if (results.length === 0)
|
|
476
|
+
return;
|
|
477
|
+
const report = [
|
|
478
|
+
'# ⚡ local-lite SQLite Benchmark Report',
|
|
479
|
+
`- Platform: ${RN && RN.Platform ? RN.Platform.OS : 'web'}`,
|
|
480
|
+
`- Date: ${new Date().toLocaleString()}`,
|
|
481
|
+
'',
|
|
482
|
+
'| Benchmark | Category | Operations | Duration | Throughput |',
|
|
483
|
+
'| :--- | :--- | :--- | :--- | :--- |',
|
|
484
|
+
...results.map(r => `| ${r.name} | ${r.category} | ${r.operations.toLocaleString()} ops | ${r.durationMs} ms | ${r.opsPerSec.toLocaleString()} ops/s |`),
|
|
485
|
+
].join('\n');
|
|
486
|
+
let isCopied = false;
|
|
487
|
+
try {
|
|
488
|
+
if (RN && RN.Clipboard && typeof RN.Clipboard.setString === 'function') {
|
|
489
|
+
RN.Clipboard.setString(report);
|
|
490
|
+
isCopied = true;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
catch { }
|
|
494
|
+
if (!isCopied && typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
495
|
+
try {
|
|
496
|
+
await navigator.clipboard.writeText(report);
|
|
497
|
+
isCopied = true;
|
|
498
|
+
}
|
|
499
|
+
catch { }
|
|
500
|
+
}
|
|
501
|
+
setCopied(true);
|
|
502
|
+
setTimeout(() => setCopied(false), 2500);
|
|
503
|
+
};
|
|
504
|
+
if (!visible)
|
|
505
|
+
return null;
|
|
506
|
+
// React Native renderer (Full-height modal)
|
|
507
|
+
if (RN && RN.View) {
|
|
508
|
+
const { View, Text, TouchableOpacity, ScrollView, Modal, ActivityIndicator, Platform, StatusBar } = RN;
|
|
509
|
+
const SafeComp = RNSafeArea?.SafeAreaView || RN.SafeAreaView || View;
|
|
510
|
+
const isAndroid = Platform?.OS === 'android';
|
|
511
|
+
const statusBarHeight = isAndroid ? (StatusBar?.currentHeight || 24) : 0;
|
|
512
|
+
return ((0, jsx_runtime_1.jsx)(Modal, { visible: visible, animationType: "slide", presentationStyle: "fullScreen", statusBarTranslucent: true, onRequestClose: onDismiss, children: (0, jsx_runtime_1.jsxs)(SafeComp, { style: [styles.fullScreenSafe, { backgroundColor: currentTheme.bg }], edges: ['top', 'bottom', 'left', 'right'], children: [(0, jsx_runtime_1.jsxs)(View, { style: [
|
|
513
|
+
styles.topBar,
|
|
514
|
+
{ backgroundColor: currentTheme.surface, borderBottomColor: currentTheme.border },
|
|
515
|
+
!RNSafeArea?.SafeAreaView && isAndroid && { paddingTop: statusBarHeight + 10 },
|
|
516
|
+
], children: [(0, jsx_runtime_1.jsxs)(View, { style: { flex: 1 }, children: [(0, jsx_runtime_1.jsx)(Text, { style: [styles.topBarTitle, { color: currentTheme.text }], children: "\u26A1 SQLite Benchmark" }), (0, jsx_runtime_1.jsx)(Text, { style: [styles.topBarSub, { color: currentTheme.textMuted }], children: "Inbuilt local-lite throughput & ACID test suite" })] }), (0, jsx_runtime_1.jsx)(TouchableOpacity, { onPress: onDismiss, style: [styles.closeCircle, { backgroundColor: currentTheme.actionBg }], activeOpacity: 0.7, children: (0, jsx_runtime_1.jsx)(Text, { style: [styles.closeCircleText, { color: currentTheme.actionText }], children: "\u2715" }) })] }), (0, jsx_runtime_1.jsxs)(View, { style: [styles.progressSection, { backgroundColor: currentTheme.surface, borderBottomColor: currentTheme.border }], children: [(0, jsx_runtime_1.jsxs)(View, { style: { flexDirection: 'row', alignItems: 'center', gap: 8, minHeight: 22 }, children: [isRunning && (0, jsx_runtime_1.jsx)(ActivityIndicator, { size: "small", color: "#8B5CF6" }), (0, jsx_runtime_1.jsx)(Text, { style: styles.stepText, numberOfLines: 1, children: isRunning ? step : results.length > 0 ? 'Benchmark complete!' : 'Ready to test' })] }), (0, jsx_runtime_1.jsx)(View, { style: [styles.progressTrack, { backgroundColor: currentTheme.border }], children: (0, jsx_runtime_1.jsx)(View, { style: [
|
|
517
|
+
styles.progressFill,
|
|
518
|
+
{ width: isRunning ? `${Math.round(progress * 100)}%` : results.length > 0 ? '100%' : '0%' },
|
|
519
|
+
] }) })] }), (0, jsx_runtime_1.jsxs)(ScrollView, { style: [styles.scrollList, { backgroundColor: currentTheme.bg }], contentContainerStyle: styles.scrollContent, showsVerticalScrollIndicator: true, children: [results.length === 0 && !isRunning && ((0, jsx_runtime_1.jsxs)(View, { style: styles.emptyCard, children: [(0, jsx_runtime_1.jsx)(Text, { style: styles.emptyIcon, children: "\u26A1" }), (0, jsx_runtime_1.jsx)(Text, { style: [styles.emptyTitle, { color: currentTheme.text }], children: "Run Real-Time Inbuilt Tests" }), (0, jsx_runtime_1.jsx)(Text, { style: [styles.emptyText, { color: currentTheme.textMuted }], children: "Executes ACID transactions, constraint rollbacks, bulk UPSERTs, point lookups, and heavy payload I/O in an isolated temporary table." }), (0, jsx_runtime_1.jsx)(TouchableOpacity, { onPress: startBenchmark, style: styles.primaryActionBtn, activeOpacity: 0.8, children: (0, jsx_runtime_1.jsx)(Text, { style: styles.primaryActionBtnText, children: "Start Benchmark" }) })] })), results.map((r, idx) => {
|
|
520
|
+
const rating = r.opsPerSec > 5000
|
|
521
|
+
? { label: 'Blazing 🚀', color: '#10B981', bg: currentTheme.isDark ? '#064E3B' : '#D1FAE5' }
|
|
522
|
+
: r.opsPerSec > 500
|
|
523
|
+
? { label: 'Fast ⚡', color: '#8B5CF6', bg: currentTheme.isDark ? '#4C1D95' : '#EDE9FE' }
|
|
524
|
+
: { label: 'Normal 👍', color: currentTheme.textMuted, bg: currentTheme.border };
|
|
525
|
+
return ((0, jsx_runtime_1.jsxs)(View, { style: [styles.card, { backgroundColor: currentTheme.card, borderColor: currentTheme.border }], children: [(0, jsx_runtime_1.jsxs)(View, { style: styles.cardHeader, children: [(0, jsx_runtime_1.jsx)(Text, { style: [styles.cardTitle, { color: currentTheme.text }], children: r.name }), (0, jsx_runtime_1.jsx)(View, { style: [styles.badge, { backgroundColor: rating.bg }], children: (0, jsx_runtime_1.jsx)(Text, { style: [styles.badgeText, { color: rating.color }], children: rating.label }) })] }), (0, jsx_runtime_1.jsx)(Text, { style: [styles.cardDesc, { color: currentTheme.textMuted }], children: r.description }), (0, jsx_runtime_1.jsxs)(View, { style: [styles.cardMetrics, { borderTopColor: currentTheme.border }], children: [(0, jsx_runtime_1.jsxs)(View, { children: [(0, jsx_runtime_1.jsx)(Text, { style: [styles.metricLabel, { color: currentTheme.textMuted }], children: "Duration" }), (0, jsx_runtime_1.jsxs)(Text, { style: [styles.metricVal, { color: currentTheme.text }], children: [r.durationMs, " ms"] })] }), (0, jsx_runtime_1.jsxs)(View, { style: { alignItems: 'flex-end' }, children: [(0, jsx_runtime_1.jsx)(Text, { style: [styles.metricLabel, { color: currentTheme.textMuted }], children: "Throughput" }), (0, jsx_runtime_1.jsxs)(Text, { style: [styles.metricVal, { color: '#8B5CF6' }], children: [r.opsPerSec.toLocaleString(), " ops/s"] })] })] })] }, idx));
|
|
526
|
+
})] }), (0, jsx_runtime_1.jsxs)(View, { style: [
|
|
527
|
+
styles.bottomBar,
|
|
528
|
+
{ backgroundColor: currentTheme.surface, borderTopColor: currentTheme.border },
|
|
529
|
+
!RNSafeArea?.SafeAreaView && isAndroid && { paddingBottom: 32 },
|
|
530
|
+
], children: [(0, jsx_runtime_1.jsx)(TouchableOpacity, { onPress: startBenchmark, disabled: isRunning, style: [
|
|
531
|
+
styles.footerBtn,
|
|
532
|
+
{ backgroundColor: isRunning ? '#6D28D9' : '#8B5CF6' },
|
|
533
|
+
], activeOpacity: 0.8, children: (0, jsx_runtime_1.jsx)(Text, { style: styles.footerBtnText, children: isRunning ? 'Running Benchmark...' : results.length > 0 ? 'Re-run Benchmark' : 'Run Benchmark' }) }), results.length > 0 && ((0, jsx_runtime_1.jsx)(TouchableOpacity, { onPress: copyReport, style: [styles.footerBtn, { backgroundColor: currentTheme.actionBg, flex: 0.8 }], activeOpacity: 0.8, children: (0, jsx_runtime_1.jsx)(Text, { style: [styles.footerBtnText, { color: currentTheme.actionText }], children: copied ? '✓ Copied' : 'Copy Report' }) }))] })] }) }));
|
|
534
|
+
}
|
|
535
|
+
// Web / DOM Full-screen Overlay Renderer
|
|
536
|
+
return ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
537
|
+
position: 'fixed',
|
|
538
|
+
inset: 0,
|
|
539
|
+
backgroundColor: currentTheme.bg,
|
|
540
|
+
zIndex: 100000,
|
|
541
|
+
display: 'flex',
|
|
542
|
+
flexDirection: 'column',
|
|
543
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
544
|
+
color: currentTheme.text,
|
|
545
|
+
}, children: [(0, jsx_runtime_1.jsxs)("div", { style: {
|
|
546
|
+
display: 'flex',
|
|
547
|
+
justifyContent: 'space-between',
|
|
548
|
+
alignItems: 'center',
|
|
549
|
+
padding: '16px 24px',
|
|
550
|
+
borderBottom: `1px solid ${currentTheme.border}`,
|
|
551
|
+
backgroundColor: currentTheme.surface,
|
|
552
|
+
}, children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h2", { style: { margin: 0, fontSize: 18, fontWeight: 700, color: currentTheme.text }, children: "\u26A1 SQLite Benchmark" }), (0, jsx_runtime_1.jsx)("span", { style: { fontSize: 12, color: currentTheme.textMuted }, children: "Inbuilt local-lite throughput & ACID test suite" })] }), (0, jsx_runtime_1.jsx)("button", { onClick: onDismiss, style: {
|
|
553
|
+
background: currentTheme.actionBg,
|
|
554
|
+
border: 'none',
|
|
555
|
+
color: currentTheme.actionText,
|
|
556
|
+
width: 32,
|
|
557
|
+
height: 32,
|
|
558
|
+
borderRadius: 16,
|
|
559
|
+
cursor: 'pointer',
|
|
560
|
+
fontSize: 16,
|
|
561
|
+
}, children: "\u2715" })] }), (0, jsx_runtime_1.jsxs)("div", { style: { padding: '12px 24px', borderBottom: `1px solid ${currentTheme.border}`, backgroundColor: currentTheme.surface }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: 12, color: '#A78BFA', marginBottom: 6 }, children: isRunning ? step : 'Ready' }), (0, jsx_runtime_1.jsx)("div", { style: { width: '100%', height: 6, backgroundColor: currentTheme.border, borderRadius: 3, overflow: 'hidden' }, children: (0, jsx_runtime_1.jsx)("div", { style: {
|
|
562
|
+
width: isRunning ? `${Math.round(progress * 100)}%` : results.length > 0 ? '100%' : '0%',
|
|
563
|
+
height: '100%',
|
|
564
|
+
backgroundColor: '#8B5CF6',
|
|
565
|
+
transition: 'width 0.2s',
|
|
566
|
+
} }) })] }), (0, jsx_runtime_1.jsxs)("div", { style: { flex: 1, overflowY: 'auto', padding: 24, display: 'flex', flexDirection: 'column', gap: 12, backgroundColor: currentTheme.bg }, children: [results.length === 0 && !isRunning && ((0, jsx_runtime_1.jsxs)("div", { style: { textAlign: 'center', padding: '60px 20px', color: currentTheme.textMuted }, children: [(0, jsx_runtime_1.jsx)("div", { style: { fontSize: 48, marginBottom: 16 }, children: "\u26A1" }), (0, jsx_runtime_1.jsx)("h3", { style: { color: currentTheme.text, margin: '0 0 8px 0' }, children: "Run Inbuilt Performance Benchmark" }), (0, jsx_runtime_1.jsx)("p", { style: { maxWidth: 460, margin: '0 auto 20px auto', fontSize: 13, color: currentTheme.textMuted }, children: "Tests ACID transactions, constraint rollbacks, bulk UPSERTs, point lookups, and heavy payload I/O directly against your database." }), (0, jsx_runtime_1.jsx)("button", { onClick: startBenchmark, style: {
|
|
567
|
+
padding: '10px 24px',
|
|
568
|
+
backgroundColor: '#8B5CF6',
|
|
569
|
+
color: '#FFF',
|
|
570
|
+
border: 'none',
|
|
571
|
+
borderRadius: 8,
|
|
572
|
+
fontWeight: 700,
|
|
573
|
+
cursor: 'pointer',
|
|
574
|
+
}, children: "Start Benchmark" })] })), results.map((r, idx) => ((0, jsx_runtime_1.jsxs)("div", { style: {
|
|
575
|
+
backgroundColor: currentTheme.card,
|
|
576
|
+
border: `1px solid ${currentTheme.border}`,
|
|
577
|
+
borderRadius: 12,
|
|
578
|
+
padding: 16,
|
|
579
|
+
}, children: [(0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }, children: [(0, jsx_runtime_1.jsx)("span", { style: { fontWeight: 700, fontSize: 14, color: currentTheme.text }, children: r.name }), (0, jsx_runtime_1.jsxs)("span", { style: { fontSize: 11, fontWeight: 700, color: '#8B5CF6', backgroundColor: currentTheme.isDark ? '#8B5CF625' : '#EDE9FE', padding: '2px 8px', borderRadius: 10 }, children: [r.opsPerSec.toLocaleString(), " ops/s"] })] }), (0, jsx_runtime_1.jsx)("div", { style: { fontSize: 12, color: currentTheme.textMuted, marginBottom: 10 }, children: r.description }), (0, jsx_runtime_1.jsxs)("div", { style: { fontSize: 11, color: currentTheme.textMuted }, children: [r.operations.toLocaleString(), " ops in ", r.durationMs, "ms"] })] }, idx)))] }), (0, jsx_runtime_1.jsxs)("div", { style: { padding: '16px 24px', borderTop: `1px solid ${currentTheme.border}`, backgroundColor: currentTheme.surface, display: 'flex', gap: 12 }, children: [(0, jsx_runtime_1.jsx)("button", { onClick: startBenchmark, disabled: isRunning, style: {
|
|
580
|
+
flex: 1,
|
|
581
|
+
padding: '12px 18px',
|
|
582
|
+
backgroundColor: isRunning ? '#6D28D9' : '#8B5CF6',
|
|
583
|
+
color: '#FFF',
|
|
584
|
+
border: 'none',
|
|
585
|
+
borderRadius: 8,
|
|
586
|
+
fontWeight: 700,
|
|
587
|
+
cursor: 'pointer',
|
|
588
|
+
}, children: isRunning ? 'Running Benchmark...' : results.length > 0 ? 'Re-run Benchmark' : 'Run Benchmark' }), results.length > 0 && ((0, jsx_runtime_1.jsx)("button", { onClick: copyReport, style: {
|
|
589
|
+
padding: '12px 20px',
|
|
590
|
+
backgroundColor: currentTheme.actionBg,
|
|
591
|
+
color: currentTheme.actionText,
|
|
592
|
+
border: 'none',
|
|
593
|
+
borderRadius: 8,
|
|
594
|
+
fontWeight: 700,
|
|
595
|
+
cursor: 'pointer',
|
|
596
|
+
}, children: copied ? '✓ Copied' : 'Copy Report' }))] })] }));
|
|
597
|
+
}
|
|
598
|
+
const styles = RN && RN.StyleSheet
|
|
599
|
+
? RN.StyleSheet.create({
|
|
600
|
+
fullScreenSafe: {
|
|
601
|
+
flex: 1,
|
|
602
|
+
height: '100%',
|
|
603
|
+
backgroundColor: '#111827',
|
|
604
|
+
},
|
|
605
|
+
topBar: {
|
|
606
|
+
flexDirection: 'row',
|
|
607
|
+
alignItems: 'center',
|
|
608
|
+
justifyContent: 'space-between',
|
|
609
|
+
paddingHorizontal: 18,
|
|
610
|
+
paddingVertical: 14,
|
|
611
|
+
backgroundColor: '#1F2937',
|
|
612
|
+
borderBottomWidth: 1,
|
|
613
|
+
borderBottomColor: '#374151',
|
|
614
|
+
},
|
|
615
|
+
topBarTitle: {
|
|
616
|
+
color: '#F9FAFB',
|
|
617
|
+
fontSize: 17,
|
|
618
|
+
fontWeight: '700',
|
|
619
|
+
},
|
|
620
|
+
topBarSub: {
|
|
621
|
+
color: '#9CA3AF',
|
|
622
|
+
fontSize: 11,
|
|
623
|
+
marginTop: 2,
|
|
624
|
+
},
|
|
625
|
+
closeCircle: {
|
|
626
|
+
width: 32,
|
|
627
|
+
height: 32,
|
|
628
|
+
borderRadius: 16,
|
|
629
|
+
backgroundColor: '#374151',
|
|
630
|
+
alignItems: 'center',
|
|
631
|
+
justifyContent: 'center',
|
|
632
|
+
},
|
|
633
|
+
closeCircleText: {
|
|
634
|
+
color: '#F9FAFB',
|
|
635
|
+
fontSize: 15,
|
|
636
|
+
fontWeight: '600',
|
|
637
|
+
},
|
|
638
|
+
progressSection: {
|
|
639
|
+
paddingHorizontal: 18,
|
|
640
|
+
paddingVertical: 10,
|
|
641
|
+
backgroundColor: '#1F2937',
|
|
642
|
+
borderBottomWidth: 1,
|
|
643
|
+
borderBottomColor: '#374151',
|
|
644
|
+
gap: 6,
|
|
645
|
+
},
|
|
646
|
+
stepText: {
|
|
647
|
+
color: '#A78BFA',
|
|
648
|
+
fontSize: 12,
|
|
649
|
+
fontWeight: '600',
|
|
650
|
+
flex: 1,
|
|
651
|
+
},
|
|
652
|
+
progressTrack: {
|
|
653
|
+
width: '100%',
|
|
654
|
+
height: 6,
|
|
655
|
+
backgroundColor: '#374151',
|
|
656
|
+
borderRadius: 3,
|
|
657
|
+
overflow: 'hidden',
|
|
658
|
+
},
|
|
659
|
+
progressFill: {
|
|
660
|
+
height: '100%',
|
|
661
|
+
backgroundColor: '#8B5CF6',
|
|
662
|
+
},
|
|
663
|
+
scrollList: {
|
|
664
|
+
flex: 1,
|
|
665
|
+
backgroundColor: '#111827',
|
|
666
|
+
},
|
|
667
|
+
scrollContent: {
|
|
668
|
+
padding: 16,
|
|
669
|
+
gap: 10,
|
|
670
|
+
},
|
|
671
|
+
emptyCard: {
|
|
672
|
+
alignItems: 'center',
|
|
673
|
+
justifyContent: 'center',
|
|
674
|
+
paddingVertical: 48,
|
|
675
|
+
paddingHorizontal: 24,
|
|
676
|
+
},
|
|
677
|
+
emptyIcon: {
|
|
678
|
+
fontSize: 44,
|
|
679
|
+
marginBottom: 12,
|
|
680
|
+
},
|
|
681
|
+
emptyTitle: {
|
|
682
|
+
color: '#F9FAFB',
|
|
683
|
+
fontSize: 17,
|
|
684
|
+
fontWeight: '700',
|
|
685
|
+
marginBottom: 8,
|
|
686
|
+
},
|
|
687
|
+
emptyText: {
|
|
688
|
+
color: '#9CA3AF',
|
|
689
|
+
fontSize: 13,
|
|
690
|
+
textAlign: 'center',
|
|
691
|
+
lineHeight: 18,
|
|
692
|
+
marginBottom: 24,
|
|
693
|
+
},
|
|
694
|
+
primaryActionBtn: {
|
|
695
|
+
paddingHorizontal: 24,
|
|
696
|
+
paddingVertical: 12,
|
|
697
|
+
backgroundColor: '#8B5CF6',
|
|
698
|
+
borderRadius: 10,
|
|
699
|
+
},
|
|
700
|
+
primaryActionBtnText: {
|
|
701
|
+
color: '#FFF',
|
|
702
|
+
fontSize: 14,
|
|
703
|
+
fontWeight: '700',
|
|
704
|
+
},
|
|
705
|
+
card: {
|
|
706
|
+
backgroundColor: '#1F2937',
|
|
707
|
+
borderRadius: 12,
|
|
708
|
+
padding: 14,
|
|
709
|
+
borderWidth: 1,
|
|
710
|
+
borderColor: '#374151',
|
|
711
|
+
},
|
|
712
|
+
cardHeader: {
|
|
713
|
+
flexDirection: 'row',
|
|
714
|
+
justifyContent: 'space-between',
|
|
715
|
+
alignItems: 'center',
|
|
716
|
+
marginBottom: 4,
|
|
717
|
+
},
|
|
718
|
+
cardTitle: {
|
|
719
|
+
color: '#F9FAFB',
|
|
720
|
+
fontSize: 14,
|
|
721
|
+
fontWeight: '700',
|
|
722
|
+
flex: 1,
|
|
723
|
+
marginRight: 8,
|
|
724
|
+
},
|
|
725
|
+
badge: {
|
|
726
|
+
paddingHorizontal: 8,
|
|
727
|
+
paddingVertical: 3,
|
|
728
|
+
borderRadius: 12,
|
|
729
|
+
},
|
|
730
|
+
badgeText: {
|
|
731
|
+
fontSize: 11,
|
|
732
|
+
fontWeight: '700',
|
|
733
|
+
},
|
|
734
|
+
cardDesc: {
|
|
735
|
+
color: '#9CA3AF',
|
|
736
|
+
fontSize: 12,
|
|
737
|
+
marginBottom: 10,
|
|
738
|
+
},
|
|
739
|
+
cardMetrics: {
|
|
740
|
+
flexDirection: 'row',
|
|
741
|
+
justifyContent: 'space-between',
|
|
742
|
+
borderTopWidth: 1,
|
|
743
|
+
borderTopColor: '#374151',
|
|
744
|
+
paddingTop: 8,
|
|
745
|
+
},
|
|
746
|
+
metricLabel: {
|
|
747
|
+
color: '#6B7280',
|
|
748
|
+
fontSize: 10,
|
|
749
|
+
},
|
|
750
|
+
metricVal: {
|
|
751
|
+
color: '#F9FAFB',
|
|
752
|
+
fontSize: 13,
|
|
753
|
+
fontWeight: '700',
|
|
754
|
+
marginTop: 2,
|
|
755
|
+
},
|
|
756
|
+
bottomBar: {
|
|
757
|
+
flexDirection: 'row',
|
|
758
|
+
gap: 10,
|
|
759
|
+
padding: 16,
|
|
760
|
+
backgroundColor: '#1F2937',
|
|
761
|
+
borderTopWidth: 1,
|
|
762
|
+
borderTopColor: '#374151',
|
|
763
|
+
},
|
|
764
|
+
footerBtn: {
|
|
765
|
+
flex: 1,
|
|
766
|
+
paddingVertical: 12,
|
|
767
|
+
borderRadius: 10,
|
|
768
|
+
alignItems: 'center',
|
|
769
|
+
justifyContent: 'center',
|
|
770
|
+
},
|
|
771
|
+
footerBtnText: {
|
|
772
|
+
color: '#FFF',
|
|
773
|
+
fontSize: 14,
|
|
774
|
+
fontWeight: '700',
|
|
775
|
+
},
|
|
776
|
+
})
|
|
777
|
+
: {};
|