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
@@ -0,0 +1,3933 @@
1
+ 'use strict';
2
+
3
+ var reactNative = require('react-native');
4
+ var react = require('react');
5
+ var Constants = require('expo-constants');
6
+ var AsyncStorage = require('@react-native-async-storage/async-storage');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+ var MaterialCommunityIcons = require('@expo/vector-icons/MaterialCommunityIcons');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var Constants__default = /*#__PURE__*/_interopDefault(Constants);
13
+ var AsyncStorage__default = /*#__PURE__*/_interopDefault(AsyncStorage);
14
+ var MaterialCommunityIcons__default = /*#__PURE__*/_interopDefault(MaterialCommunityIcons);
15
+
16
+ // src/types/protocol.ts
17
+ var DevToolsRole = {
18
+ MOBILE: "mobile",
19
+ BROWSER: "browser"
20
+ };
21
+ var MessageType = {
22
+ REGISTER: "register",
23
+ PING: "ping",
24
+ PONG: "pong",
25
+ DEVICE_STATUS: "deviceStatus",
26
+ BROADCAST: "broadcast",
27
+ REFRESH_REQUEST: "refreshRequest",
28
+ SNAPSHOT_UPLOAD_REQUESTED: "snapshotUploadRequested",
29
+ REFRESH_STATUS: "refreshStatus",
30
+ SNAPSHOT_READY: "snapshotReady",
31
+ REFRESH_ERROR: "refreshError",
32
+ EXPORT_SNAPSHOT_REQUEST: "exportSnapshotRequest",
33
+ EXPORT_SNAPSHOT_ERROR: "exportSnapshotError",
34
+ BEGIN_TRANSACTION_REQUEST: "beginTransactionRequest",
35
+ COMMIT_TRANSACTION_REQUEST: "commitTransactionRequest",
36
+ ROLLBACK_TRANSACTION_REQUEST: "rollbackTransactionRequest",
37
+ WRITE_REQUEST: "writeRequest",
38
+ BEGIN_TRANSACTION: "beginTransaction",
39
+ COMMIT_TRANSACTION: "commitTransaction",
40
+ ROLLBACK_TRANSACTION: "rollbackTransaction",
41
+ EXECUTE_WRITE: "executeWrite",
42
+ TRANSACTION_ACK: "transactionAck",
43
+ WRITE_ACK: "writeAck",
44
+ TRANSACTION_STATUS: "transactionStatus",
45
+ WRITE_RESULT: "writeResult",
46
+ WRITE_ERROR: "writeError"
47
+ };
48
+ var DEVTOOLS_WS_PATH = "/ws";
49
+ var DEFAULT_DEVTOOLS_PORT = 3847;
50
+ var DEFAULT_DEVTOOLS_HOST = "0.0.0.0";
51
+ var HEARTBEAT_INTERVAL_MS = 3e4;
52
+ var HEARTBEAT_TIMEOUT_MS = 1e4;
53
+ var DEVICE_SNAPSHOT_API_PATH = "/api/devices";
54
+ var SNAPSHOT_API_PATH = DEVICE_SNAPSHOT_API_PATH;
55
+ var REFRESH_TIMEOUT_MS = 6e4;
56
+ var SYNC_TIMEOUT_MS = REFRESH_TIMEOUT_MS;
57
+ var MAX_SNAPSHOT_BYTES = 50 * 1024 * 1024;
58
+ function createMessage(message) {
59
+ return { ...message, timestamp: Date.now() };
60
+ }
61
+ function buildDevToolsHttpUrl(host = "localhost", port = DEFAULT_DEVTOOLS_PORT) {
62
+ return `http://${host}:${port}`;
63
+ }
64
+ function buildDevToolsWsUrl(host = "localhost", port = DEFAULT_DEVTOOLS_PORT) {
65
+ return `ws://${host}:${port}${DEVTOOLS_WS_PATH}`;
66
+ }
67
+ function buildDeviceSnapshotUrl(httpBaseUrl, deviceId) {
68
+ const base = httpBaseUrl.replace(/\/$/, "");
69
+ return `${base}${DEVICE_SNAPSHOT_API_PATH}/${encodeURIComponent(deviceId)}/snapshot`;
70
+ }
71
+ function buildSnapshotUrl(httpBaseUrl, deviceId) {
72
+ return buildDeviceSnapshotUrl(httpBaseUrl, deviceId);
73
+ }
74
+ function wsUrlToHttpUrl(wsUrl) {
75
+ const url = new URL(wsUrl);
76
+ url.protocol = url.protocol === "wss:" ? "https:" : "http:";
77
+ url.pathname = "";
78
+ url.search = "";
79
+ url.hash = "";
80
+ return url.toString().replace(/\/$/, "");
81
+ }
82
+ function hasMessageType(value) {
83
+ return typeof value === "object" && value !== null && typeof value.type === "string";
84
+ }
85
+ function isRegisterMessage(value) {
86
+ if (!hasMessageType(value) || value.type !== MessageType.REGISTER) {
87
+ return false;
88
+ }
89
+ const message = value;
90
+ return message.role === DevToolsRole.MOBILE || message.role === DevToolsRole.BROWSER;
91
+ }
92
+ function isPongMessage(value) {
93
+ if (!hasMessageType(value) || value.type !== MessageType.PONG) {
94
+ return false;
95
+ }
96
+ const message = value;
97
+ return typeof message.pingId === "string";
98
+ }
99
+ function isRefreshRequestMessage(value) {
100
+ if (!hasMessageType(value) || value.type !== MessageType.REFRESH_REQUEST) {
101
+ return false;
102
+ }
103
+ const message = value;
104
+ return typeof message.deviceId === "string" && message.refreshType === "snapshot";
105
+ }
106
+ function isSnapshotUploadRequestedMessage(value) {
107
+ if (!hasMessageType(value) || value.type !== MessageType.SNAPSHOT_UPLOAD_REQUESTED) {
108
+ return false;
109
+ }
110
+ const message = value;
111
+ return typeof message.deviceId === "string" && message.refreshType === "snapshot";
112
+ }
113
+ function isRefreshStatusMessage(value) {
114
+ if (!hasMessageType(value) || value.type !== MessageType.REFRESH_STATUS) {
115
+ return false;
116
+ }
117
+ const message = value;
118
+ return typeof message.deviceId === "string" && typeof message.state === "string";
119
+ }
120
+ function isSnapshotReadyMessage(value) {
121
+ if (!hasMessageType(value) || value.type !== MessageType.SNAPSHOT_READY) {
122
+ return false;
123
+ }
124
+ const message = value;
125
+ return typeof message.deviceId === "string" && typeof message.size === "number" && typeof message.exportedAt === "number" && typeof message.kind === "string" && typeof message.mimeType === "string";
126
+ }
127
+ function isRefreshErrorMessage(value) {
128
+ if (!hasMessageType(value) || value.type !== MessageType.REFRESH_ERROR) {
129
+ return false;
130
+ }
131
+ const message = value;
132
+ return typeof message.deviceId === "string" && typeof message.code === "string";
133
+ }
134
+ function isExportSnapshotRequestMessage(value) {
135
+ if (!hasMessageType(value) || value.type !== MessageType.EXPORT_SNAPSHOT_REQUEST) {
136
+ return false;
137
+ }
138
+ const message = value;
139
+ return message.refreshType === "snapshot";
140
+ }
141
+ function isExportSnapshotErrorMessage(value) {
142
+ if (!hasMessageType(value) || value.type !== MessageType.EXPORT_SNAPSHOT_ERROR) {
143
+ return false;
144
+ }
145
+ const message = value;
146
+ return typeof message.code === "string" && typeof message.message === "string";
147
+ }
148
+ function isBeginTransactionRequestMessage(value) {
149
+ if (!hasMessageType(value) || value.type !== MessageType.BEGIN_TRANSACTION_REQUEST) {
150
+ return false;
151
+ }
152
+ const message = value;
153
+ return typeof message.transactionId === "string" && typeof message.deviceId === "string";
154
+ }
155
+ function isCommitTransactionRequestMessage(value) {
156
+ if (!hasMessageType(value) || value.type !== MessageType.COMMIT_TRANSACTION_REQUEST) {
157
+ return false;
158
+ }
159
+ const message = value;
160
+ return typeof message.transactionId === "string" && typeof message.deviceId === "string";
161
+ }
162
+ function isRollbackTransactionRequestMessage(value) {
163
+ if (!hasMessageType(value) || value.type !== MessageType.ROLLBACK_TRANSACTION_REQUEST) {
164
+ return false;
165
+ }
166
+ const message = value;
167
+ return typeof message.transactionId === "string" && typeof message.deviceId === "string";
168
+ }
169
+ function isWriteRequestMessage(value) {
170
+ if (!hasMessageType(value) || value.type !== MessageType.WRITE_REQUEST) {
171
+ return false;
172
+ }
173
+ const message = value;
174
+ return typeof message.writeId === "string" && typeof message.transactionId === "string" && typeof message.deviceId === "string" && typeof message.operation === "object" && message.operation !== null;
175
+ }
176
+ function isTransactionAckMessage(value) {
177
+ if (!hasMessageType(value) || value.type !== MessageType.TRANSACTION_ACK) {
178
+ return false;
179
+ }
180
+ const message = value;
181
+ return typeof message.transactionId === "string" && (message.action === "begin" || message.action === "commit" || message.action === "rollback") && typeof message.ok === "boolean";
182
+ }
183
+ function isWriteAckMessage(value) {
184
+ if (!hasMessageType(value) || value.type !== MessageType.WRITE_ACK) {
185
+ return false;
186
+ }
187
+ const message = value;
188
+ return typeof message.writeId === "string" && typeof message.transactionId === "string" && typeof message.ok === "boolean";
189
+ }
190
+ function isClientMessage(value) {
191
+ return isRegisterMessage(value) || isPongMessage(value) || isRefreshRequestMessage(value) || isExportSnapshotRequestMessage(value) || isBeginTransactionRequestMessage(value) || isCommitTransactionRequestMessage(value) || isRollbackTransactionRequestMessage(value) || isWriteRequestMessage(value) || isTransactionAckMessage(value) || isWriteAckMessage(value);
192
+ }
193
+ function isPingMessage(value) {
194
+ if (!hasMessageType(value) || value.type !== MessageType.PING) {
195
+ return false;
196
+ }
197
+ const message = value;
198
+ return typeof message.pingId === "string";
199
+ }
200
+ function isDeviceStatusMessage(value) {
201
+ if (!hasMessageType(value) || value.type !== MessageType.DEVICE_STATUS) {
202
+ return false;
203
+ }
204
+ const message = value;
205
+ return typeof message.payload === "object" && message.payload !== null;
206
+ }
207
+ function isBroadcastMessage(value) {
208
+ if (!hasMessageType(value) || value.type !== MessageType.BROADCAST) {
209
+ return false;
210
+ }
211
+ return true;
212
+ }
213
+ function isBeginTransactionMessage(value) {
214
+ if (!hasMessageType(value) || value.type !== MessageType.BEGIN_TRANSACTION) {
215
+ return false;
216
+ }
217
+ const message = value;
218
+ return typeof message.transactionId === "string";
219
+ }
220
+ function isCommitTransactionMessage(value) {
221
+ if (!hasMessageType(value) || value.type !== MessageType.COMMIT_TRANSACTION) {
222
+ return false;
223
+ }
224
+ const message = value;
225
+ return typeof message.transactionId === "string";
226
+ }
227
+ function isRollbackTransactionMessage(value) {
228
+ if (!hasMessageType(value) || value.type !== MessageType.ROLLBACK_TRANSACTION) {
229
+ return false;
230
+ }
231
+ const message = value;
232
+ return typeof message.transactionId === "string";
233
+ }
234
+ function isExecuteWriteMessage(value) {
235
+ if (!hasMessageType(value) || value.type !== MessageType.EXECUTE_WRITE) {
236
+ return false;
237
+ }
238
+ const message = value;
239
+ return typeof message.writeId === "string" && typeof message.transactionId === "string" && typeof message.operation === "object" && message.operation !== null;
240
+ }
241
+ function isTransactionStatusMessage(value) {
242
+ if (!hasMessageType(value) || value.type !== MessageType.TRANSACTION_STATUS) {
243
+ return false;
244
+ }
245
+ const message = value;
246
+ return typeof message.transactionId === "string" && typeof message.deviceId === "string" && typeof message.state === "string";
247
+ }
248
+ function isWriteResultMessage(value) {
249
+ if (!hasMessageType(value) || value.type !== MessageType.WRITE_RESULT) {
250
+ return false;
251
+ }
252
+ const message = value;
253
+ return typeof message.writeId === "string" && typeof message.transactionId === "string" && typeof message.rowsAffected === "number";
254
+ }
255
+ function isWriteErrorMessage(value) {
256
+ if (!hasMessageType(value) || value.type !== MessageType.WRITE_ERROR) {
257
+ return false;
258
+ }
259
+ const message = value;
260
+ return typeof message.transactionId === "string" && typeof message.code === "string";
261
+ }
262
+ function isServerMessage(value) {
263
+ return isPingMessage(value) || isDeviceStatusMessage(value) || isBroadcastMessage(value) || isSnapshotUploadRequestedMessage(value) || isRefreshStatusMessage(value) || isSnapshotReadyMessage(value) || isRefreshErrorMessage(value) || isExportSnapshotErrorMessage(value) || isBeginTransactionMessage(value) || isCommitTransactionMessage(value) || isRollbackTransactionMessage(value) || isExecuteWriteMessage(value) || isTransactionStatusMessage(value) || isWriteResultMessage(value) || isWriteErrorMessage(value);
264
+ }
265
+
266
+ // src/types/adapter.ts
267
+ function isWritableDatabaseAdapter(adapter) {
268
+ if (!adapter?.capabilities.transactionalWrites) {
269
+ return false;
270
+ }
271
+ const candidate = adapter;
272
+ return typeof candidate.beginTransaction === "function" && typeof candidate.commitTransaction === "function" && typeof candidate.rollbackTransaction === "function" && typeof candidate.executeWrite === "function";
273
+ }
274
+ var isEditableDatabaseAdapter = isWritableDatabaseAdapter;
275
+
276
+ // src/types/kind.ts
277
+ var DATABASE_DEVTOOLS_KIND = /* @__PURE__ */ Symbol.for("@database-devtools/kind");
278
+ function readDatabaseKindMarker(value) {
279
+ if (typeof value !== "object" || value === null) {
280
+ return void 0;
281
+ }
282
+ const marker = value[DATABASE_DEVTOOLS_KIND];
283
+ return typeof marker === "string" ? marker : void 0;
284
+ }
285
+
286
+ // src/types/snapshot.ts
287
+ var SNAPSHOT_KIND_HEADER = "x-database-kind";
288
+ var SNAPSHOT_MIME_HEADER = "x-snapshot-mime-type";
289
+ var SNAPSHOT_NAME_HEADER = "x-database-name";
290
+ var SQLITE_SNAPSHOT_MIME_TYPE = "application/x-sqlite3";
291
+
292
+ // src/adapter/errors.ts
293
+ var AdapterResolutionError = class extends Error {
294
+ constructor(supported, hint) {
295
+ super(formatAdapterResolutionMessage(supported, hint));
296
+ this.supported = supported;
297
+ this.hint = hint;
298
+ }
299
+ supported;
300
+ hint;
301
+ name = "AdapterResolutionError";
302
+ };
303
+ function formatAdapterResolutionMessage(supported, hint) {
304
+ const lines = [
305
+ "Unable to determine the database type.",
306
+ "",
307
+ "Supported databases:",
308
+ ...supported.map((entry) => ` \u2022 ${entry.displayName}`),
309
+ "",
310
+ "Try specifying a type:",
311
+ ' <DatabaseDevTools database={db} type="sqlite" />',
312
+ "",
313
+ "Or provide a custom adapter:",
314
+ " <DatabaseDevTools database={db} adapter={myAdapter} />"
315
+ ];
316
+ if (hint === "install-package") {
317
+ lines.push(
318
+ "",
319
+ "For SQLite, install expo-sqlite:",
320
+ " npm install expo-sqlite"
321
+ );
322
+ }
323
+ return lines.join("\n");
324
+ }
325
+
326
+ // src/adapter/registry.ts
327
+ var AdapterRegistry = class {
328
+ definitions = /* @__PURE__ */ new Map();
329
+ register(definition) {
330
+ this.definitions.set(definition.kind, definition);
331
+ }
332
+ get(kind) {
333
+ return this.definitions.get(kind);
334
+ }
335
+ listSupported() {
336
+ return [...this.definitions.values()].sort((left, right) => left.displayName.localeCompare(right.displayName)).map((definition) => ({
337
+ kind: definition.kind,
338
+ displayName: definition.displayName
339
+ }));
340
+ }
341
+ detect(database) {
342
+ const markerKind = readDatabaseKindMarker(database);
343
+ if (markerKind) {
344
+ const marked = this.definitions.get(markerKind);
345
+ if (marked) {
346
+ return marked;
347
+ }
348
+ }
349
+ const matches = [...this.definitions.values()].filter((definition) => {
350
+ try {
351
+ return definition.detect(database);
352
+ } catch {
353
+ return false;
354
+ }
355
+ }).sort((left, right) => right.priority - left.priority);
356
+ return matches[0];
357
+ }
358
+ };
359
+ var globalRegistry = null;
360
+ function getAdapterRegistry() {
361
+ if (!globalRegistry) {
362
+ globalRegistry = new AdapterRegistry();
363
+ }
364
+ return globalRegistry;
365
+ }
366
+
367
+ // src/adapters/sqlite/buildWriteSql.ts
368
+ var IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
369
+ function quoteIdentifier(name) {
370
+ if (!IDENTIFIER_PATTERN.test(name)) {
371
+ throw new Error(`Invalid identifier: ${name}`);
372
+ }
373
+ return `"${name.replace(/"/g, '""')}"`;
374
+ }
375
+ function buildInsertSql(operation) {
376
+ const columns = Object.keys(operation.values);
377
+ if (columns.length === 0) {
378
+ throw new Error("Insert requires at least one column value");
379
+ }
380
+ const quotedColumns = columns.map(quoteIdentifier).join(", ");
381
+ const placeholders = columns.map(() => "?").join(", ");
382
+ const sql = `INSERT INTO ${quoteIdentifier(operation.table)} (${quotedColumns}) VALUES (${placeholders})`;
383
+ const params = columns.map((column) => operation.values[column] ?? null);
384
+ return { sql, params };
385
+ }
386
+ function buildUpdateSql(operation) {
387
+ const valueColumns = Object.keys(operation.values);
388
+ const pkColumns = Object.keys(operation.primaryKey);
389
+ if (valueColumns.length === 0) {
390
+ throw new Error("Update requires at least one column value");
391
+ }
392
+ if (pkColumns.length === 0) {
393
+ throw new Error("Update requires a primary key");
394
+ }
395
+ const setClause = valueColumns.map((column) => `${quoteIdentifier(column)} = ?`).join(", ");
396
+ const whereClause = pkColumns.map((column) => `${quoteIdentifier(column)} = ?`).join(" AND ");
397
+ const sql = `UPDATE ${quoteIdentifier(operation.table)} SET ${setClause} WHERE ${whereClause}`;
398
+ const params = [
399
+ ...valueColumns.map((column) => operation.values[column] ?? null),
400
+ ...pkColumns.map((column) => operation.primaryKey[column] ?? null)
401
+ ];
402
+ return { sql, params };
403
+ }
404
+ function buildDeleteSql(operation) {
405
+ const pkColumns = Object.keys(operation.primaryKey);
406
+ if (pkColumns.length === 0) {
407
+ throw new Error("Delete requires a primary key");
408
+ }
409
+ const whereClause = pkColumns.map((column) => `${quoteIdentifier(column)} = ?`).join(" AND ");
410
+ const sql = `DELETE FROM ${quoteIdentifier(operation.table)} WHERE ${whereClause}`;
411
+ const params = pkColumns.map((column) => operation.primaryKey[column] ?? null);
412
+ return { sql, params };
413
+ }
414
+
415
+ // src/adapters/sqlite/createExpoSqliteAdapter.ts
416
+ function createExpoSqliteAdapter(options) {
417
+ const { database } = options;
418
+ let inTransaction = false;
419
+ let operationQueue = Promise.resolve();
420
+ const enqueue = async (operation) => {
421
+ const next = operationQueue.then(operation);
422
+ operationQueue = next.then(
423
+ () => void 0,
424
+ () => void 0
425
+ );
426
+ return next;
427
+ };
428
+ const assertTransaction = () => {
429
+ if (!inTransaction) {
430
+ throw new Error("No open transaction. Call beginTransaction() first.");
431
+ }
432
+ };
433
+ const runWrite = async (operation) => {
434
+ assertTransaction();
435
+ let sql;
436
+ let params;
437
+ switch (operation.kind) {
438
+ case "insert": {
439
+ const built = buildInsertSql(operation);
440
+ sql = built.sql;
441
+ params = built.params;
442
+ break;
443
+ }
444
+ case "update": {
445
+ const built = buildUpdateSql(operation);
446
+ sql = built.sql;
447
+ params = built.params;
448
+ break;
449
+ }
450
+ case "delete": {
451
+ const built = buildDeleteSql(operation);
452
+ sql = built.sql;
453
+ params = built.params;
454
+ break;
455
+ }
456
+ default: {
457
+ const exhaustive = operation;
458
+ throw new Error(`Unsupported write operation: ${exhaustive.kind}`);
459
+ }
460
+ }
461
+ const result = await database.runAsync(sql, params);
462
+ return { rowsAffected: result.changes };
463
+ };
464
+ return {
465
+ kind: "sqlite",
466
+ dialect: "sqlite",
467
+ id: options.id ?? database.databasePath,
468
+ name: options.name ?? "SQLite",
469
+ capabilities: {
470
+ exportSnapshot: true,
471
+ executeQuery: false,
472
+ listTables: false,
473
+ getSchema: false,
474
+ transactionalWrites: true,
475
+ observeChanges: false,
476
+ importSnapshot: false
477
+ },
478
+ async exportSnapshot() {
479
+ return enqueue(async () => {
480
+ if (inTransaction) {
481
+ throw new Error("Cannot export while a write transaction is open");
482
+ }
483
+ await database.execAsync("PRAGMA wal_checkpoint(FULL)");
484
+ const bytes = await database.serializeAsync();
485
+ return {
486
+ bytes,
487
+ mimeType: SQLITE_SNAPSHOT_MIME_TYPE,
488
+ kind: "sqlite",
489
+ exportedAt: Date.now()
490
+ };
491
+ });
492
+ },
493
+ async beginTransaction() {
494
+ return enqueue(async () => {
495
+ if (inTransaction) {
496
+ throw new Error("Transaction already open");
497
+ }
498
+ await database.execAsync("BEGIN IMMEDIATE");
499
+ inTransaction = true;
500
+ });
501
+ },
502
+ async commitTransaction() {
503
+ return enqueue(async () => {
504
+ assertTransaction();
505
+ try {
506
+ await database.execAsync("COMMIT");
507
+ } finally {
508
+ inTransaction = false;
509
+ }
510
+ });
511
+ },
512
+ async rollbackTransaction() {
513
+ return enqueue(async () => {
514
+ if (!inTransaction) {
515
+ return;
516
+ }
517
+ try {
518
+ await database.execAsync("ROLLBACK");
519
+ } finally {
520
+ inTransaction = false;
521
+ }
522
+ });
523
+ },
524
+ executeWrite: (operation) => enqueue(() => runWrite(operation))
525
+ };
526
+ }
527
+
528
+ // src/adapters/sqlite/detect.ts
529
+ function detectExpoSqlite(database) {
530
+ return typeof database === "object" && database !== null && typeof database.execAsync === "function" && typeof database.serializeAsync === "function" && typeof database.runAsync === "function" && typeof database.databasePath === "string";
531
+ }
532
+ function detectExpoSqliteInspectable(database) {
533
+ return detectExpoSqlite(database) && typeof database.getAllAsync === "function" && typeof database.getFirstAsync === "function";
534
+ }
535
+
536
+ // src/adapters/sqlite/register.ts
537
+ var sqliteAdapterDefinition = {
538
+ kind: "sqlite",
539
+ displayName: "SQLite",
540
+ priority: 100,
541
+ detect: detectExpoSqlite,
542
+ create: (database) => createExpoSqliteAdapter({
543
+ database,
544
+ name: database.databasePath
545
+ })
546
+ };
547
+ function registerSqliteAdapter() {
548
+ getAdapterRegistry().register(sqliteAdapterDefinition);
549
+ }
550
+
551
+ // src/adapter/registerBuiltins.ts
552
+ async function registerBuiltInAdapters() {
553
+ registerSqliteAdapter();
554
+ }
555
+
556
+ // src/adapter/resolveAdapter.ts
557
+ async function resolveAdapter(database, options = {}) {
558
+ if (options.adapter) {
559
+ return options.adapter;
560
+ }
561
+ if (!database) {
562
+ throw new AdapterResolutionError([], "adapter");
563
+ }
564
+ await registerBuiltInAdapters();
565
+ const registry = getAdapterRegistry();
566
+ if (options.type) {
567
+ const definition = registry.get(options.type);
568
+ if (!definition) {
569
+ throw new AdapterResolutionError(registry.listSupported(), "type");
570
+ }
571
+ return definition.create(database);
572
+ }
573
+ const detected = registry.detect(database);
574
+ if (!detected) {
575
+ throw new AdapterResolutionError(
576
+ registry.listSupported(),
577
+ registry.listSupported().length === 0 ? "install-package" : "type"
578
+ );
579
+ }
580
+ return detected.create(database);
581
+ }
582
+
583
+ // src/inspector/registry.ts
584
+ var InspectorRegistry = class {
585
+ definitions = /* @__PURE__ */ new Map();
586
+ register(definition) {
587
+ this.definitions.set(definition.kind, definition);
588
+ }
589
+ get(kind) {
590
+ return this.definitions.get(kind);
591
+ }
592
+ listSupported() {
593
+ return [...this.definitions.values()].sort((left, right) => left.displayName.localeCompare(right.displayName)).map((definition) => ({
594
+ kind: definition.kind,
595
+ displayName: definition.displayName
596
+ }));
597
+ }
598
+ detectFromBytes(bytes) {
599
+ return [...this.definitions.values()].find((definition) => definition.canOpenBytes?.(bytes));
600
+ }
601
+ detectFromSnapshot(input) {
602
+ const byKind = this.definitions.get(input.kind);
603
+ if (byKind) {
604
+ return byKind;
605
+ }
606
+ return [...this.definitions.values()].find(
607
+ (definition) => definition.mimeTypes.includes(input.mimeType)
608
+ );
609
+ }
610
+ };
611
+ var globalInspectorRegistry = null;
612
+ function getInspectorRegistry() {
613
+ if (!globalInspectorRegistry) {
614
+ globalInspectorRegistry = new InspectorRegistry();
615
+ }
616
+ return globalInspectorRegistry;
617
+ }
618
+ async function createInspectorForSnapshot(input) {
619
+ const registry = getInspectorRegistry();
620
+ const definition = registry.get(input.kind) ?? registry.detectFromBytes(input.bytes) ?? registry.detectFromSnapshot(input);
621
+ if (!definition) {
622
+ throw new Error(`No inspector registered for database kind "${input.kind}"`);
623
+ }
624
+ const inspector = await definition.createInspector();
625
+ await inspector.open(input.bytes);
626
+ return inspector;
627
+ }
628
+
629
+ // src/utils/ids.ts
630
+ function generateDeviceId() {
631
+ return generateUniqueId("device");
632
+ }
633
+ function generateTransactionId() {
634
+ return generateUniqueId("txn");
635
+ }
636
+ function generateWriteId() {
637
+ return generateUniqueId("write");
638
+ }
639
+ function generateUniqueId(prefix) {
640
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
641
+ return `${prefix}-${crypto.randomUUID()}`;
642
+ }
643
+ const random = Math.random().toString(36).slice(2, 10);
644
+ return `${prefix}-${Date.now()}-${random}`;
645
+ }
646
+
647
+ // src/utils/reconnect.ts
648
+ var DEFAULT_BASE_MS = 1e3;
649
+ var DEFAULT_MAX_MS = 3e4;
650
+ var DEFAULT_JITTER_MS = 500;
651
+ function calculateBackoffDelay(attempt, options = {}) {
652
+ const baseMs = options.baseMs ?? DEFAULT_BASE_MS;
653
+ const maxMs = options.maxMs ?? DEFAULT_MAX_MS;
654
+ const jitterMs = options.jitterMs ?? DEFAULT_JITTER_MS;
655
+ const exponential = baseMs * 2 ** attempt;
656
+ const capped = Math.min(exponential, maxMs);
657
+ const jitter = Math.floor(Math.random() * jitterMs);
658
+ return capped + jitter;
659
+ }
660
+ var ReconnectScheduler = class {
661
+ attempt = 0;
662
+ timer = null;
663
+ onReconnect;
664
+ options;
665
+ constructor({ onReconnect, ...options }) {
666
+ this.onReconnect = onReconnect;
667
+ this.options = options;
668
+ }
669
+ schedule() {
670
+ if (this.timer !== null) {
671
+ return;
672
+ }
673
+ const delay = calculateBackoffDelay(this.attempt, this.options);
674
+ this.attempt += 1;
675
+ this.timer = setTimeout(() => {
676
+ this.timer = null;
677
+ this.onReconnect();
678
+ }, delay);
679
+ }
680
+ reset() {
681
+ this.attempt = 0;
682
+ this.cancel();
683
+ }
684
+ cancel() {
685
+ if (this.timer !== null) {
686
+ clearTimeout(this.timer);
687
+ this.timer = null;
688
+ }
689
+ }
690
+ };
691
+
692
+ // src/client/createDevToolsClient.ts
693
+ var WRITE_TIMEOUT_MS = 3e4;
694
+ function createDevToolsClient(options = {}) {
695
+ const role = options.role ?? DevToolsRole.MOBILE;
696
+ const WebSocketImpl = options.WebSocketImpl ?? globalThis.WebSocket;
697
+ if (!WebSocketImpl) {
698
+ throw new Error("WebSocket is not available in this environment.");
699
+ }
700
+ let socket = null;
701
+ let deviceStatus = null;
702
+ let connectionState = "disconnected";
703
+ let lastConnectionError = null;
704
+ let intentionalDisconnect = false;
705
+ let transactionState = "idle";
706
+ let activeTransactionId = null;
707
+ let activeDeviceId = null;
708
+ const pendingTransaction = /* @__PURE__ */ new Map();
709
+ const pendingWrites = /* @__PURE__ */ new Map();
710
+ const deviceId = role === DevToolsRole.MOBILE ? options.deviceId ?? generateDeviceId() : void 0;
711
+ let wsUrl = options.serverUrl ?? buildDevToolsWsUrl("localhost", DEFAULT_DEVTOOLS_PORT);
712
+ const setConnectionState = (state) => {
713
+ if (connectionState === state) {
714
+ return;
715
+ }
716
+ connectionState = state;
717
+ options.onConnectionStateChange?.(state);
718
+ };
719
+ const setTransactionState = (state) => {
720
+ if (transactionState === state) {
721
+ return;
722
+ }
723
+ transactionState = state;
724
+ options.onTransactionStateChange?.(state);
725
+ };
726
+ const resetTransaction = () => {
727
+ setTransactionState("idle");
728
+ activeTransactionId = null;
729
+ activeDeviceId = null;
730
+ };
731
+ const rejectPendingTransaction = (transactionId, error) => {
732
+ const pending = pendingTransaction.get(transactionId);
733
+ if (pending) {
734
+ pending.reject(error);
735
+ pendingTransaction.delete(transactionId);
736
+ }
737
+ };
738
+ const rejectAllPending = (error) => {
739
+ for (const [id, pending] of pendingTransaction) {
740
+ pending.reject(error);
741
+ pendingTransaction.delete(id);
742
+ }
743
+ for (const [id, pending] of pendingWrites) {
744
+ pending.reject(error);
745
+ pendingWrites.delete(id);
746
+ }
747
+ resetTransaction();
748
+ };
749
+ const reconnectScheduler = new ReconnectScheduler({
750
+ onReconnect: () => {
751
+ openSocket();
752
+ }
753
+ });
754
+ const send = (message) => {
755
+ if (!socket || socket.readyState !== WebSocketImpl.OPEN) {
756
+ return;
757
+ }
758
+ socket.send(JSON.stringify({ ...message, timestamp: Date.now() }));
759
+ };
760
+ const sendRegister = () => {
761
+ send({
762
+ type: MessageType.REGISTER,
763
+ role,
764
+ ...deviceId ? { deviceId } : {},
765
+ ...options.metadata ? { metadata: options.metadata } : {}
766
+ });
767
+ };
768
+ const handleServerMessage = (parsed) => {
769
+ if (isPingMessage(parsed)) {
770
+ send({
771
+ type: MessageType.PONG,
772
+ pingId: parsed.pingId
773
+ });
774
+ return;
775
+ }
776
+ if (isDeviceStatusMessage(parsed)) {
777
+ deviceStatus = parsed.payload;
778
+ options.onDeviceStatus?.(parsed.payload);
779
+ return;
780
+ }
781
+ if (isSnapshotUploadRequestedMessage(parsed)) {
782
+ options.onSnapshotUploadRequested?.(parsed);
783
+ return;
784
+ }
785
+ if (isExportSnapshotErrorMessage(parsed)) {
786
+ options.onExportSnapshotError?.(parsed);
787
+ return;
788
+ }
789
+ if (isRefreshStatusMessage(parsed)) {
790
+ options.onRefreshStatus?.(parsed);
791
+ return;
792
+ }
793
+ if (isSnapshotReadyMessage(parsed)) {
794
+ options.onSnapshotReady?.(parsed);
795
+ return;
796
+ }
797
+ if (isRefreshErrorMessage(parsed)) {
798
+ options.onRefreshError?.(parsed);
799
+ return;
800
+ }
801
+ if (isBeginTransactionMessage(parsed)) {
802
+ options.onBeginTransaction?.(parsed);
803
+ return;
804
+ }
805
+ if (isCommitTransactionMessage(parsed)) {
806
+ options.onCommitTransaction?.(parsed);
807
+ return;
808
+ }
809
+ if (isRollbackTransactionMessage(parsed)) {
810
+ options.onRollbackTransaction?.(parsed);
811
+ return;
812
+ }
813
+ if (isExecuteWriteMessage(parsed)) {
814
+ options.onExecuteWrite?.(parsed);
815
+ return;
816
+ }
817
+ if (isTransactionStatusMessage(parsed)) {
818
+ options.onTransactionStatus?.(parsed);
819
+ if (role !== DevToolsRole.BROWSER) {
820
+ return;
821
+ }
822
+ const pending = pendingTransaction.get(parsed.transactionId);
823
+ if (parsed.state === "open" && pending?.action === "begin") {
824
+ setTransactionState("open");
825
+ activeTransactionId = parsed.transactionId;
826
+ activeDeviceId = parsed.deviceId;
827
+ pending.resolve();
828
+ pendingTransaction.delete(parsed.transactionId);
829
+ return;
830
+ }
831
+ if (parsed.state === "idle" && pending && (pending.action === "commit" || pending.action === "rollback")) {
832
+ resetTransaction();
833
+ pending.resolve();
834
+ pendingTransaction.delete(parsed.transactionId);
835
+ return;
836
+ }
837
+ if (parsed.state === "failed") {
838
+ rejectPendingTransaction(
839
+ parsed.transactionId,
840
+ new Error(parsed.message ?? "Transaction failed")
841
+ );
842
+ resetTransaction();
843
+ }
844
+ return;
845
+ }
846
+ if (isWriteResultMessage(parsed)) {
847
+ options.onWriteResult?.(parsed);
848
+ const pending = pendingWrites.get(parsed.writeId);
849
+ if (pending) {
850
+ pending.resolve({ rowsAffected: parsed.rowsAffected });
851
+ pendingWrites.delete(parsed.writeId);
852
+ }
853
+ return;
854
+ }
855
+ if (isWriteErrorMessage(parsed)) {
856
+ options.onWriteError?.(parsed);
857
+ const error = new Error(parsed.message);
858
+ if (parsed.writeId) {
859
+ const pendingWrite = pendingWrites.get(parsed.writeId);
860
+ if (pendingWrite) {
861
+ pendingWrite.reject(error);
862
+ pendingWrites.delete(parsed.writeId);
863
+ }
864
+ } else {
865
+ rejectPendingTransaction(parsed.transactionId, error);
866
+ resetTransaction();
867
+ }
868
+ }
869
+ };
870
+ const openSocket = () => {
871
+ if (socket && (socket.readyState === WebSocketImpl.OPEN || socket.readyState === WebSocketImpl.CONNECTING)) {
872
+ return;
873
+ }
874
+ setConnectionState(intentionalDisconnect ? "disconnected" : connectionState === "disconnected" ? "connecting" : "reconnecting");
875
+ socket = new WebSocketImpl(wsUrl);
876
+ socket.addEventListener("open", () => {
877
+ reconnectScheduler.reset();
878
+ lastConnectionError = null;
879
+ setConnectionState("connected");
880
+ options.onConnect?.();
881
+ sendRegister();
882
+ });
883
+ socket.addEventListener("message", (event) => {
884
+ let parsed;
885
+ try {
886
+ parsed = JSON.parse(String(event.data));
887
+ } catch {
888
+ return;
889
+ }
890
+ handleServerMessage(parsed);
891
+ });
892
+ socket.addEventListener("close", () => {
893
+ socket = null;
894
+ options.onDisconnect?.();
895
+ rejectAllPending(new Error("Disconnected from DevTools hub"));
896
+ if (intentionalDisconnect) {
897
+ setConnectionState("disconnected");
898
+ return;
899
+ }
900
+ setConnectionState("reconnecting");
901
+ reconnectScheduler.schedule();
902
+ });
903
+ socket.addEventListener("error", () => {
904
+ const error = new Error(`WebSocket connection failed: ${wsUrl}`);
905
+ lastConnectionError = error.message;
906
+ options.onError?.(error);
907
+ });
908
+ };
909
+ const connect = () => {
910
+ intentionalDisconnect = false;
911
+ openSocket();
912
+ };
913
+ const disconnect = () => {
914
+ intentionalDisconnect = true;
915
+ reconnectScheduler.cancel();
916
+ socket?.close();
917
+ socket = null;
918
+ rejectAllPending(new Error("Disconnected"));
919
+ setConnectionState("disconnected");
920
+ };
921
+ const setServerUrl = (url) => {
922
+ wsUrl = url;
923
+ const wasConnected = connectionState === "connected" || connectionState === "connecting" || connectionState === "reconnecting";
924
+ disconnect();
925
+ intentionalDisconnect = false;
926
+ if (wasConnected) {
927
+ connect();
928
+ }
929
+ };
930
+ const requestRefresh = (targetDeviceId) => {
931
+ send({
932
+ type: MessageType.REFRESH_REQUEST,
933
+ deviceId: targetDeviceId,
934
+ refreshType: "snapshot"
935
+ });
936
+ };
937
+ const requestExportSnapshot = () => {
938
+ send({
939
+ type: MessageType.EXPORT_SNAPSHOT_REQUEST,
940
+ refreshType: "snapshot"
941
+ });
942
+ };
943
+ const sendTransactionAck = (message) => {
944
+ send({
945
+ type: MessageType.TRANSACTION_ACK,
946
+ ...message
947
+ });
948
+ };
949
+ const sendWriteAck = (message) => {
950
+ send({
951
+ type: MessageType.WRITE_ACK,
952
+ ...message
953
+ });
954
+ };
955
+ const waitForTransaction = (transactionId, action) => {
956
+ return new Promise((resolve, reject) => {
957
+ const timer = setTimeout(() => {
958
+ pendingTransaction.delete(transactionId);
959
+ reject(new Error(`Transaction ${action} timed out`));
960
+ }, WRITE_TIMEOUT_MS);
961
+ pendingTransaction.set(transactionId, {
962
+ action,
963
+ resolve: () => {
964
+ clearTimeout(timer);
965
+ resolve();
966
+ },
967
+ reject: (error) => {
968
+ clearTimeout(timer);
969
+ reject(error);
970
+ }
971
+ });
972
+ });
973
+ };
974
+ const beginTransaction = async (targetDeviceId) => {
975
+ if (role !== DevToolsRole.BROWSER) {
976
+ throw new Error("beginTransaction is only available for browser clients");
977
+ }
978
+ if (transactionState !== "idle") {
979
+ throw new Error("A transaction is already active");
980
+ }
981
+ const transactionId = generateTransactionId();
982
+ setTransactionState("opening");
983
+ activeTransactionId = transactionId;
984
+ activeDeviceId = targetDeviceId;
985
+ const promise = waitForTransaction(transactionId, "begin");
986
+ send({
987
+ type: MessageType.BEGIN_TRANSACTION_REQUEST,
988
+ transactionId,
989
+ deviceId: targetDeviceId
990
+ });
991
+ await promise;
992
+ };
993
+ const commitTransaction = async () => {
994
+ if (role !== DevToolsRole.BROWSER) {
995
+ throw new Error("commitTransaction is only available for browser clients");
996
+ }
997
+ if (!activeTransactionId || !activeDeviceId || transactionState !== "open") {
998
+ throw new Error("No open transaction");
999
+ }
1000
+ const transactionId = activeTransactionId;
1001
+ const deviceIdForCommit = activeDeviceId;
1002
+ setTransactionState("committing");
1003
+ const promise = waitForTransaction(transactionId, "commit");
1004
+ send({
1005
+ type: MessageType.COMMIT_TRANSACTION_REQUEST,
1006
+ transactionId,
1007
+ deviceId: deviceIdForCommit
1008
+ });
1009
+ await promise;
1010
+ };
1011
+ const rollbackTransaction = async () => {
1012
+ if (role !== DevToolsRole.BROWSER) {
1013
+ throw new Error("rollbackTransaction is only available for browser clients");
1014
+ }
1015
+ if (!activeTransactionId || !activeDeviceId) {
1016
+ throw new Error("No active transaction");
1017
+ }
1018
+ const transactionId = activeTransactionId;
1019
+ const deviceIdForRollback = activeDeviceId;
1020
+ setTransactionState("rolling_back");
1021
+ const promise = waitForTransaction(transactionId, "rollback");
1022
+ send({
1023
+ type: MessageType.ROLLBACK_TRANSACTION_REQUEST,
1024
+ transactionId,
1025
+ deviceId: deviceIdForRollback
1026
+ });
1027
+ await promise;
1028
+ };
1029
+ const executeWrite = async (operation) => {
1030
+ if (role !== DevToolsRole.BROWSER) {
1031
+ throw new Error("executeWrite is only available for browser clients");
1032
+ }
1033
+ if (!activeTransactionId || !activeDeviceId || transactionState !== "open") {
1034
+ throw new Error("No open transaction");
1035
+ }
1036
+ const writeId = generateWriteId();
1037
+ const promise = new Promise((resolve, reject) => {
1038
+ const timer = setTimeout(() => {
1039
+ pendingWrites.delete(writeId);
1040
+ reject(new Error("Write operation timed out"));
1041
+ }, WRITE_TIMEOUT_MS);
1042
+ pendingWrites.set(writeId, {
1043
+ resolve: (result) => {
1044
+ clearTimeout(timer);
1045
+ resolve(result);
1046
+ },
1047
+ reject: (error) => {
1048
+ clearTimeout(timer);
1049
+ reject(error);
1050
+ }
1051
+ });
1052
+ });
1053
+ send({
1054
+ type: MessageType.WRITE_REQUEST,
1055
+ writeId,
1056
+ transactionId: activeTransactionId,
1057
+ deviceId: activeDeviceId,
1058
+ operation
1059
+ });
1060
+ return promise;
1061
+ };
1062
+ return {
1063
+ connect,
1064
+ disconnect,
1065
+ getConnectionState: () => connectionState,
1066
+ getLastConnectionError: () => lastConnectionError,
1067
+ getDeviceStatus: () => deviceStatus,
1068
+ getDeviceId: () => deviceId,
1069
+ getServerUrl: () => wsUrl,
1070
+ setServerUrl,
1071
+ send,
1072
+ requestRefresh,
1073
+ requestExportSnapshot,
1074
+ sendTransactionAck,
1075
+ sendWriteAck,
1076
+ beginTransaction,
1077
+ commitTransaction,
1078
+ rollbackTransaction,
1079
+ executeWrite,
1080
+ getTransactionState: () => transactionState,
1081
+ getActiveTransactionId: () => activeTransactionId
1082
+ };
1083
+ }
1084
+
1085
+ // src/client/fetchSnapshot.ts
1086
+ async function fetchSnapshot(downloadUrl) {
1087
+ let response;
1088
+ try {
1089
+ response = await fetch(downloadUrl);
1090
+ } catch (error) {
1091
+ const networkMessage = error instanceof Error ? error.message : String(error);
1092
+ throw new Error(
1093
+ `Network error fetching snapshot from ${downloadUrl}. ${networkMessage} If the web UI runs on a different port than the hub, ensure CORS is enabled on the hub HTTP API.`
1094
+ );
1095
+ }
1096
+ if (!response.ok) {
1097
+ throw new Error(
1098
+ `Failed to fetch snapshot from ${downloadUrl}: ${response.status} ${response.statusText}`
1099
+ );
1100
+ }
1101
+ return response.arrayBuffer();
1102
+ }
1103
+
1104
+ // src/client/snapshotUpload.ts
1105
+ function createSnapshotUploadBody(bytes) {
1106
+ return bytes;
1107
+ }
1108
+ async function uploadSnapshot(uploadUrl, bytes, options) {
1109
+ const headers = {
1110
+ "Content-Type": "application/octet-stream",
1111
+ [options.kindHeader]: options.kind,
1112
+ [options.mimeHeader]: options.mimeType
1113
+ };
1114
+ if (options.databaseName && options.nameHeader) {
1115
+ headers[options.nameHeader] = options.databaseName;
1116
+ }
1117
+ const response = await fetch(uploadUrl, {
1118
+ method: "POST",
1119
+ headers,
1120
+ body: createSnapshotUploadBody(bytes)
1121
+ });
1122
+ if (!response.ok) {
1123
+ throw new Error(`Upload failed with status ${response.status}`);
1124
+ }
1125
+ }
1126
+
1127
+ // src/client/handleDeviceSnapshot.ts
1128
+ async function handleDeviceSnapshotUpload(database, message, options = {}) {
1129
+ if (options.deviceId && message.deviceId !== options.deviceId) {
1130
+ return;
1131
+ }
1132
+ if (!database) {
1133
+ throw new Error("No database adapter configured");
1134
+ }
1135
+ if (!options.hubServerUrl) {
1136
+ throw new Error("hubServerUrl is required to upload a device snapshot");
1137
+ }
1138
+ const snapshot = await database.exportSnapshot();
1139
+ const hubHttp = wsUrlToHttpUrl(options.hubServerUrl);
1140
+ const uploadUrl = buildDeviceSnapshotUrl(hubHttp, message.deviceId);
1141
+ await uploadSnapshot(uploadUrl, snapshot.bytes, {
1142
+ kind: snapshot.kind,
1143
+ mimeType: snapshot.mimeType,
1144
+ kindHeader: SNAPSHOT_KIND_HEADER,
1145
+ mimeHeader: SNAPSHOT_MIME_HEADER,
1146
+ databaseName: database.name,
1147
+ nameHeader: SNAPSHOT_NAME_HEADER
1148
+ });
1149
+ }
1150
+
1151
+ // src/utils/isDevToolsEnabled.ts
1152
+ function isDevToolsEnabled(enabled) {
1153
+ if (enabled === false) {
1154
+ return false;
1155
+ }
1156
+ if (enabled === true) {
1157
+ return true;
1158
+ }
1159
+ return typeof __DEV__ !== "undefined" && __DEV__;
1160
+ }
1161
+
1162
+ // src/client/formatSyncError.ts
1163
+ function formatRefreshErrorMessage(code, message) {
1164
+ switch (code) {
1165
+ case "DEVICE_OFFLINE":
1166
+ return "Selected device disconnected. Reconnect the mobile app and try again.";
1167
+ case "REFRESH_IN_PROGRESS":
1168
+ return "A refresh is already in progress for this device.";
1169
+ case "EXPORT_FAILED":
1170
+ return `Device failed to export or upload snapshot: ${message}`;
1171
+ case "UPLOAD_FAILED":
1172
+ return `Snapshot upload failed on the hub: ${message}`;
1173
+ case "TIMEOUT":
1174
+ return "Timed out waiting for the device to upload a snapshot.";
1175
+ case "SNAPSHOT_NOT_FOUND":
1176
+ return "Snapshot expired or was not found. Click Refresh again.";
1177
+ case "INVALID_REQUEST":
1178
+ return `Invalid refresh request: ${message}`;
1179
+ default:
1180
+ return message;
1181
+ }
1182
+ }
1183
+
1184
+ // src/client/handleWriteOperations.ts
1185
+ async function handleBeginTransaction(adapter, message, report) {
1186
+ if (!isWritableDatabaseAdapter(adapter)) {
1187
+ report({
1188
+ transactionId: message.transactionId,
1189
+ action: "begin",
1190
+ ok: false,
1191
+ message: "Database adapter does not support writes"
1192
+ });
1193
+ return;
1194
+ }
1195
+ try {
1196
+ await adapter.beginTransaction();
1197
+ report({
1198
+ transactionId: message.transactionId,
1199
+ action: "begin",
1200
+ ok: true
1201
+ });
1202
+ } catch (error) {
1203
+ report({
1204
+ transactionId: message.transactionId,
1205
+ action: "begin",
1206
+ ok: false,
1207
+ message: error instanceof Error ? error.message : "Failed to begin transaction"
1208
+ });
1209
+ }
1210
+ }
1211
+ async function handleCommitTransaction(adapter, message, report) {
1212
+ if (!isWritableDatabaseAdapter(adapter)) {
1213
+ report({
1214
+ transactionId: message.transactionId,
1215
+ action: "commit",
1216
+ ok: false,
1217
+ message: "Database adapter does not support writes"
1218
+ });
1219
+ return;
1220
+ }
1221
+ try {
1222
+ await adapter.commitTransaction();
1223
+ report({
1224
+ transactionId: message.transactionId,
1225
+ action: "commit",
1226
+ ok: true
1227
+ });
1228
+ } catch (error) {
1229
+ report({
1230
+ transactionId: message.transactionId,
1231
+ action: "commit",
1232
+ ok: false,
1233
+ message: error instanceof Error ? error.message : "Failed to commit transaction"
1234
+ });
1235
+ }
1236
+ }
1237
+ async function handleRollbackTransaction(adapter, message, report) {
1238
+ if (!isWritableDatabaseAdapter(adapter)) {
1239
+ report({
1240
+ transactionId: message.transactionId,
1241
+ action: "rollback",
1242
+ ok: false,
1243
+ message: "Database adapter does not support writes"
1244
+ });
1245
+ return;
1246
+ }
1247
+ try {
1248
+ await adapter.rollbackTransaction();
1249
+ report({
1250
+ transactionId: message.transactionId,
1251
+ action: "rollback",
1252
+ ok: true
1253
+ });
1254
+ } catch (error) {
1255
+ report({
1256
+ transactionId: message.transactionId,
1257
+ action: "rollback",
1258
+ ok: false,
1259
+ message: error instanceof Error ? error.message : "Failed to roll back transaction"
1260
+ });
1261
+ }
1262
+ }
1263
+ async function handleExecuteWrite(adapter, message, report) {
1264
+ if (!isWritableDatabaseAdapter(adapter)) {
1265
+ report({
1266
+ writeId: message.writeId,
1267
+ transactionId: message.transactionId,
1268
+ ok: false,
1269
+ message: "Database adapter does not support writes"
1270
+ });
1271
+ return;
1272
+ }
1273
+ try {
1274
+ const result = await adapter.executeWrite(message.operation);
1275
+ report({
1276
+ writeId: message.writeId,
1277
+ transactionId: message.transactionId,
1278
+ ok: true,
1279
+ rowsAffected: result.rowsAffected
1280
+ });
1281
+ } catch (error) {
1282
+ report({
1283
+ writeId: message.writeId,
1284
+ transactionId: message.transactionId,
1285
+ ok: false,
1286
+ message: error instanceof Error ? error.message : "Write operation failed"
1287
+ });
1288
+ }
1289
+ }
1290
+ var DevToolsContext = react.createContext(null);
1291
+ function useDevTools() {
1292
+ const context = react.useContext(DevToolsContext);
1293
+ if (!context) {
1294
+ throw new Error("useDevTools must be used within DevToolsProvider");
1295
+ }
1296
+ return context;
1297
+ }
1298
+ var ANDROID_LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
1299
+ var ANDROID_EMULATOR_HOST = "10.0.2.2";
1300
+ function isAndroidLoopbackHost(host) {
1301
+ return ANDROID_LOOPBACK_HOSTS.has(host.toLowerCase());
1302
+ }
1303
+ function readMetroDevHostFromConstants(constants) {
1304
+ const hostUri = constants?.expoConfig?.hostUri ?? constants?.expoGoConfig?.debuggerHost ?? constants?.manifest2?.extra?.expoGo?.debuggerHost ?? constants?.manifest?.debuggerHost;
1305
+ if (typeof hostUri !== "string" || hostUri.length === 0) {
1306
+ return void 0;
1307
+ }
1308
+ return hostUri.split(":")[0];
1309
+ }
1310
+ function resolveDevToolsHostFromInputs(platformOs, metroHost) {
1311
+ if (platformOs === "android") {
1312
+ if (metroHost && !isAndroidLoopbackHost(metroHost)) {
1313
+ return metroHost;
1314
+ }
1315
+ return ANDROID_EMULATOR_HOST;
1316
+ }
1317
+ if (metroHost) {
1318
+ return metroHost;
1319
+ }
1320
+ return "localhost";
1321
+ }
1322
+ function getConnectionHintForPlatform(platformOs, serverUrl) {
1323
+ try {
1324
+ const parsed = new URL(serverUrl);
1325
+ const host = parsed.hostname.toLowerCase();
1326
+ if (platformOs === "android" && isAndroidLoopbackHost(host)) {
1327
+ return `On Android, "${host}" refers to the device itself. Use ${ANDROID_EMULATOR_HOST} (emulator) or your PC's LAN IP (physical device).`;
1328
+ }
1329
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
1330
+ return "Server URL must start with ws:// or wss://";
1331
+ }
1332
+ } catch {
1333
+ return "Invalid WebSocket URL.";
1334
+ }
1335
+ return null;
1336
+ }
1337
+ function getPlatformOs() {
1338
+ return reactNative.Platform.OS;
1339
+ }
1340
+ function readMetroDevHostRuntime() {
1341
+ return readMetroDevHostFromConstants(Constants__default.default);
1342
+ }
1343
+ function resolveDevToolsHost() {
1344
+ return resolveDevToolsHostFromInputs(getPlatformOs(), readMetroDevHostRuntime());
1345
+ }
1346
+ function getConnectionHint(serverUrl) {
1347
+ return getConnectionHintForPlatform(getPlatformOs(), serverUrl);
1348
+ }
1349
+ function getExpoConstants() {
1350
+ return Constants__default.default;
1351
+ }
1352
+ function resolveDeviceMetadata() {
1353
+ const constants = getExpoConstants();
1354
+ const config = constants?.expoConfig;
1355
+ const metadata = {
1356
+ platform: reactNative.Platform.OS,
1357
+ appName: config?.name ?? "React Native App"
1358
+ };
1359
+ if (config?.version) {
1360
+ metadata.appVersion = config.version;
1361
+ }
1362
+ const bundleId = config?.ios?.bundleIdentifier ?? config?.android?.package;
1363
+ if (bundleId) {
1364
+ metadata.bundleId = bundleId;
1365
+ }
1366
+ return metadata;
1367
+ }
1368
+
1369
+ // src/utils/resolveServerUrl.ts
1370
+ function normalizeServerUrl(url) {
1371
+ const trimmed = url.trim();
1372
+ if (trimmed.startsWith("ws://") || trimmed.startsWith("wss://")) {
1373
+ return trimmed;
1374
+ }
1375
+ return buildDevToolsWsUrl(trimmed.replace(/^https?:\/\//, ""), DEFAULT_DEVTOOLS_PORT);
1376
+ }
1377
+ function resolveServerUrl(serverUrl) {
1378
+ if (serverUrl) {
1379
+ return normalizeServerUrl(serverUrl);
1380
+ }
1381
+ const envUrl = typeof process !== "undefined" ? process.env.EXPO_PUBLIC_DATABASE_DEVTOOLS_URL : void 0;
1382
+ if (envUrl) {
1383
+ return normalizeServerUrl(envUrl);
1384
+ }
1385
+ return buildDevToolsWsUrl(resolveDevToolsHost(), DEFAULT_DEVTOOLS_PORT);
1386
+ }
1387
+ var PERSISTED_DEVICE_ID_STORAGE_KEY = "@database-devtools/device-id";
1388
+ var memoryDeviceId = null;
1389
+ function hasAsyncStorageNativeModule() {
1390
+ return Boolean(
1391
+ reactNative.NativeModules.RNCAsyncStorage ?? reactNative.NativeModules.RNC_AsyncSQLiteDBStoragePassThru ?? reactNative.NativeModules.PlatformLocalStorage
1392
+ );
1393
+ }
1394
+ function createAsyncStorageDeviceIdStore() {
1395
+ if (!hasAsyncStorageNativeModule()) {
1396
+ return null;
1397
+ }
1398
+ return AsyncStorage__default.default;
1399
+ }
1400
+ function loadOrCreateMemoryDeviceId() {
1401
+ if (!memoryDeviceId) {
1402
+ memoryDeviceId = generateDeviceId();
1403
+ }
1404
+ return memoryDeviceId;
1405
+ }
1406
+ async function loadOrCreateDeviceId(storage = createAsyncStorageDeviceIdStore()) {
1407
+ if (storage) {
1408
+ try {
1409
+ const stored = await storage.getItem(PERSISTED_DEVICE_ID_STORAGE_KEY);
1410
+ if (stored) {
1411
+ return stored;
1412
+ }
1413
+ const created = generateDeviceId();
1414
+ await storage.setItem(PERSISTED_DEVICE_ID_STORAGE_KEY, created);
1415
+ return created;
1416
+ } catch {
1417
+ }
1418
+ }
1419
+ return loadOrCreateMemoryDeviceId();
1420
+ }
1421
+
1422
+ // src/utils/sqlSafety.ts
1423
+ var BLOCKED_KEYWORDS = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|REPLACE|ATTACH|DETACH|REINDEX|VACUUM|TRUNCATE)\b/i;
1424
+ function validateReadOnlySql(sql) {
1425
+ const trimmed = sql.trim();
1426
+ if (!trimmed) {
1427
+ throw new Error("SQL query is empty");
1428
+ }
1429
+ if (BLOCKED_KEYWORDS.test(trimmed)) {
1430
+ throw new Error(
1431
+ "DevTools is read-only; only SELECT, PRAGMA, EXPLAIN, and WITH queries are allowed"
1432
+ );
1433
+ }
1434
+ const statements = trimmed.split(";").map((statement) => statement.trim()).filter(Boolean);
1435
+ for (const statement of statements) {
1436
+ const leading = statement.replace(/^\s*(--[^\n]*\n|\/\*[\s\S]*?\*\/\s*)*/g, "").trim();
1437
+ const keyword = leading.split(/\s+/)[0]?.toUpperCase();
1438
+ if (!keyword) {
1439
+ continue;
1440
+ }
1441
+ if (keyword !== "SELECT" && keyword !== "PRAGMA" && keyword !== "EXPLAIN" && keyword !== "WITH") {
1442
+ throw new Error(`DevTools is read-only; "${keyword}" statements are not allowed`);
1443
+ }
1444
+ }
1445
+ }
1446
+
1447
+ // src/mobile/sqliteTableQuery.ts
1448
+ function quoteIdentifier2(name) {
1449
+ return `"${name.replace(/"/g, '""')}"`;
1450
+ }
1451
+ function buildSearchClause(columns, search) {
1452
+ const term = search?.trim();
1453
+ if (!term || columns.length === 0) {
1454
+ return { clause: "", params: [] };
1455
+ }
1456
+ const pattern = `%${term}%`;
1457
+ const conditions = columns.map(
1458
+ (column) => `CAST(${quoteIdentifier2(column.name)} AS TEXT) LIKE ?`
1459
+ );
1460
+ return {
1461
+ clause: ` WHERE (${conditions.join(" OR ")})`,
1462
+ params: columns.map(() => pattern)
1463
+ };
1464
+ }
1465
+ function resolveSortColumn(columns, sortColumn) {
1466
+ if (!sortColumn) {
1467
+ return null;
1468
+ }
1469
+ return columns.some((column) => column.name === sortColumn) ? sortColumn : null;
1470
+ }
1471
+
1472
+ // src/mobile/createExpoSqliteInspector.ts
1473
+ function normalizeCell(cell) {
1474
+ if (cell === null || cell === void 0) {
1475
+ return null;
1476
+ }
1477
+ if (typeof cell === "number" || typeof cell === "string") {
1478
+ return cell;
1479
+ }
1480
+ if (typeof cell === "boolean") {
1481
+ return cell ? 1 : 0;
1482
+ }
1483
+ if (cell instanceof Uint8Array) {
1484
+ return `[BLOB ${cell.byteLength} bytes]`;
1485
+ }
1486
+ return String(cell);
1487
+ }
1488
+ function objectRowsToMatrix(rows) {
1489
+ if (rows.length === 0) {
1490
+ return { columns: [], matrix: [] };
1491
+ }
1492
+ const columns = Object.keys(rows[0]);
1493
+ return {
1494
+ columns,
1495
+ matrix: rows.map((row) => columns.map((column) => normalizeCell(row[column])))
1496
+ };
1497
+ }
1498
+ function createExpoSqliteInspector(database) {
1499
+ let operationQueue = Promise.resolve();
1500
+ const enqueue = async (operation) => {
1501
+ const next = operationQueue.then(operation);
1502
+ operationQueue = next.then(
1503
+ () => void 0,
1504
+ () => void 0
1505
+ );
1506
+ return next;
1507
+ };
1508
+ const listTablesInternal = async () => {
1509
+ const masterRows = await database.getAllAsync(
1510
+ `SELECT name FROM sqlite_master
1511
+ WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
1512
+ ORDER BY name`
1513
+ );
1514
+ const tables = [];
1515
+ for (const row of masterRows) {
1516
+ const countRow = await database.getFirstAsync(
1517
+ `SELECT COUNT(*) AS count FROM ${quoteIdentifier2(row.name)}`
1518
+ );
1519
+ tables.push({
1520
+ name: row.name,
1521
+ rowCount: Number(countRow?.count ?? 0)
1522
+ });
1523
+ }
1524
+ return tables;
1525
+ };
1526
+ const getTableColumnsInternal = async (table) => {
1527
+ const rows = await database.getAllAsync(
1528
+ `PRAGMA table_info(${quoteIdentifier2(table)})`
1529
+ );
1530
+ return rows.map((row) => ({
1531
+ name: row.name,
1532
+ type: row.type || "ANY",
1533
+ notNull: row.notnull === 1,
1534
+ pk: row.pk === 1,
1535
+ defaultValue: row.dflt_value === null || row.dflt_value === void 0 ? null : String(row.dflt_value)
1536
+ }));
1537
+ };
1538
+ return {
1539
+ listTables: () => enqueue(listTablesInternal),
1540
+ getSchema: () => enqueue(async () => {
1541
+ const tables = await listTablesInternal();
1542
+ return Promise.all(
1543
+ tables.map(async (table) => ({
1544
+ name: table.name,
1545
+ columns: await getTableColumnsInternal(table.name)
1546
+ }))
1547
+ );
1548
+ }),
1549
+ getTableColumns: (table) => enqueue(() => getTableColumnsInternal(table)),
1550
+ getDatabaseInfo: () => enqueue(async () => {
1551
+ const tables = await listTablesInternal();
1552
+ const versionRow = await database.getFirstAsync(
1553
+ "SELECT sqlite_version() AS version"
1554
+ );
1555
+ const pageRow = await database.getFirstAsync(
1556
+ "SELECT page_size, page_count FROM pragma_page_size(), pragma_page_count()"
1557
+ );
1558
+ const pageSize = Number(pageRow?.page_size ?? 0);
1559
+ const pageCount = Number(pageRow?.page_count ?? 0);
1560
+ return {
1561
+ name: database.databasePath.split("/").pop() ?? database.databasePath,
1562
+ path: database.databasePath,
1563
+ tableCount: tables.length,
1564
+ sqliteVersion: versionRow?.version ?? "unknown",
1565
+ pageSize,
1566
+ pageCount,
1567
+ estimatedSizeBytes: pageSize * pageCount
1568
+ };
1569
+ }),
1570
+ fetchTablePage: (request) => enqueue(async () => {
1571
+ const columns = await getTableColumnsInternal(request.table);
1572
+ const { clause, params: searchParams } = buildSearchClause(columns, request.search);
1573
+ const quotedTable = quoteIdentifier2(request.table);
1574
+ const countRow = await database.getFirstAsync(
1575
+ `SELECT COUNT(*) AS count FROM ${quotedTable}${clause}`,
1576
+ searchParams
1577
+ );
1578
+ const totalCount = Number(countRow?.count ?? 0);
1579
+ const pageSize = Math.max(1, request.pageSize);
1580
+ const page = Math.max(1, request.page);
1581
+ const offset = (page - 1) * pageSize;
1582
+ const sortColumn = resolveSortColumn(columns, request.sortColumn);
1583
+ const orderClause = sortColumn ? ` ORDER BY ${quoteIdentifier2(sortColumn)} ${request.sortDir === "desc" ? "DESC" : "ASC"}` : "";
1584
+ const rows = await database.getAllAsync(
1585
+ `SELECT * FROM ${quotedTable}${clause}${orderClause} LIMIT ? OFFSET ?`,
1586
+ [...searchParams, pageSize, offset]
1587
+ );
1588
+ const { columns: resultColumns, matrix } = objectRowsToMatrix(rows);
1589
+ return {
1590
+ columns: resultColumns,
1591
+ rows: matrix,
1592
+ totalCount,
1593
+ page,
1594
+ pageSize
1595
+ };
1596
+ }),
1597
+ executeQuery: (sql) => enqueue(async () => {
1598
+ validateReadOnlySql(sql);
1599
+ const started = Date.now();
1600
+ const rows = await database.getAllAsync(sql);
1601
+ const { columns, matrix } = objectRowsToMatrix(rows);
1602
+ return {
1603
+ columns,
1604
+ rows: matrix,
1605
+ rowCount: matrix.length,
1606
+ durationMs: Date.now() - started
1607
+ };
1608
+ })
1609
+ };
1610
+ }
1611
+ function DevToolsProvider({
1612
+ children,
1613
+ database,
1614
+ type,
1615
+ adapter: explicitAdapter,
1616
+ serverUrl: initialServerUrl,
1617
+ onConnectionStateChange
1618
+ }) {
1619
+ const [connectionState, setConnectionState] = react.useState("connecting");
1620
+ const [connectionError, setConnectionError] = react.useState(null);
1621
+ const [serverUrl, setServerUrl] = react.useState(() => resolveServerUrl(initialServerUrl));
1622
+ const [deviceId, setDeviceId] = react.useState();
1623
+ const [deviceIdReady, setDeviceIdReady] = react.useState(false);
1624
+ const [settingsVisible, setSettingsVisible] = react.useState(false);
1625
+ const [launcherVisible, setLauncherVisible] = react.useState(false);
1626
+ const [explorerVisible, setExplorerVisible] = react.useState(false);
1627
+ const [mobileInspector, setMobileInspector] = react.useState(null);
1628
+ const [resolvedAdapter, setResolvedAdapter] = react.useState(explicitAdapter);
1629
+ const [adapterError, setAdapterError] = react.useState(null);
1630
+ const [exportState, setExportState] = react.useState("idle");
1631
+ const [exportError, setExportError] = react.useState(null);
1632
+ const metadata = react.useMemo(() => resolveDeviceMetadata(), []);
1633
+ const clientRef = react.useRef(null);
1634
+ const onConnectionStateChangeRef = react.useRef(onConnectionStateChange);
1635
+ const databaseRef = react.useRef(resolvedAdapter);
1636
+ const rollbackOpenTransactionRef = react.useRef(null);
1637
+ const exportWaitersRef = react.useRef(null);
1638
+ const exportTimeoutRef = react.useRef(null);
1639
+ const exportSuccessTimeoutRef = react.useRef(null);
1640
+ const clearExportWaiters = react.useCallback(() => {
1641
+ if (exportTimeoutRef.current) {
1642
+ clearTimeout(exportTimeoutRef.current);
1643
+ exportTimeoutRef.current = null;
1644
+ }
1645
+ exportWaitersRef.current = null;
1646
+ }, []);
1647
+ const resetExportSuccessLater = react.useCallback(() => {
1648
+ if (exportSuccessTimeoutRef.current) {
1649
+ clearTimeout(exportSuccessTimeoutRef.current);
1650
+ }
1651
+ exportSuccessTimeoutRef.current = setTimeout(() => {
1652
+ setExportState((current) => current === "success" ? "idle" : current);
1653
+ exportSuccessTimeoutRef.current = null;
1654
+ }, 3e3);
1655
+ }, []);
1656
+ const finishExportSuccess = react.useCallback(() => {
1657
+ clearExportWaiters();
1658
+ setExportState("success");
1659
+ setExportError(null);
1660
+ resetExportSuccessLater();
1661
+ }, [clearExportWaiters, resetExportSuccessLater]);
1662
+ const finishExportFailure = react.useCallback(
1663
+ (message) => {
1664
+ clearExportWaiters();
1665
+ setExportState("error");
1666
+ setExportError(message);
1667
+ },
1668
+ [clearExportWaiters]
1669
+ );
1670
+ react.useEffect(() => {
1671
+ onConnectionStateChangeRef.current = onConnectionStateChange;
1672
+ }, [onConnectionStateChange]);
1673
+ react.useEffect(() => {
1674
+ let cancelled = false;
1675
+ async function resolveDatabaseAdapter() {
1676
+ if (explicitAdapter) {
1677
+ setResolvedAdapter(explicitAdapter);
1678
+ setAdapterError(null);
1679
+ return;
1680
+ }
1681
+ if (!database) {
1682
+ setResolvedAdapter(void 0);
1683
+ setAdapterError(null);
1684
+ return;
1685
+ }
1686
+ try {
1687
+ const nextAdapter = await resolveAdapter(database, { type });
1688
+ if (!cancelled) {
1689
+ setResolvedAdapter(nextAdapter);
1690
+ setAdapterError(null);
1691
+ }
1692
+ } catch (error) {
1693
+ if (!cancelled) {
1694
+ setResolvedAdapter(void 0);
1695
+ setAdapterError(error instanceof Error ? error.message : "Failed to resolve database adapter");
1696
+ }
1697
+ }
1698
+ }
1699
+ void resolveDatabaseAdapter();
1700
+ return () => {
1701
+ cancelled = true;
1702
+ };
1703
+ }, [database, type, explicitAdapter]);
1704
+ react.useEffect(() => {
1705
+ if (detectExpoSqliteInspectable(database)) {
1706
+ setMobileInspector(createExpoSqliteInspector(database));
1707
+ return;
1708
+ }
1709
+ setMobileInspector(null);
1710
+ }, [database]);
1711
+ react.useEffect(() => {
1712
+ databaseRef.current = resolvedAdapter;
1713
+ }, [resolvedAdapter]);
1714
+ react.useEffect(() => {
1715
+ rollbackOpenTransactionRef.current = async () => {
1716
+ const adapter = databaseRef.current;
1717
+ if (!isWritableDatabaseAdapter(adapter)) {
1718
+ return;
1719
+ }
1720
+ try {
1721
+ await adapter.rollbackTransaction();
1722
+ } catch {
1723
+ }
1724
+ };
1725
+ }, [resolvedAdapter]);
1726
+ react.useEffect(() => {
1727
+ let cancelled = false;
1728
+ void loadOrCreateDeviceId().then((persistedId) => {
1729
+ if (!cancelled) {
1730
+ setDeviceId(persistedId);
1731
+ setDeviceIdReady(true);
1732
+ }
1733
+ });
1734
+ return () => {
1735
+ cancelled = true;
1736
+ };
1737
+ }, []);
1738
+ react.useEffect(() => {
1739
+ if (!deviceIdReady || !deviceId) {
1740
+ return;
1741
+ }
1742
+ const client = createDevToolsClient({
1743
+ serverUrl,
1744
+ role: DevToolsRole.MOBILE,
1745
+ deviceId,
1746
+ metadata,
1747
+ onConnect: () => {
1748
+ setConnectionError(null);
1749
+ },
1750
+ onConnectionStateChange: (state) => {
1751
+ setConnectionState(state);
1752
+ onConnectionStateChangeRef.current?.(state);
1753
+ },
1754
+ onError: (error) => {
1755
+ setConnectionError(error.message);
1756
+ },
1757
+ onSnapshotUploadRequested: async (message) => {
1758
+ try {
1759
+ await handleDeviceSnapshotUpload(databaseRef.current, message, {
1760
+ hubServerUrl: serverUrl,
1761
+ deviceId: clientRef.current?.getDeviceId()
1762
+ });
1763
+ if (exportWaitersRef.current) {
1764
+ exportWaitersRef.current.resolve();
1765
+ clearExportWaiters();
1766
+ }
1767
+ } catch (error) {
1768
+ const errorMessage = error instanceof Error ? error.message : "Database export or upload failed";
1769
+ if (exportWaitersRef.current) {
1770
+ exportWaitersRef.current.reject(new Error(errorMessage));
1771
+ clearExportWaiters();
1772
+ return;
1773
+ }
1774
+ console.error("[database-devtools] Snapshot export or upload failed:", errorMessage);
1775
+ }
1776
+ },
1777
+ onExportSnapshotError: (message) => {
1778
+ const errorMessage = formatRefreshErrorMessage(message.code, message.message);
1779
+ if (exportWaitersRef.current) {
1780
+ exportWaitersRef.current.reject(new Error(errorMessage));
1781
+ clearExportWaiters();
1782
+ }
1783
+ },
1784
+ onBeginTransaction: async (message) => {
1785
+ const client2 = clientRef.current;
1786
+ if (!client2) {
1787
+ return;
1788
+ }
1789
+ await handleBeginTransaction(databaseRef.current, message, (ack) => {
1790
+ client2.sendTransactionAck(ack);
1791
+ });
1792
+ },
1793
+ onCommitTransaction: async (message) => {
1794
+ const client2 = clientRef.current;
1795
+ if (!client2) {
1796
+ return;
1797
+ }
1798
+ await handleCommitTransaction(databaseRef.current, message, (ack) => {
1799
+ client2.sendTransactionAck(ack);
1800
+ });
1801
+ },
1802
+ onRollbackTransaction: async (message) => {
1803
+ const client2 = clientRef.current;
1804
+ if (!client2) {
1805
+ return;
1806
+ }
1807
+ await handleRollbackTransaction(databaseRef.current, message, (ack) => {
1808
+ client2.sendTransactionAck(ack);
1809
+ });
1810
+ },
1811
+ onExecuteWrite: async (message) => {
1812
+ const client2 = clientRef.current;
1813
+ if (!client2) {
1814
+ return;
1815
+ }
1816
+ await handleExecuteWrite(databaseRef.current, message, (ack) => {
1817
+ client2.sendWriteAck(ack);
1818
+ });
1819
+ }
1820
+ });
1821
+ clientRef.current = client;
1822
+ setDeviceId(client.getDeviceId());
1823
+ client.connect();
1824
+ return () => {
1825
+ void rollbackOpenTransactionRef.current?.();
1826
+ client.disconnect();
1827
+ clientRef.current = null;
1828
+ clearExportWaiters();
1829
+ if (exportSuccessTimeoutRef.current) {
1830
+ clearTimeout(exportSuccessTimeoutRef.current);
1831
+ exportSuccessTimeoutRef.current = null;
1832
+ }
1833
+ };
1834
+ }, [serverUrl, metadata, clearExportWaiters, deviceIdReady, deviceId]);
1835
+ const openLauncher = react.useCallback(() => {
1836
+ setLauncherVisible(true);
1837
+ }, []);
1838
+ const closeLauncher = react.useCallback(() => {
1839
+ setLauncherVisible(false);
1840
+ }, []);
1841
+ const openExplorer = react.useCallback(() => {
1842
+ setLauncherVisible(false);
1843
+ setExplorerVisible(true);
1844
+ }, []);
1845
+ const closeExplorer = react.useCallback(() => {
1846
+ setExplorerVisible(false);
1847
+ }, []);
1848
+ const openSettings = react.useCallback(() => {
1849
+ setLauncherVisible(false);
1850
+ setSettingsVisible(true);
1851
+ }, []);
1852
+ const closeSettings = react.useCallback(() => {
1853
+ setSettingsVisible(false);
1854
+ }, []);
1855
+ const reconnect = react.useCallback((url) => {
1856
+ setConnectionError(null);
1857
+ setServerUrl(normalizeServerUrl(url));
1858
+ }, []);
1859
+ const exportDatabase = react.useCallback(async () => {
1860
+ if (exportState === "exporting") {
1861
+ return;
1862
+ }
1863
+ if (connectionState !== "connected") {
1864
+ finishExportFailure("Connect to the DevTools hub first");
1865
+ return;
1866
+ }
1867
+ if (!resolvedAdapter) {
1868
+ finishExportFailure(adapterError ?? "No database adapter connected");
1869
+ return;
1870
+ }
1871
+ const client = clientRef.current;
1872
+ if (!client) {
1873
+ finishExportFailure("DevTools client is not ready");
1874
+ return;
1875
+ }
1876
+ setExportState("exporting");
1877
+ setExportError(null);
1878
+ try {
1879
+ await new Promise((resolve, reject) => {
1880
+ exportWaitersRef.current = { resolve, reject };
1881
+ exportTimeoutRef.current = setTimeout(() => {
1882
+ exportWaitersRef.current?.reject(new Error("Export timed out"));
1883
+ clearExportWaiters();
1884
+ }, REFRESH_TIMEOUT_MS);
1885
+ client.requestExportSnapshot();
1886
+ });
1887
+ finishExportSuccess();
1888
+ } catch (error) {
1889
+ const message = error instanceof Error ? error.message : "Export failed";
1890
+ finishExportFailure(message);
1891
+ }
1892
+ }, [
1893
+ adapterError,
1894
+ connectionState,
1895
+ exportState,
1896
+ finishExportFailure,
1897
+ resolvedAdapter
1898
+ ]);
1899
+ const connectionHint = react.useMemo(() => getConnectionHint(serverUrl), [serverUrl]);
1900
+ const value = react.useMemo(
1901
+ () => ({
1902
+ connectionState,
1903
+ connectionError,
1904
+ connectionHint,
1905
+ deviceId,
1906
+ serverUrl,
1907
+ metadata,
1908
+ database: resolvedAdapter,
1909
+ adapterError,
1910
+ mobileInspector,
1911
+ launcherVisible,
1912
+ openLauncher,
1913
+ closeLauncher,
1914
+ explorerVisible,
1915
+ openExplorer,
1916
+ closeExplorer,
1917
+ settingsVisible,
1918
+ openSettings,
1919
+ closeSettings,
1920
+ reconnect,
1921
+ exportState,
1922
+ exportError,
1923
+ exportDatabase
1924
+ }),
1925
+ [
1926
+ connectionState,
1927
+ connectionError,
1928
+ connectionHint,
1929
+ deviceId,
1930
+ serverUrl,
1931
+ metadata,
1932
+ resolvedAdapter,
1933
+ adapterError,
1934
+ mobileInspector,
1935
+ launcherVisible,
1936
+ openLauncher,
1937
+ closeLauncher,
1938
+ explorerVisible,
1939
+ openExplorer,
1940
+ closeExplorer,
1941
+ settingsVisible,
1942
+ openSettings,
1943
+ closeSettings,
1944
+ reconnect,
1945
+ exportState,
1946
+ exportError,
1947
+ exportDatabase
1948
+ ]
1949
+ );
1950
+ return /* @__PURE__ */ jsxRuntime.jsx(DevToolsContext.Provider, { value, children });
1951
+ }
1952
+ function getStatusConfig(state) {
1953
+ switch (state) {
1954
+ case "connected":
1955
+ return { label: "Connected", color: "#166534", backgroundColor: "#dcfce7" };
1956
+ case "reconnecting":
1957
+ return { label: "Reconnecting", color: "#92400e", backgroundColor: "#fef3c7" };
1958
+ case "connecting":
1959
+ return { label: "Connecting", color: "#92400e", backgroundColor: "#fef3c7" };
1960
+ case "disconnected":
1961
+ return { label: "Disconnected", color: "#991b1b", backgroundColor: "#fee2e2" };
1962
+ }
1963
+ }
1964
+ function getConnectionDotColor(state) {
1965
+ switch (state) {
1966
+ case "connected":
1967
+ return "#22c55e";
1968
+ case "reconnecting":
1969
+ case "connecting":
1970
+ return "#eab308";
1971
+ case "disconnected":
1972
+ return "#ef4444";
1973
+ }
1974
+ }
1975
+ function ConnectionStatusBadge({ state }) {
1976
+ const config = getStatusConfig(state);
1977
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles.badge, { backgroundColor: config.backgroundColor }], children: [
1978
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles.dot, { backgroundColor: getConnectionDotColor(state) }] }),
1979
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles.label, { color: config.color }], children: config.label })
1980
+ ] });
1981
+ }
1982
+ var styles = reactNative.StyleSheet.create({
1983
+ badge: {
1984
+ flexDirection: "row",
1985
+ alignItems: "center",
1986
+ alignSelf: "flex-start",
1987
+ paddingHorizontal: 10,
1988
+ paddingVertical: 6,
1989
+ borderRadius: 999,
1990
+ gap: 6
1991
+ },
1992
+ dot: {
1993
+ width: 8,
1994
+ height: 8,
1995
+ borderRadius: 4
1996
+ },
1997
+ label: {
1998
+ fontSize: 13,
1999
+ fontWeight: "600"
2000
+ }
2001
+ });
2002
+ function DevToolsLauncherModal() {
2003
+ const {
2004
+ launcherVisible,
2005
+ closeLauncher,
2006
+ openExplorer,
2007
+ openSettings,
2008
+ connectionState,
2009
+ mobileInspector,
2010
+ exportState,
2011
+ exportError,
2012
+ exportDatabase,
2013
+ database,
2014
+ adapterError
2015
+ } = useDevTools();
2016
+ const isExporting = exportState === "exporting";
2017
+ const exportDisabled = connectionState !== "connected" || !database || isExporting || Boolean(adapterError);
2018
+ return /* @__PURE__ */ jsxRuntime.jsx(
2019
+ reactNative.Modal,
2020
+ {
2021
+ animationType: "slide",
2022
+ onRequestClose: closeLauncher,
2023
+ transparent: true,
2024
+ visible: launcherVisible,
2025
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles2.overlay, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.sheet, children: [
2026
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.header, children: [
2027
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.title, children: "Database DevTools" }),
2028
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { accessibilityLabel: "Close launcher", onPress: closeLauncher, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.closeButton, children: "Close" }) })
2029
+ ] }),
2030
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.body, children: [
2031
+ /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatusBadge, { state: connectionState }),
2032
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.hint, children: "Browse and query the on-device database, or connect to the web inspector." }),
2033
+ /* @__PURE__ */ jsxRuntime.jsx(
2034
+ reactNative.Pressable,
2035
+ {
2036
+ accessibilityLabel: "View database on device",
2037
+ disabled: !mobileInspector,
2038
+ onPress: openExplorer,
2039
+ style: [styles2.primaryButton, !mobileInspector && styles2.primaryButtonDisabled],
2040
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.primaryLabel, children: "View Database" })
2041
+ }
2042
+ ),
2043
+ !mobileInspector ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.warning, children: "On-device explorer requires an Expo SQLite database instance." }) : null,
2044
+ /* @__PURE__ */ jsxRuntime.jsx(
2045
+ reactNative.Pressable,
2046
+ {
2047
+ accessibilityLabel: "Open DevTools settings",
2048
+ onPress: openSettings,
2049
+ style: styles2.secondaryButton,
2050
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.secondaryLabel, children: "DevTools Settings" })
2051
+ }
2052
+ ),
2053
+ /* @__PURE__ */ jsxRuntime.jsx(
2054
+ reactNative.Pressable,
2055
+ {
2056
+ accessibilityLabel: "Export database to web inspector",
2057
+ accessibilityState: { busy: isExporting, disabled: exportDisabled },
2058
+ disabled: exportDisabled,
2059
+ onPress: () => void exportDatabase(),
2060
+ style: [styles2.ghostButton, exportDisabled && !isExporting && styles2.buttonDisabled],
2061
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles2.exportContent, children: [
2062
+ isExporting ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: "#0f172a", size: "small" }) : null,
2063
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.ghostLabel, children: isExporting ? "Exporting\u2026" : "Export to Web Inspector" })
2064
+ ] })
2065
+ }
2066
+ ),
2067
+ connectionState !== "connected" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.hint, children: "Connect to the hub in settings before exporting." }) : null,
2068
+ exportState === "success" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.success, children: "Sent to web inspector" }) : null,
2069
+ exportError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.error, children: exportError }) : null,
2070
+ adapterError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles2.error, children: adapterError }) : null
2071
+ ] })
2072
+ ] }) })
2073
+ }
2074
+ );
2075
+ }
2076
+ var styles2 = reactNative.StyleSheet.create({
2077
+ overlay: {
2078
+ flex: 1,
2079
+ backgroundColor: "rgba(0, 0, 0, 0.45)",
2080
+ justifyContent: "flex-end"
2081
+ },
2082
+ sheet: {
2083
+ backgroundColor: "#ffffff",
2084
+ borderTopLeftRadius: 16,
2085
+ borderTopRightRadius: 16,
2086
+ overflow: "hidden"
2087
+ },
2088
+ header: {
2089
+ flexDirection: "row",
2090
+ alignItems: "center",
2091
+ justifyContent: "space-between",
2092
+ paddingHorizontal: 20,
2093
+ paddingVertical: 16,
2094
+ borderBottomWidth: reactNative.StyleSheet.hairlineWidth,
2095
+ borderBottomColor: "#e2e8f0"
2096
+ },
2097
+ title: {
2098
+ fontSize: 18,
2099
+ fontWeight: "700",
2100
+ color: "#0f172a"
2101
+ },
2102
+ closeButton: {
2103
+ fontSize: 16,
2104
+ color: "#2563eb",
2105
+ fontWeight: "600"
2106
+ },
2107
+ body: {
2108
+ padding: 20,
2109
+ gap: 12
2110
+ },
2111
+ hint: {
2112
+ fontSize: 13,
2113
+ color: "#64748b",
2114
+ lineHeight: 20
2115
+ },
2116
+ warning: {
2117
+ fontSize: 12,
2118
+ color: "#b45309",
2119
+ marginTop: -4
2120
+ },
2121
+ primaryButton: {
2122
+ backgroundColor: "#2563eb",
2123
+ borderRadius: 8,
2124
+ paddingVertical: 14,
2125
+ alignItems: "center"
2126
+ },
2127
+ primaryButtonDisabled: {
2128
+ opacity: 0.5
2129
+ },
2130
+ primaryLabel: {
2131
+ color: "#f8fafc",
2132
+ fontSize: 16,
2133
+ fontWeight: "700"
2134
+ },
2135
+ secondaryButton: {
2136
+ backgroundColor: "#1e293b",
2137
+ borderRadius: 8,
2138
+ paddingVertical: 12,
2139
+ alignItems: "center"
2140
+ },
2141
+ secondaryLabel: {
2142
+ color: "#f8fafc",
2143
+ fontSize: 15,
2144
+ fontWeight: "600"
2145
+ },
2146
+ ghostButton: {
2147
+ borderWidth: 1,
2148
+ borderColor: "#cbd5e1",
2149
+ borderRadius: 8,
2150
+ paddingVertical: 12,
2151
+ alignItems: "center"
2152
+ },
2153
+ buttonDisabled: {
2154
+ opacity: 0.5
2155
+ },
2156
+ ghostLabel: {
2157
+ color: "#0f172a",
2158
+ fontSize: 15,
2159
+ fontWeight: "600"
2160
+ },
2161
+ exportContent: {
2162
+ flexDirection: "row",
2163
+ alignItems: "center",
2164
+ gap: 8
2165
+ },
2166
+ success: {
2167
+ fontSize: 13,
2168
+ color: "#15803d"
2169
+ },
2170
+ error: {
2171
+ fontSize: 13,
2172
+ color: "#dc2626"
2173
+ }
2174
+ });
2175
+ function DevToolsSettingsModal() {
2176
+ const {
2177
+ settingsVisible,
2178
+ closeSettings,
2179
+ connectionState,
2180
+ connectionError,
2181
+ connectionHint,
2182
+ deviceId,
2183
+ serverUrl,
2184
+ metadata,
2185
+ database,
2186
+ adapterError,
2187
+ reconnect,
2188
+ exportState,
2189
+ exportError,
2190
+ exportDatabase
2191
+ } = useDevTools();
2192
+ const [draftUrl, setDraftUrl] = react.useState(serverUrl);
2193
+ react.useEffect(() => {
2194
+ if (settingsVisible) {
2195
+ setDraftUrl(serverUrl);
2196
+ }
2197
+ }, [settingsVisible, serverUrl]);
2198
+ const handleReconnect = () => {
2199
+ const trimmed = draftUrl.trim();
2200
+ if (trimmed) {
2201
+ reconnect(trimmed);
2202
+ }
2203
+ };
2204
+ const handleExportDatabase = () => {
2205
+ void exportDatabase();
2206
+ };
2207
+ const isExporting = exportState === "exporting";
2208
+ const exportDisabled = connectionState !== "connected" || !database || isExporting;
2209
+ return /* @__PURE__ */ jsxRuntime.jsx(
2210
+ reactNative.Modal,
2211
+ {
2212
+ animationType: "slide",
2213
+ onRequestClose: closeSettings,
2214
+ transparent: true,
2215
+ visible: settingsVisible,
2216
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles3.overlay, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.sheet, children: [
2217
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.header, children: [
2218
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.title, children: "Database DevTools" }),
2219
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { accessibilityLabel: "Close settings", onPress: closeSettings, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.closeButton, children: "Close" }) })
2220
+ ] }),
2221
+ /* @__PURE__ */ jsxRuntime.jsxs(
2222
+ reactNative.ScrollView,
2223
+ {
2224
+ contentContainerStyle: styles3.content,
2225
+ style: styles3.scrollBody,
2226
+ children: [
2227
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Connection", children: [
2228
+ /* @__PURE__ */ jsxRuntime.jsx(ConnectionStatusBadge, { state: connectionState }),
2229
+ /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "Server URL", value: serverUrl, mono: true }),
2230
+ connectionError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.errorText, children: connectionError }) : null,
2231
+ connectionHint ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.hintText, children: connectionHint }) : null,
2232
+ connectionState === "reconnecting" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.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
2233
+ ] }),
2234
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Device", children: [
2235
+ /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "Device ID", value: deviceId ?? "\u2014", mono: true }),
2236
+ /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "Platform", value: metadata.platform ?? "\u2014" }),
2237
+ /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "App", value: metadata.appName ?? "\u2014" }),
2238
+ metadata.appVersion ? /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "Version", value: metadata.appVersion }) : null,
2239
+ metadata.bundleId ? /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "Bundle ID", value: metadata.bundleId, mono: true }) : null
2240
+ ] }),
2241
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Server", children: [
2242
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.fieldLabel, children: "WebSocket URL" }),
2243
+ /* @__PURE__ */ jsxRuntime.jsx(
2244
+ reactNative.TextInput,
2245
+ {
2246
+ autoCapitalize: "none",
2247
+ autoCorrect: false,
2248
+ onChangeText: setDraftUrl,
2249
+ placeholder: "ws://10.0.2.2:3847/ws",
2250
+ style: styles3.input,
2251
+ value: draftUrl
2252
+ }
2253
+ ),
2254
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: handleReconnect, style: styles3.reconnectButton, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.reconnectLabel, children: "Reconnect" }) })
2255
+ ] }),
2256
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Database", children: database ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2257
+ /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "Kind", value: database.kind }),
2258
+ /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "Name", value: database.name }),
2259
+ /* @__PURE__ */ jsxRuntime.jsx(InfoRow, { label: "ID", value: database.id, mono: true })
2260
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.placeholder, children: adapterError ? "Adapter not connected" : "No adapter connected" }) })
2261
+ ]
2262
+ }
2263
+ ),
2264
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.exportFooter, children: [
2265
+ adapterError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.errorText, children: adapterError }) : null,
2266
+ database ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2267
+ /* @__PURE__ */ jsxRuntime.jsx(
2268
+ reactNative.Pressable,
2269
+ {
2270
+ accessibilityLabel: "Export database to web inspector",
2271
+ accessibilityState: { busy: isExporting, disabled: exportDisabled },
2272
+ disabled: exportDisabled,
2273
+ onPress: handleExportDatabase,
2274
+ style: [
2275
+ styles3.exportButton,
2276
+ exportDisabled && !isExporting && styles3.exportButtonDisabled
2277
+ ],
2278
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.exportButtonContent, children: [
2279
+ isExporting ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: "#f8fafc", size: "small" }) : null,
2280
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.exportLabel, children: isExporting ? "Exporting\u2026" : "Export Database" })
2281
+ ] })
2282
+ }
2283
+ ),
2284
+ connectionState !== "connected" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.hintText, children: "Connect to the hub before exporting." }) : null,
2285
+ exportState === "success" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.successText, children: "Sent to web inspector" }) : null,
2286
+ exportError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.errorText, children: exportError }) : null
2287
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.placeholder, children: adapterError ? "Adapter not connected" : "No adapter connected" })
2288
+ ] })
2289
+ ] }) })
2290
+ }
2291
+ );
2292
+ }
2293
+ function Section({ title, children }) {
2294
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.section, children: [
2295
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.sectionTitle, children: title }),
2296
+ children
2297
+ ] });
2298
+ }
2299
+ function InfoRow({
2300
+ label,
2301
+ value,
2302
+ mono = false
2303
+ }) {
2304
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles3.infoRow, children: [
2305
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles3.infoLabel, children: label }),
2306
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles3.infoValue, mono && styles3.mono], selectable: true, children: value })
2307
+ ] });
2308
+ }
2309
+ var styles3 = reactNative.StyleSheet.create({
2310
+ overlay: {
2311
+ flex: 1,
2312
+ backgroundColor: "rgba(0, 0, 0, 0.45)",
2313
+ justifyContent: "flex-end"
2314
+ },
2315
+ sheet: {
2316
+ backgroundColor: "#ffffff",
2317
+ borderTopLeftRadius: 16,
2318
+ borderTopRightRadius: 16,
2319
+ maxHeight: "85%",
2320
+ overflow: "hidden"
2321
+ },
2322
+ scrollBody: {
2323
+ flexGrow: 0,
2324
+ flexShrink: 1
2325
+ },
2326
+ header: {
2327
+ flexDirection: "row",
2328
+ alignItems: "center",
2329
+ justifyContent: "space-between",
2330
+ paddingHorizontal: 20,
2331
+ paddingVertical: 16,
2332
+ borderBottomWidth: reactNative.StyleSheet.hairlineWidth,
2333
+ borderBottomColor: "#e2e8f0"
2334
+ },
2335
+ title: {
2336
+ fontSize: 18,
2337
+ fontWeight: "700",
2338
+ color: "#0f172a"
2339
+ },
2340
+ closeButton: {
2341
+ fontSize: 16,
2342
+ color: "#2563eb",
2343
+ fontWeight: "600"
2344
+ },
2345
+ exportFooter: {
2346
+ paddingHorizontal: 20,
2347
+ paddingTop: 12,
2348
+ paddingBottom: 20,
2349
+ gap: 8,
2350
+ borderTopWidth: reactNative.StyleSheet.hairlineWidth,
2351
+ borderTopColor: "#e2e8f0",
2352
+ backgroundColor: "#ffffff"
2353
+ },
2354
+ content: {
2355
+ padding: 20,
2356
+ gap: 20
2357
+ },
2358
+ section: {
2359
+ gap: 10
2360
+ },
2361
+ sectionTitle: {
2362
+ fontSize: 13,
2363
+ fontWeight: "600",
2364
+ color: "#64748b",
2365
+ textTransform: "uppercase",
2366
+ letterSpacing: 0.5
2367
+ },
2368
+ infoRow: {
2369
+ gap: 2
2370
+ },
2371
+ infoLabel: {
2372
+ fontSize: 12,
2373
+ color: "#94a3b8"
2374
+ },
2375
+ infoValue: {
2376
+ fontSize: 14,
2377
+ color: "#0f172a"
2378
+ },
2379
+ mono: {
2380
+ fontFamily: "monospace",
2381
+ fontSize: 12
2382
+ },
2383
+ fieldLabel: {
2384
+ fontSize: 12,
2385
+ color: "#64748b",
2386
+ marginBottom: 4
2387
+ },
2388
+ input: {
2389
+ borderWidth: 1,
2390
+ borderColor: "#cbd5e1",
2391
+ borderRadius: 8,
2392
+ paddingHorizontal: 12,
2393
+ paddingVertical: 10,
2394
+ fontSize: 14,
2395
+ color: "#0f172a",
2396
+ fontFamily: "monospace"
2397
+ },
2398
+ reconnectButton: {
2399
+ marginTop: 8,
2400
+ backgroundColor: "#1e293b",
2401
+ borderRadius: 8,
2402
+ paddingVertical: 12,
2403
+ alignItems: "center"
2404
+ },
2405
+ reconnectLabel: {
2406
+ color: "#f8fafc",
2407
+ fontSize: 15,
2408
+ fontWeight: "600"
2409
+ },
2410
+ exportButton: {
2411
+ backgroundColor: "#2563eb",
2412
+ borderRadius: 8,
2413
+ paddingVertical: 12,
2414
+ paddingHorizontal: 16,
2415
+ alignItems: "center",
2416
+ justifyContent: "center",
2417
+ minHeight: 44
2418
+ },
2419
+ exportButtonContent: {
2420
+ flexDirection: "row",
2421
+ alignItems: "center",
2422
+ justifyContent: "center",
2423
+ gap: 8
2424
+ },
2425
+ exportButtonDisabled: {
2426
+ opacity: 0.5
2427
+ },
2428
+ exportLabel: {
2429
+ color: "#f8fafc",
2430
+ fontSize: 15,
2431
+ fontWeight: "600"
2432
+ },
2433
+ successText: {
2434
+ fontSize: 13,
2435
+ color: "#15803d"
2436
+ },
2437
+ placeholder: {
2438
+ fontSize: 14,
2439
+ color: "#94a3b8",
2440
+ fontStyle: "italic"
2441
+ },
2442
+ errorText: {
2443
+ fontSize: 13,
2444
+ color: "#dc2626",
2445
+ marginBottom: 8
2446
+ },
2447
+ hintText: {
2448
+ fontSize: 12,
2449
+ color: "#b45309",
2450
+ lineHeight: 18
2451
+ }
2452
+ });
2453
+
2454
+ // src/utils/floatingButtonPosition.ts
2455
+ var FLOATING_BUTTON_SIZE = 48;
2456
+ var FLOATING_BUTTON_MARGIN = 16;
2457
+ var FLOATING_BUTTON_BOTTOM = 24;
2458
+ var FLOATING_BUTTON_TAP_THRESHOLD = 8;
2459
+ function getCornerPosition(corner, layout, bottomInset = FLOATING_BUTTON_BOTTOM) {
2460
+ const { windowWidth, windowHeight, buttonSize, margin } = layout;
2461
+ const y = windowHeight - bottomInset - buttonSize;
2462
+ return {
2463
+ x: corner === "bottom-right" ? windowWidth - margin - buttonSize : margin,
2464
+ y
2465
+ };
2466
+ }
2467
+ function clampFloatingPosition(position, layout) {
2468
+ const { windowWidth, windowHeight, buttonSize, margin } = layout;
2469
+ return {
2470
+ x: Math.min(Math.max(margin, position.x), windowWidth - margin - buttonSize),
2471
+ y: Math.min(Math.max(margin, position.y), windowHeight - margin - buttonSize)
2472
+ };
2473
+ }
2474
+ function snapFloatingPositionToEdges(position, layout) {
2475
+ const { windowWidth, windowHeight, buttonSize, margin } = layout;
2476
+ const clamped = clampFloatingPosition(position, layout);
2477
+ const centerX = clamped.x + buttonSize / 2;
2478
+ const centerY = clamped.y + buttonSize / 2;
2479
+ let x = centerX < windowWidth / 2 ? margin : windowWidth - margin - buttonSize;
2480
+ let y = clamped.y;
2481
+ if (centerY < windowHeight * 0.2) {
2482
+ y = margin;
2483
+ } else if (centerY > windowHeight * 0.8) {
2484
+ y = windowHeight - margin - buttonSize;
2485
+ }
2486
+ return { x, y };
2487
+ }
2488
+ function isFloatingButtonTap(totalMovement) {
2489
+ return totalMovement < FLOATING_BUTTON_TAP_THRESHOLD;
2490
+ }
2491
+ var DEFAULT_ICON_SIZE = 22;
2492
+ var DEFAULT_ICON_COLOR = "#f8fafc";
2493
+ function FloatingDevToolsButton({
2494
+ position = "bottom-right",
2495
+ iconStyle,
2496
+ draggable = true,
2497
+ snapToEdges = true,
2498
+ floatingPosition,
2499
+ onFloatingPositionChange
2500
+ }) {
2501
+ const { connectionState, openLauncher } = useDevTools();
2502
+ const { width: windowWidth, height: windowHeight } = reactNative.useWindowDimensions();
2503
+ const [internalPosition, setInternalPosition] = react.useState(null);
2504
+ const [dragPosition, setDragPosition] = react.useState(null);
2505
+ const dragOrigin = react.useRef({ x: 0, y: 0 });
2506
+ const hasInitialized = react.useRef(false);
2507
+ const onFloatingPositionChangeRef = react.useRef(onFloatingPositionChange);
2508
+ react.useEffect(() => {
2509
+ onFloatingPositionChangeRef.current = onFloatingPositionChange;
2510
+ }, [onFloatingPositionChange]);
2511
+ const layout = react.useMemo(
2512
+ () => ({
2513
+ windowWidth,
2514
+ windowHeight,
2515
+ buttonSize: FLOATING_BUTTON_SIZE,
2516
+ margin: FLOATING_BUTTON_MARGIN
2517
+ }),
2518
+ [windowWidth, windowHeight]
2519
+ );
2520
+ const committedPosition = floatingPosition ?? internalPosition;
2521
+ const renderedPosition = dragPosition ?? committedPosition;
2522
+ const commitPosition = react.useCallback(
2523
+ (next) => {
2524
+ const clamped = clampFloatingPosition(next, layout);
2525
+ if (!floatingPosition) {
2526
+ setInternalPosition(clamped);
2527
+ }
2528
+ onFloatingPositionChangeRef.current?.(clamped);
2529
+ return clamped;
2530
+ },
2531
+ [floatingPosition, layout]
2532
+ );
2533
+ react.useEffect(() => {
2534
+ if (windowWidth <= 0 || windowHeight <= 0) {
2535
+ return;
2536
+ }
2537
+ if (floatingPosition) {
2538
+ hasInitialized.current = true;
2539
+ return;
2540
+ }
2541
+ if (!hasInitialized.current) {
2542
+ setInternalPosition(getCornerPosition(position, layout));
2543
+ hasInitialized.current = true;
2544
+ }
2545
+ }, [floatingPosition, layout, position, windowHeight, windowWidth]);
2546
+ react.useEffect(() => {
2547
+ if (!committedPosition || windowWidth <= 0 || windowHeight <= 0) {
2548
+ return;
2549
+ }
2550
+ const clamped = clampFloatingPosition(committedPosition, layout);
2551
+ if (clamped.x !== committedPosition.x || clamped.y !== committedPosition.y) {
2552
+ commitPosition(clamped);
2553
+ }
2554
+ }, [commitPosition, committedPosition, layout, windowHeight, windowWidth]);
2555
+ const panResponder = react.useMemo(
2556
+ () => reactNative.PanResponder.create({
2557
+ onStartShouldSetPanResponder: () => draggable,
2558
+ onMoveShouldSetPanResponder: (_, gesture) => draggable && (Math.abs(gesture.dx) > 2 || Math.abs(gesture.dy) > 2),
2559
+ onPanResponderGrant: () => {
2560
+ if (!committedPosition) {
2561
+ return;
2562
+ }
2563
+ dragOrigin.current = committedPosition;
2564
+ },
2565
+ onPanResponderMove: (_, gesture) => {
2566
+ setDragPosition(
2567
+ clampFloatingPosition(
2568
+ {
2569
+ x: dragOrigin.current.x + gesture.dx,
2570
+ y: dragOrigin.current.y + gesture.dy
2571
+ },
2572
+ layout
2573
+ )
2574
+ );
2575
+ },
2576
+ onPanResponderRelease: (_, gesture) => {
2577
+ setDragPosition(null);
2578
+ const movement = Math.hypot(gesture.dx, gesture.dy);
2579
+ if (isFloatingButtonTap(movement)) {
2580
+ openLauncher();
2581
+ return;
2582
+ }
2583
+ const raw = {
2584
+ x: dragOrigin.current.x + gesture.dx,
2585
+ y: dragOrigin.current.y + gesture.dy
2586
+ };
2587
+ const next = snapToEdges ? snapFloatingPositionToEdges(raw, layout) : clampFloatingPosition(raw, layout);
2588
+ commitPosition(next);
2589
+ },
2590
+ onPanResponderTerminate: () => {
2591
+ setDragPosition(null);
2592
+ }
2593
+ }),
2594
+ [commitPosition, committedPosition, draggable, layout, openLauncher, snapToEdges]
2595
+ );
2596
+ if (!renderedPosition) {
2597
+ return null;
2598
+ }
2599
+ const buttonContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2600
+ /* @__PURE__ */ jsxRuntime.jsx(
2601
+ MaterialCommunityIcons__default.default,
2602
+ {
2603
+ color: DEFAULT_ICON_COLOR,
2604
+ name: "database",
2605
+ size: DEFAULT_ICON_SIZE,
2606
+ style: iconStyle
2607
+ }
2608
+ ),
2609
+ /* @__PURE__ */ jsxRuntime.jsx(
2610
+ reactNative.View,
2611
+ {
2612
+ style: [styles4.statusDot, { backgroundColor: getConnectionDotColor(connectionState) }]
2613
+ }
2614
+ )
2615
+ ] });
2616
+ if (!draggable) {
2617
+ const positionStyle = position === "bottom-left" ? styles4.bottomLeft : styles4.bottomRight;
2618
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles4.fixedContainer, positionStyle], pointerEvents: "box-none", children: /* @__PURE__ */ jsxRuntime.jsx(
2619
+ reactNative.Pressable,
2620
+ {
2621
+ accessibilityLabel: "Open Database DevTools",
2622
+ accessibilityRole: "button",
2623
+ onPress: openLauncher,
2624
+ style: ({ pressed }) => [styles4.button, pressed && styles4.buttonPressed],
2625
+ children: buttonContent
2626
+ }
2627
+ ) });
2628
+ }
2629
+ return /* @__PURE__ */ jsxRuntime.jsx(
2630
+ reactNative.View,
2631
+ {
2632
+ pointerEvents: "box-none",
2633
+ style: [
2634
+ styles4.draggableContainer,
2635
+ {
2636
+ transform: [
2637
+ { translateX: renderedPosition.x },
2638
+ { translateY: renderedPosition.y }
2639
+ ]
2640
+ }
2641
+ ],
2642
+ ...panResponder.panHandlers,
2643
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2644
+ reactNative.View,
2645
+ {
2646
+ accessibilityHint: "Drag to move. Tap to open DevTools launcher.",
2647
+ accessibilityLabel: "Open Database DevTools",
2648
+ accessibilityRole: "button",
2649
+ style: [styles4.button, dragPosition && styles4.buttonDragging],
2650
+ children: buttonContent
2651
+ }
2652
+ )
2653
+ }
2654
+ );
2655
+ }
2656
+ var styles4 = reactNative.StyleSheet.create({
2657
+ fixedContainer: {
2658
+ position: "absolute",
2659
+ bottom: 24,
2660
+ zIndex: 9999
2661
+ },
2662
+ bottomRight: {
2663
+ right: 16
2664
+ },
2665
+ bottomLeft: {
2666
+ left: 16
2667
+ },
2668
+ draggableContainer: {
2669
+ position: "absolute",
2670
+ top: 0,
2671
+ left: 0,
2672
+ zIndex: 9999
2673
+ },
2674
+ button: {
2675
+ width: FLOATING_BUTTON_SIZE,
2676
+ height: FLOATING_BUTTON_SIZE,
2677
+ borderRadius: FLOATING_BUTTON_SIZE / 2,
2678
+ backgroundColor: "#1e293b",
2679
+ alignItems: "center",
2680
+ justifyContent: "center",
2681
+ shadowColor: "#000",
2682
+ shadowOffset: { width: 0, height: 2 },
2683
+ shadowOpacity: 0.25,
2684
+ shadowRadius: 4,
2685
+ elevation: 6
2686
+ },
2687
+ buttonPressed: {
2688
+ opacity: 0.85
2689
+ },
2690
+ buttonDragging: {
2691
+ opacity: 0.92,
2692
+ transform: [{ scale: 1.04 }]
2693
+ },
2694
+ statusDot: {
2695
+ position: "absolute",
2696
+ top: 2,
2697
+ right: 2,
2698
+ width: 10,
2699
+ height: 10,
2700
+ borderRadius: 5,
2701
+ borderWidth: 2,
2702
+ borderColor: "#1e293b"
2703
+ }
2704
+ });
2705
+ var explorerColors = {
2706
+ background: "#ffffff",
2707
+ surface: "#f8fafc",
2708
+ border: "#e2e8f0",
2709
+ text: "#0f172a",
2710
+ muted: "#64748b",
2711
+ subtle: "#94a3b8",
2712
+ accent: "#2563eb",
2713
+ accentSoft: "#dbeafe",
2714
+ error: "#dc2626"};
2715
+ var explorerStyles = reactNative.StyleSheet.create({
2716
+ fullScreen: {
2717
+ flex: 1,
2718
+ backgroundColor: explorerColors.background
2719
+ },
2720
+ header: {
2721
+ flexDirection: "row",
2722
+ alignItems: "center",
2723
+ justifyContent: "space-between",
2724
+ paddingHorizontal: 16,
2725
+ paddingVertical: 14,
2726
+ borderBottomWidth: reactNative.StyleSheet.hairlineWidth,
2727
+ borderBottomColor: explorerColors.border,
2728
+ backgroundColor: explorerColors.background
2729
+ },
2730
+ headerTitle: {
2731
+ fontSize: 17,
2732
+ fontWeight: "700",
2733
+ color: explorerColors.text
2734
+ },
2735
+ headerAction: {
2736
+ fontSize: 16,
2737
+ color: explorerColors.accent,
2738
+ fontWeight: "600"
2739
+ },
2740
+ tabRow: {
2741
+ flexDirection: "row",
2742
+ borderBottomWidth: reactNative.StyleSheet.hairlineWidth,
2743
+ borderBottomColor: explorerColors.border,
2744
+ backgroundColor: explorerColors.surface
2745
+ },
2746
+ tab: {
2747
+ flex: 1,
2748
+ paddingVertical: 12,
2749
+ alignItems: "center",
2750
+ borderBottomWidth: 2,
2751
+ borderBottomColor: "transparent"
2752
+ },
2753
+ tabActive: {
2754
+ borderBottomColor: explorerColors.accent
2755
+ },
2756
+ tabLabel: {
2757
+ fontSize: 14,
2758
+ color: explorerColors.muted,
2759
+ fontWeight: "500"
2760
+ },
2761
+ tabLabelActive: {
2762
+ color: explorerColors.accent,
2763
+ fontWeight: "700"
2764
+ },
2765
+ content: {
2766
+ flex: 1,
2767
+ padding: 16
2768
+ },
2769
+ sectionTitle: {
2770
+ fontSize: 12,
2771
+ fontWeight: "600",
2772
+ color: explorerColors.muted,
2773
+ textTransform: "uppercase",
2774
+ letterSpacing: 0.5,
2775
+ marginBottom: 8
2776
+ },
2777
+ infoRow: {
2778
+ marginBottom: 10
2779
+ },
2780
+ infoLabel: {
2781
+ fontSize: 12,
2782
+ color: explorerColors.subtle,
2783
+ marginBottom: 2
2784
+ },
2785
+ infoValue: {
2786
+ fontSize: 14,
2787
+ color: explorerColors.text
2788
+ },
2789
+ mono: {
2790
+ fontFamily: "monospace",
2791
+ fontSize: 12
2792
+ },
2793
+ listItem: {
2794
+ flexDirection: "row",
2795
+ alignItems: "center",
2796
+ justifyContent: "space-between",
2797
+ paddingVertical: 12,
2798
+ paddingHorizontal: 12,
2799
+ borderRadius: 8,
2800
+ backgroundColor: explorerColors.surface,
2801
+ marginBottom: 8,
2802
+ borderWidth: 1,
2803
+ borderColor: explorerColors.border
2804
+ },
2805
+ listItemTitle: {
2806
+ fontSize: 15,
2807
+ fontWeight: "600",
2808
+ color: explorerColors.text,
2809
+ fontFamily: "monospace"
2810
+ },
2811
+ listItemMeta: {
2812
+ fontSize: 12,
2813
+ color: explorerColors.muted
2814
+ },
2815
+ searchInput: {
2816
+ borderWidth: 1,
2817
+ borderColor: explorerColors.border,
2818
+ borderRadius: 8,
2819
+ paddingHorizontal: 12,
2820
+ paddingVertical: 10,
2821
+ fontSize: 14,
2822
+ color: explorerColors.text,
2823
+ backgroundColor: explorerColors.background,
2824
+ marginBottom: 12
2825
+ },
2826
+ primaryButton: {
2827
+ backgroundColor: explorerColors.accent,
2828
+ borderRadius: 8,
2829
+ paddingVertical: 12,
2830
+ alignItems: "center",
2831
+ justifyContent: "center",
2832
+ minHeight: 44
2833
+ },
2834
+ primaryButtonDisabled: {
2835
+ opacity: 0.5
2836
+ },
2837
+ primaryButtonLabel: {
2838
+ color: "#f8fafc",
2839
+ fontSize: 15,
2840
+ fontWeight: "600"
2841
+ },
2842
+ secondaryButton: {
2843
+ backgroundColor: "#1e293b",
2844
+ borderRadius: 8,
2845
+ paddingVertical: 12,
2846
+ alignItems: "center",
2847
+ justifyContent: "center",
2848
+ minHeight: 44
2849
+ },
2850
+ secondaryButtonLabel: {
2851
+ color: "#f8fafc",
2852
+ fontSize: 15,
2853
+ fontWeight: "600"
2854
+ },
2855
+ ghostButton: {
2856
+ borderWidth: 1,
2857
+ borderColor: explorerColors.border,
2858
+ borderRadius: 8,
2859
+ paddingVertical: 12,
2860
+ alignItems: "center",
2861
+ justifyContent: "center",
2862
+ minHeight: 44,
2863
+ backgroundColor: explorerColors.background
2864
+ },
2865
+ ghostButtonLabel: {
2866
+ color: explorerColors.text,
2867
+ fontSize: 15,
2868
+ fontWeight: "600"
2869
+ },
2870
+ errorText: {
2871
+ fontSize: 13,
2872
+ color: explorerColors.error,
2873
+ marginTop: 8
2874
+ },
2875
+ hintText: {
2876
+ fontSize: 12,
2877
+ color: explorerColors.muted,
2878
+ marginTop: 8,
2879
+ lineHeight: 18
2880
+ },
2881
+ placeholder: {
2882
+ fontSize: 14,
2883
+ color: explorerColors.subtle,
2884
+ fontStyle: "italic",
2885
+ textAlign: "center",
2886
+ marginTop: 24
2887
+ },
2888
+ sqlInput: {
2889
+ minHeight: 120,
2890
+ borderWidth: 1,
2891
+ borderColor: explorerColors.border,
2892
+ borderRadius: 8,
2893
+ paddingHorizontal: 12,
2894
+ paddingVertical: 10,
2895
+ fontSize: 13,
2896
+ fontFamily: "monospace",
2897
+ color: explorerColors.text,
2898
+ textAlignVertical: "top",
2899
+ backgroundColor: explorerColors.surface
2900
+ },
2901
+ sqlTabRoot: {
2902
+ flex: 1,
2903
+ minHeight: 0
2904
+ },
2905
+ sqlQueryPane: {
2906
+ flexShrink: 0,
2907
+ paddingHorizontal: 16,
2908
+ paddingTop: 16,
2909
+ paddingBottom: 12,
2910
+ gap: 12,
2911
+ borderBottomWidth: reactNative.StyleSheet.hairlineWidth,
2912
+ borderBottomColor: explorerColors.border,
2913
+ backgroundColor: explorerColors.background
2914
+ },
2915
+ sqlInputCompact: {
2916
+ minHeight: 96,
2917
+ maxHeight: 120,
2918
+ borderWidth: 1,
2919
+ borderColor: explorerColors.border,
2920
+ borderRadius: 8,
2921
+ paddingHorizontal: 12,
2922
+ paddingVertical: 10,
2923
+ fontSize: 13,
2924
+ fontFamily: "monospace",
2925
+ color: explorerColors.text,
2926
+ textAlignVertical: "top",
2927
+ backgroundColor: explorerColors.surface
2928
+ },
2929
+ sqlResultsPane: {
2930
+ flex: 1,
2931
+ minHeight: 0,
2932
+ paddingHorizontal: 16,
2933
+ paddingTop: 8,
2934
+ paddingBottom: 16
2935
+ },
2936
+ sqlResultsEmpty: {
2937
+ flex: 1,
2938
+ justifyContent: "center",
2939
+ paddingHorizontal: 16
2940
+ },
2941
+ subTabRow: {
2942
+ flexDirection: "row",
2943
+ gap: 8,
2944
+ marginBottom: 12
2945
+ },
2946
+ subTab: {
2947
+ paddingHorizontal: 12,
2948
+ paddingVertical: 6,
2949
+ borderRadius: 999,
2950
+ borderWidth: 1,
2951
+ borderColor: explorerColors.border,
2952
+ backgroundColor: explorerColors.background
2953
+ },
2954
+ subTabActive: {
2955
+ borderColor: explorerColors.accent,
2956
+ backgroundColor: explorerColors.accentSoft
2957
+ },
2958
+ subTabLabel: {
2959
+ fontSize: 13,
2960
+ color: explorerColors.muted
2961
+ },
2962
+ subTabLabelActive: {
2963
+ color: explorerColors.accent,
2964
+ fontWeight: "600"
2965
+ },
2966
+ paginationRow: {
2967
+ flexDirection: "row",
2968
+ alignItems: "center",
2969
+ justifyContent: "space-between",
2970
+ paddingVertical: 12,
2971
+ borderTopWidth: reactNative.StyleSheet.hairlineWidth,
2972
+ borderTopColor: explorerColors.border
2973
+ },
2974
+ paginationLabel: {
2975
+ fontSize: 12,
2976
+ color: explorerColors.muted
2977
+ }
2978
+ });
2979
+ var tableStyles = reactNative.StyleSheet.create({
2980
+ viewRoot: {
2981
+ flex: 1,
2982
+ minHeight: 0
2983
+ },
2984
+ viewToolbar: {
2985
+ flexDirection: "row",
2986
+ alignItems: "center",
2987
+ justifyContent: "space-between",
2988
+ gap: 8,
2989
+ marginBottom: 8,
2990
+ flexWrap: "wrap"
2991
+ },
2992
+ viewMeta: {
2993
+ flex: 1,
2994
+ fontSize: 12,
2995
+ color: explorerColors.muted
2996
+ },
2997
+ viewToggle: {
2998
+ flexDirection: "row",
2999
+ borderWidth: 1,
3000
+ borderColor: explorerColors.border,
3001
+ borderRadius: 8,
3002
+ overflow: "hidden"
3003
+ },
3004
+ viewToggleButton: {
3005
+ paddingHorizontal: 10,
3006
+ paddingVertical: 6,
3007
+ backgroundColor: explorerColors.background
3008
+ },
3009
+ viewToggleButtonActive: {
3010
+ backgroundColor: explorerColors.accentSoft
3011
+ },
3012
+ viewToggleLabel: {
3013
+ fontSize: 12,
3014
+ fontWeight: "600",
3015
+ color: explorerColors.muted
3016
+ },
3017
+ viewToggleLabelActive: {
3018
+ color: explorerColors.accent
3019
+ },
3020
+ footerHint: {
3021
+ flexShrink: 0,
3022
+ marginTop: 8,
3023
+ fontSize: 11,
3024
+ color: explorerColors.subtle,
3025
+ textAlign: "center"
3026
+ },
3027
+ tableContainer: {
3028
+ flex: 1,
3029
+ minHeight: 0,
3030
+ borderWidth: 1,
3031
+ borderColor: explorerColors.border,
3032
+ borderRadius: 8,
3033
+ overflow: "hidden",
3034
+ backgroundColor: explorerColors.background
3035
+ },
3036
+ horizontalScroll: {
3037
+ flex: 1
3038
+ },
3039
+ scrollHint: {
3040
+ paddingHorizontal: 10,
3041
+ paddingVertical: 6,
3042
+ fontSize: 11,
3043
+ color: explorerColors.muted,
3044
+ backgroundColor: explorerColors.surface,
3045
+ borderBottomWidth: reactNative.StyleSheet.hairlineWidth,
3046
+ borderBottomColor: explorerColors.border
3047
+ },
3048
+ headerRow: {
3049
+ flexDirection: "row",
3050
+ backgroundColor: "#f1f5f9",
3051
+ borderBottomWidth: 1,
3052
+ borderBottomColor: explorerColors.border
3053
+ },
3054
+ headerCell: {
3055
+ backgroundColor: "#f1f5f9"
3056
+ },
3057
+ headerText: {
3058
+ fontSize: 12,
3059
+ fontWeight: "700",
3060
+ color: explorerColors.text,
3061
+ fontFamily: "monospace"
3062
+ },
3063
+ bodyList: {
3064
+ flex: 1
3065
+ },
3066
+ dataRow: {
3067
+ flexDirection: "row",
3068
+ backgroundColor: explorerColors.background
3069
+ },
3070
+ dataRowAlt: {
3071
+ backgroundColor: explorerColors.surface
3072
+ },
3073
+ cell: {
3074
+ paddingHorizontal: 10,
3075
+ paddingVertical: 8,
3076
+ borderRightWidth: 1,
3077
+ borderBottomWidth: 1,
3078
+ borderColor: explorerColors.border,
3079
+ justifyContent: "center"
3080
+ },
3081
+ cellText: {
3082
+ fontSize: 13,
3083
+ color: explorerColors.text
3084
+ },
3085
+ cellTextMono: {
3086
+ fontFamily: "monospace",
3087
+ fontSize: 12
3088
+ },
3089
+ cellNull: {
3090
+ color: explorerColors.subtle,
3091
+ fontStyle: "italic"
3092
+ },
3093
+ copiedHint: {
3094
+ marginTop: 2,
3095
+ fontSize: 10,
3096
+ color: explorerColors.accent,
3097
+ fontWeight: "600"
3098
+ },
3099
+ cardList: {
3100
+ paddingBottom: 8,
3101
+ gap: 8
3102
+ },
3103
+ card: {
3104
+ borderWidth: 1,
3105
+ borderColor: explorerColors.border,
3106
+ borderRadius: 8,
3107
+ backgroundColor: explorerColors.surface,
3108
+ padding: 12,
3109
+ gap: 10
3110
+ },
3111
+ cardField: {
3112
+ gap: 2
3113
+ },
3114
+ cardLabel: {
3115
+ fontSize: 11,
3116
+ fontWeight: "600",
3117
+ color: explorerColors.muted,
3118
+ textTransform: "uppercase",
3119
+ letterSpacing: 0.4
3120
+ },
3121
+ cardValue: {
3122
+ fontSize: 14,
3123
+ color: explorerColors.text
3124
+ },
3125
+ cardValueMono: {
3126
+ fontFamily: "monospace",
3127
+ fontSize: 13
3128
+ },
3129
+ detailBody: {
3130
+ flex: 1,
3131
+ minHeight: 0,
3132
+ paddingHorizontal: 16
3133
+ },
3134
+ detailDataPane: {
3135
+ flex: 1,
3136
+ minHeight: 0
3137
+ }
3138
+ });
3139
+ function formatBytes(bytes) {
3140
+ if (bytes < 1024) {
3141
+ return `${bytes} B`;
3142
+ }
3143
+ if (bytes < 1024 * 1024) {
3144
+ return `${(bytes / 1024).toFixed(1)} KB`;
3145
+ }
3146
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3147
+ }
3148
+ function ExplorerOverviewTab({
3149
+ info,
3150
+ loading,
3151
+ error,
3152
+ onOpenTables,
3153
+ onOpenSql
3154
+ }) {
3155
+ if (loading) {
3156
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.placeholder, children: "Loading database info\u2026" });
3157
+ }
3158
+ if (error) {
3159
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.errorText, children: error });
3160
+ }
3161
+ if (!info) {
3162
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.placeholder, children: "No database information available." });
3163
+ }
3164
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.ScrollView, { contentContainerStyle: explorerStyles.content, children: [
3165
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.sectionTitle, children: "Database" }),
3166
+ /* @__PURE__ */ jsxRuntime.jsx(Info, { label: "Name", value: info.name }),
3167
+ /* @__PURE__ */ jsxRuntime.jsx(Info, { label: "Path", value: info.path, mono: true }),
3168
+ /* @__PURE__ */ jsxRuntime.jsx(Info, { label: "SQLite version", value: info.sqliteVersion }),
3169
+ /* @__PURE__ */ jsxRuntime.jsx(Info, { label: "Tables", value: String(info.tableCount) }),
3170
+ /* @__PURE__ */ jsxRuntime.jsx(Info, { label: "Estimated size", value: formatBytes(info.estimatedSizeBytes) }),
3171
+ /* @__PURE__ */ jsxRuntime.jsx(
3172
+ Info,
3173
+ {
3174
+ label: "Pages",
3175
+ value: `${info.pageCount.toLocaleString()} \xD7 ${info.pageSize} bytes`
3176
+ }
3177
+ ),
3178
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { height: 16 } }),
3179
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.sectionTitle, children: "Quick actions" }),
3180
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { gap: 8 }, children: [
3181
+ /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { label: "Browse tables", onPress: onOpenTables }),
3182
+ /* @__PURE__ */ jsxRuntime.jsx(ActionButton, { label: "Run SQL query", onPress: onOpenSql })
3183
+ ] })
3184
+ ] });
3185
+ }
3186
+ function Info({ label, value, mono = false }) {
3187
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: explorerStyles.infoRow, children: [
3188
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.infoLabel, children: label }),
3189
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [explorerStyles.infoValue, mono && explorerStyles.mono], selectable: true, children: value })
3190
+ ] });
3191
+ }
3192
+ function ActionButton({ label, onPress }) {
3193
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress, style: explorerStyles.ghostButton, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.ghostButtonLabel, children: label }) });
3194
+ }
3195
+
3196
+ // src/mobile/constants.ts
3197
+ var DEFAULT_MOBILE_SQL = "";
3198
+ var DEFAULT_PAGE_SIZE = 50;
3199
+
3200
+ // src/mobile/computeColumnWidths.ts
3201
+ var CHAR_WIDTH = 7.5;
3202
+ var CELL_PADDING = 24;
3203
+ var MIN_COLUMN_WIDTH = 72;
3204
+ var MAX_COLUMN_WIDTH = 280;
3205
+ var SAMPLE_ROW_LIMIT = 20;
3206
+ function hintMinWidth(columnName) {
3207
+ const lower = columnName.toLowerCase();
3208
+ if (lower === "id" || lower.endsWith("_id")) {
3209
+ return 80;
3210
+ }
3211
+ if (lower.includes("email")) {
3212
+ return 168;
3213
+ }
3214
+ if (lower.includes("created_at") || lower.includes("updated_at") || lower.endsWith("_at")) {
3215
+ return 188;
3216
+ }
3217
+ if (lower === "name" || lower.includes("title")) {
3218
+ return 128;
3219
+ }
3220
+ if (lower === "status" || lower === "type") {
3221
+ return 96;
3222
+ }
3223
+ return MIN_COLUMN_WIDTH;
3224
+ }
3225
+ function measureText(text) {
3226
+ return Math.ceil(text.length * CHAR_WIDTH) + CELL_PADDING;
3227
+ }
3228
+ function formatCellValue(value) {
3229
+ if (value === null) {
3230
+ return "NULL";
3231
+ }
3232
+ return String(value);
3233
+ }
3234
+ function computeColumnWidths(columns, rows) {
3235
+ const sampleRows = rows.slice(0, SAMPLE_ROW_LIMIT);
3236
+ return columns.map((column, columnIndex) => {
3237
+ let maxWidth = measureText(column);
3238
+ for (const row of sampleRows) {
3239
+ const cell = formatCellValue(row[columnIndex] ?? null);
3240
+ maxWidth = Math.max(maxWidth, measureText(cell));
3241
+ }
3242
+ maxWidth = Math.max(maxWidth, hintMinWidth(column));
3243
+ return Math.min(MAX_COLUMN_WIDTH, Math.max(MIN_COLUMN_WIDTH, maxWidth));
3244
+ });
3245
+ }
3246
+ function totalTableWidth(columnWidths) {
3247
+ return columnWidths.reduce((sum, width) => sum + width, 0);
3248
+ }
3249
+ function isMonospaceColumn(columnName) {
3250
+ const lower = columnName.toLowerCase();
3251
+ return lower === "id" || lower.endsWith("_id") || lower.includes("created_at") || lower.includes("updated_at") || lower.endsWith("_at") || lower.includes("date") || lower.includes("time");
3252
+ }
3253
+
3254
+ // src/mobile/copyToClipboard.ts
3255
+ async function copyToClipboard(_text) {
3256
+ return false;
3257
+ }
3258
+ function formatValue(value) {
3259
+ return value === null ? "NULL" : String(value);
3260
+ }
3261
+ function MobileDataCardList({ columns, rows }) {
3262
+ const [copiedKey, setCopiedKey] = react.useState(null);
3263
+ const copyValue = async (value, key) => {
3264
+ await copyToClipboard();
3265
+ setCopiedKey(key);
3266
+ setTimeout(() => setCopiedKey(null), 1500);
3267
+ };
3268
+ return /* @__PURE__ */ jsxRuntime.jsx(
3269
+ reactNative.FlatList,
3270
+ {
3271
+ data: rows,
3272
+ keyExtractor: (_, index) => String(index),
3273
+ contentContainerStyle: tableStyles.cardList,
3274
+ style: tableStyles.bodyList,
3275
+ renderItem: ({ item: row, index: rowIndex }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: tableStyles.card, children: columns.map((column, columnIndex) => {
3276
+ const value = formatValue(row[columnIndex] ?? null);
3277
+ const key = `${rowIndex}-${column}`;
3278
+ const isNull = row[columnIndex] === null;
3279
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3280
+ reactNative.Pressable,
3281
+ {
3282
+ onPress: () => void copyValue(value, key),
3283
+ style: tableStyles.cardField,
3284
+ children: [
3285
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: tableStyles.cardLabel, children: column }),
3286
+ /* @__PURE__ */ jsxRuntime.jsx(
3287
+ reactNative.Text,
3288
+ {
3289
+ style: [
3290
+ tableStyles.cardValue,
3291
+ isMonospaceColumn(column) && tableStyles.cardValueMono,
3292
+ isNull && tableStyles.cellNull
3293
+ ],
3294
+ children: value
3295
+ }
3296
+ ),
3297
+ copiedKey === key && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: tableStyles.copiedHint, children: "Copied" })
3298
+ ]
3299
+ },
3300
+ key
3301
+ );
3302
+ }) })
3303
+ }
3304
+ );
3305
+ }
3306
+ function formatValue2(value) {
3307
+ return value === null ? "NULL" : String(value);
3308
+ }
3309
+ function MobileDataTable({ columns, rows, columnWidths: columnWidthsProp }) {
3310
+ const { width: windowWidth } = reactNative.useWindowDimensions();
3311
+ const [copiedKey, setCopiedKey] = react.useState(null);
3312
+ const columnWidths = react.useMemo(
3313
+ () => columnWidthsProp ?? computeColumnWidths(columns, rows),
3314
+ [columnWidthsProp, columns, rows]
3315
+ );
3316
+ const tableWidth = react.useMemo(() => totalTableWidth(columnWidths), [columnWidths]);
3317
+ const showScrollHint = tableWidth > windowWidth - 32;
3318
+ const copyCell = async (value, key) => {
3319
+ await copyToClipboard();
3320
+ setCopiedKey(key);
3321
+ setTimeout(() => setCopiedKey(null), 1500);
3322
+ };
3323
+ if (columns.length === 0) {
3324
+ return null;
3325
+ }
3326
+ const headerRow = /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: tableStyles.headerRow, children: columns.map((column, index) => /* @__PURE__ */ jsxRuntime.jsx(
3327
+ reactNative.View,
3328
+ {
3329
+ style: [tableStyles.cell, tableStyles.headerCell, { width: columnWidths[index] }],
3330
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: tableStyles.headerText, children: column })
3331
+ },
3332
+ column
3333
+ )) });
3334
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: tableStyles.tableContainer, children: [
3335
+ showScrollHint && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: tableStyles.scrollHint, children: "Swipe horizontally for more columns" }),
3336
+ /* @__PURE__ */ jsxRuntime.jsx(
3337
+ reactNative.ScrollView,
3338
+ {
3339
+ horizontal: true,
3340
+ bounces: false,
3341
+ nestedScrollEnabled: true,
3342
+ showsHorizontalScrollIndicator: true,
3343
+ style: tableStyles.horizontalScroll,
3344
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { width: tableWidth, flex: 1 }, children: [
3345
+ headerRow,
3346
+ /* @__PURE__ */ jsxRuntime.jsx(
3347
+ reactNative.FlatList,
3348
+ {
3349
+ data: rows,
3350
+ keyExtractor: (_, index) => String(index),
3351
+ nestedScrollEnabled: true,
3352
+ style: tableStyles.bodyList,
3353
+ renderItem: ({ item: row, index: rowIndex }) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [tableStyles.dataRow, rowIndex % 2 === 1 && tableStyles.dataRowAlt], children: row.map((cell, cellIndex) => {
3354
+ const column = columns[cellIndex];
3355
+ const display = formatValue2(cell);
3356
+ const key = `${rowIndex}-${cellIndex}`;
3357
+ const isNull = cell === null;
3358
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3359
+ reactNative.Pressable,
3360
+ {
3361
+ onPress: () => void copyCell(display, key),
3362
+ style: [tableStyles.cell, { width: columnWidths[cellIndex] }],
3363
+ children: [
3364
+ /* @__PURE__ */ jsxRuntime.jsx(
3365
+ reactNative.Text,
3366
+ {
3367
+ numberOfLines: 2,
3368
+ selectable: true,
3369
+ style: [
3370
+ tableStyles.cellText,
3371
+ isMonospaceColumn(column) && tableStyles.cellTextMono,
3372
+ isNull && tableStyles.cellNull
3373
+ ],
3374
+ children: display
3375
+ }
3376
+ ),
3377
+ copiedKey === key && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: tableStyles.copiedHint, children: "Copied" })
3378
+ ]
3379
+ },
3380
+ key
3381
+ );
3382
+ }) })
3383
+ }
3384
+ )
3385
+ ] })
3386
+ }
3387
+ )
3388
+ ] });
3389
+ }
3390
+ function MobileDataView({
3391
+ columns,
3392
+ rows,
3393
+ metaSuffix,
3394
+ defaultMode = "table",
3395
+ dataKey
3396
+ }) {
3397
+ const [mode, setMode] = react.useState(defaultMode);
3398
+ const columnWidths = react.useMemo(() => computeColumnWidths(columns, rows), [columns, rows]);
3399
+ const tableWidth = react.useMemo(() => totalTableWidth(columnWidths), [columnWidths]);
3400
+ const columnsKey = columns.join("|");
3401
+ react.useEffect(() => {
3402
+ setMode(defaultMode);
3403
+ }, [columnsKey, dataKey, defaultMode, rows.length]);
3404
+ if (columns.length === 0) {
3405
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.placeholder, children: "No rows to display." });
3406
+ }
3407
+ const meta = `${rows.length} row${rows.length === 1 ? "" : "s"} \xB7 ${columns.length} column${columns.length === 1 ? "" : "s"}${metaSuffix ? ` \xB7 ${metaSuffix}` : ""}`;
3408
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: tableStyles.viewRoot, children: [
3409
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: tableStyles.viewToolbar, children: [
3410
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: tableStyles.viewMeta, children: meta }),
3411
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: tableStyles.viewToggle, children: [
3412
+ /* @__PURE__ */ jsxRuntime.jsx(
3413
+ reactNative.Pressable,
3414
+ {
3415
+ onPress: () => setMode("cards"),
3416
+ style: [tableStyles.viewToggleButton, mode === "cards" && tableStyles.viewToggleButtonActive],
3417
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3418
+ reactNative.Text,
3419
+ {
3420
+ style: [tableStyles.viewToggleLabel, mode === "cards" && tableStyles.viewToggleLabelActive],
3421
+ children: "Cards"
3422
+ }
3423
+ )
3424
+ }
3425
+ ),
3426
+ /* @__PURE__ */ jsxRuntime.jsx(
3427
+ reactNative.Pressable,
3428
+ {
3429
+ onPress: () => setMode("table"),
3430
+ style: [tableStyles.viewToggleButton, mode === "table" && tableStyles.viewToggleButtonActive],
3431
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3432
+ reactNative.Text,
3433
+ {
3434
+ style: [tableStyles.viewToggleLabel, mode === "table" && tableStyles.viewToggleLabelActive],
3435
+ children: "Table"
3436
+ }
3437
+ )
3438
+ }
3439
+ )
3440
+ ] })
3441
+ ] }),
3442
+ mode === "cards" ? /* @__PURE__ */ jsxRuntime.jsx(MobileDataCardList, { columns, rows }) : /* @__PURE__ */ jsxRuntime.jsx(MobileDataTable, { columnWidths, columns, rows }),
3443
+ mode === "table" && tableWidth > 320 && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: tableStyles.footerHint, children: "Tap a cell to copy its value" }),
3444
+ mode === "cards" && /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: tableStyles.footerHint, children: "Tap a field to copy its value" })
3445
+ ] });
3446
+ }
3447
+ function ExplorerSqlTab({ inspector }) {
3448
+ const [sql, setSql] = react.useState(DEFAULT_MOBILE_SQL);
3449
+ const [result, setResult] = react.useState(null);
3450
+ const [error, setError] = react.useState(null);
3451
+ const [running, setRunning] = react.useState(false);
3452
+ const [panel, setPanel] = react.useState("results");
3453
+ const [resultKey, setResultKey] = react.useState(0);
3454
+ const runQuery = async () => {
3455
+ setRunning(true);
3456
+ setError(null);
3457
+ try {
3458
+ const queryResult = await inspector.executeQuery(sql);
3459
+ setResult(queryResult);
3460
+ setResultKey((current) => current + 1);
3461
+ setPanel("results");
3462
+ } catch (runError) {
3463
+ const message = runError instanceof Error ? runError.message : "Query failed";
3464
+ setError(message);
3465
+ setResult(null);
3466
+ setPanel("output");
3467
+ } finally {
3468
+ setRunning(false);
3469
+ }
3470
+ };
3471
+ const renderResultsContent = () => {
3472
+ if (panel === "output") {
3473
+ if (error) {
3474
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.errorText, children: error });
3475
+ }
3476
+ if (result) {
3477
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: explorerStyles.infoValue, children: [
3478
+ "Query executed successfully. ",
3479
+ result.rowCount,
3480
+ " row",
3481
+ result.rowCount === 1 ? "" : "s",
3482
+ " in ",
3483
+ result.durationMs.toFixed(1),
3484
+ " ms."
3485
+ ] });
3486
+ }
3487
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.placeholder, children: "Messages and errors appear here." });
3488
+ }
3489
+ if (!result) {
3490
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: explorerStyles.sqlResultsEmpty, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [explorerStyles.placeholder, { marginTop: 0 }], children: "Run a query to see results." }) });
3491
+ }
3492
+ if (result.columns.length === 0) {
3493
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: explorerStyles.sqlResultsEmpty, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [explorerStyles.placeholder, { marginTop: 0 }], children: [
3494
+ "Query completed with no result set (",
3495
+ result.durationMs.toFixed(1),
3496
+ " ms)."
3497
+ ] }) });
3498
+ }
3499
+ return /* @__PURE__ */ jsxRuntime.jsx(
3500
+ MobileDataView,
3501
+ {
3502
+ columns: result.columns,
3503
+ dataKey: String(resultKey),
3504
+ defaultMode: "table",
3505
+ metaSuffix: `${result.durationMs.toFixed(1)} ms`,
3506
+ rows: result.rows
3507
+ }
3508
+ );
3509
+ };
3510
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: explorerStyles.sqlTabRoot, children: [
3511
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: explorerStyles.sqlQueryPane, children: [
3512
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.sectionTitle, children: "SQL (read-only)" }),
3513
+ /* @__PURE__ */ jsxRuntime.jsx(
3514
+ reactNative.TextInput,
3515
+ {
3516
+ autoCapitalize: "none",
3517
+ autoCorrect: false,
3518
+ multiline: true,
3519
+ onChangeText: setSql,
3520
+ scrollEnabled: true,
3521
+ style: explorerStyles.sqlInputCompact,
3522
+ value: sql
3523
+ }
3524
+ ),
3525
+ /* @__PURE__ */ jsxRuntime.jsx(
3526
+ reactNative.Pressable,
3527
+ {
3528
+ disabled: running,
3529
+ onPress: () => void runQuery(),
3530
+ style: [explorerStyles.primaryButton, running && explorerStyles.primaryButtonDisabled],
3531
+ children: running ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: "#f8fafc" }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.primaryButtonLabel, children: "Run query" })
3532
+ }
3533
+ ),
3534
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [explorerStyles.hintText, { marginTop: 0 }], children: "Allowed: SELECT, PRAGMA, EXPLAIN, WITH. Writes are blocked in DevTools." })
3535
+ ] }),
3536
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [explorerStyles.tabRow, { flexShrink: 0 }], children: [
3537
+ /* @__PURE__ */ jsxRuntime.jsx(
3538
+ reactNative.Pressable,
3539
+ {
3540
+ onPress: () => setPanel("results"),
3541
+ style: [explorerStyles.tab, panel === "results" && explorerStyles.tabActive],
3542
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
3543
+ reactNative.Text,
3544
+ {
3545
+ style: [explorerStyles.tabLabel, panel === "results" && explorerStyles.tabLabelActive],
3546
+ children: [
3547
+ "Results",
3548
+ result ? ` (${result.rowCount})` : ""
3549
+ ]
3550
+ }
3551
+ )
3552
+ }
3553
+ ),
3554
+ /* @__PURE__ */ jsxRuntime.jsx(
3555
+ reactNative.Pressable,
3556
+ {
3557
+ onPress: () => setPanel("output"),
3558
+ style: [explorerStyles.tab, panel === "output" && explorerStyles.tabActive],
3559
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3560
+ reactNative.Text,
3561
+ {
3562
+ style: [explorerStyles.tabLabel, panel === "output" && explorerStyles.tabLabelActive],
3563
+ children: "Output"
3564
+ }
3565
+ )
3566
+ }
3567
+ )
3568
+ ] }),
3569
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: explorerStyles.sqlResultsPane, children: renderResultsContent() })
3570
+ ] });
3571
+ }
3572
+ function ExplorerTableDetail({ inspector, tableName, onBack }) {
3573
+ const [view, setView] = react.useState("data");
3574
+ const [columns, setColumns] = react.useState([]);
3575
+ const [pageResult, setPageResult] = react.useState(null);
3576
+ const [search, setSearch] = react.useState("");
3577
+ const [page, setPage] = react.useState(1);
3578
+ const [loading, setLoading] = react.useState(true);
3579
+ const [error, setError] = react.useState(null);
3580
+ const loadData = react.useCallback(async () => {
3581
+ setLoading(true);
3582
+ setError(null);
3583
+ try {
3584
+ const nextColumns = await inspector.getTableColumns(tableName);
3585
+ setColumns(nextColumns);
3586
+ const result = await inspector.fetchTablePage({
3587
+ table: tableName,
3588
+ page,
3589
+ pageSize: DEFAULT_PAGE_SIZE,
3590
+ search
3591
+ });
3592
+ setPageResult(result);
3593
+ } catch (loadError) {
3594
+ setError(loadError instanceof Error ? loadError.message : "Failed to load table");
3595
+ } finally {
3596
+ setLoading(false);
3597
+ }
3598
+ }, [inspector, page, search, tableName]);
3599
+ react.useEffect(() => {
3600
+ void loadData();
3601
+ }, [loadData]);
3602
+ const totalPages = pageResult ? Math.max(1, Math.ceil(pageResult.totalCount / pageResult.pageSize)) : 1;
3603
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: explorerStyles.fullScreen, children: [
3604
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: explorerStyles.header, children: [
3605
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onBack, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.headerAction, children: "\u2190 Back" }) }),
3606
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.headerTitle, children: tableName }),
3607
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { width: 48 } })
3608
+ ] }),
3609
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [explorerStyles.subTabRow, { paddingHorizontal: 16, paddingTop: 12 }], children: [
3610
+ /* @__PURE__ */ jsxRuntime.jsx(
3611
+ reactNative.Pressable,
3612
+ {
3613
+ onPress: () => setView("data"),
3614
+ style: [explorerStyles.subTab, view === "data" && explorerStyles.subTabActive],
3615
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [explorerStyles.subTabLabel, view === "data" && explorerStyles.subTabLabelActive], children: "Data" })
3616
+ }
3617
+ ),
3618
+ /* @__PURE__ */ jsxRuntime.jsx(
3619
+ reactNative.Pressable,
3620
+ {
3621
+ onPress: () => setView("schema"),
3622
+ style: [explorerStyles.subTab, view === "schema" && explorerStyles.subTabActive],
3623
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3624
+ reactNative.Text,
3625
+ {
3626
+ style: [explorerStyles.subTabLabel, view === "schema" && explorerStyles.subTabLabelActive],
3627
+ children: "Schema"
3628
+ }
3629
+ )
3630
+ }
3631
+ )
3632
+ ] }),
3633
+ view === "data" ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: tableStyles.detailBody, children: [
3634
+ /* @__PURE__ */ jsxRuntime.jsx(
3635
+ reactNative.TextInput,
3636
+ {
3637
+ autoCapitalize: "none",
3638
+ autoCorrect: false,
3639
+ onChangeText: (value) => {
3640
+ setSearch(value);
3641
+ setPage(1);
3642
+ },
3643
+ placeholder: "Search rows\u2026",
3644
+ style: explorerStyles.searchInput,
3645
+ value: search
3646
+ }
3647
+ ),
3648
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { style: { marginTop: 24 } }) : error ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.errorText, children: error }) : pageResult ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3649
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: tableStyles.detailDataPane, children: /* @__PURE__ */ jsxRuntime.jsx(
3650
+ MobileDataView,
3651
+ {
3652
+ columns: pageResult.columns,
3653
+ dataKey: `${tableName}-${page}-${search}`,
3654
+ defaultMode: "table",
3655
+ metaSuffix: `page ${page} of ${totalPages}`,
3656
+ rows: pageResult.rows
3657
+ }
3658
+ ) }),
3659
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: explorerStyles.paginationRow, children: [
3660
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { disabled: page <= 1, onPress: () => setPage((current) => current - 1), children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.headerAction, children: "Previous" }) }),
3661
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: explorerStyles.paginationLabel, children: [
3662
+ "Page ",
3663
+ page,
3664
+ " of ",
3665
+ totalPages,
3666
+ " \xB7 ",
3667
+ pageResult.totalCount.toLocaleString(),
3668
+ " rows"
3669
+ ] }),
3670
+ /* @__PURE__ */ jsxRuntime.jsx(
3671
+ reactNative.Pressable,
3672
+ {
3673
+ disabled: page >= totalPages,
3674
+ onPress: () => setPage((current) => current + 1),
3675
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.headerAction, children: "Next" })
3676
+ }
3677
+ )
3678
+ ] })
3679
+ ] }) : null
3680
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(reactNative.ScrollView, { contentContainerStyle: explorerStyles.content, children: columns.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.placeholder, children: "No schema information." }) : columns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: explorerStyles.listItem, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
3681
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.listItemTitle, children: column.name }),
3682
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: explorerStyles.listItemMeta, children: [
3683
+ column.type,
3684
+ column.pk ? " \xB7 PK" : "",
3685
+ column.notNull ? " \xB7 NOT NULL" : "",
3686
+ column.defaultValue ? ` \xB7 DEFAULT ${column.defaultValue}` : ""
3687
+ ] })
3688
+ ] }) }, column.name)) })
3689
+ ] });
3690
+ }
3691
+ function ExplorerTablesTab({
3692
+ tables,
3693
+ loading,
3694
+ error,
3695
+ onSelectTable
3696
+ }) {
3697
+ const [search, setSearch] = react.useState("");
3698
+ const filtered = react.useMemo(() => {
3699
+ const term = search.trim().toLowerCase();
3700
+ if (!term) {
3701
+ return tables;
3702
+ }
3703
+ return tables.filter((table) => table.name.toLowerCase().includes(term));
3704
+ }, [search, tables]);
3705
+ if (loading) {
3706
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.placeholder, children: "Loading tables\u2026" });
3707
+ }
3708
+ if (error) {
3709
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.errorText, children: error });
3710
+ }
3711
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, padding: 16 }, children: [
3712
+ /* @__PURE__ */ jsxRuntime.jsx(
3713
+ reactNative.TextInput,
3714
+ {
3715
+ autoCapitalize: "none",
3716
+ autoCorrect: false,
3717
+ onChangeText: setSearch,
3718
+ placeholder: "Filter tables\u2026",
3719
+ style: explorerStyles.searchInput,
3720
+ value: search
3721
+ }
3722
+ ),
3723
+ filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.placeholder, children: "No tables match your search." }) : /* @__PURE__ */ jsxRuntime.jsx(
3724
+ reactNative.FlatList,
3725
+ {
3726
+ data: filtered,
3727
+ keyExtractor: (item) => item.name,
3728
+ style: { flex: 1 },
3729
+ renderItem: ({ item }) => /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress: () => onSelectTable(item.name), style: explorerStyles.listItem, children: [
3730
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
3731
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.listItemTitle, children: item.name }),
3732
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: explorerStyles.listItemMeta, children: [
3733
+ item.rowCount.toLocaleString(),
3734
+ " row",
3735
+ item.rowCount === 1 ? "" : "s"
3736
+ ] })
3737
+ ] }),
3738
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.listItemMeta, children: "\u203A" })
3739
+ ] })
3740
+ }
3741
+ )
3742
+ ] });
3743
+ }
3744
+ function MobileDatabaseExplorer() {
3745
+ const { explorerVisible, closeExplorer, mobileInspector } = useDevTools();
3746
+ const [tab, setTab] = react.useState("overview");
3747
+ const [screen, setScreen] = react.useState({ kind: "tabs" });
3748
+ const [info, setInfo] = react.useState(null);
3749
+ const [tables, setTables] = react.useState([]);
3750
+ const [loading, setLoading] = react.useState(false);
3751
+ const [error, setError] = react.useState(null);
3752
+ const loadOverview = react.useCallback(async () => {
3753
+ if (!mobileInspector) {
3754
+ return;
3755
+ }
3756
+ setLoading(true);
3757
+ setError(null);
3758
+ try {
3759
+ const [nextInfo, nextTables] = await Promise.all([
3760
+ mobileInspector.getDatabaseInfo(),
3761
+ mobileInspector.listTables()
3762
+ ]);
3763
+ setInfo(nextInfo);
3764
+ setTables(nextTables);
3765
+ } catch (loadError) {
3766
+ setError(loadError instanceof Error ? loadError.message : "Failed to load database");
3767
+ } finally {
3768
+ setLoading(false);
3769
+ }
3770
+ }, [mobileInspector]);
3771
+ react.useEffect(() => {
3772
+ if (!explorerVisible) {
3773
+ setScreen({ kind: "tabs" });
3774
+ setTab("overview");
3775
+ return;
3776
+ }
3777
+ void loadOverview();
3778
+ }, [explorerVisible, loadOverview]);
3779
+ if (!mobileInspector) {
3780
+ return null;
3781
+ }
3782
+ return /* @__PURE__ */ jsxRuntime.jsx(reactNative.Modal, { animationType: "slide", onRequestClose: closeExplorer, visible: explorerVisible, children: screen.kind === "table" ? /* @__PURE__ */ jsxRuntime.jsx(
3783
+ ExplorerTableDetail,
3784
+ {
3785
+ inspector: mobileInspector,
3786
+ onBack: () => setScreen({ kind: "tabs" }),
3787
+ tableName: screen.tableName
3788
+ }
3789
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: explorerStyles.fullScreen, children: [
3790
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: explorerStyles.header, children: [
3791
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.headerTitle, children: "Database Explorer" }),
3792
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { accessibilityLabel: "Close database explorer", onPress: closeExplorer, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: explorerStyles.headerAction, children: "Close" }) })
3793
+ ] }),
3794
+ /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: explorerStyles.tabRow, children: ["overview", "tables", "sql"].map((item) => /* @__PURE__ */ jsxRuntime.jsx(
3795
+ reactNative.Pressable,
3796
+ {
3797
+ onPress: () => setTab(item),
3798
+ style: [explorerStyles.tab, tab === item && explorerStyles.tabActive],
3799
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3800
+ reactNative.Text,
3801
+ {
3802
+ style: [explorerStyles.tabLabel, tab === item && explorerStyles.tabLabelActive],
3803
+ children: item === "overview" ? "Overview" : item === "tables" ? "Tables" : "SQL"
3804
+ }
3805
+ )
3806
+ },
3807
+ item
3808
+ )) }),
3809
+ /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
3810
+ tab === "overview" && /* @__PURE__ */ jsxRuntime.jsx(
3811
+ ExplorerOverviewTab,
3812
+ {
3813
+ error,
3814
+ info,
3815
+ loading,
3816
+ onOpenSql: () => setTab("sql"),
3817
+ onOpenTables: () => setTab("tables")
3818
+ }
3819
+ ),
3820
+ tab === "tables" && /* @__PURE__ */ jsxRuntime.jsx(
3821
+ ExplorerTablesTab,
3822
+ {
3823
+ error,
3824
+ loading,
3825
+ onSelectTable: (tableName) => setScreen({ kind: "table", tableName }),
3826
+ tables
3827
+ }
3828
+ ),
3829
+ tab === "sql" && /* @__PURE__ */ jsxRuntime.jsx(ExplorerSqlTab, { inspector: mobileInspector })
3830
+ ] })
3831
+ ] }) });
3832
+ }
3833
+ function DatabaseDevTools({
3834
+ database,
3835
+ type,
3836
+ adapter,
3837
+ serverUrl,
3838
+ enabled,
3839
+ position = "bottom-right",
3840
+ draggable = true,
3841
+ snapToEdges = true,
3842
+ floatingPosition,
3843
+ onFloatingPositionChange,
3844
+ style,
3845
+ onConnectionStateChange
3846
+ }) {
3847
+ if (!isDevToolsEnabled(enabled)) {
3848
+ return null;
3849
+ }
3850
+ return /* @__PURE__ */ jsxRuntime.jsx(
3851
+ DevToolsProvider,
3852
+ {
3853
+ adapter,
3854
+ database,
3855
+ onConnectionStateChange,
3856
+ serverUrl,
3857
+ type,
3858
+ children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { pointerEvents: "box-none", style: reactNative.StyleSheet.absoluteFill, children: [
3859
+ /* @__PURE__ */ jsxRuntime.jsx(
3860
+ FloatingDevToolsButton,
3861
+ {
3862
+ draggable,
3863
+ floatingPosition,
3864
+ iconStyle: style,
3865
+ onFloatingPositionChange,
3866
+ position,
3867
+ snapToEdges
3868
+ }
3869
+ ),
3870
+ /* @__PURE__ */ jsxRuntime.jsx(DevToolsLauncherModal, {}),
3871
+ /* @__PURE__ */ jsxRuntime.jsx(DevToolsSettingsModal, {}),
3872
+ /* @__PURE__ */ jsxRuntime.jsx(MobileDatabaseExplorer, {})
3873
+ ] })
3874
+ }
3875
+ );
3876
+ }
3877
+
3878
+ exports.AdapterResolutionError = AdapterResolutionError;
3879
+ exports.DATABASE_DEVTOOLS_KIND = DATABASE_DEVTOOLS_KIND;
3880
+ exports.DEFAULT_DEVTOOLS_HOST = DEFAULT_DEVTOOLS_HOST;
3881
+ exports.DEFAULT_DEVTOOLS_PORT = DEFAULT_DEVTOOLS_PORT;
3882
+ exports.DEVICE_SNAPSHOT_API_PATH = DEVICE_SNAPSHOT_API_PATH;
3883
+ exports.DEVTOOLS_WS_PATH = DEVTOOLS_WS_PATH;
3884
+ exports.DatabaseDevTools = DatabaseDevTools;
3885
+ exports.DevToolsRole = DevToolsRole;
3886
+ exports.HEARTBEAT_INTERVAL_MS = HEARTBEAT_INTERVAL_MS;
3887
+ exports.HEARTBEAT_TIMEOUT_MS = HEARTBEAT_TIMEOUT_MS;
3888
+ exports.MAX_SNAPSHOT_BYTES = MAX_SNAPSHOT_BYTES;
3889
+ exports.MessageType = MessageType;
3890
+ exports.REFRESH_TIMEOUT_MS = REFRESH_TIMEOUT_MS;
3891
+ exports.SNAPSHOT_API_PATH = SNAPSHOT_API_PATH;
3892
+ exports.SNAPSHOT_KIND_HEADER = SNAPSHOT_KIND_HEADER;
3893
+ exports.SNAPSHOT_MIME_HEADER = SNAPSHOT_MIME_HEADER;
3894
+ exports.SNAPSHOT_NAME_HEADER = SNAPSHOT_NAME_HEADER;
3895
+ exports.SQLITE_SNAPSHOT_MIME_TYPE = SQLITE_SNAPSHOT_MIME_TYPE;
3896
+ exports.SYNC_TIMEOUT_MS = SYNC_TIMEOUT_MS;
3897
+ exports.buildDevToolsHttpUrl = buildDevToolsHttpUrl;
3898
+ exports.buildDevToolsWsUrl = buildDevToolsWsUrl;
3899
+ exports.buildDeviceSnapshotUrl = buildDeviceSnapshotUrl;
3900
+ exports.buildSnapshotUrl = buildSnapshotUrl;
3901
+ exports.createDevToolsClient = createDevToolsClient;
3902
+ exports.createExpoSqliteAdapter = createExpoSqliteAdapter;
3903
+ exports.createExpoSqliteInspector = createExpoSqliteInspector;
3904
+ exports.createInspectorForSnapshot = createInspectorForSnapshot;
3905
+ exports.createMessage = createMessage;
3906
+ exports.detectExpoSqlite = detectExpoSqlite;
3907
+ exports.detectExpoSqliteInspectable = detectExpoSqliteInspectable;
3908
+ exports.fetchSnapshot = fetchSnapshot;
3909
+ exports.getAdapterRegistry = getAdapterRegistry;
3910
+ exports.getInspectorRegistry = getInspectorRegistry;
3911
+ exports.handleDeviceSnapshotUpload = handleDeviceSnapshotUpload;
3912
+ exports.isBroadcastMessage = isBroadcastMessage;
3913
+ exports.isClientMessage = isClientMessage;
3914
+ exports.isDeviceStatusMessage = isDeviceStatusMessage;
3915
+ exports.isEditableDatabaseAdapter = isEditableDatabaseAdapter;
3916
+ exports.isPingMessage = isPingMessage;
3917
+ exports.isPongMessage = isPongMessage;
3918
+ exports.isRefreshErrorMessage = isRefreshErrorMessage;
3919
+ exports.isRefreshRequestMessage = isRefreshRequestMessage;
3920
+ exports.isRefreshStatusMessage = isRefreshStatusMessage;
3921
+ exports.isRegisterMessage = isRegisterMessage;
3922
+ exports.isServerMessage = isServerMessage;
3923
+ exports.isSnapshotReadyMessage = isSnapshotReadyMessage;
3924
+ exports.isSnapshotUploadRequestedMessage = isSnapshotUploadRequestedMessage;
3925
+ exports.isWritableDatabaseAdapter = isWritableDatabaseAdapter;
3926
+ exports.readDatabaseKindMarker = readDatabaseKindMarker;
3927
+ exports.registerBuiltInAdapters = registerBuiltInAdapters;
3928
+ exports.registerSqliteAdapter = registerSqliteAdapter;
3929
+ exports.resolveAdapter = resolveAdapter;
3930
+ exports.sqliteAdapterDefinition = sqliteAdapterDefinition;
3931
+ exports.wsUrlToHttpUrl = wsUrlToHttpUrl;
3932
+ //# sourceMappingURL=native.cjs.map
3933
+ //# sourceMappingURL=native.cjs.map