database-devtools 0.1.1 → 0.1.3

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.
Files changed (62) hide show
  1. package/dist/DatabaseDevTools-CdpQJBqG.d.ts +39 -0
  2. package/dist/DatabaseDevTools-sw1wX5nW.d.cts +39 -0
  3. package/dist/adapter/index.d.cts +3 -3
  4. package/dist/adapter/index.d.ts +3 -3
  5. package/dist/adapter/index.js +0 -1
  6. package/dist/{adapter-BjJm71wW.d.cts → adapter-DQyQ5ycj.d.cts} +1 -1
  7. package/dist/{adapter-DJSjDewg.d.ts → adapter-cIF5P6qI.d.ts} +1 -1
  8. package/dist/adapters/sqlite/index.d.cts +4 -4
  9. package/dist/adapters/sqlite/index.d.ts +4 -4
  10. package/dist/adapters/sqlite/index.js +0 -1
  11. package/dist/chunk-NHHA5CZP.js +2730 -0
  12. package/dist/chunk-NHHA5CZP.js.map +1 -0
  13. package/dist/chunk-S4Z7A4TW.js +116 -0
  14. package/dist/chunk-S4Z7A4TW.js.map +1 -0
  15. package/dist/{chunk-ZS5MZR75.js → chunk-UNVCJMJK.js} +4 -114
  16. package/dist/chunk-UNVCJMJK.js.map +1 -0
  17. package/dist/client/index.d.cts +4 -3
  18. package/dist/client/index.d.ts +4 -3
  19. package/dist/client/index.js +2 -2
  20. package/dist/{createDevToolsServer-B7NQ_5H4.d.cts → createDevToolsServer-Cqb7xzIv.d.cts} +1 -1
  21. package/dist/{createDevToolsServer-DlNMoCJR.d.ts → createDevToolsServer-D3QnP8Yb.d.ts} +1 -1
  22. package/dist/{handleDeviceSnapshot-cYGuitSc.d.cts → handleDeviceSnapshot-CIZ_00fE.d.cts} +2 -50
  23. package/dist/{handleDeviceSnapshot-CR-4QMdk.d.ts → handleDeviceSnapshot-DF9sS1Pm.d.ts} +2 -50
  24. package/dist/index.cjs +30 -43
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +13 -45
  27. package/dist/index.d.ts +13 -45
  28. package/dist/index.js +7 -2741
  29. package/dist/index.js.map +1 -1
  30. package/dist/inspector/index.d.cts +2 -2
  31. package/dist/inspector/index.d.ts +2 -2
  32. package/dist/inspector/index.js +0 -1
  33. package/dist/inspectors/sqlite/index.d.cts +2 -2
  34. package/dist/inspectors/sqlite/index.d.ts +2 -2
  35. package/dist/inspectors/sqlite/index.js +0 -1
  36. package/dist/inspectors/sqlite/index.js.map +1 -1
  37. package/dist/native.cjs +3933 -0
  38. package/dist/native.cjs.map +1 -0
  39. package/dist/native.d.cts +37 -0
  40. package/dist/native.d.ts +37 -0
  41. package/dist/native.js +12 -0
  42. package/dist/native.js.map +1 -0
  43. package/dist/{register-BuicFu9L.d.cts → register-DEZ0kK1p.d.cts} +2 -2
  44. package/dist/{register-aITLN9Q9.d.ts → register-Dawue1_L.d.ts} +2 -2
  45. package/dist/resolveDeviceDatabaseUrl-DhRP-h1Y.d.cts +49 -0
  46. package/dist/resolveDeviceDatabaseUrl-DhRP-h1Y.d.ts +49 -0
  47. package/dist/server/index.d.cts +1 -1
  48. package/dist/server/index.d.ts +1 -1
  49. package/dist/server/index.js +0 -1
  50. package/dist/{snapshot-Dhyecgo2.d.cts → snapshot-Dknp_qP1.d.cts} +1 -1
  51. package/dist/{snapshot-Dhyecgo2.d.ts → snapshot-Dknp_qP1.d.ts} +1 -1
  52. package/dist/types/protocol.js +0 -1
  53. package/dist/{types-CKVRCX-e.d.ts → types-6nco_RRp.d.ts} +2 -2
  54. package/dist/{types-CIA11diJ.d.cts → types-BQHZtFwx.d.cts} +2 -2
  55. package/dist/{types-CF6qEU-_.d.ts → types-DkVlU6t-.d.ts} +2 -2
  56. package/dist/{types-BRLJdBRY.d.cts → types-gT3gDw_T.d.cts} +2 -2
  57. package/dist/web/assets/{index-DQrPRTOy.js → index-DogMAkgu.js} +1 -1
  58. package/dist/web/index.html +1 -1
  59. package/package.json +8 -6
  60. package/dist/chunk-DGUM43GV.js +0 -10
  61. package/dist/chunk-DGUM43GV.js.map +0 -1
  62. package/dist/chunk-ZS5MZR75.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,2748 +1,14 @@
1
- import { createDevToolsClient, formatRefreshErrorMessage, handleDeviceSnapshotUpload } from './chunk-ZS5MZR75.js';
2
- export { createDevToolsClient, fetchDeviceExportDatabase, fetchDeviceExportMeta, fetchDeviceExports, fetchProjectDatabase, fetchProjectDatabaseMeta, fetchProjectDatabases, fetchSnapshot, handleDeviceSnapshotUpload, resolveDeviceExportDatabaseMetaUrl, resolveDeviceExportDatabaseUrl, resolveDeviceExportsUrl, resolveProjectDatabaseMetaUrl, resolveProjectDatabaseUrl, resolveProjectDatabasesUrl } from './chunk-ZS5MZR75.js';
1
+ export { DatabaseDevTools, handleBeginTransaction, handleCommitTransaction, handleExecuteWrite, handleRollbackTransaction, isEditableDatabaseAdapter, isWritableDatabaseAdapter } from './chunk-NHHA5CZP.js';
2
+ import './chunk-O6AO7KHA.js';
3
+ export { fetchDeviceExportDatabase, fetchDeviceExportMeta, fetchDeviceExports, fetchProjectDatabase, fetchProjectDatabaseMeta, fetchProjectDatabases, resolveDeviceExportDatabaseMetaUrl, resolveDeviceExportDatabaseUrl, resolveDeviceExportsUrl, resolveProjectDatabaseMetaUrl, resolveProjectDatabaseUrl, resolveProjectDatabasesUrl } from './chunk-S4Z7A4TW.js';
4
+ export { createDevToolsClient, fetchSnapshot, handleDeviceSnapshotUpload } from './chunk-UNVCJMJK.js';
3
5
  export { createDevToolsServer } from './chunk-TNDOAHT5.js';
4
- import { generateDeviceId } from './chunk-AZECU6D7.js';
5
- import { DevToolsRole, REFRESH_TIMEOUT_MS, buildDevToolsWsUrl, DEFAULT_DEVTOOLS_PORT } from './chunk-CPVVIH5G.js';
6
+ import './chunk-AZECU6D7.js';
6
7
  export { DEFAULT_DEVTOOLS_HOST, DEFAULT_DEVTOOLS_PORT, DEVICE_SNAPSHOT_API_PATH, DEVTOOLS_WS_PATH, DevToolsRole, HEARTBEAT_INTERVAL_MS, HEARTBEAT_TIMEOUT_MS, MAX_SNAPSHOT_BYTES, MessageType, PROJECT_DATABASES_API_PATH, PROJECT_DATABASE_API_PATH, PROJECT_DATABASE_META_API_PATH, PROJECT_DEVICE_EXPORTS_API_PATH, REFRESH_TIMEOUT_MS, SNAPSHOT_API_PATH, SYNC_TIMEOUT_MS, buildDevToolsHttpUrl, buildDevToolsWsUrl, buildDeviceSnapshotUrl, buildSnapshotUrl, createMessage, isBroadcastMessage, isClientMessage, isDeviceStatusMessage, isExportSnapshotErrorMessage, isExportSnapshotRequestMessage, isPingMessage, isPongMessage, isRefreshErrorMessage, isRefreshRequestMessage, isRefreshStatusMessage, isRegisterMessage, isServerMessage, isSnapshotReadyMessage, isSnapshotUploadRequestedMessage, wsUrlToHttpUrl } from './chunk-CPVVIH5G.js';
7
- import { resolveAdapter } from './chunk-BLOSGEQV.js';
8
8
  export { AdapterResolutionError, registerBuiltInAdapters, resolveAdapter } from './chunk-BLOSGEQV.js';
9
- import './chunk-MXVIGMVT.js';
10
- export { InspectorRegistry, createInspectorForSnapshot, getInspectorRegistry, resetInspectorRegistry } from './chunk-2JGL2JVD.js';
11
- import './chunk-O6AO7KHA.js';
12
- import { detectExpoSqliteInspectable } from './chunk-I65IM47D.js';
13
9
  export { AdapterRegistry, DATABASE_DEVTOOLS_KIND, createExpoSqliteAdapter, detectExpoSqlite, getAdapterRegistry, readDatabaseKindMarker, registerSqliteAdapter, resetAdapterRegistry, sqliteAdapterDefinition } from './chunk-I65IM47D.js';
14
10
  export { SNAPSHOT_KIND_HEADER, SNAPSHOT_MIME_HEADER, SNAPSHOT_NAME_HEADER, SQLITE_SNAPSHOT_MIME_TYPE } from './chunk-HIDZTOJ3.js';
15
- import { __require } from './chunk-DGUM43GV.js';
16
- import { StyleSheet, View, useWindowDimensions, PanResponder, Pressable, Modal, Text, ActivityIndicator, ScrollView, TextInput, Platform, FlatList } from 'react-native';
17
- import { createContext, useState, useMemo, useRef, useCallback, useEffect, useContext } from 'react';
18
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
19
- import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
20
-
21
- // src/types/adapter.ts
22
- function isWritableDatabaseAdapter(adapter) {
23
- if (!adapter?.capabilities.transactionalWrites) {
24
- return false;
25
- }
26
- const candidate = adapter;
27
- return typeof candidate.beginTransaction === "function" && typeof candidate.commitTransaction === "function" && typeof candidate.rollbackTransaction === "function" && typeof candidate.executeWrite === "function";
28
- }
29
- var isEditableDatabaseAdapter = isWritableDatabaseAdapter;
30
-
31
- // src/client/handleWriteOperations.ts
32
- async function handleBeginTransaction(adapter, message, report) {
33
- if (!isWritableDatabaseAdapter(adapter)) {
34
- report({
35
- transactionId: message.transactionId,
36
- action: "begin",
37
- ok: false,
38
- message: "Database adapter does not support writes"
39
- });
40
- return;
41
- }
42
- try {
43
- await adapter.beginTransaction();
44
- report({
45
- transactionId: message.transactionId,
46
- action: "begin",
47
- ok: true
48
- });
49
- } catch (error) {
50
- report({
51
- transactionId: message.transactionId,
52
- action: "begin",
53
- ok: false,
54
- message: error instanceof Error ? error.message : "Failed to begin transaction"
55
- });
56
- }
57
- }
58
- async function handleCommitTransaction(adapter, message, report) {
59
- if (!isWritableDatabaseAdapter(adapter)) {
60
- report({
61
- transactionId: message.transactionId,
62
- action: "commit",
63
- ok: false,
64
- message: "Database adapter does not support writes"
65
- });
66
- return;
67
- }
68
- try {
69
- await adapter.commitTransaction();
70
- report({
71
- transactionId: message.transactionId,
72
- action: "commit",
73
- ok: true
74
- });
75
- } catch (error) {
76
- report({
77
- transactionId: message.transactionId,
78
- action: "commit",
79
- ok: false,
80
- message: error instanceof Error ? error.message : "Failed to commit transaction"
81
- });
82
- }
83
- }
84
- async function handleRollbackTransaction(adapter, message, report) {
85
- if (!isWritableDatabaseAdapter(adapter)) {
86
- report({
87
- transactionId: message.transactionId,
88
- action: "rollback",
89
- ok: false,
90
- message: "Database adapter does not support writes"
91
- });
92
- return;
93
- }
94
- try {
95
- await adapter.rollbackTransaction();
96
- report({
97
- transactionId: message.transactionId,
98
- action: "rollback",
99
- ok: true
100
- });
101
- } catch (error) {
102
- report({
103
- transactionId: message.transactionId,
104
- action: "rollback",
105
- ok: false,
106
- message: error instanceof Error ? error.message : "Failed to roll back transaction"
107
- });
108
- }
109
- }
110
- async function handleExecuteWrite(adapter, message, report) {
111
- if (!isWritableDatabaseAdapter(adapter)) {
112
- report({
113
- writeId: message.writeId,
114
- transactionId: message.transactionId,
115
- ok: false,
116
- message: "Database adapter does not support writes"
117
- });
118
- return;
119
- }
120
- try {
121
- const result = await adapter.executeWrite(message.operation);
122
- report({
123
- writeId: message.writeId,
124
- transactionId: message.transactionId,
125
- ok: true,
126
- rowsAffected: result.rowsAffected
127
- });
128
- } catch (error) {
129
- report({
130
- writeId: message.writeId,
131
- transactionId: message.transactionId,
132
- ok: false,
133
- message: error instanceof Error ? error.message : "Write operation failed"
134
- });
135
- }
136
- }
137
-
138
- // src/utils/isDevToolsEnabled.ts
139
- function isDevToolsEnabled(enabled) {
140
- if (enabled === false) {
141
- return false;
142
- }
143
- if (enabled === true) {
144
- return true;
145
- }
146
- return typeof __DEV__ !== "undefined" && __DEV__;
147
- }
148
- var DevToolsContext = createContext(null);
149
- function useDevTools() {
150
- const context = useContext(DevToolsContext);
151
- if (!context) {
152
- throw new Error("useDevTools must be used within DevToolsProvider");
153
- }
154
- return context;
155
- }
156
-
157
- // src/utils/resolveDevToolsHost.ts
158
- var ANDROID_LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
159
- var ANDROID_EMULATOR_HOST = "10.0.2.2";
160
- function isAndroidLoopbackHost(host) {
161
- return ANDROID_LOOPBACK_HOSTS.has(host.toLowerCase());
162
- }
163
- function readMetroDevHostFromConstants(constants) {
164
- const hostUri = constants?.expoConfig?.hostUri ?? constants?.expoGoConfig?.debuggerHost ?? constants?.manifest2?.extra?.expoGo?.debuggerHost ?? constants?.manifest?.debuggerHost;
165
- if (typeof hostUri !== "string" || hostUri.length === 0) {
166
- return void 0;
167
- }
168
- return hostUri.split(":")[0];
169
- }
170
- function resolveDevToolsHostFromInputs(platformOs, metroHost) {
171
- if (platformOs === "android") {
172
- if (metroHost && !isAndroidLoopbackHost(metroHost)) {
173
- return metroHost;
174
- }
175
- return ANDROID_EMULATOR_HOST;
176
- }
177
- if (metroHost) {
178
- return metroHost;
179
- }
180
- return "localhost";
181
- }
182
- function getConnectionHintForPlatform(platformOs, serverUrl) {
183
- try {
184
- const parsed = new URL(serverUrl);
185
- const host = parsed.hostname.toLowerCase();
186
- if (platformOs === "android" && isAndroidLoopbackHost(host)) {
187
- return `On Android, "${host}" refers to the device itself. Use ${ANDROID_EMULATOR_HOST} (emulator) or your PC's LAN IP (physical device).`;
188
- }
189
- if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
190
- return "Server URL must start with ws:// or wss://";
191
- }
192
- } catch {
193
- return "Invalid WebSocket URL.";
194
- }
195
- return null;
196
- }
197
- function getPlatformOs() {
198
- try {
199
- const { Platform: Platform2 } = __require("react-native");
200
- return Platform2.OS;
201
- } catch {
202
- return "web";
203
- }
204
- }
205
- function readMetroDevHostRuntime() {
206
- try {
207
- const mod = __require("expo-constants");
208
- return readMetroDevHostFromConstants(mod.default);
209
- } catch {
210
- return void 0;
211
- }
212
- }
213
- function resolveDevToolsHost() {
214
- return resolveDevToolsHostFromInputs(getPlatformOs(), readMetroDevHostRuntime());
215
- }
216
- function getConnectionHint(serverUrl) {
217
- return getConnectionHintForPlatform(getPlatformOs(), serverUrl);
218
- }
219
- function getExpoConstants() {
220
- try {
221
- const mod = __require("expo-constants");
222
- return mod.default;
223
- } catch {
224
- return void 0;
225
- }
226
- }
227
- function resolveDeviceMetadata() {
228
- const constants = getExpoConstants();
229
- const config = constants?.expoConfig;
230
- const metadata = {
231
- platform: Platform.OS,
232
- appName: config?.name ?? "React Native App"
233
- };
234
- if (config?.version) {
235
- metadata.appVersion = config.version;
236
- }
237
- const bundleId = config?.ios?.bundleIdentifier ?? config?.android?.package;
238
- if (bundleId) {
239
- metadata.bundleId = bundleId;
240
- }
241
- return metadata;
242
- }
243
-
244
- // src/utils/resolveServerUrl.ts
245
- function normalizeServerUrl(url) {
246
- const trimmed = url.trim();
247
- if (trimmed.startsWith("ws://") || trimmed.startsWith("wss://")) {
248
- return trimmed;
249
- }
250
- return buildDevToolsWsUrl(trimmed.replace(/^https?:\/\//, ""), DEFAULT_DEVTOOLS_PORT);
251
- }
252
- function resolveServerUrl(serverUrl) {
253
- if (serverUrl) {
254
- return normalizeServerUrl(serverUrl);
255
- }
256
- const envUrl = typeof process !== "undefined" ? process.env.EXPO_PUBLIC_DATABASE_DEVTOOLS_URL : void 0;
257
- if (envUrl) {
258
- return normalizeServerUrl(envUrl);
259
- }
260
- return buildDevToolsWsUrl(resolveDevToolsHost(), DEFAULT_DEVTOOLS_PORT);
261
- }
262
-
263
- // src/utils/persistDeviceId.ts
264
- var PERSISTED_DEVICE_ID_STORAGE_KEY = "@database-devtools/device-id";
265
- var memoryDeviceId = null;
266
- function createAsyncStorageDeviceIdStore() {
267
- try {
268
- const AsyncStorage = __require("@react-native-async-storage/async-storage").default;
269
- return AsyncStorage;
270
- } catch {
271
- return null;
272
- }
273
- }
274
- async function loadOrCreateDeviceId(storage = createAsyncStorageDeviceIdStore()) {
275
- if (storage) {
276
- const stored = await storage.getItem(PERSISTED_DEVICE_ID_STORAGE_KEY);
277
- if (stored) {
278
- return stored;
279
- }
280
- const created = generateDeviceId();
281
- await storage.setItem(PERSISTED_DEVICE_ID_STORAGE_KEY, created);
282
- return created;
283
- }
284
- if (!memoryDeviceId) {
285
- memoryDeviceId = generateDeviceId();
286
- }
287
- return memoryDeviceId;
288
- }
289
-
290
- // src/utils/sqlSafety.ts
291
- var BLOCKED_KEYWORDS = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|REPLACE|ATTACH|DETACH|REINDEX|VACUUM|TRUNCATE)\b/i;
292
- function validateReadOnlySql(sql) {
293
- const trimmed = sql.trim();
294
- if (!trimmed) {
295
- throw new Error("SQL query is empty");
296
- }
297
- if (BLOCKED_KEYWORDS.test(trimmed)) {
298
- throw new Error(
299
- "DevTools is read-only; only SELECT, PRAGMA, EXPLAIN, and WITH queries are allowed"
300
- );
301
- }
302
- const statements = trimmed.split(";").map((statement) => statement.trim()).filter(Boolean);
303
- for (const statement of statements) {
304
- const leading = statement.replace(/^\s*(--[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*/g, "").trim();
305
- const keyword = leading.split(/\s+/)[0]?.toUpperCase();
306
- if (!keyword) {
307
- continue;
308
- }
309
- if (keyword !== "SELECT" && keyword !== "PRAGMA" && keyword !== "EXPLAIN" && keyword !== "WITH") {
310
- throw new Error(`DevTools is read-only; "${keyword}" statements are not allowed`);
311
- }
312
- }
313
- }
314
-
315
- // src/mobile/sqliteTableQuery.ts
316
- function quoteIdentifier(name) {
317
- return `"${name.replace(/"/g, '""')}"`;
318
- }
319
- function buildSearchClause(columns, search) {
320
- const term = search?.trim();
321
- if (!term || columns.length === 0) {
322
- return { clause: "", params: [] };
323
- }
324
- const pattern = `%${term}%`;
325
- const conditions = columns.map(
326
- (column) => `CAST(${quoteIdentifier(column.name)} AS TEXT) LIKE ?`
327
- );
328
- return {
329
- clause: ` WHERE (${conditions.join(" OR ")})`,
330
- params: columns.map(() => pattern)
331
- };
332
- }
333
- function resolveSortColumn(columns, sortColumn) {
334
- if (!sortColumn) {
335
- return null;
336
- }
337
- return columns.some((column) => column.name === sortColumn) ? sortColumn : null;
338
- }
339
-
340
- // src/mobile/createExpoSqliteInspector.ts
341
- function normalizeCell(cell) {
342
- if (cell === null || cell === void 0) {
343
- return null;
344
- }
345
- if (typeof cell === "number" || typeof cell === "string") {
346
- return cell;
347
- }
348
- if (typeof cell === "boolean") {
349
- return cell ? 1 : 0;
350
- }
351
- if (cell instanceof Uint8Array) {
352
- return `[BLOB ${cell.byteLength} bytes]`;
353
- }
354
- return String(cell);
355
- }
356
- function objectRowsToMatrix(rows) {
357
- if (rows.length === 0) {
358
- return { columns: [], matrix: [] };
359
- }
360
- const columns = Object.keys(rows[0]);
361
- return {
362
- columns,
363
- matrix: rows.map((row) => columns.map((column) => normalizeCell(row[column])))
364
- };
365
- }
366
- function createExpoSqliteInspector(database) {
367
- let operationQueue = Promise.resolve();
368
- const enqueue = async (operation) => {
369
- const next = operationQueue.then(operation);
370
- operationQueue = next.then(
371
- () => void 0,
372
- () => void 0
373
- );
374
- return next;
375
- };
376
- const listTablesInternal = async () => {
377
- const masterRows = await database.getAllAsync(
378
- `SELECT name FROM sqlite_master
379
- WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
380
- ORDER BY name`
381
- );
382
- const tables = [];
383
- for (const row of masterRows) {
384
- const countRow = await database.getFirstAsync(
385
- `SELECT COUNT(*) AS count FROM ${quoteIdentifier(row.name)}`
386
- );
387
- tables.push({
388
- name: row.name,
389
- rowCount: Number(countRow?.count ?? 0)
390
- });
391
- }
392
- return tables;
393
- };
394
- const getTableColumnsInternal = async (table) => {
395
- const rows = await database.getAllAsync(
396
- `PRAGMA table_info(${quoteIdentifier(table)})`
397
- );
398
- return rows.map((row) => ({
399
- name: row.name,
400
- type: row.type || "ANY",
401
- notNull: row.notnull === 1,
402
- pk: row.pk === 1,
403
- defaultValue: row.dflt_value === null || row.dflt_value === void 0 ? null : String(row.dflt_value)
404
- }));
405
- };
406
- return {
407
- listTables: () => enqueue(listTablesInternal),
408
- getSchema: () => enqueue(async () => {
409
- const tables = await listTablesInternal();
410
- return Promise.all(
411
- tables.map(async (table) => ({
412
- name: table.name,
413
- columns: await getTableColumnsInternal(table.name)
414
- }))
415
- );
416
- }),
417
- getTableColumns: (table) => enqueue(() => getTableColumnsInternal(table)),
418
- getDatabaseInfo: () => enqueue(async () => {
419
- const tables = await listTablesInternal();
420
- const versionRow = await database.getFirstAsync(
421
- "SELECT sqlite_version() AS version"
422
- );
423
- const pageRow = await database.getFirstAsync(
424
- "SELECT page_size, page_count FROM pragma_page_size(), pragma_page_count()"
425
- );
426
- const pageSize = Number(pageRow?.page_size ?? 0);
427
- const pageCount = Number(pageRow?.page_count ?? 0);
428
- return {
429
- name: database.databasePath.split("/").pop() ?? database.databasePath,
430
- path: database.databasePath,
431
- tableCount: tables.length,
432
- sqliteVersion: versionRow?.version ?? "unknown",
433
- pageSize,
434
- pageCount,
435
- estimatedSizeBytes: pageSize * pageCount
436
- };
437
- }),
438
- fetchTablePage: (request) => enqueue(async () => {
439
- const columns = await getTableColumnsInternal(request.table);
440
- const { clause, params: searchParams } = buildSearchClause(columns, request.search);
441
- const quotedTable = quoteIdentifier(request.table);
442
- const countRow = await database.getFirstAsync(
443
- `SELECT COUNT(*) AS count FROM ${quotedTable}${clause}`,
444
- searchParams
445
- );
446
- const totalCount = Number(countRow?.count ?? 0);
447
- const pageSize = Math.max(1, request.pageSize);
448
- const page = Math.max(1, request.page);
449
- const offset = (page - 1) * pageSize;
450
- const sortColumn = resolveSortColumn(columns, request.sortColumn);
451
- const orderClause = sortColumn ? ` ORDER BY ${quoteIdentifier(sortColumn)} ${request.sortDir === "desc" ? "DESC" : "ASC"}` : "";
452
- const rows = await database.getAllAsync(
453
- `SELECT * FROM ${quotedTable}${clause}${orderClause} LIMIT ? OFFSET ?`,
454
- [...searchParams, pageSize, offset]
455
- );
456
- const { columns: resultColumns, matrix } = objectRowsToMatrix(rows);
457
- return {
458
- columns: resultColumns,
459
- rows: matrix,
460
- totalCount,
461
- page,
462
- pageSize
463
- };
464
- }),
465
- executeQuery: (sql) => enqueue(async () => {
466
- validateReadOnlySql(sql);
467
- const started = Date.now();
468
- const rows = await database.getAllAsync(sql);
469
- const { columns, matrix } = objectRowsToMatrix(rows);
470
- return {
471
- columns,
472
- rows: matrix,
473
- rowCount: matrix.length,
474
- durationMs: Date.now() - started
475
- };
476
- })
477
- };
478
- }
479
- function DevToolsProvider({
480
- children,
481
- database,
482
- type,
483
- adapter: explicitAdapter,
484
- serverUrl: initialServerUrl,
485
- onConnectionStateChange
486
- }) {
487
- const [connectionState, setConnectionState] = useState("connecting");
488
- const [connectionError, setConnectionError] = useState(null);
489
- const [serverUrl, setServerUrl] = useState(() => resolveServerUrl(initialServerUrl));
490
- const [deviceId, setDeviceId] = useState();
491
- const [deviceIdReady, setDeviceIdReady] = useState(false);
492
- const [settingsVisible, setSettingsVisible] = useState(false);
493
- const [launcherVisible, setLauncherVisible] = useState(false);
494
- const [explorerVisible, setExplorerVisible] = useState(false);
495
- const [mobileInspector, setMobileInspector] = useState(null);
496
- const [resolvedAdapter, setResolvedAdapter] = useState(explicitAdapter);
497
- const [adapterError, setAdapterError] = useState(null);
498
- const [exportState, setExportState] = useState("idle");
499
- const [exportError, setExportError] = useState(null);
500
- const metadata = useMemo(() => resolveDeviceMetadata(), []);
501
- const clientRef = useRef(null);
502
- const onConnectionStateChangeRef = useRef(onConnectionStateChange);
503
- const databaseRef = useRef(resolvedAdapter);
504
- const rollbackOpenTransactionRef = useRef(null);
505
- const exportWaitersRef = useRef(null);
506
- const exportTimeoutRef = useRef(null);
507
- const exportSuccessTimeoutRef = useRef(null);
508
- const clearExportWaiters = useCallback(() => {
509
- if (exportTimeoutRef.current) {
510
- clearTimeout(exportTimeoutRef.current);
511
- exportTimeoutRef.current = null;
512
- }
513
- exportWaitersRef.current = null;
514
- }, []);
515
- const resetExportSuccessLater = useCallback(() => {
516
- if (exportSuccessTimeoutRef.current) {
517
- clearTimeout(exportSuccessTimeoutRef.current);
518
- }
519
- exportSuccessTimeoutRef.current = setTimeout(() => {
520
- setExportState((current) => current === "success" ? "idle" : current);
521
- exportSuccessTimeoutRef.current = null;
522
- }, 3e3);
523
- }, []);
524
- const finishExportSuccess = useCallback(() => {
525
- clearExportWaiters();
526
- setExportState("success");
527
- setExportError(null);
528
- resetExportSuccessLater();
529
- }, [clearExportWaiters, resetExportSuccessLater]);
530
- const finishExportFailure = useCallback(
531
- (message) => {
532
- clearExportWaiters();
533
- setExportState("error");
534
- setExportError(message);
535
- },
536
- [clearExportWaiters]
537
- );
538
- useEffect(() => {
539
- onConnectionStateChangeRef.current = onConnectionStateChange;
540
- }, [onConnectionStateChange]);
541
- useEffect(() => {
542
- let cancelled = false;
543
- async function resolveDatabaseAdapter() {
544
- if (explicitAdapter) {
545
- setResolvedAdapter(explicitAdapter);
546
- setAdapterError(null);
547
- return;
548
- }
549
- if (!database) {
550
- setResolvedAdapter(void 0);
551
- setAdapterError(null);
552
- return;
553
- }
554
- try {
555
- const nextAdapter = await resolveAdapter(database, { type });
556
- if (!cancelled) {
557
- setResolvedAdapter(nextAdapter);
558
- setAdapterError(null);
559
- }
560
- } catch (error) {
561
- if (!cancelled) {
562
- setResolvedAdapter(void 0);
563
- setAdapterError(error instanceof Error ? error.message : "Failed to resolve database adapter");
564
- }
565
- }
566
- }
567
- void resolveDatabaseAdapter();
568
- return () => {
569
- cancelled = true;
570
- };
571
- }, [database, type, explicitAdapter]);
572
- useEffect(() => {
573
- if (detectExpoSqliteInspectable(database)) {
574
- setMobileInspector(createExpoSqliteInspector(database));
575
- return;
576
- }
577
- setMobileInspector(null);
578
- }, [database]);
579
- useEffect(() => {
580
- databaseRef.current = resolvedAdapter;
581
- }, [resolvedAdapter]);
582
- useEffect(() => {
583
- rollbackOpenTransactionRef.current = async () => {
584
- const adapter = databaseRef.current;
585
- if (!isWritableDatabaseAdapter(adapter)) {
586
- return;
587
- }
588
- try {
589
- await adapter.rollbackTransaction();
590
- } catch {
591
- }
592
- };
593
- }, [resolvedAdapter]);
594
- useEffect(() => {
595
- let cancelled = false;
596
- void loadOrCreateDeviceId().then((persistedId) => {
597
- if (!cancelled) {
598
- setDeviceId(persistedId);
599
- setDeviceIdReady(true);
600
- }
601
- });
602
- return () => {
603
- cancelled = true;
604
- };
605
- }, []);
606
- useEffect(() => {
607
- if (!deviceIdReady || !deviceId) {
608
- return;
609
- }
610
- const client = createDevToolsClient({
611
- serverUrl,
612
- role: DevToolsRole.MOBILE,
613
- deviceId,
614
- metadata,
615
- onConnect: () => {
616
- setConnectionError(null);
617
- },
618
- onConnectionStateChange: (state) => {
619
- setConnectionState(state);
620
- onConnectionStateChangeRef.current?.(state);
621
- },
622
- onError: (error) => {
623
- setConnectionError(error.message);
624
- },
625
- onSnapshotUploadRequested: async (message) => {
626
- try {
627
- await handleDeviceSnapshotUpload(databaseRef.current, message, {
628
- hubServerUrl: serverUrl,
629
- deviceId: clientRef.current?.getDeviceId()
630
- });
631
- if (exportWaitersRef.current) {
632
- exportWaitersRef.current.resolve();
633
- clearExportWaiters();
634
- }
635
- } catch (error) {
636
- const errorMessage = error instanceof Error ? error.message : "Database export or upload failed";
637
- if (exportWaitersRef.current) {
638
- exportWaitersRef.current.reject(new Error(errorMessage));
639
- clearExportWaiters();
640
- return;
641
- }
642
- console.error("[database-devtools] Snapshot export or upload failed:", errorMessage);
643
- }
644
- },
645
- onExportSnapshotError: (message) => {
646
- const errorMessage = formatRefreshErrorMessage(message.code, message.message);
647
- if (exportWaitersRef.current) {
648
- exportWaitersRef.current.reject(new Error(errorMessage));
649
- clearExportWaiters();
650
- }
651
- },
652
- onBeginTransaction: async (message) => {
653
- const client2 = clientRef.current;
654
- if (!client2) {
655
- return;
656
- }
657
- await handleBeginTransaction(databaseRef.current, message, (ack) => {
658
- client2.sendTransactionAck(ack);
659
- });
660
- },
661
- onCommitTransaction: async (message) => {
662
- const client2 = clientRef.current;
663
- if (!client2) {
664
- return;
665
- }
666
- await handleCommitTransaction(databaseRef.current, message, (ack) => {
667
- client2.sendTransactionAck(ack);
668
- });
669
- },
670
- onRollbackTransaction: async (message) => {
671
- const client2 = clientRef.current;
672
- if (!client2) {
673
- return;
674
- }
675
- await handleRollbackTransaction(databaseRef.current, message, (ack) => {
676
- client2.sendTransactionAck(ack);
677
- });
678
- },
679
- onExecuteWrite: async (message) => {
680
- const client2 = clientRef.current;
681
- if (!client2) {
682
- return;
683
- }
684
- await handleExecuteWrite(databaseRef.current, message, (ack) => {
685
- client2.sendWriteAck(ack);
686
- });
687
- }
688
- });
689
- clientRef.current = client;
690
- setDeviceId(client.getDeviceId());
691
- client.connect();
692
- return () => {
693
- void rollbackOpenTransactionRef.current?.();
694
- client.disconnect();
695
- clientRef.current = null;
696
- clearExportWaiters();
697
- if (exportSuccessTimeoutRef.current) {
698
- clearTimeout(exportSuccessTimeoutRef.current);
699
- exportSuccessTimeoutRef.current = null;
700
- }
701
- };
702
- }, [serverUrl, metadata, clearExportWaiters, deviceIdReady, deviceId]);
703
- const openLauncher = useCallback(() => {
704
- setLauncherVisible(true);
705
- }, []);
706
- const closeLauncher = useCallback(() => {
707
- setLauncherVisible(false);
708
- }, []);
709
- const openExplorer = useCallback(() => {
710
- setLauncherVisible(false);
711
- setExplorerVisible(true);
712
- }, []);
713
- const closeExplorer = useCallback(() => {
714
- setExplorerVisible(false);
715
- }, []);
716
- const openSettings = useCallback(() => {
717
- setLauncherVisible(false);
718
- setSettingsVisible(true);
719
- }, []);
720
- const closeSettings = useCallback(() => {
721
- setSettingsVisible(false);
722
- }, []);
723
- const reconnect = useCallback((url) => {
724
- setConnectionError(null);
725
- setServerUrl(normalizeServerUrl(url));
726
- }, []);
727
- const exportDatabase = useCallback(async () => {
728
- if (exportState === "exporting") {
729
- return;
730
- }
731
- if (connectionState !== "connected") {
732
- finishExportFailure("Connect to the DevTools hub first");
733
- return;
734
- }
735
- if (!resolvedAdapter) {
736
- finishExportFailure(adapterError ?? "No database adapter connected");
737
- return;
738
- }
739
- const client = clientRef.current;
740
- if (!client) {
741
- finishExportFailure("DevTools client is not ready");
742
- return;
743
- }
744
- setExportState("exporting");
745
- setExportError(null);
746
- try {
747
- await new Promise((resolve, reject) => {
748
- exportWaitersRef.current = { resolve, reject };
749
- exportTimeoutRef.current = setTimeout(() => {
750
- exportWaitersRef.current?.reject(new Error("Export timed out"));
751
- clearExportWaiters();
752
- }, REFRESH_TIMEOUT_MS);
753
- client.requestExportSnapshot();
754
- });
755
- finishExportSuccess();
756
- } catch (error) {
757
- const message = error instanceof Error ? error.message : "Export failed";
758
- finishExportFailure(message);
759
- }
760
- }, [
761
- adapterError,
762
- connectionState,
763
- exportState,
764
- finishExportFailure,
765
- resolvedAdapter
766
- ]);
767
- const connectionHint = useMemo(() => getConnectionHint(serverUrl), [serverUrl]);
768
- const value = useMemo(
769
- () => ({
770
- connectionState,
771
- connectionError,
772
- connectionHint,
773
- deviceId,
774
- serverUrl,
775
- metadata,
776
- database: resolvedAdapter,
777
- adapterError,
778
- mobileInspector,
779
- launcherVisible,
780
- openLauncher,
781
- closeLauncher,
782
- explorerVisible,
783
- openExplorer,
784
- closeExplorer,
785
- settingsVisible,
786
- openSettings,
787
- closeSettings,
788
- reconnect,
789
- exportState,
790
- exportError,
791
- exportDatabase
792
- }),
793
- [
794
- connectionState,
795
- connectionError,
796
- connectionHint,
797
- deviceId,
798
- serverUrl,
799
- metadata,
800
- resolvedAdapter,
801
- adapterError,
802
- mobileInspector,
803
- launcherVisible,
804
- openLauncher,
805
- closeLauncher,
806
- explorerVisible,
807
- openExplorer,
808
- closeExplorer,
809
- settingsVisible,
810
- openSettings,
811
- closeSettings,
812
- reconnect,
813
- exportState,
814
- exportError,
815
- exportDatabase
816
- ]
817
- );
818
- return /* @__PURE__ */ jsx(DevToolsContext.Provider, { value, children });
819
- }
820
- function getStatusConfig(state) {
821
- switch (state) {
822
- case "connected":
823
- return { label: "Connected", color: "#166534", backgroundColor: "#dcfce7" };
824
- case "reconnecting":
825
- return { label: "Reconnecting", color: "#92400e", backgroundColor: "#fef3c7" };
826
- case "connecting":
827
- return { label: "Connecting", color: "#92400e", backgroundColor: "#fef3c7" };
828
- case "disconnected":
829
- return { label: "Disconnected", color: "#991b1b", backgroundColor: "#fee2e2" };
830
- }
831
- }
832
- function getConnectionDotColor(state) {
833
- switch (state) {
834
- case "connected":
835
- return "#22c55e";
836
- case "reconnecting":
837
- case "connecting":
838
- return "#eab308";
839
- case "disconnected":
840
- return "#ef4444";
841
- }
842
- }
843
- function ConnectionStatusBadge({ state }) {
844
- const config = getStatusConfig(state);
845
- return /* @__PURE__ */ jsxs(View, { style: [styles.badge, { backgroundColor: config.backgroundColor }], children: [
846
- /* @__PURE__ */ jsx(View, { style: [styles.dot, { backgroundColor: getConnectionDotColor(state) }] }),
847
- /* @__PURE__ */ jsx(Text, { style: [styles.label, { color: config.color }], children: config.label })
848
- ] });
849
- }
850
- var styles = StyleSheet.create({
851
- badge: {
852
- flexDirection: "row",
853
- alignItems: "center",
854
- alignSelf: "flex-start",
855
- paddingHorizontal: 10,
856
- paddingVertical: 6,
857
- borderRadius: 999,
858
- gap: 6
859
- },
860
- dot: {
861
- width: 8,
862
- height: 8,
863
- borderRadius: 4
864
- },
865
- label: {
866
- fontSize: 13,
867
- fontWeight: "600"
868
- }
869
- });
870
- function DevToolsLauncherModal() {
871
- const {
872
- launcherVisible,
873
- closeLauncher,
874
- openExplorer,
875
- openSettings,
876
- connectionState,
877
- mobileInspector,
878
- exportState,
879
- exportError,
880
- exportDatabase,
881
- database,
882
- adapterError
883
- } = useDevTools();
884
- const isExporting = exportState === "exporting";
885
- const exportDisabled = connectionState !== "connected" || !database || isExporting || Boolean(adapterError);
886
- return /* @__PURE__ */ jsx(
887
- Modal,
888
- {
889
- animationType: "slide",
890
- onRequestClose: closeLauncher,
891
- transparent: true,
892
- visible: launcherVisible,
893
- children: /* @__PURE__ */ jsx(View, { style: styles2.overlay, children: /* @__PURE__ */ jsxs(View, { style: styles2.sheet, children: [
894
- /* @__PURE__ */ jsxs(View, { style: styles2.header, children: [
895
- /* @__PURE__ */ jsx(Text, { style: styles2.title, children: "Database DevTools" }),
896
- /* @__PURE__ */ jsx(Pressable, { accessibilityLabel: "Close launcher", onPress: closeLauncher, children: /* @__PURE__ */ jsx(Text, { style: styles2.closeButton, children: "Close" }) })
897
- ] }),
898
- /* @__PURE__ */ jsxs(View, { style: styles2.body, children: [
899
- /* @__PURE__ */ jsx(ConnectionStatusBadge, { state: connectionState }),
900
- /* @__PURE__ */ jsx(Text, { style: styles2.hint, children: "Browse and query the on-device database, or connect to the web inspector." }),
901
- /* @__PURE__ */ jsx(
902
- Pressable,
903
- {
904
- accessibilityLabel: "View database on device",
905
- disabled: !mobileInspector,
906
- onPress: openExplorer,
907
- style: [styles2.primaryButton, !mobileInspector && styles2.primaryButtonDisabled],
908
- children: /* @__PURE__ */ jsx(Text, { style: styles2.primaryLabel, children: "View Database" })
909
- }
910
- ),
911
- !mobileInspector ? /* @__PURE__ */ jsx(Text, { style: styles2.warning, children: "On-device explorer requires an Expo SQLite database instance." }) : null,
912
- /* @__PURE__ */ jsx(
913
- Pressable,
914
- {
915
- accessibilityLabel: "Open DevTools settings",
916
- onPress: openSettings,
917
- style: styles2.secondaryButton,
918
- children: /* @__PURE__ */ jsx(Text, { style: styles2.secondaryLabel, children: "DevTools Settings" })
919
- }
920
- ),
921
- /* @__PURE__ */ jsx(
922
- Pressable,
923
- {
924
- accessibilityLabel: "Export database to web inspector",
925
- accessibilityState: { busy: isExporting, disabled: exportDisabled },
926
- disabled: exportDisabled,
927
- onPress: () => void exportDatabase(),
928
- style: [styles2.ghostButton, exportDisabled && !isExporting && styles2.buttonDisabled],
929
- children: /* @__PURE__ */ jsxs(View, { style: styles2.exportContent, children: [
930
- isExporting ? /* @__PURE__ */ jsx(ActivityIndicator, { color: "#0f172a", size: "small" }) : null,
931
- /* @__PURE__ */ jsx(Text, { style: styles2.ghostLabel, children: isExporting ? "Exporting\u2026" : "Export to Web Inspector" })
932
- ] })
933
- }
934
- ),
935
- connectionState !== "connected" ? /* @__PURE__ */ jsx(Text, { style: styles2.hint, children: "Connect to the hub in settings before exporting." }) : null,
936
- exportState === "success" ? /* @__PURE__ */ jsx(Text, { style: styles2.success, children: "Sent to web inspector" }) : null,
937
- exportError ? /* @__PURE__ */ jsx(Text, { style: styles2.error, children: exportError }) : null,
938
- adapterError ? /* @__PURE__ */ jsx(Text, { style: styles2.error, children: adapterError }) : null
939
- ] })
940
- ] }) })
941
- }
942
- );
943
- }
944
- var styles2 = StyleSheet.create({
945
- overlay: {
946
- flex: 1,
947
- backgroundColor: "rgba(0, 0, 0, 0.45)",
948
- justifyContent: "flex-end"
949
- },
950
- sheet: {
951
- backgroundColor: "#ffffff",
952
- borderTopLeftRadius: 16,
953
- borderTopRightRadius: 16,
954
- overflow: "hidden"
955
- },
956
- header: {
957
- flexDirection: "row",
958
- alignItems: "center",
959
- justifyContent: "space-between",
960
- paddingHorizontal: 20,
961
- paddingVertical: 16,
962
- borderBottomWidth: StyleSheet.hairlineWidth,
963
- borderBottomColor: "#e2e8f0"
964
- },
965
- title: {
966
- fontSize: 18,
967
- fontWeight: "700",
968
- color: "#0f172a"
969
- },
970
- closeButton: {
971
- fontSize: 16,
972
- color: "#2563eb",
973
- fontWeight: "600"
974
- },
975
- body: {
976
- padding: 20,
977
- gap: 12
978
- },
979
- hint: {
980
- fontSize: 13,
981
- color: "#64748b",
982
- lineHeight: 20
983
- },
984
- warning: {
985
- fontSize: 12,
986
- color: "#b45309",
987
- marginTop: -4
988
- },
989
- primaryButton: {
990
- backgroundColor: "#2563eb",
991
- borderRadius: 8,
992
- paddingVertical: 14,
993
- alignItems: "center"
994
- },
995
- primaryButtonDisabled: {
996
- opacity: 0.5
997
- },
998
- primaryLabel: {
999
- color: "#f8fafc",
1000
- fontSize: 16,
1001
- fontWeight: "700"
1002
- },
1003
- secondaryButton: {
1004
- backgroundColor: "#1e293b",
1005
- borderRadius: 8,
1006
- paddingVertical: 12,
1007
- alignItems: "center"
1008
- },
1009
- secondaryLabel: {
1010
- color: "#f8fafc",
1011
- fontSize: 15,
1012
- fontWeight: "600"
1013
- },
1014
- ghostButton: {
1015
- borderWidth: 1,
1016
- borderColor: "#cbd5e1",
1017
- borderRadius: 8,
1018
- paddingVertical: 12,
1019
- alignItems: "center"
1020
- },
1021
- buttonDisabled: {
1022
- opacity: 0.5
1023
- },
1024
- ghostLabel: {
1025
- color: "#0f172a",
1026
- fontSize: 15,
1027
- fontWeight: "600"
1028
- },
1029
- exportContent: {
1030
- flexDirection: "row",
1031
- alignItems: "center",
1032
- gap: 8
1033
- },
1034
- success: {
1035
- fontSize: 13,
1036
- color: "#15803d"
1037
- },
1038
- error: {
1039
- fontSize: 13,
1040
- color: "#dc2626"
1041
- }
1042
- });
1043
- function DevToolsSettingsModal() {
1044
- const {
1045
- settingsVisible,
1046
- closeSettings,
1047
- connectionState,
1048
- connectionError,
1049
- connectionHint,
1050
- deviceId,
1051
- serverUrl,
1052
- metadata,
1053
- database,
1054
- adapterError,
1055
- reconnect,
1056
- exportState,
1057
- exportError,
1058
- exportDatabase
1059
- } = useDevTools();
1060
- const [draftUrl, setDraftUrl] = useState(serverUrl);
1061
- useEffect(() => {
1062
- if (settingsVisible) {
1063
- setDraftUrl(serverUrl);
1064
- }
1065
- }, [settingsVisible, serverUrl]);
1066
- const handleReconnect = () => {
1067
- const trimmed = draftUrl.trim();
1068
- if (trimmed) {
1069
- reconnect(trimmed);
1070
- }
1071
- };
1072
- const handleExportDatabase = () => {
1073
- void exportDatabase();
1074
- };
1075
- const isExporting = exportState === "exporting";
1076
- const exportDisabled = connectionState !== "connected" || !database || isExporting;
1077
- return /* @__PURE__ */ jsx(
1078
- Modal,
1079
- {
1080
- animationType: "slide",
1081
- onRequestClose: closeSettings,
1082
- transparent: true,
1083
- visible: settingsVisible,
1084
- children: /* @__PURE__ */ jsx(View, { style: styles3.overlay, children: /* @__PURE__ */ jsxs(View, { style: styles3.sheet, children: [
1085
- /* @__PURE__ */ jsxs(View, { style: styles3.header, children: [
1086
- /* @__PURE__ */ jsx(Text, { style: styles3.title, children: "Database DevTools" }),
1087
- /* @__PURE__ */ jsx(Pressable, { accessibilityLabel: "Close settings", onPress: closeSettings, children: /* @__PURE__ */ jsx(Text, { style: styles3.closeButton, children: "Close" }) })
1088
- ] }),
1089
- /* @__PURE__ */ jsxs(
1090
- ScrollView,
1091
- {
1092
- contentContainerStyle: styles3.content,
1093
- style: styles3.scrollBody,
1094
- children: [
1095
- /* @__PURE__ */ jsxs(Section, { title: "Connection", children: [
1096
- /* @__PURE__ */ jsx(ConnectionStatusBadge, { state: connectionState }),
1097
- /* @__PURE__ */ jsx(InfoRow, { label: "Server URL", value: serverUrl, mono: true }),
1098
- connectionError ? /* @__PURE__ */ jsx(Text, { style: styles3.errorText, children: connectionError }) : null,
1099
- connectionHint ? /* @__PURE__ */ jsx(Text, { style: styles3.hintText, children: connectionHint }) : null,
1100
- connectionState === "reconnecting" ? /* @__PURE__ */ jsx(Text, { style: styles3.hintText, children: "Ensure the inspector hub is running (npx database-devtools) and the URL reaches your development machine \u2014 not the phone itself." }) : null
1101
- ] }),
1102
- /* @__PURE__ */ jsxs(Section, { title: "Device", children: [
1103
- /* @__PURE__ */ jsx(InfoRow, { label: "Device ID", value: deviceId ?? "\u2014", mono: true }),
1104
- /* @__PURE__ */ jsx(InfoRow, { label: "Platform", value: metadata.platform ?? "\u2014" }),
1105
- /* @__PURE__ */ jsx(InfoRow, { label: "App", value: metadata.appName ?? "\u2014" }),
1106
- metadata.appVersion ? /* @__PURE__ */ jsx(InfoRow, { label: "Version", value: metadata.appVersion }) : null,
1107
- metadata.bundleId ? /* @__PURE__ */ jsx(InfoRow, { label: "Bundle ID", value: metadata.bundleId, mono: true }) : null
1108
- ] }),
1109
- /* @__PURE__ */ jsxs(Section, { title: "Server", children: [
1110
- /* @__PURE__ */ jsx(Text, { style: styles3.fieldLabel, children: "WebSocket URL" }),
1111
- /* @__PURE__ */ jsx(
1112
- TextInput,
1113
- {
1114
- autoCapitalize: "none",
1115
- autoCorrect: false,
1116
- onChangeText: setDraftUrl,
1117
- placeholder: "ws://10.0.2.2:3847/ws",
1118
- style: styles3.input,
1119
- value: draftUrl
1120
- }
1121
- ),
1122
- /* @__PURE__ */ jsx(Pressable, { onPress: handleReconnect, style: styles3.reconnectButton, children: /* @__PURE__ */ jsx(Text, { style: styles3.reconnectLabel, children: "Reconnect" }) })
1123
- ] }),
1124
- /* @__PURE__ */ jsx(Section, { title: "Database", children: database ? /* @__PURE__ */ jsxs(Fragment, { children: [
1125
- /* @__PURE__ */ jsx(InfoRow, { label: "Kind", value: database.kind }),
1126
- /* @__PURE__ */ jsx(InfoRow, { label: "Name", value: database.name }),
1127
- /* @__PURE__ */ jsx(InfoRow, { label: "ID", value: database.id, mono: true })
1128
- ] }) : /* @__PURE__ */ jsx(Text, { style: styles3.placeholder, children: adapterError ? "Adapter not connected" : "No adapter connected" }) })
1129
- ]
1130
- }
1131
- ),
1132
- /* @__PURE__ */ jsxs(View, { style: styles3.exportFooter, children: [
1133
- adapterError ? /* @__PURE__ */ jsx(Text, { style: styles3.errorText, children: adapterError }) : null,
1134
- database ? /* @__PURE__ */ jsxs(Fragment, { children: [
1135
- /* @__PURE__ */ jsx(
1136
- Pressable,
1137
- {
1138
- accessibilityLabel: "Export database to web inspector",
1139
- accessibilityState: { busy: isExporting, disabled: exportDisabled },
1140
- disabled: exportDisabled,
1141
- onPress: handleExportDatabase,
1142
- style: [
1143
- styles3.exportButton,
1144
- exportDisabled && !isExporting && styles3.exportButtonDisabled
1145
- ],
1146
- children: /* @__PURE__ */ jsxs(View, { style: styles3.exportButtonContent, children: [
1147
- isExporting ? /* @__PURE__ */ jsx(ActivityIndicator, { color: "#f8fafc", size: "small" }) : null,
1148
- /* @__PURE__ */ jsx(Text, { style: styles3.exportLabel, children: isExporting ? "Exporting\u2026" : "Export Database" })
1149
- ] })
1150
- }
1151
- ),
1152
- connectionState !== "connected" ? /* @__PURE__ */ jsx(Text, { style: styles3.hintText, children: "Connect to the hub before exporting." }) : null,
1153
- exportState === "success" ? /* @__PURE__ */ jsx(Text, { style: styles3.successText, children: "Sent to web inspector" }) : null,
1154
- exportError ? /* @__PURE__ */ jsx(Text, { style: styles3.errorText, children: exportError }) : null
1155
- ] }) : /* @__PURE__ */ jsx(Text, { style: styles3.placeholder, children: adapterError ? "Adapter not connected" : "No adapter connected" })
1156
- ] })
1157
- ] }) })
1158
- }
1159
- );
1160
- }
1161
- function Section({ title, children }) {
1162
- return /* @__PURE__ */ jsxs(View, { style: styles3.section, children: [
1163
- /* @__PURE__ */ jsx(Text, { style: styles3.sectionTitle, children: title }),
1164
- children
1165
- ] });
1166
- }
1167
- function InfoRow({
1168
- label,
1169
- value,
1170
- mono = false
1171
- }) {
1172
- return /* @__PURE__ */ jsxs(View, { style: styles3.infoRow, children: [
1173
- /* @__PURE__ */ jsx(Text, { style: styles3.infoLabel, children: label }),
1174
- /* @__PURE__ */ jsx(Text, { style: [styles3.infoValue, mono && styles3.mono], selectable: true, children: value })
1175
- ] });
1176
- }
1177
- var styles3 = StyleSheet.create({
1178
- overlay: {
1179
- flex: 1,
1180
- backgroundColor: "rgba(0, 0, 0, 0.45)",
1181
- justifyContent: "flex-end"
1182
- },
1183
- sheet: {
1184
- backgroundColor: "#ffffff",
1185
- borderTopLeftRadius: 16,
1186
- borderTopRightRadius: 16,
1187
- maxHeight: "85%",
1188
- overflow: "hidden"
1189
- },
1190
- scrollBody: {
1191
- flexGrow: 0,
1192
- flexShrink: 1
1193
- },
1194
- header: {
1195
- flexDirection: "row",
1196
- alignItems: "center",
1197
- justifyContent: "space-between",
1198
- paddingHorizontal: 20,
1199
- paddingVertical: 16,
1200
- borderBottomWidth: StyleSheet.hairlineWidth,
1201
- borderBottomColor: "#e2e8f0"
1202
- },
1203
- title: {
1204
- fontSize: 18,
1205
- fontWeight: "700",
1206
- color: "#0f172a"
1207
- },
1208
- closeButton: {
1209
- fontSize: 16,
1210
- color: "#2563eb",
1211
- fontWeight: "600"
1212
- },
1213
- exportFooter: {
1214
- paddingHorizontal: 20,
1215
- paddingTop: 12,
1216
- paddingBottom: 20,
1217
- gap: 8,
1218
- borderTopWidth: StyleSheet.hairlineWidth,
1219
- borderTopColor: "#e2e8f0",
1220
- backgroundColor: "#ffffff"
1221
- },
1222
- content: {
1223
- padding: 20,
1224
- gap: 20
1225
- },
1226
- section: {
1227
- gap: 10
1228
- },
1229
- sectionTitle: {
1230
- fontSize: 13,
1231
- fontWeight: "600",
1232
- color: "#64748b",
1233
- textTransform: "uppercase",
1234
- letterSpacing: 0.5
1235
- },
1236
- infoRow: {
1237
- gap: 2
1238
- },
1239
- infoLabel: {
1240
- fontSize: 12,
1241
- color: "#94a3b8"
1242
- },
1243
- infoValue: {
1244
- fontSize: 14,
1245
- color: "#0f172a"
1246
- },
1247
- mono: {
1248
- fontFamily: "monospace",
1249
- fontSize: 12
1250
- },
1251
- fieldLabel: {
1252
- fontSize: 12,
1253
- color: "#64748b",
1254
- marginBottom: 4
1255
- },
1256
- input: {
1257
- borderWidth: 1,
1258
- borderColor: "#cbd5e1",
1259
- borderRadius: 8,
1260
- paddingHorizontal: 12,
1261
- paddingVertical: 10,
1262
- fontSize: 14,
1263
- color: "#0f172a",
1264
- fontFamily: "monospace"
1265
- },
1266
- reconnectButton: {
1267
- marginTop: 8,
1268
- backgroundColor: "#1e293b",
1269
- borderRadius: 8,
1270
- paddingVertical: 12,
1271
- alignItems: "center"
1272
- },
1273
- reconnectLabel: {
1274
- color: "#f8fafc",
1275
- fontSize: 15,
1276
- fontWeight: "600"
1277
- },
1278
- exportButton: {
1279
- backgroundColor: "#2563eb",
1280
- borderRadius: 8,
1281
- paddingVertical: 12,
1282
- paddingHorizontal: 16,
1283
- alignItems: "center",
1284
- justifyContent: "center",
1285
- minHeight: 44
1286
- },
1287
- exportButtonContent: {
1288
- flexDirection: "row",
1289
- alignItems: "center",
1290
- justifyContent: "center",
1291
- gap: 8
1292
- },
1293
- exportButtonDisabled: {
1294
- opacity: 0.5
1295
- },
1296
- exportLabel: {
1297
- color: "#f8fafc",
1298
- fontSize: 15,
1299
- fontWeight: "600"
1300
- },
1301
- successText: {
1302
- fontSize: 13,
1303
- color: "#15803d"
1304
- },
1305
- placeholder: {
1306
- fontSize: 14,
1307
- color: "#94a3b8",
1308
- fontStyle: "italic"
1309
- },
1310
- errorText: {
1311
- fontSize: 13,
1312
- color: "#dc2626",
1313
- marginBottom: 8
1314
- },
1315
- hintText: {
1316
- fontSize: 12,
1317
- color: "#b45309",
1318
- lineHeight: 18
1319
- }
1320
- });
1321
-
1322
- // src/utils/floatingButtonPosition.ts
1323
- var FLOATING_BUTTON_SIZE = 48;
1324
- var FLOATING_BUTTON_MARGIN = 16;
1325
- var FLOATING_BUTTON_BOTTOM = 24;
1326
- var FLOATING_BUTTON_TAP_THRESHOLD = 8;
1327
- function getCornerPosition(corner, layout, bottomInset = FLOATING_BUTTON_BOTTOM) {
1328
- const { windowWidth, windowHeight, buttonSize, margin } = layout;
1329
- const y = windowHeight - bottomInset - buttonSize;
1330
- return {
1331
- x: corner === "bottom-right" ? windowWidth - margin - buttonSize : margin,
1332
- y
1333
- };
1334
- }
1335
- function clampFloatingPosition(position, layout) {
1336
- const { windowWidth, windowHeight, buttonSize, margin } = layout;
1337
- return {
1338
- x: Math.min(Math.max(margin, position.x), windowWidth - margin - buttonSize),
1339
- y: Math.min(Math.max(margin, position.y), windowHeight - margin - buttonSize)
1340
- };
1341
- }
1342
- function snapFloatingPositionToEdges(position, layout) {
1343
- const { windowWidth, windowHeight, buttonSize, margin } = layout;
1344
- const clamped = clampFloatingPosition(position, layout);
1345
- const centerX = clamped.x + buttonSize / 2;
1346
- const centerY = clamped.y + buttonSize / 2;
1347
- let x = centerX < windowWidth / 2 ? margin : windowWidth - margin - buttonSize;
1348
- let y = clamped.y;
1349
- if (centerY < windowHeight * 0.2) {
1350
- y = margin;
1351
- } else if (centerY > windowHeight * 0.8) {
1352
- y = windowHeight - margin - buttonSize;
1353
- }
1354
- return { x, y };
1355
- }
1356
- function isFloatingButtonTap(totalMovement) {
1357
- return totalMovement < FLOATING_BUTTON_TAP_THRESHOLD;
1358
- }
1359
- var DEFAULT_ICON_SIZE = 22;
1360
- var DEFAULT_ICON_COLOR = "#f8fafc";
1361
- function FloatingDevToolsButton({
1362
- position = "bottom-right",
1363
- iconStyle,
1364
- draggable = true,
1365
- snapToEdges = true,
1366
- floatingPosition,
1367
- onFloatingPositionChange
1368
- }) {
1369
- const { connectionState, openLauncher } = useDevTools();
1370
- const { width: windowWidth, height: windowHeight } = useWindowDimensions();
1371
- const [internalPosition, setInternalPosition] = useState(null);
1372
- const [dragPosition, setDragPosition] = useState(null);
1373
- const dragOrigin = useRef({ x: 0, y: 0 });
1374
- const hasInitialized = useRef(false);
1375
- const onFloatingPositionChangeRef = useRef(onFloatingPositionChange);
1376
- useEffect(() => {
1377
- onFloatingPositionChangeRef.current = onFloatingPositionChange;
1378
- }, [onFloatingPositionChange]);
1379
- const layout = useMemo(
1380
- () => ({
1381
- windowWidth,
1382
- windowHeight,
1383
- buttonSize: FLOATING_BUTTON_SIZE,
1384
- margin: FLOATING_BUTTON_MARGIN
1385
- }),
1386
- [windowWidth, windowHeight]
1387
- );
1388
- const committedPosition = floatingPosition ?? internalPosition;
1389
- const renderedPosition = dragPosition ?? committedPosition;
1390
- const commitPosition = useCallback(
1391
- (next) => {
1392
- const clamped = clampFloatingPosition(next, layout);
1393
- if (!floatingPosition) {
1394
- setInternalPosition(clamped);
1395
- }
1396
- onFloatingPositionChangeRef.current?.(clamped);
1397
- return clamped;
1398
- },
1399
- [floatingPosition, layout]
1400
- );
1401
- useEffect(() => {
1402
- if (windowWidth <= 0 || windowHeight <= 0) {
1403
- return;
1404
- }
1405
- if (floatingPosition) {
1406
- hasInitialized.current = true;
1407
- return;
1408
- }
1409
- if (!hasInitialized.current) {
1410
- setInternalPosition(getCornerPosition(position, layout));
1411
- hasInitialized.current = true;
1412
- }
1413
- }, [floatingPosition, layout, position, windowHeight, windowWidth]);
1414
- useEffect(() => {
1415
- if (!committedPosition || windowWidth <= 0 || windowHeight <= 0) {
1416
- return;
1417
- }
1418
- const clamped = clampFloatingPosition(committedPosition, layout);
1419
- if (clamped.x !== committedPosition.x || clamped.y !== committedPosition.y) {
1420
- commitPosition(clamped);
1421
- }
1422
- }, [commitPosition, committedPosition, layout, windowHeight, windowWidth]);
1423
- const panResponder = useMemo(
1424
- () => PanResponder.create({
1425
- onStartShouldSetPanResponder: () => draggable,
1426
- onMoveShouldSetPanResponder: (_, gesture) => draggable && (Math.abs(gesture.dx) > 2 || Math.abs(gesture.dy) > 2),
1427
- onPanResponderGrant: () => {
1428
- if (!committedPosition) {
1429
- return;
1430
- }
1431
- dragOrigin.current = committedPosition;
1432
- },
1433
- onPanResponderMove: (_, gesture) => {
1434
- setDragPosition(
1435
- clampFloatingPosition(
1436
- {
1437
- x: dragOrigin.current.x + gesture.dx,
1438
- y: dragOrigin.current.y + gesture.dy
1439
- },
1440
- layout
1441
- )
1442
- );
1443
- },
1444
- onPanResponderRelease: (_, gesture) => {
1445
- setDragPosition(null);
1446
- const movement = Math.hypot(gesture.dx, gesture.dy);
1447
- if (isFloatingButtonTap(movement)) {
1448
- openLauncher();
1449
- return;
1450
- }
1451
- const raw = {
1452
- x: dragOrigin.current.x + gesture.dx,
1453
- y: dragOrigin.current.y + gesture.dy
1454
- };
1455
- const next = snapToEdges ? snapFloatingPositionToEdges(raw, layout) : clampFloatingPosition(raw, layout);
1456
- commitPosition(next);
1457
- },
1458
- onPanResponderTerminate: () => {
1459
- setDragPosition(null);
1460
- }
1461
- }),
1462
- [commitPosition, committedPosition, draggable, layout, openLauncher, snapToEdges]
1463
- );
1464
- if (!renderedPosition) {
1465
- return null;
1466
- }
1467
- const buttonContent = /* @__PURE__ */ jsxs(Fragment, { children: [
1468
- /* @__PURE__ */ jsx(
1469
- MaterialCommunityIcons,
1470
- {
1471
- color: DEFAULT_ICON_COLOR,
1472
- name: "database",
1473
- size: DEFAULT_ICON_SIZE,
1474
- style: iconStyle
1475
- }
1476
- ),
1477
- /* @__PURE__ */ jsx(
1478
- View,
1479
- {
1480
- style: [styles4.statusDot, { backgroundColor: getConnectionDotColor(connectionState) }]
1481
- }
1482
- )
1483
- ] });
1484
- if (!draggable) {
1485
- const positionStyle = position === "bottom-left" ? styles4.bottomLeft : styles4.bottomRight;
1486
- return /* @__PURE__ */ jsx(View, { style: [styles4.fixedContainer, positionStyle], pointerEvents: "box-none", children: /* @__PURE__ */ jsx(
1487
- Pressable,
1488
- {
1489
- accessibilityLabel: "Open Database DevTools",
1490
- accessibilityRole: "button",
1491
- onPress: openLauncher,
1492
- style: ({ pressed }) => [styles4.button, pressed && styles4.buttonPressed],
1493
- children: buttonContent
1494
- }
1495
- ) });
1496
- }
1497
- return /* @__PURE__ */ jsx(
1498
- View,
1499
- {
1500
- pointerEvents: "box-none",
1501
- style: [
1502
- styles4.draggableContainer,
1503
- {
1504
- transform: [
1505
- { translateX: renderedPosition.x },
1506
- { translateY: renderedPosition.y }
1507
- ]
1508
- }
1509
- ],
1510
- ...panResponder.panHandlers,
1511
- children: /* @__PURE__ */ jsx(
1512
- View,
1513
- {
1514
- accessibilityHint: "Drag to move. Tap to open DevTools launcher.",
1515
- accessibilityLabel: "Open Database DevTools",
1516
- accessibilityRole: "button",
1517
- style: [styles4.button, dragPosition && styles4.buttonDragging],
1518
- children: buttonContent
1519
- }
1520
- )
1521
- }
1522
- );
1523
- }
1524
- var styles4 = StyleSheet.create({
1525
- fixedContainer: {
1526
- position: "absolute",
1527
- bottom: 24,
1528
- zIndex: 9999
1529
- },
1530
- bottomRight: {
1531
- right: 16
1532
- },
1533
- bottomLeft: {
1534
- left: 16
1535
- },
1536
- draggableContainer: {
1537
- position: "absolute",
1538
- top: 0,
1539
- left: 0,
1540
- zIndex: 9999
1541
- },
1542
- button: {
1543
- width: FLOATING_BUTTON_SIZE,
1544
- height: FLOATING_BUTTON_SIZE,
1545
- borderRadius: FLOATING_BUTTON_SIZE / 2,
1546
- backgroundColor: "#1e293b",
1547
- alignItems: "center",
1548
- justifyContent: "center",
1549
- shadowColor: "#000",
1550
- shadowOffset: { width: 0, height: 2 },
1551
- shadowOpacity: 0.25,
1552
- shadowRadius: 4,
1553
- elevation: 6
1554
- },
1555
- buttonPressed: {
1556
- opacity: 0.85
1557
- },
1558
- buttonDragging: {
1559
- opacity: 0.92,
1560
- transform: [{ scale: 1.04 }]
1561
- },
1562
- statusDot: {
1563
- position: "absolute",
1564
- top: 2,
1565
- right: 2,
1566
- width: 10,
1567
- height: 10,
1568
- borderRadius: 5,
1569
- borderWidth: 2,
1570
- borderColor: "#1e293b"
1571
- }
1572
- });
1573
- var explorerColors = {
1574
- background: "#ffffff",
1575
- surface: "#f8fafc",
1576
- border: "#e2e8f0",
1577
- text: "#0f172a",
1578
- muted: "#64748b",
1579
- subtle: "#94a3b8",
1580
- accent: "#2563eb",
1581
- accentSoft: "#dbeafe",
1582
- error: "#dc2626"};
1583
- var explorerStyles = StyleSheet.create({
1584
- fullScreen: {
1585
- flex: 1,
1586
- backgroundColor: explorerColors.background
1587
- },
1588
- header: {
1589
- flexDirection: "row",
1590
- alignItems: "center",
1591
- justifyContent: "space-between",
1592
- paddingHorizontal: 16,
1593
- paddingVertical: 14,
1594
- borderBottomWidth: StyleSheet.hairlineWidth,
1595
- borderBottomColor: explorerColors.border,
1596
- backgroundColor: explorerColors.background
1597
- },
1598
- headerTitle: {
1599
- fontSize: 17,
1600
- fontWeight: "700",
1601
- color: explorerColors.text
1602
- },
1603
- headerAction: {
1604
- fontSize: 16,
1605
- color: explorerColors.accent,
1606
- fontWeight: "600"
1607
- },
1608
- tabRow: {
1609
- flexDirection: "row",
1610
- borderBottomWidth: StyleSheet.hairlineWidth,
1611
- borderBottomColor: explorerColors.border,
1612
- backgroundColor: explorerColors.surface
1613
- },
1614
- tab: {
1615
- flex: 1,
1616
- paddingVertical: 12,
1617
- alignItems: "center",
1618
- borderBottomWidth: 2,
1619
- borderBottomColor: "transparent"
1620
- },
1621
- tabActive: {
1622
- borderBottomColor: explorerColors.accent
1623
- },
1624
- tabLabel: {
1625
- fontSize: 14,
1626
- color: explorerColors.muted,
1627
- fontWeight: "500"
1628
- },
1629
- tabLabelActive: {
1630
- color: explorerColors.accent,
1631
- fontWeight: "700"
1632
- },
1633
- content: {
1634
- flex: 1,
1635
- padding: 16
1636
- },
1637
- sectionTitle: {
1638
- fontSize: 12,
1639
- fontWeight: "600",
1640
- color: explorerColors.muted,
1641
- textTransform: "uppercase",
1642
- letterSpacing: 0.5,
1643
- marginBottom: 8
1644
- },
1645
- infoRow: {
1646
- marginBottom: 10
1647
- },
1648
- infoLabel: {
1649
- fontSize: 12,
1650
- color: explorerColors.subtle,
1651
- marginBottom: 2
1652
- },
1653
- infoValue: {
1654
- fontSize: 14,
1655
- color: explorerColors.text
1656
- },
1657
- mono: {
1658
- fontFamily: "monospace",
1659
- fontSize: 12
1660
- },
1661
- listItem: {
1662
- flexDirection: "row",
1663
- alignItems: "center",
1664
- justifyContent: "space-between",
1665
- paddingVertical: 12,
1666
- paddingHorizontal: 12,
1667
- borderRadius: 8,
1668
- backgroundColor: explorerColors.surface,
1669
- marginBottom: 8,
1670
- borderWidth: 1,
1671
- borderColor: explorerColors.border
1672
- },
1673
- listItemTitle: {
1674
- fontSize: 15,
1675
- fontWeight: "600",
1676
- color: explorerColors.text,
1677
- fontFamily: "monospace"
1678
- },
1679
- listItemMeta: {
1680
- fontSize: 12,
1681
- color: explorerColors.muted
1682
- },
1683
- searchInput: {
1684
- borderWidth: 1,
1685
- borderColor: explorerColors.border,
1686
- borderRadius: 8,
1687
- paddingHorizontal: 12,
1688
- paddingVertical: 10,
1689
- fontSize: 14,
1690
- color: explorerColors.text,
1691
- backgroundColor: explorerColors.background,
1692
- marginBottom: 12
1693
- },
1694
- primaryButton: {
1695
- backgroundColor: explorerColors.accent,
1696
- borderRadius: 8,
1697
- paddingVertical: 12,
1698
- alignItems: "center",
1699
- justifyContent: "center",
1700
- minHeight: 44
1701
- },
1702
- primaryButtonDisabled: {
1703
- opacity: 0.5
1704
- },
1705
- primaryButtonLabel: {
1706
- color: "#f8fafc",
1707
- fontSize: 15,
1708
- fontWeight: "600"
1709
- },
1710
- secondaryButton: {
1711
- backgroundColor: "#1e293b",
1712
- borderRadius: 8,
1713
- paddingVertical: 12,
1714
- alignItems: "center",
1715
- justifyContent: "center",
1716
- minHeight: 44
1717
- },
1718
- secondaryButtonLabel: {
1719
- color: "#f8fafc",
1720
- fontSize: 15,
1721
- fontWeight: "600"
1722
- },
1723
- ghostButton: {
1724
- borderWidth: 1,
1725
- borderColor: explorerColors.border,
1726
- borderRadius: 8,
1727
- paddingVertical: 12,
1728
- alignItems: "center",
1729
- justifyContent: "center",
1730
- minHeight: 44,
1731
- backgroundColor: explorerColors.background
1732
- },
1733
- ghostButtonLabel: {
1734
- color: explorerColors.text,
1735
- fontSize: 15,
1736
- fontWeight: "600"
1737
- },
1738
- errorText: {
1739
- fontSize: 13,
1740
- color: explorerColors.error,
1741
- marginTop: 8
1742
- },
1743
- hintText: {
1744
- fontSize: 12,
1745
- color: explorerColors.muted,
1746
- marginTop: 8,
1747
- lineHeight: 18
1748
- },
1749
- placeholder: {
1750
- fontSize: 14,
1751
- color: explorerColors.subtle,
1752
- fontStyle: "italic",
1753
- textAlign: "center",
1754
- marginTop: 24
1755
- },
1756
- sqlInput: {
1757
- minHeight: 120,
1758
- borderWidth: 1,
1759
- borderColor: explorerColors.border,
1760
- borderRadius: 8,
1761
- paddingHorizontal: 12,
1762
- paddingVertical: 10,
1763
- fontSize: 13,
1764
- fontFamily: "monospace",
1765
- color: explorerColors.text,
1766
- textAlignVertical: "top",
1767
- backgroundColor: explorerColors.surface
1768
- },
1769
- sqlTabRoot: {
1770
- flex: 1,
1771
- minHeight: 0
1772
- },
1773
- sqlQueryPane: {
1774
- flexShrink: 0,
1775
- paddingHorizontal: 16,
1776
- paddingTop: 16,
1777
- paddingBottom: 12,
1778
- gap: 12,
1779
- borderBottomWidth: StyleSheet.hairlineWidth,
1780
- borderBottomColor: explorerColors.border,
1781
- backgroundColor: explorerColors.background
1782
- },
1783
- sqlInputCompact: {
1784
- minHeight: 96,
1785
- maxHeight: 120,
1786
- borderWidth: 1,
1787
- borderColor: explorerColors.border,
1788
- borderRadius: 8,
1789
- paddingHorizontal: 12,
1790
- paddingVertical: 10,
1791
- fontSize: 13,
1792
- fontFamily: "monospace",
1793
- color: explorerColors.text,
1794
- textAlignVertical: "top",
1795
- backgroundColor: explorerColors.surface
1796
- },
1797
- sqlResultsPane: {
1798
- flex: 1,
1799
- minHeight: 0,
1800
- paddingHorizontal: 16,
1801
- paddingTop: 8,
1802
- paddingBottom: 16
1803
- },
1804
- sqlResultsEmpty: {
1805
- flex: 1,
1806
- justifyContent: "center",
1807
- paddingHorizontal: 16
1808
- },
1809
- subTabRow: {
1810
- flexDirection: "row",
1811
- gap: 8,
1812
- marginBottom: 12
1813
- },
1814
- subTab: {
1815
- paddingHorizontal: 12,
1816
- paddingVertical: 6,
1817
- borderRadius: 999,
1818
- borderWidth: 1,
1819
- borderColor: explorerColors.border,
1820
- backgroundColor: explorerColors.background
1821
- },
1822
- subTabActive: {
1823
- borderColor: explorerColors.accent,
1824
- backgroundColor: explorerColors.accentSoft
1825
- },
1826
- subTabLabel: {
1827
- fontSize: 13,
1828
- color: explorerColors.muted
1829
- },
1830
- subTabLabelActive: {
1831
- color: explorerColors.accent,
1832
- fontWeight: "600"
1833
- },
1834
- paginationRow: {
1835
- flexDirection: "row",
1836
- alignItems: "center",
1837
- justifyContent: "space-between",
1838
- paddingVertical: 12,
1839
- borderTopWidth: StyleSheet.hairlineWidth,
1840
- borderTopColor: explorerColors.border
1841
- },
1842
- paginationLabel: {
1843
- fontSize: 12,
1844
- color: explorerColors.muted
1845
- }
1846
- });
1847
- var tableStyles = StyleSheet.create({
1848
- viewRoot: {
1849
- flex: 1,
1850
- minHeight: 0
1851
- },
1852
- viewToolbar: {
1853
- flexDirection: "row",
1854
- alignItems: "center",
1855
- justifyContent: "space-between",
1856
- gap: 8,
1857
- marginBottom: 8,
1858
- flexWrap: "wrap"
1859
- },
1860
- viewMeta: {
1861
- flex: 1,
1862
- fontSize: 12,
1863
- color: explorerColors.muted
1864
- },
1865
- viewToggle: {
1866
- flexDirection: "row",
1867
- borderWidth: 1,
1868
- borderColor: explorerColors.border,
1869
- borderRadius: 8,
1870
- overflow: "hidden"
1871
- },
1872
- viewToggleButton: {
1873
- paddingHorizontal: 10,
1874
- paddingVertical: 6,
1875
- backgroundColor: explorerColors.background
1876
- },
1877
- viewToggleButtonActive: {
1878
- backgroundColor: explorerColors.accentSoft
1879
- },
1880
- viewToggleLabel: {
1881
- fontSize: 12,
1882
- fontWeight: "600",
1883
- color: explorerColors.muted
1884
- },
1885
- viewToggleLabelActive: {
1886
- color: explorerColors.accent
1887
- },
1888
- footerHint: {
1889
- flexShrink: 0,
1890
- marginTop: 8,
1891
- fontSize: 11,
1892
- color: explorerColors.subtle,
1893
- textAlign: "center"
1894
- },
1895
- tableContainer: {
1896
- flex: 1,
1897
- minHeight: 0,
1898
- borderWidth: 1,
1899
- borderColor: explorerColors.border,
1900
- borderRadius: 8,
1901
- overflow: "hidden",
1902
- backgroundColor: explorerColors.background
1903
- },
1904
- horizontalScroll: {
1905
- flex: 1
1906
- },
1907
- scrollHint: {
1908
- paddingHorizontal: 10,
1909
- paddingVertical: 6,
1910
- fontSize: 11,
1911
- color: explorerColors.muted,
1912
- backgroundColor: explorerColors.surface,
1913
- borderBottomWidth: StyleSheet.hairlineWidth,
1914
- borderBottomColor: explorerColors.border
1915
- },
1916
- headerRow: {
1917
- flexDirection: "row",
1918
- backgroundColor: "#f1f5f9",
1919
- borderBottomWidth: 1,
1920
- borderBottomColor: explorerColors.border
1921
- },
1922
- headerCell: {
1923
- backgroundColor: "#f1f5f9"
1924
- },
1925
- headerText: {
1926
- fontSize: 12,
1927
- fontWeight: "700",
1928
- color: explorerColors.text,
1929
- fontFamily: "monospace"
1930
- },
1931
- bodyList: {
1932
- flex: 1
1933
- },
1934
- dataRow: {
1935
- flexDirection: "row",
1936
- backgroundColor: explorerColors.background
1937
- },
1938
- dataRowAlt: {
1939
- backgroundColor: explorerColors.surface
1940
- },
1941
- cell: {
1942
- paddingHorizontal: 10,
1943
- paddingVertical: 8,
1944
- borderRightWidth: 1,
1945
- borderBottomWidth: 1,
1946
- borderColor: explorerColors.border,
1947
- justifyContent: "center"
1948
- },
1949
- cellText: {
1950
- fontSize: 13,
1951
- color: explorerColors.text
1952
- },
1953
- cellTextMono: {
1954
- fontFamily: "monospace",
1955
- fontSize: 12
1956
- },
1957
- cellNull: {
1958
- color: explorerColors.subtle,
1959
- fontStyle: "italic"
1960
- },
1961
- copiedHint: {
1962
- marginTop: 2,
1963
- fontSize: 10,
1964
- color: explorerColors.accent,
1965
- fontWeight: "600"
1966
- },
1967
- cardList: {
1968
- paddingBottom: 8,
1969
- gap: 8
1970
- },
1971
- card: {
1972
- borderWidth: 1,
1973
- borderColor: explorerColors.border,
1974
- borderRadius: 8,
1975
- backgroundColor: explorerColors.surface,
1976
- padding: 12,
1977
- gap: 10
1978
- },
1979
- cardField: {
1980
- gap: 2
1981
- },
1982
- cardLabel: {
1983
- fontSize: 11,
1984
- fontWeight: "600",
1985
- color: explorerColors.muted,
1986
- textTransform: "uppercase",
1987
- letterSpacing: 0.4
1988
- },
1989
- cardValue: {
1990
- fontSize: 14,
1991
- color: explorerColors.text
1992
- },
1993
- cardValueMono: {
1994
- fontFamily: "monospace",
1995
- fontSize: 13
1996
- },
1997
- detailBody: {
1998
- flex: 1,
1999
- minHeight: 0,
2000
- paddingHorizontal: 16
2001
- },
2002
- detailDataPane: {
2003
- flex: 1,
2004
- minHeight: 0
2005
- }
2006
- });
2007
- function formatBytes(bytes) {
2008
- if (bytes < 1024) {
2009
- return `${bytes} B`;
2010
- }
2011
- if (bytes < 1024 * 1024) {
2012
- return `${(bytes / 1024).toFixed(1)} KB`;
2013
- }
2014
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2015
- }
2016
- function ExplorerOverviewTab({
2017
- info,
2018
- loading,
2019
- error,
2020
- onOpenTables,
2021
- onOpenSql
2022
- }) {
2023
- if (loading) {
2024
- return /* @__PURE__ */ jsx(Text, { style: explorerStyles.placeholder, children: "Loading database info\u2026" });
2025
- }
2026
- if (error) {
2027
- return /* @__PURE__ */ jsx(Text, { style: explorerStyles.errorText, children: error });
2028
- }
2029
- if (!info) {
2030
- return /* @__PURE__ */ jsx(Text, { style: explorerStyles.placeholder, children: "No database information available." });
2031
- }
2032
- return /* @__PURE__ */ jsxs(ScrollView, { contentContainerStyle: explorerStyles.content, children: [
2033
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.sectionTitle, children: "Database" }),
2034
- /* @__PURE__ */ jsx(Info, { label: "Name", value: info.name }),
2035
- /* @__PURE__ */ jsx(Info, { label: "Path", value: info.path, mono: true }),
2036
- /* @__PURE__ */ jsx(Info, { label: "SQLite version", value: info.sqliteVersion }),
2037
- /* @__PURE__ */ jsx(Info, { label: "Tables", value: String(info.tableCount) }),
2038
- /* @__PURE__ */ jsx(Info, { label: "Estimated size", value: formatBytes(info.estimatedSizeBytes) }),
2039
- /* @__PURE__ */ jsx(
2040
- Info,
2041
- {
2042
- label: "Pages",
2043
- value: `${info.pageCount.toLocaleString()} \xD7 ${info.pageSize} bytes`
2044
- }
2045
- ),
2046
- /* @__PURE__ */ jsx(View, { style: { height: 16 } }),
2047
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.sectionTitle, children: "Quick actions" }),
2048
- /* @__PURE__ */ jsxs(View, { style: { gap: 8 }, children: [
2049
- /* @__PURE__ */ jsx(ActionButton, { label: "Browse tables", onPress: onOpenTables }),
2050
- /* @__PURE__ */ jsx(ActionButton, { label: "Run SQL query", onPress: onOpenSql })
2051
- ] })
2052
- ] });
2053
- }
2054
- function Info({ label, value, mono = false }) {
2055
- return /* @__PURE__ */ jsxs(View, { style: explorerStyles.infoRow, children: [
2056
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.infoLabel, children: label }),
2057
- /* @__PURE__ */ jsx(Text, { style: [explorerStyles.infoValue, mono && explorerStyles.mono], selectable: true, children: value })
2058
- ] });
2059
- }
2060
- function ActionButton({ label, onPress }) {
2061
- return /* @__PURE__ */ jsx(Pressable, { onPress, style: explorerStyles.ghostButton, children: /* @__PURE__ */ jsx(Text, { style: explorerStyles.ghostButtonLabel, children: label }) });
2062
- }
2063
-
2064
- // src/mobile/constants.ts
2065
- var DEFAULT_MOBILE_SQL = "";
2066
- var DEFAULT_PAGE_SIZE = 50;
2067
-
2068
- // src/mobile/computeColumnWidths.ts
2069
- var CHAR_WIDTH = 7.5;
2070
- var CELL_PADDING = 24;
2071
- var MIN_COLUMN_WIDTH = 72;
2072
- var MAX_COLUMN_WIDTH = 280;
2073
- var SAMPLE_ROW_LIMIT = 20;
2074
- function hintMinWidth(columnName) {
2075
- const lower = columnName.toLowerCase();
2076
- if (lower === "id" || lower.endsWith("_id")) {
2077
- return 80;
2078
- }
2079
- if (lower.includes("email")) {
2080
- return 168;
2081
- }
2082
- if (lower.includes("created_at") || lower.includes("updated_at") || lower.endsWith("_at")) {
2083
- return 188;
2084
- }
2085
- if (lower === "name" || lower.includes("title")) {
2086
- return 128;
2087
- }
2088
- if (lower === "status" || lower === "type") {
2089
- return 96;
2090
- }
2091
- return MIN_COLUMN_WIDTH;
2092
- }
2093
- function measureText(text) {
2094
- return Math.ceil(text.length * CHAR_WIDTH) + CELL_PADDING;
2095
- }
2096
- function formatCellValue(value) {
2097
- if (value === null) {
2098
- return "NULL";
2099
- }
2100
- return String(value);
2101
- }
2102
- function computeColumnWidths(columns, rows) {
2103
- const sampleRows = rows.slice(0, SAMPLE_ROW_LIMIT);
2104
- return columns.map((column, columnIndex) => {
2105
- let maxWidth = measureText(column);
2106
- for (const row of sampleRows) {
2107
- const cell = formatCellValue(row[columnIndex] ?? null);
2108
- maxWidth = Math.max(maxWidth, measureText(cell));
2109
- }
2110
- maxWidth = Math.max(maxWidth, hintMinWidth(column));
2111
- return Math.min(MAX_COLUMN_WIDTH, Math.max(MIN_COLUMN_WIDTH, maxWidth));
2112
- });
2113
- }
2114
- function totalTableWidth(columnWidths) {
2115
- return columnWidths.reduce((sum, width) => sum + width, 0);
2116
- }
2117
- function isMonospaceColumn(columnName) {
2118
- const lower = columnName.toLowerCase();
2119
- return lower === "id" || lower.endsWith("_id") || lower.includes("created_at") || lower.includes("updated_at") || lower.endsWith("_at") || lower.includes("date") || lower.includes("time");
2120
- }
2121
-
2122
- // src/mobile/copyToClipboard.ts
2123
- async function copyToClipboard(_text) {
2124
- return false;
2125
- }
2126
- function formatValue(value) {
2127
- return value === null ? "NULL" : String(value);
2128
- }
2129
- function MobileDataCardList({ columns, rows }) {
2130
- const [copiedKey, setCopiedKey] = useState(null);
2131
- const copyValue = async (value, key) => {
2132
- await copyToClipboard();
2133
- setCopiedKey(key);
2134
- setTimeout(() => setCopiedKey(null), 1500);
2135
- };
2136
- return /* @__PURE__ */ jsx(
2137
- FlatList,
2138
- {
2139
- data: rows,
2140
- keyExtractor: (_, index) => String(index),
2141
- contentContainerStyle: tableStyles.cardList,
2142
- style: tableStyles.bodyList,
2143
- renderItem: ({ item: row, index: rowIndex }) => /* @__PURE__ */ jsx(View, { style: tableStyles.card, children: columns.map((column, columnIndex) => {
2144
- const value = formatValue(row[columnIndex] ?? null);
2145
- const key = `${rowIndex}-${column}`;
2146
- const isNull = row[columnIndex] === null;
2147
- return /* @__PURE__ */ jsxs(
2148
- Pressable,
2149
- {
2150
- onPress: () => void copyValue(value, key),
2151
- style: tableStyles.cardField,
2152
- children: [
2153
- /* @__PURE__ */ jsx(Text, { style: tableStyles.cardLabel, children: column }),
2154
- /* @__PURE__ */ jsx(
2155
- Text,
2156
- {
2157
- style: [
2158
- tableStyles.cardValue,
2159
- isMonospaceColumn(column) && tableStyles.cardValueMono,
2160
- isNull && tableStyles.cellNull
2161
- ],
2162
- children: value
2163
- }
2164
- ),
2165
- copiedKey === key && /* @__PURE__ */ jsx(Text, { style: tableStyles.copiedHint, children: "Copied" })
2166
- ]
2167
- },
2168
- key
2169
- );
2170
- }) })
2171
- }
2172
- );
2173
- }
2174
- function formatValue2(value) {
2175
- return value === null ? "NULL" : String(value);
2176
- }
2177
- function MobileDataTable({ columns, rows, columnWidths: columnWidthsProp }) {
2178
- const { width: windowWidth } = useWindowDimensions();
2179
- const [copiedKey, setCopiedKey] = useState(null);
2180
- const columnWidths = useMemo(
2181
- () => columnWidthsProp ?? computeColumnWidths(columns, rows),
2182
- [columnWidthsProp, columns, rows]
2183
- );
2184
- const tableWidth = useMemo(() => totalTableWidth(columnWidths), [columnWidths]);
2185
- const showScrollHint = tableWidth > windowWidth - 32;
2186
- const copyCell = async (value, key) => {
2187
- await copyToClipboard();
2188
- setCopiedKey(key);
2189
- setTimeout(() => setCopiedKey(null), 1500);
2190
- };
2191
- if (columns.length === 0) {
2192
- return null;
2193
- }
2194
- const headerRow = /* @__PURE__ */ jsx(View, { style: tableStyles.headerRow, children: columns.map((column, index) => /* @__PURE__ */ jsx(
2195
- View,
2196
- {
2197
- style: [tableStyles.cell, tableStyles.headerCell, { width: columnWidths[index] }],
2198
- children: /* @__PURE__ */ jsx(Text, { style: tableStyles.headerText, children: column })
2199
- },
2200
- column
2201
- )) });
2202
- return /* @__PURE__ */ jsxs(View, { style: tableStyles.tableContainer, children: [
2203
- showScrollHint && /* @__PURE__ */ jsx(Text, { style: tableStyles.scrollHint, children: "Swipe horizontally for more columns" }),
2204
- /* @__PURE__ */ jsx(
2205
- ScrollView,
2206
- {
2207
- horizontal: true,
2208
- bounces: false,
2209
- nestedScrollEnabled: true,
2210
- showsHorizontalScrollIndicator: true,
2211
- style: tableStyles.horizontalScroll,
2212
- children: /* @__PURE__ */ jsxs(View, { style: { width: tableWidth, flex: 1 }, children: [
2213
- headerRow,
2214
- /* @__PURE__ */ jsx(
2215
- FlatList,
2216
- {
2217
- data: rows,
2218
- keyExtractor: (_, index) => String(index),
2219
- nestedScrollEnabled: true,
2220
- style: tableStyles.bodyList,
2221
- renderItem: ({ item: row, index: rowIndex }) => /* @__PURE__ */ jsx(View, { style: [tableStyles.dataRow, rowIndex % 2 === 1 && tableStyles.dataRowAlt], children: row.map((cell, cellIndex) => {
2222
- const column = columns[cellIndex];
2223
- const display = formatValue2(cell);
2224
- const key = `${rowIndex}-${cellIndex}`;
2225
- const isNull = cell === null;
2226
- return /* @__PURE__ */ jsxs(
2227
- Pressable,
2228
- {
2229
- onPress: () => void copyCell(display, key),
2230
- style: [tableStyles.cell, { width: columnWidths[cellIndex] }],
2231
- children: [
2232
- /* @__PURE__ */ jsx(
2233
- Text,
2234
- {
2235
- numberOfLines: 2,
2236
- selectable: true,
2237
- style: [
2238
- tableStyles.cellText,
2239
- isMonospaceColumn(column) && tableStyles.cellTextMono,
2240
- isNull && tableStyles.cellNull
2241
- ],
2242
- children: display
2243
- }
2244
- ),
2245
- copiedKey === key && /* @__PURE__ */ jsx(Text, { style: tableStyles.copiedHint, children: "Copied" })
2246
- ]
2247
- },
2248
- key
2249
- );
2250
- }) })
2251
- }
2252
- )
2253
- ] })
2254
- }
2255
- )
2256
- ] });
2257
- }
2258
- function MobileDataView({
2259
- columns,
2260
- rows,
2261
- metaSuffix,
2262
- defaultMode = "table",
2263
- dataKey
2264
- }) {
2265
- const [mode, setMode] = useState(defaultMode);
2266
- const columnWidths = useMemo(() => computeColumnWidths(columns, rows), [columns, rows]);
2267
- const tableWidth = useMemo(() => totalTableWidth(columnWidths), [columnWidths]);
2268
- const columnsKey = columns.join("|");
2269
- useEffect(() => {
2270
- setMode(defaultMode);
2271
- }, [columnsKey, dataKey, defaultMode, rows.length]);
2272
- if (columns.length === 0) {
2273
- return /* @__PURE__ */ jsx(Text, { style: explorerStyles.placeholder, children: "No rows to display." });
2274
- }
2275
- const meta = `${rows.length} row${rows.length === 1 ? "" : "s"} \xB7 ${columns.length} column${columns.length === 1 ? "" : "s"}${metaSuffix ? ` \xB7 ${metaSuffix}` : ""}`;
2276
- return /* @__PURE__ */ jsxs(View, { style: tableStyles.viewRoot, children: [
2277
- /* @__PURE__ */ jsxs(View, { style: tableStyles.viewToolbar, children: [
2278
- /* @__PURE__ */ jsx(Text, { style: tableStyles.viewMeta, children: meta }),
2279
- /* @__PURE__ */ jsxs(View, { style: tableStyles.viewToggle, children: [
2280
- /* @__PURE__ */ jsx(
2281
- Pressable,
2282
- {
2283
- onPress: () => setMode("cards"),
2284
- style: [tableStyles.viewToggleButton, mode === "cards" && tableStyles.viewToggleButtonActive],
2285
- children: /* @__PURE__ */ jsx(
2286
- Text,
2287
- {
2288
- style: [tableStyles.viewToggleLabel, mode === "cards" && tableStyles.viewToggleLabelActive],
2289
- children: "Cards"
2290
- }
2291
- )
2292
- }
2293
- ),
2294
- /* @__PURE__ */ jsx(
2295
- Pressable,
2296
- {
2297
- onPress: () => setMode("table"),
2298
- style: [tableStyles.viewToggleButton, mode === "table" && tableStyles.viewToggleButtonActive],
2299
- children: /* @__PURE__ */ jsx(
2300
- Text,
2301
- {
2302
- style: [tableStyles.viewToggleLabel, mode === "table" && tableStyles.viewToggleLabelActive],
2303
- children: "Table"
2304
- }
2305
- )
2306
- }
2307
- )
2308
- ] })
2309
- ] }),
2310
- mode === "cards" ? /* @__PURE__ */ jsx(MobileDataCardList, { columns, rows }) : /* @__PURE__ */ jsx(MobileDataTable, { columnWidths, columns, rows }),
2311
- mode === "table" && tableWidth > 320 && /* @__PURE__ */ jsx(Text, { style: tableStyles.footerHint, children: "Tap a cell to copy its value" }),
2312
- mode === "cards" && /* @__PURE__ */ jsx(Text, { style: tableStyles.footerHint, children: "Tap a field to copy its value" })
2313
- ] });
2314
- }
2315
- function ExplorerSqlTab({ inspector }) {
2316
- const [sql, setSql] = useState(DEFAULT_MOBILE_SQL);
2317
- const [result, setResult] = useState(null);
2318
- const [error, setError] = useState(null);
2319
- const [running, setRunning] = useState(false);
2320
- const [panel, setPanel] = useState("results");
2321
- const [resultKey, setResultKey] = useState(0);
2322
- const runQuery = async () => {
2323
- setRunning(true);
2324
- setError(null);
2325
- try {
2326
- const queryResult = await inspector.executeQuery(sql);
2327
- setResult(queryResult);
2328
- setResultKey((current) => current + 1);
2329
- setPanel("results");
2330
- } catch (runError) {
2331
- const message = runError instanceof Error ? runError.message : "Query failed";
2332
- setError(message);
2333
- setResult(null);
2334
- setPanel("output");
2335
- } finally {
2336
- setRunning(false);
2337
- }
2338
- };
2339
- const renderResultsContent = () => {
2340
- if (panel === "output") {
2341
- if (error) {
2342
- return /* @__PURE__ */ jsx(Text, { style: explorerStyles.errorText, children: error });
2343
- }
2344
- if (result) {
2345
- return /* @__PURE__ */ jsxs(Text, { style: explorerStyles.infoValue, children: [
2346
- "Query executed successfully. ",
2347
- result.rowCount,
2348
- " row",
2349
- result.rowCount === 1 ? "" : "s",
2350
- " in ",
2351
- result.durationMs.toFixed(1),
2352
- " ms."
2353
- ] });
2354
- }
2355
- return /* @__PURE__ */ jsx(Text, { style: explorerStyles.placeholder, children: "Messages and errors appear here." });
2356
- }
2357
- if (!result) {
2358
- return /* @__PURE__ */ jsx(View, { style: explorerStyles.sqlResultsEmpty, children: /* @__PURE__ */ jsx(Text, { style: [explorerStyles.placeholder, { marginTop: 0 }], children: "Run a query to see results." }) });
2359
- }
2360
- if (result.columns.length === 0) {
2361
- return /* @__PURE__ */ jsx(View, { style: explorerStyles.sqlResultsEmpty, children: /* @__PURE__ */ jsxs(Text, { style: [explorerStyles.placeholder, { marginTop: 0 }], children: [
2362
- "Query completed with no result set (",
2363
- result.durationMs.toFixed(1),
2364
- " ms)."
2365
- ] }) });
2366
- }
2367
- return /* @__PURE__ */ jsx(
2368
- MobileDataView,
2369
- {
2370
- columns: result.columns,
2371
- dataKey: String(resultKey),
2372
- defaultMode: "table",
2373
- metaSuffix: `${result.durationMs.toFixed(1)} ms`,
2374
- rows: result.rows
2375
- }
2376
- );
2377
- };
2378
- return /* @__PURE__ */ jsxs(View, { style: explorerStyles.sqlTabRoot, children: [
2379
- /* @__PURE__ */ jsxs(View, { style: explorerStyles.sqlQueryPane, children: [
2380
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.sectionTitle, children: "SQL (read-only)" }),
2381
- /* @__PURE__ */ jsx(
2382
- TextInput,
2383
- {
2384
- autoCapitalize: "none",
2385
- autoCorrect: false,
2386
- multiline: true,
2387
- onChangeText: setSql,
2388
- scrollEnabled: true,
2389
- style: explorerStyles.sqlInputCompact,
2390
- value: sql
2391
- }
2392
- ),
2393
- /* @__PURE__ */ jsx(
2394
- Pressable,
2395
- {
2396
- disabled: running,
2397
- onPress: () => void runQuery(),
2398
- style: [explorerStyles.primaryButton, running && explorerStyles.primaryButtonDisabled],
2399
- children: running ? /* @__PURE__ */ jsx(ActivityIndicator, { color: "#f8fafc" }) : /* @__PURE__ */ jsx(Text, { style: explorerStyles.primaryButtonLabel, children: "Run query" })
2400
- }
2401
- ),
2402
- /* @__PURE__ */ jsx(Text, { style: [explorerStyles.hintText, { marginTop: 0 }], children: "Allowed: SELECT, PRAGMA, EXPLAIN, WITH. Writes are blocked in DevTools." })
2403
- ] }),
2404
- /* @__PURE__ */ jsxs(View, { style: [explorerStyles.tabRow, { flexShrink: 0 }], children: [
2405
- /* @__PURE__ */ jsx(
2406
- Pressable,
2407
- {
2408
- onPress: () => setPanel("results"),
2409
- style: [explorerStyles.tab, panel === "results" && explorerStyles.tabActive],
2410
- children: /* @__PURE__ */ jsxs(
2411
- Text,
2412
- {
2413
- style: [explorerStyles.tabLabel, panel === "results" && explorerStyles.tabLabelActive],
2414
- children: [
2415
- "Results",
2416
- result ? ` (${result.rowCount})` : ""
2417
- ]
2418
- }
2419
- )
2420
- }
2421
- ),
2422
- /* @__PURE__ */ jsx(
2423
- Pressable,
2424
- {
2425
- onPress: () => setPanel("output"),
2426
- style: [explorerStyles.tab, panel === "output" && explorerStyles.tabActive],
2427
- children: /* @__PURE__ */ jsx(
2428
- Text,
2429
- {
2430
- style: [explorerStyles.tabLabel, panel === "output" && explorerStyles.tabLabelActive],
2431
- children: "Output"
2432
- }
2433
- )
2434
- }
2435
- )
2436
- ] }),
2437
- /* @__PURE__ */ jsx(View, { style: explorerStyles.sqlResultsPane, children: renderResultsContent() })
2438
- ] });
2439
- }
2440
- function ExplorerTableDetail({ inspector, tableName, onBack }) {
2441
- const [view, setView] = useState("data");
2442
- const [columns, setColumns] = useState([]);
2443
- const [pageResult, setPageResult] = useState(null);
2444
- const [search, setSearch] = useState("");
2445
- const [page, setPage] = useState(1);
2446
- const [loading, setLoading] = useState(true);
2447
- const [error, setError] = useState(null);
2448
- const loadData = useCallback(async () => {
2449
- setLoading(true);
2450
- setError(null);
2451
- try {
2452
- const nextColumns = await inspector.getTableColumns(tableName);
2453
- setColumns(nextColumns);
2454
- const result = await inspector.fetchTablePage({
2455
- table: tableName,
2456
- page,
2457
- pageSize: DEFAULT_PAGE_SIZE,
2458
- search
2459
- });
2460
- setPageResult(result);
2461
- } catch (loadError) {
2462
- setError(loadError instanceof Error ? loadError.message : "Failed to load table");
2463
- } finally {
2464
- setLoading(false);
2465
- }
2466
- }, [inspector, page, search, tableName]);
2467
- useEffect(() => {
2468
- void loadData();
2469
- }, [loadData]);
2470
- const totalPages = pageResult ? Math.max(1, Math.ceil(pageResult.totalCount / pageResult.pageSize)) : 1;
2471
- return /* @__PURE__ */ jsxs(View, { style: explorerStyles.fullScreen, children: [
2472
- /* @__PURE__ */ jsxs(View, { style: explorerStyles.header, children: [
2473
- /* @__PURE__ */ jsx(Pressable, { onPress: onBack, children: /* @__PURE__ */ jsx(Text, { style: explorerStyles.headerAction, children: "\u2190 Back" }) }),
2474
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.headerTitle, children: tableName }),
2475
- /* @__PURE__ */ jsx(View, { style: { width: 48 } })
2476
- ] }),
2477
- /* @__PURE__ */ jsxs(View, { style: [explorerStyles.subTabRow, { paddingHorizontal: 16, paddingTop: 12 }], children: [
2478
- /* @__PURE__ */ jsx(
2479
- Pressable,
2480
- {
2481
- onPress: () => setView("data"),
2482
- style: [explorerStyles.subTab, view === "data" && explorerStyles.subTabActive],
2483
- children: /* @__PURE__ */ jsx(Text, { style: [explorerStyles.subTabLabel, view === "data" && explorerStyles.subTabLabelActive], children: "Data" })
2484
- }
2485
- ),
2486
- /* @__PURE__ */ jsx(
2487
- Pressable,
2488
- {
2489
- onPress: () => setView("schema"),
2490
- style: [explorerStyles.subTab, view === "schema" && explorerStyles.subTabActive],
2491
- children: /* @__PURE__ */ jsx(
2492
- Text,
2493
- {
2494
- style: [explorerStyles.subTabLabel, view === "schema" && explorerStyles.subTabLabelActive],
2495
- children: "Schema"
2496
- }
2497
- )
2498
- }
2499
- )
2500
- ] }),
2501
- view === "data" ? /* @__PURE__ */ jsxs(View, { style: tableStyles.detailBody, children: [
2502
- /* @__PURE__ */ jsx(
2503
- TextInput,
2504
- {
2505
- autoCapitalize: "none",
2506
- autoCorrect: false,
2507
- onChangeText: (value) => {
2508
- setSearch(value);
2509
- setPage(1);
2510
- },
2511
- placeholder: "Search rows\u2026",
2512
- style: explorerStyles.searchInput,
2513
- value: search
2514
- }
2515
- ),
2516
- loading ? /* @__PURE__ */ jsx(ActivityIndicator, { style: { marginTop: 24 } }) : error ? /* @__PURE__ */ jsx(Text, { style: explorerStyles.errorText, children: error }) : pageResult ? /* @__PURE__ */ jsxs(Fragment, { children: [
2517
- /* @__PURE__ */ jsx(View, { style: tableStyles.detailDataPane, children: /* @__PURE__ */ jsx(
2518
- MobileDataView,
2519
- {
2520
- columns: pageResult.columns,
2521
- dataKey: `${tableName}-${page}-${search}`,
2522
- defaultMode: "table",
2523
- metaSuffix: `page ${page} of ${totalPages}`,
2524
- rows: pageResult.rows
2525
- }
2526
- ) }),
2527
- /* @__PURE__ */ jsxs(View, { style: explorerStyles.paginationRow, children: [
2528
- /* @__PURE__ */ jsx(Pressable, { disabled: page <= 1, onPress: () => setPage((current) => current - 1), children: /* @__PURE__ */ jsx(Text, { style: explorerStyles.headerAction, children: "Previous" }) }),
2529
- /* @__PURE__ */ jsxs(Text, { style: explorerStyles.paginationLabel, children: [
2530
- "Page ",
2531
- page,
2532
- " of ",
2533
- totalPages,
2534
- " \xB7 ",
2535
- pageResult.totalCount.toLocaleString(),
2536
- " rows"
2537
- ] }),
2538
- /* @__PURE__ */ jsx(
2539
- Pressable,
2540
- {
2541
- disabled: page >= totalPages,
2542
- onPress: () => setPage((current) => current + 1),
2543
- children: /* @__PURE__ */ jsx(Text, { style: explorerStyles.headerAction, children: "Next" })
2544
- }
2545
- )
2546
- ] })
2547
- ] }) : null
2548
- ] }) : /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: explorerStyles.content, children: columns.length === 0 ? /* @__PURE__ */ jsx(Text, { style: explorerStyles.placeholder, children: "No schema information." }) : columns.map((column) => /* @__PURE__ */ jsx(View, { style: explorerStyles.listItem, children: /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
2549
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.listItemTitle, children: column.name }),
2550
- /* @__PURE__ */ jsxs(Text, { style: explorerStyles.listItemMeta, children: [
2551
- column.type,
2552
- column.pk ? " \xB7 PK" : "",
2553
- column.notNull ? " \xB7 NOT NULL" : "",
2554
- column.defaultValue ? ` \xB7 DEFAULT ${column.defaultValue}` : ""
2555
- ] })
2556
- ] }) }, column.name)) })
2557
- ] });
2558
- }
2559
- function ExplorerTablesTab({
2560
- tables,
2561
- loading,
2562
- error,
2563
- onSelectTable
2564
- }) {
2565
- const [search, setSearch] = useState("");
2566
- const filtered = useMemo(() => {
2567
- const term = search.trim().toLowerCase();
2568
- if (!term) {
2569
- return tables;
2570
- }
2571
- return tables.filter((table) => table.name.toLowerCase().includes(term));
2572
- }, [search, tables]);
2573
- if (loading) {
2574
- return /* @__PURE__ */ jsx(Text, { style: explorerStyles.placeholder, children: "Loading tables\u2026" });
2575
- }
2576
- if (error) {
2577
- return /* @__PURE__ */ jsx(Text, { style: explorerStyles.errorText, children: error });
2578
- }
2579
- return /* @__PURE__ */ jsxs(View, { style: { flex: 1, padding: 16 }, children: [
2580
- /* @__PURE__ */ jsx(
2581
- TextInput,
2582
- {
2583
- autoCapitalize: "none",
2584
- autoCorrect: false,
2585
- onChangeText: setSearch,
2586
- placeholder: "Filter tables\u2026",
2587
- style: explorerStyles.searchInput,
2588
- value: search
2589
- }
2590
- ),
2591
- filtered.length === 0 ? /* @__PURE__ */ jsx(Text, { style: explorerStyles.placeholder, children: "No tables match your search." }) : /* @__PURE__ */ jsx(
2592
- FlatList,
2593
- {
2594
- data: filtered,
2595
- keyExtractor: (item) => item.name,
2596
- style: { flex: 1 },
2597
- renderItem: ({ item }) => /* @__PURE__ */ jsxs(Pressable, { onPress: () => onSelectTable(item.name), style: explorerStyles.listItem, children: [
2598
- /* @__PURE__ */ jsxs(View, { children: [
2599
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.listItemTitle, children: item.name }),
2600
- /* @__PURE__ */ jsxs(Text, { style: explorerStyles.listItemMeta, children: [
2601
- item.rowCount.toLocaleString(),
2602
- " row",
2603
- item.rowCount === 1 ? "" : "s"
2604
- ] })
2605
- ] }),
2606
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.listItemMeta, children: "\u203A" })
2607
- ] })
2608
- }
2609
- )
2610
- ] });
2611
- }
2612
- function MobileDatabaseExplorer() {
2613
- const { explorerVisible, closeExplorer, mobileInspector } = useDevTools();
2614
- const [tab, setTab] = useState("overview");
2615
- const [screen, setScreen] = useState({ kind: "tabs" });
2616
- const [info, setInfo] = useState(null);
2617
- const [tables, setTables] = useState([]);
2618
- const [loading, setLoading] = useState(false);
2619
- const [error, setError] = useState(null);
2620
- const loadOverview = useCallback(async () => {
2621
- if (!mobileInspector) {
2622
- return;
2623
- }
2624
- setLoading(true);
2625
- setError(null);
2626
- try {
2627
- const [nextInfo, nextTables] = await Promise.all([
2628
- mobileInspector.getDatabaseInfo(),
2629
- mobileInspector.listTables()
2630
- ]);
2631
- setInfo(nextInfo);
2632
- setTables(nextTables);
2633
- } catch (loadError) {
2634
- setError(loadError instanceof Error ? loadError.message : "Failed to load database");
2635
- } finally {
2636
- setLoading(false);
2637
- }
2638
- }, [mobileInspector]);
2639
- useEffect(() => {
2640
- if (!explorerVisible) {
2641
- setScreen({ kind: "tabs" });
2642
- setTab("overview");
2643
- return;
2644
- }
2645
- void loadOverview();
2646
- }, [explorerVisible, loadOverview]);
2647
- if (!mobileInspector) {
2648
- return null;
2649
- }
2650
- return /* @__PURE__ */ jsx(Modal, { animationType: "slide", onRequestClose: closeExplorer, visible: explorerVisible, children: screen.kind === "table" ? /* @__PURE__ */ jsx(
2651
- ExplorerTableDetail,
2652
- {
2653
- inspector: mobileInspector,
2654
- onBack: () => setScreen({ kind: "tabs" }),
2655
- tableName: screen.tableName
2656
- }
2657
- ) : /* @__PURE__ */ jsxs(View, { style: explorerStyles.fullScreen, children: [
2658
- /* @__PURE__ */ jsxs(View, { style: explorerStyles.header, children: [
2659
- /* @__PURE__ */ jsx(Text, { style: explorerStyles.headerTitle, children: "Database Explorer" }),
2660
- /* @__PURE__ */ jsx(Pressable, { accessibilityLabel: "Close database explorer", onPress: closeExplorer, children: /* @__PURE__ */ jsx(Text, { style: explorerStyles.headerAction, children: "Close" }) })
2661
- ] }),
2662
- /* @__PURE__ */ jsx(View, { style: explorerStyles.tabRow, children: ["overview", "tables", "sql"].map((item) => /* @__PURE__ */ jsx(
2663
- Pressable,
2664
- {
2665
- onPress: () => setTab(item),
2666
- style: [explorerStyles.tab, tab === item && explorerStyles.tabActive],
2667
- children: /* @__PURE__ */ jsx(
2668
- Text,
2669
- {
2670
- style: [explorerStyles.tabLabel, tab === item && explorerStyles.tabLabelActive],
2671
- children: item === "overview" ? "Overview" : item === "tables" ? "Tables" : "SQL"
2672
- }
2673
- )
2674
- },
2675
- item
2676
- )) }),
2677
- /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
2678
- tab === "overview" && /* @__PURE__ */ jsx(
2679
- ExplorerOverviewTab,
2680
- {
2681
- error,
2682
- info,
2683
- loading,
2684
- onOpenSql: () => setTab("sql"),
2685
- onOpenTables: () => setTab("tables")
2686
- }
2687
- ),
2688
- tab === "tables" && /* @__PURE__ */ jsx(
2689
- ExplorerTablesTab,
2690
- {
2691
- error,
2692
- loading,
2693
- onSelectTable: (tableName) => setScreen({ kind: "table", tableName }),
2694
- tables
2695
- }
2696
- ),
2697
- tab === "sql" && /* @__PURE__ */ jsx(ExplorerSqlTab, { inspector: mobileInspector })
2698
- ] })
2699
- ] }) });
2700
- }
2701
- function DatabaseDevTools({
2702
- database,
2703
- type,
2704
- adapter,
2705
- serverUrl,
2706
- enabled,
2707
- position = "bottom-right",
2708
- draggable = true,
2709
- snapToEdges = true,
2710
- floatingPosition,
2711
- onFloatingPositionChange,
2712
- style,
2713
- onConnectionStateChange
2714
- }) {
2715
- if (!isDevToolsEnabled(enabled)) {
2716
- return null;
2717
- }
2718
- return /* @__PURE__ */ jsx(
2719
- DevToolsProvider,
2720
- {
2721
- adapter,
2722
- database,
2723
- onConnectionStateChange,
2724
- serverUrl,
2725
- type,
2726
- children: /* @__PURE__ */ jsxs(View, { pointerEvents: "box-none", style: StyleSheet.absoluteFill, children: [
2727
- /* @__PURE__ */ jsx(
2728
- FloatingDevToolsButton,
2729
- {
2730
- draggable,
2731
- floatingPosition,
2732
- iconStyle: style,
2733
- onFloatingPositionChange,
2734
- position,
2735
- snapToEdges
2736
- }
2737
- ),
2738
- /* @__PURE__ */ jsx(DevToolsLauncherModal, {}),
2739
- /* @__PURE__ */ jsx(DevToolsSettingsModal, {}),
2740
- /* @__PURE__ */ jsx(MobileDatabaseExplorer, {})
2741
- ] })
2742
- }
2743
- );
2744
- }
2745
-
2746
- export { DatabaseDevTools, handleBeginTransaction, handleCommitTransaction, handleExecuteWrite, handleRollbackTransaction, isEditableDatabaseAdapter, isWritableDatabaseAdapter };
11
+ import './chunk-MXVIGMVT.js';
12
+ export { InspectorRegistry, createInspectorForSnapshot, getInspectorRegistry, resetInspectorRegistry } from './chunk-2JGL2JVD.js';
2747
13
  //# sourceMappingURL=index.js.map
2748
14
  //# sourceMappingURL=index.js.map